diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-03-09 17:12:35 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-04-01 16:12:23 -0400 |
| commit | a9b62055bfa61c11e9fe0b2984437e2c3e32bf0e (patch) | |
| tree | 366027c7069edd56d49e9d540ae6a14fbe9e16fe /lib/sqlalchemy/sql/selectable.py | |
| parent | e6250123a30e457068878394e49b7ca07ca4d3b0 (diff) | |
| download | sqlalchemy-a9b62055bfa61c11e9fe0b2984437e2c3e32bf0e.tar.gz | |
Try to measure new style caching in the ORM, take two
Supercedes: If78fbb557c6f2cae637799c3fec2cbc5ac248aaf
Trying to see if by making the cache key memoized, we
still can have the older "identity" form of caching
which is the cheapest of all, at the same time as the
newer "cache key each time" version that is not nearly
as cheap; but still much cheaper than no caching at all.
Also needed is a per-execution update of _keymap when
we invoke from a cached select, so that Column objects
that are anonymous or otherwise adapted will match up.
this is analogous to the adaption of bound parameters
from the cache key.
Adds test coverage for the keymap / construct_params()
changes related to caching. Also hones performance
to a large extent for statement construction and
cache key generation.
Also includes a new memoized attribute
approach that vastly simplifies the previous approach
of "group_expirable_memoized_property" and finally
integrates cleanly with _clone(), _generate(), etc.
no more hardcoding of attributes is needed, as well
as that most _reset_memoization() calls are no longer
needed as the reset is inherent in a _generate() call;
this also has dramatic performance improvements.
Change-Id: I95c560ffcbfa30b26644999412fb6a385125f663
Diffstat (limited to 'lib/sqlalchemy/sql/selectable.py')
| -rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 132 |
1 files changed, 86 insertions, 46 deletions
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 4eab60801..e39d61fdb 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -106,31 +106,33 @@ class ReturnsRows(roles.ReturnsRowsRole, ClauseElement): def selectable(self): raise NotImplementedError() + def _exported_columns_iterator(self): + """An iterator of column objects that represents the "exported" + columns of this :class:`.ReturnsRows`. -class Selectable(ReturnsRows): - """mark a class as being selectable. - - """ + This is the same set of columns as are returned by + :meth:`.ReturnsRows.exported_columns` except they are returned + as a simple iterator or sequence, rather than as a + :class:`.ColumnCollection` namespace. - __visit_name__ = "selectable" - - is_selectable = True + Subclasses should re-implement this method to bypass the interim + creation of the :class:`.ColumnCollection` if appropriate. - @property - def selectable(self): - return self + """ + return iter(self.exported_columns) @property def exported_columns(self): """A :class:`.ColumnCollection` that represents the "exported" - columns of this :class:`.Selectable`. + columns of this :class:`.ReturnsRows`. The "exported" columns represent the collection of :class:`.ColumnElement` expressions that are rendered by this SQL - construct. There are two primary varieties which are the + construct. There are primary varieties which are the "FROM clause columns" of a FROM clause, such as a table, join, - or subquery, and the "SELECTed columns", which are the columns in - the "columns clause" of a SELECT statement. + or subquery, the "SELECTed columns", which are the columns in + the "columns clause" of a SELECT statement, and the RETURNING + columns in a DML statement.. .. versionadded:: 1.4 @@ -143,6 +145,20 @@ class Selectable(ReturnsRows): raise NotImplementedError() + +class Selectable(ReturnsRows): + """mark a class as being selectable. + + """ + + __visit_name__ = "selectable" + + is_selectable = True + + @property + def selectable(self): + return self + def _refresh_for_new_column(self, column): raise NotImplementedError() @@ -312,7 +328,7 @@ class HasSuffixes(object): ) -class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable): +class FromClause(roles.AnonymizedFromClauseRole, Selectable): """Represent an element that can be used within the ``FROM`` clause of a ``SELECT`` statement. @@ -350,8 +366,6 @@ class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable): _use_schema_map = False - _memoized_property = util.group_expirable_memoized_property(["_columns"]) - @util.deprecated( "1.1", message="The :meth:`.FromClause.count` method is deprecated, " @@ -571,7 +585,7 @@ class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable): """ return self.columns - @_memoized_property + @util.memoized_property def columns(self): """A named-based collection of :class:`.ColumnElement` objects maintained by this :class:`.FromClause`. @@ -589,7 +603,7 @@ class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable): self._populate_column_collection() return self._columns.as_immutable() - @_memoized_property + @util.memoized_property def primary_key(self): """Return the collection of Column objects which comprise the primary key of this FromClause.""" @@ -598,7 +612,7 @@ class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable): self._populate_column_collection() return self.primary_key - @_memoized_property + @util.memoized_property def foreign_keys(self): """Return the collection of ForeignKey objects which this FromClause references.""" @@ -607,6 +621,23 @@ class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable): self._populate_column_collection() return self.foreign_keys + def _reset_column_collection(self): + """Reset the attributes linked to the FromClause.c attribute. + + This collection is separate from all the other memoized things + as it has shown to be sensitive to being cleared out in situations + where enclosing code, typically in a replacement traversal scenario, + has already established strong relationships + with the exported columns. + + The collection is cleared for the case where a table is having a + column added to it as well as within a Join during copy internals. + + """ + + for key in ["_columns", "columns", "primary_key", "foreign_keys"]: + self.__dict__.pop(key, None) + c = property( attrgetter("columns"), doc="An alias for the :attr:`.columns` attribute.", @@ -659,7 +690,7 @@ class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable): derivations. """ - self._reset_exported() + self._reset_column_collection() class Join(FromClause): @@ -1239,7 +1270,7 @@ class AliasedReturnsRows(NoInit, FromClause): # same object. don't reset exported .c. collections and other # memoized details if nothing changed if element is not self.element: - self._reset_exported() + self._reset_column_collection() self.element = element @property @@ -2141,7 +2172,6 @@ class SelectBase( roles.DMLSelectRole, roles.CompoundElementRole, roles.InElementRole, - HasMemoized, HasCTE, Executable, SupportsCloneAnnotations, @@ -2158,8 +2188,6 @@ class SelectBase( _is_select_statement = True - _memoized_property = util.group_expirable_memoized_property() - def _generate_fromclause_column_proxies(self, fromclause): # type: (FromClause) -> None raise NotImplementedError() @@ -2254,7 +2282,7 @@ class SelectBase( def outerjoin(self, *arg, **kw): return self._implicit_subquery.outerjoin(*arg, **kw) - @_memoized_property + @HasMemoized.memoized_attribute def _implicit_subquery(self): return self.subquery() @@ -2315,15 +2343,6 @@ class SelectBase( """ return Lateral._factory(self, name) - def _generate(self): - """Override the default _generate() method to also clear out - exported collections.""" - - s = self.__class__.__new__(self.__class__) - s.__dict__ = self.__dict__.copy() - s._reset_memoizations() - return s - @property def _from_objects(self): return [self] @@ -2431,6 +2450,9 @@ class SelectStatementGrouping(GroupedElement, SelectBase): def _generate_proxy_for_new_column(self, column, subquery): return self.element._generate_proxy_for_new_column(subquery) + def _exported_columns_iterator(self): + return self.element._exported_columns_iterator() + @property def selected_columns(self): """A :class:`.ColumnCollection` representing the columns that @@ -3046,6 +3068,9 @@ class CompoundSelect(HasCompileState, GenerativeSelect): for select in self.selects: select._refresh_for_new_column(column) + def _exported_columns_iterator(self): + return self.selects[0]._exported_columns_iterator() + @property def selected_columns(self): """A :class:`.ColumnCollection` representing the columns that @@ -3339,8 +3364,6 @@ class Select( _from_obj = () _auto_correlate = True - _memoized_property = SelectBase._memoized_property - _traverse_internals = ( [ ("_from_obj", InternalTraversal.dp_clauseelement_list), @@ -3400,8 +3423,7 @@ class Select( self = cls.__new__(cls) self._raw_columns = [ - coercions.expect(roles.ColumnsClauseRole, ent) - for ent in util.to_list(entities) + coercions.expect(roles.ColumnsClauseRole, ent) for ent in entities ] GenerativeSelect.__init__(self) @@ -3739,8 +3761,12 @@ class Select( """an iterator of all ColumnElement expressions which would be rendered into the columns clause of the resulting SELECT statement. + This method is legacy as of 1.4 and is superseded by the + :attr:`.Select.exported_columns` collection. + """ - return _select_iterables(self._raw_columns) + + return self._exported_columns_iterator() def is_derived_from(self, fromclause): if self in fromclause._cloned_set: @@ -3786,7 +3812,10 @@ class Select( clone=clone, omit_attrs=("_from_obj",), **kw ) - self._reset_memoizations() + # memoizations should be cleared here as of + # I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this + # is the case for now. + self._assert_no_memoizations() def get_children(self, **kwargs): return list(set(self._iterate_from_elements())) + super( @@ -3809,7 +3838,10 @@ class Select( :class:`.Select` object. """ - self._reset_memoizations() + # memoizations should be cleared here as of + # I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this + # is the case for now. + self._assert_no_memoizations() self._raw_columns = self._raw_columns + [ coercions.expect(roles.ColumnsClauseRole, column,) @@ -3861,7 +3893,7 @@ class Select( """ return self.with_only_columns( util.preloaded.sql_util.reduce_columns( - self.inner_columns, + self._exported_columns_iterator(), only_synonyms=only_synonyms, *(self._where_criteria + self._from_obj) ) @@ -3935,7 +3967,12 @@ class Select( being asked to select both from ``table1`` as well as itself. """ - self._reset_memoizations() + + # memoizations should be cleared here as of + # I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this + # is the case for now. + self._assert_no_memoizations() + rc = [] for c in columns: c = coercions.expect(roles.ColumnsClauseRole, c,) @@ -4112,7 +4149,7 @@ class Select( coercions.expect(roles.FromClauseRole, f) for f in fromclauses ) - @_memoized_property + @HasMemoized.memoized_attribute def selected_columns(self): """A :class:`.ColumnCollection` representing the columns that this SELECT statement or similar construct returns in its result set. @@ -4167,6 +4204,9 @@ class Select( return ColumnCollection(collection).as_immutable() + def _exported_columns_iterator(self): + return _select_iterables(self._raw_columns) + def _ensure_disambiguated_names(self): if self._label_style is LABEL_STYLE_NONE: self = self._set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY) @@ -4558,7 +4598,7 @@ class TextualSelect(SelectBase): ] self.positional = positional - @SelectBase._memoized_property + @HasMemoized.memoized_attribute def selected_columns(self): """A :class:`.ColumnCollection` representing the columns that this SELECT statement or similar construct returns in its result set. |
