summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/compiler.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2020-02-23 13:37:18 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2020-03-06 11:01:51 -0500
commit851fb8f5a661c66ee76308181118369c8c4df9e0 (patch)
treeb6c786e78e090752f5c0922d1f09d277ab94e365 /lib/sqlalchemy/sql/compiler.py
parentd72bda5ed23a46bcbf31d40684200dcb79012a33 (diff)
downloadsqlalchemy-851fb8f5a661c66ee76308181118369c8c4df9e0.tar.gz
Decouple compiler state from DML objects; make cacheable
Targeting select / insert / update / delete, the goal is to minimize overhead of construction and generative methods so that only the raw arguments passed are handled. An interim stage that converts the raw state into more compiler-ready state is added, which is analogous to the ORM QueryContext which will also be rolled in to be a similar concept, as is currently being prototyped in I19e05b3424b07114cce6c439b05198ac47f7ac10. the ORM update/delete BulkUD concept is also going to be rolled onto this idea. So while the compiler-ready state object, here called DMLState, looks a little thin, it's the base of a bigger pattern that will allow for ORM functionality to embed itself directly into the compiler, execution context, and result set objects. This change targets the DML objects, primarily focused on the values() method which is the most complex process. The work done by values() is minimized as much as possible while still being able to create a cache key. Additional computation is then offloaded to a new object ValuesState that is handled by the compiler. Architecturally, a big change here is that insert.values() and update.values() will generate BindParameter objects for the values now, which are then carefully received by crud.py so that they generate the expected names. This is so that the values() portion of these constructs is cacheable. for the "multi-values" version of Insert, this is all skipped and the plan right now is that a multi-values insert is not worth caching (can always be revisited). Using the coercions system in values() also gets us nicer validation for free, we can remove the NotAClauseElement thing from schema, and we also now require scalar_subquery() is called for an insert/update that uses a SELECT as a column value, 1.x deprecation path is added. The traversal system is then applied to the DML objects including tests so that they have traversal, cloning, and cache key support. cloning is not a use case for DML however having it present allows better validation of the structure within the tests. Special per-dialect DML is explicitly not cacheable at the moment, more as a proof of concept that third party DML constructs can exist as gracefully not-cacheable rather than producing an incomplete cache key. A few selected performance improvements have been added as well, simplifying the immutabledict.union() method and adding a new SQLCompiler function that can generate delimeter-separated clauses like WHERE and ORDER BY without having to build a ClauseList object at all. The use of ClauseList will be removed from Select in an upcoming commit. Overall, ClaustList is unnecessary for internal use and only adds overhead to statement construction and will likely be removed as much as possible except for explcit use of conjunctions like and_() and or_(). Change-Id: I408e0b8be91fddd77cf279da97f55020871f75a9
Diffstat (limited to 'lib/sqlalchemy/sql/compiler.py')
-rw-r--r--lib/sqlalchemy/sql/compiler.py87
1 files changed, 62 insertions, 25 deletions
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 424282951..3ebcf24b0 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -653,6 +653,20 @@ class SQLCompiler(Compiled):
insert_prefetch = update_prefetch = ()
+ compile_state = None
+ """Optional :class:`.CompileState` object that maintains additional
+ state used by the compiler.
+
+ Major executable objects such as :class:`.Insert`, :class:`.Update`,
+ :class:`.Delete`, :class:`.Select` will generate this state when compiled
+ in order to calculate additional information about the object. For the
+ top level object that is to be executed, the state can be stored here where
+ it can also have applicability towards result set processing.
+
+ .. versionadded:: 1.4
+
+ """
+
def __init__(
self,
dialect,
@@ -1292,6 +1306,13 @@ class SQLCompiler(Compiled):
else:
return "0"
+ def _generate_delimited_list(self, elements, separator, **kw):
+ return separator.join(
+ s
+ for s in (c._compiler_dispatch(self, **kw) for c in elements)
+ if s
+ )
+
def visit_clauselist(self, clauselist, **kw):
sep = clauselist.operator
if sep is None:
@@ -1299,13 +1320,7 @@ class SQLCompiler(Compiled):
else:
sep = OPERATORS[clauselist.operator]
- text = sep.join(
- s
- for s in (
- c._compiler_dispatch(self, **kw) for c in clauselist.clauses
- )
- if s
- )
+ text = self._generate_delimited_list(clauselist.clauses, sep, **kw)
if clauselist._tuple_values and self.dialect.tuple_in_values:
text = "VALUES " + text
return text
@@ -2810,8 +2825,18 @@ class SQLCompiler(Compiled):
return dialect_hints, table_text
def visit_insert(self, insert_stmt, **kw):
+
+ compile_state = insert_stmt._compile_state_cls(
+ insert_stmt, self, isinsert=True, **kw
+ )
+ insert_stmt = compile_state.statement
+
toplevel = not self.stack
+ if toplevel:
+ self.isinsert = True
+ self.compile_state = compile_state
+
self.stack.append(
{
"correlate_froms": set(),
@@ -2820,8 +2845,8 @@ class SQLCompiler(Compiled):
}
)
- crud_params = crud._setup_crud_params(
- self, insert_stmt, crud.ISINSERT, **kw
+ crud_params = crud._get_crud_params(
+ self, insert_stmt, compile_state, **kw
)
if (
@@ -2835,7 +2860,7 @@ class SQLCompiler(Compiled):
"inserts." % self.dialect.name
)
- if insert_stmt._has_multi_parameters:
+ if compile_state._has_multi_parameters:
if not self.dialect.supports_multivalues_insert:
raise exc.CompileError(
"The '%s' dialect with current database "
@@ -2888,7 +2913,7 @@ class SQLCompiler(Compiled):
text += " %s" % select_text
elif not crud_params and supports_default_values:
text += " DEFAULT VALUES"
- elif insert_stmt._has_multi_parameters:
+ elif compile_state._has_multi_parameters:
text += " VALUES %s" % (
", ".join(
"(%s)" % (", ".join(c[1] for c in crud_param_set))
@@ -2947,9 +2972,16 @@ class SQLCompiler(Compiled):
)
def visit_update(self, update_stmt, **kw):
+ compile_state = update_stmt._compile_state_cls(
+ update_stmt, self, isupdate=True, **kw
+ )
+ update_stmt = compile_state.statement
+
toplevel = not self.stack
+ if toplevel:
+ self.isupdate = True
- extra_froms = update_stmt._extra_froms
+ extra_froms = compile_state._extra_froms
is_multitable = bool(extra_froms)
if is_multitable:
@@ -2981,8 +3013,8 @@ class SQLCompiler(Compiled):
table_text = self.update_tables_clause(
update_stmt, update_stmt.table, render_extra_froms, **kw
)
- crud_params = crud._setup_crud_params(
- self, update_stmt, crud.ISUPDATE, **kw
+ crud_params = crud._get_crud_params(
+ self, update_stmt, compile_state, **kw
)
if update_stmt._hints:
@@ -3022,8 +3054,10 @@ class SQLCompiler(Compiled):
if extra_from_text:
text += " " + extra_from_text
- if update_stmt._whereclause is not None:
- t = self.process(update_stmt._whereclause, **kw)
+ if update_stmt._where_criteria:
+ t = self._generate_delimited_list(
+ update_stmt._where_criteria, OPERATORS[operators.and_], **kw
+ )
if t:
text += " WHERE " + t
@@ -3045,10 +3079,6 @@ class SQLCompiler(Compiled):
return text
- @util.memoized_property
- def _key_getters_for_crud_column(self):
- return crud._key_getters_for_crud_column(self, self.statement)
-
def delete_extra_from_clause(
self, update_stmt, from_table, extra_froms, from_hints, **kw
):
@@ -3069,11 +3099,16 @@ class SQLCompiler(Compiled):
return from_table._compiler_dispatch(self, asfrom=True, iscrud=True)
def visit_delete(self, delete_stmt, **kw):
- toplevel = not self.stack
+ compile_state = delete_stmt._compile_state_cls(
+ delete_stmt, self, isdelete=True, **kw
+ )
+ delete_stmt = compile_state.statement
- crud._setup_crud_params(self, delete_stmt, crud.ISDELETE, **kw)
+ toplevel = not self.stack
+ if toplevel:
+ self.isdelete = True
- extra_froms = delete_stmt._extra_froms
+ extra_froms = compile_state._extra_froms
correlate_froms = {delete_stmt.table}.union(extra_froms)
self.stack.append(
@@ -3122,8 +3157,10 @@ class SQLCompiler(Compiled):
if extra_from_text:
text += " " + extra_from_text
- if delete_stmt._whereclause is not None:
- t = delete_stmt._whereclause._compiler_dispatch(self, **kw)
+ if delete_stmt._where_criteria:
+ t = self._generate_delimited_list(
+ delete_stmt._where_criteria, OPERATORS[operators.and_], **kw
+ )
if t:
text += " WHERE " + t