diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-08-29 14:45:23 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2019-11-04 13:22:43 -0500 |
commit | 29330ec1596f12462c501a65404ff52005b16b6c (patch) | |
tree | be20b85ae3939cdbc4f790fadd4f4372421891d4 /lib/sqlalchemy/sql/elements.py | |
parent | db47859dca999b9d1679b513fe855e408d7d07c4 (diff) | |
download | sqlalchemy-29330ec1596f12462c501a65404ff52005b16b6c.tar.gz |
Add anonymizing context to cache keys, comparison; convert traversal
Created new visitor system called "internal traversal" that
applies a data driven approach to the concept of a class that
defines its own traversal steps, in contrast to the existing
style of traversal now known as "external traversal" where
the visitor class defines the traversal, i.e. the SQLCompiler.
The internal traversal system now implements get_children(),
_copy_internals(), compare() and _cache_key() for most Core elements.
Core elements with special needs like Select still implement
some of these methods directly however most of these methods
are no longer explicitly implemented.
The data-driven system is also applied to ORM elements that
take part in SQL expressions so that these objects, like mappers,
aliasedclass, query options, etc. can all participate in the
cache key process.
Still not considered is that this approach to defining traversibility
will be used to create some kind of generic introspection system
that works across Core / ORM. It's also not clear if
real statement caching using the _cache_key() method is feasible,
if it is shown that running _cache_key() is nearly as expensive as
compiling in any case. Because it is data driven, it is more
straightforward to optimize using inlined code, as is the case now,
as well as potentially using C code to speed it up.
In addition, the caching sytem now accommodates for anonymous
name labels, which is essential so that constructs which have
anonymous labels can be cacheable, that is, their position
within a statement in relation to other anonymous names causes
them to generate an integer counter relative to that construct
which will be the same every time. Gathering of bound parameters
from any cache key generation is also now required as there is
no use case for a cache key that does not extract bound parameter
values.
Applies-to: #4639
Change-Id: I0660584def8627cad566719ee98d3be045db4b8d
Diffstat (limited to 'lib/sqlalchemy/sql/elements.py')
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 515 |
1 files changed, 186 insertions, 329 deletions
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index e6f57b8d1..ba615bc3f 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -16,23 +16,29 @@ import itertools import operator import re -from . import clause_compare from . import coercions from . import operators from . import roles +from . import traversals from . import type_api from .annotation import Annotated from .annotation import SupportsWrappingAnnotations from .base import _clone from .base import _generative from .base import Executable +from .base import HasCacheKey +from .base import HasMemoized from .base import Immutable from .base import NO_ARG from .base import PARSE_AUTOCOMMIT from .coercions import _document_text_coercion +from .traversals import _copy_internals +from .traversals import _get_children +from .traversals import NO_CACHE from .visitors import cloned_traverse +from .visitors import InternalTraversal from .visitors import traverse -from .visitors import Visitable +from .visitors import Traversible from .. import exc from .. import inspection from .. import util @@ -162,7 +168,9 @@ def not_(clause): @inspection._self_inspects -class ClauseElement(roles.SQLRole, SupportsWrappingAnnotations, Visitable): +class ClauseElement( + roles.SQLRole, SupportsWrappingAnnotations, HasCacheKey, Traversible +): """Base class for elements of a programmatically constructed SQL expression. @@ -190,6 +198,13 @@ class ClauseElement(roles.SQLRole, SupportsWrappingAnnotations, Visitable): _order_by_label_element = None + @property + def _cache_key_traversal(self): + try: + return self._traverse_internals + except AttributeError: + return NO_CACHE + def _clone(self): """Create a shallow copy of this ClauseElement. @@ -221,28 +236,6 @@ class ClauseElement(roles.SQLRole, SupportsWrappingAnnotations, Visitable): """ return self - def _cache_key(self, **kw): - """return an optional cache key. - - The cache key is a tuple which can contain any series of - objects that are hashable and also identifies - this object uniquely within the presence of a larger SQL expression - or statement, for the purposes of caching the resulting query. - - The cache key should be based on the SQL compiled structure that would - ultimately be produced. That is, two structures that are composed in - exactly the same way should produce the same cache key; any difference - in the strucures that would affect the SQL string or the type handlers - should result in a different cache key. - - If a structure cannot produce a useful cache key, it should raise - NotImplementedError, which will result in the entire structure - for which it's part of not being useful as a cache key. - - - """ - raise NotImplementedError() - @property def _constructor(self): """return the 'constructor' for this ClauseElement. @@ -336,9 +329,9 @@ class ClauseElement(roles.SQLRole, SupportsWrappingAnnotations, Visitable): (see :class:`.ColumnElement`) """ - return clause_compare.compare(self, other, **kw) + return traversals.compare(self, other, **kw) - def _copy_internals(self, clone=_clone, **kw): + def _copy_internals(self, **kw): """Reassign internal elements to be clones of themselves. Called during a copy-and-traverse operation on newly @@ -349,21 +342,46 @@ class ClauseElement(roles.SQLRole, SupportsWrappingAnnotations, Visitable): traversal, cloned traversal, annotations). """ - pass - def get_children(self, **kwargs): - r"""Return immediate child elements of this :class:`.ClauseElement`. + try: + traverse_internals = self._traverse_internals + except AttributeError: + return + + for attrname, obj, meth in _copy_internals.run_generated_dispatch( + self, traverse_internals, "_generated_copy_internals_traversal" + ): + if obj is not None: + result = meth(self, obj, **kw) + if result is not None: + setattr(self, attrname, result) + + def get_children(self, omit_attrs=None, **kw): + r"""Return immediate child :class:`.Traversible` elements of this + :class:`.Traversible`. This is used for visit traversal. - \**kwargs may contain flags that change the collection that is + \**kw may contain flags that change the collection that is returned, for example to return a subset of items in order to cut down on larger traversals, or to return child items from a different context (such as schema-level collections instead of clause-level). """ - return [] + result = [] + try: + traverse_internals = self._traverse_internals + except AttributeError: + return result + + for attrname, obj, meth in _get_children.run_generated_dispatch( + self, traverse_internals, "_generated_get_children_traversal" + ): + if obj is None or omit_attrs and attrname in omit_attrs: + continue + result.extend(meth(obj, **kw)) + return result def self_group(self, against=None): # type: (Optional[Any]) -> ClauseElement @@ -501,6 +519,8 @@ class ClauseElement(roles.SQLRole, SupportsWrappingAnnotations, Visitable): return or_(self, other) def __invert__(self): + # undocumented element currently used by the ORM for + # relationship.contains() if hasattr(self, "negation_clause"): return self.negation_clause else: @@ -508,9 +528,7 @@ class ClauseElement(roles.SQLRole, SupportsWrappingAnnotations, Visitable): def _negate(self): return UnaryExpression( - self.self_group(against=operators.inv), - operator=operators.inv, - negate=None, + self.self_group(against=operators.inv), operator=operators.inv ) def __bool__(self): @@ -731,9 +749,6 @@ class ColumnElement( else: return comparator_factory(self) - def _cache_key(self, **kw): - raise NotImplementedError(self.__class__) - def __getattr__(self, key): try: return getattr(self.comparator, key) @@ -969,6 +984,13 @@ class BindParameter(roles.InElementRole, ColumnElement): __visit_name__ = "bindparam" + _traverse_internals = [ + ("key", InternalTraversal.dp_anon_name), + ("type", InternalTraversal.dp_type), + ("callable", InternalTraversal.dp_plain_dict), + ("value", InternalTraversal.dp_plain_obj), + ] + _is_crud = False _expanding_in_types = () @@ -1321,26 +1343,19 @@ class BindParameter(roles.InElementRole, ColumnElement): ) return c - def _cache_key(self, bindparams=None, **kw): - if bindparams is None: - # even though _cache_key is a private method, we would like to - # be super paranoid about this point. You can't include the - # "value" or "callable" in the cache key, because the value is - # not part of the structure of a statement and is likely to - # change every time. However you cannot *throw it away* either, - # because you can't invoke the statement without the parameter - # values that were explicitly placed. So require that they - # are collected here to make sure this happens. - if self._value_required_for_cache: - raise NotImplementedError( - "bindparams collection argument required for _cache_key " - "implementation. Bound parameter cache keys are not safe " - "to use without accommodating for the value or callable " - "within the parameter itself." - ) - else: - bindparams.append(self) - return (BindParameter, self.type._cache_key, self._orig_key) + def _gen_cache_key(self, anon_map, bindparams): + if self in anon_map: + return (anon_map[self], self.__class__) + + id_ = anon_map[self] + bindparams.append(self) + + return ( + id_, + self.__class__, + self.type._gen_cache_key, + traversals._resolve_name_for_compare(self, self.key, anon_map), + ) def _convert_to_unique(self): if not self.unique: @@ -1377,12 +1392,11 @@ class TypeClause(ClauseElement): __visit_name__ = "typeclause" + _traverse_internals = [("type", InternalTraversal.dp_type)] + def __init__(self, type_): self.type = type_ - def _cache_key(self, **kw): - return (TypeClause, self.type._cache_key) - class TextClause( roles.DDLConstraintColumnRole, @@ -1419,6 +1433,11 @@ class TextClause( __visit_name__ = "textclause" + _traverse_internals = [ + ("_bindparams", InternalTraversal.dp_string_clauseelement_dict), + ("text", InternalTraversal.dp_string), + ] + _is_text_clause = True _is_textual = True @@ -1861,19 +1880,6 @@ class TextClause( else: return self - def _copy_internals(self, clone=_clone, **kw): - self._bindparams = dict( - (b.key, clone(b, **kw)) for b in self._bindparams.values() - ) - - def get_children(self, **kwargs): - return list(self._bindparams.values()) - - def _cache_key(self, **kw): - return (self.text,) + tuple( - bind._cache_key for bind in self._bindparams.values() - ) - class Null(roles.ConstExprRole, ColumnElement): """Represent the NULL keyword in a SQL statement. @@ -1885,6 +1891,8 @@ class Null(roles.ConstExprRole, ColumnElement): __visit_name__ = "null" + _traverse_internals = [] + @util.memoized_property def type(self): return type_api.NULLTYPE @@ -1895,9 +1903,6 @@ class Null(roles.ConstExprRole, ColumnElement): return Null() - def _cache_key(self, **kw): - return (Null,) - class False_(roles.ConstExprRole, ColumnElement): """Represent the ``false`` keyword, or equivalent, in a SQL statement. @@ -1908,6 +1913,7 @@ class False_(roles.ConstExprRole, ColumnElement): """ __visit_name__ = "false" + _traverse_internals = [] @util.memoized_property def type(self): @@ -1954,9 +1960,6 @@ class False_(roles.ConstExprRole, ColumnElement): return False_() - def _cache_key(self, **kw): - return (False_,) - class True_(roles.ConstExprRole, ColumnElement): """Represent the ``true`` keyword, or equivalent, in a SQL statement. @@ -1968,6 +1971,8 @@ class True_(roles.ConstExprRole, ColumnElement): __visit_name__ = "true" + _traverse_internals = [] + @util.memoized_property def type(self): return type_api.BOOLEANTYPE @@ -2020,9 +2025,6 @@ class True_(roles.ConstExprRole, ColumnElement): return True_() - def _cache_key(self, **kw): - return (True_,) - class ClauseList( roles.InElementRole, @@ -2038,6 +2040,11 @@ class ClauseList( __visit_name__ = "clauselist" + _traverse_internals = [ + ("clauses", InternalTraversal.dp_clauseelement_list), + ("operator", InternalTraversal.dp_operator), + ] + def __init__(self, *clauses, **kwargs): self.operator = kwargs.pop("operator", operators.comma_op) self.group = kwargs.pop("group", True) @@ -2082,17 +2089,6 @@ class ClauseList( coercions.expect(self._text_converter_role, clause) ) - def _copy_internals(self, clone=_clone, **kw): - self.clauses = [clone(clause, **kw) for clause in self.clauses] - - def get_children(self, **kwargs): - return self.clauses - - def _cache_key(self, **kw): - return (ClauseList, self.operator) + tuple( - clause._cache_key(**kw) for clause in self.clauses - ) - @property def _from_objects(self): return list(itertools.chain(*[c._from_objects for c in self.clauses])) @@ -2115,11 +2111,6 @@ class BooleanClauseList(ClauseList, ColumnElement): "BooleanClauseList has a private constructor" ) - def _cache_key(self, **kw): - return (BooleanClauseList, self.operator) + tuple( - clause._cache_key(**kw) for clause in self.clauses - ) - @classmethod def _construct(cls, operator, continue_on, skip_on, *clauses, **kw): convert_clauses = [] @@ -2250,6 +2241,8 @@ or_ = BooleanClauseList.or_ class Tuple(ClauseList, ColumnElement): """Represent a SQL tuple.""" + _traverse_internals = ClauseList._traverse_internals + [] + def __init__(self, *clauses, **kw): """Return a :class:`.Tuple`. @@ -2289,11 +2282,6 @@ class Tuple(ClauseList, ColumnElement): def _select_iterable(self): return (self,) - def _cache_key(self, **kw): - return (Tuple,) + tuple( - clause._cache_key(**kw) for clause in self.clauses - ) - def _bind_param(self, operator, obj, type_=None): return Tuple( *[ @@ -2339,6 +2327,12 @@ class Case(ColumnElement): __visit_name__ = "case" + _traverse_internals = [ + ("value", InternalTraversal.dp_clauseelement), + ("whens", InternalTraversal.dp_clauseelement_tuples), + ("else_", InternalTraversal.dp_clauseelement), + ] + def __init__(self, whens, value=None, else_=None): r"""Produce a ``CASE`` expression. @@ -2501,40 +2495,6 @@ class Case(ColumnElement): else: self.else_ = None - def _copy_internals(self, clone=_clone, **kw): - if self.value is not None: - self.value = clone(self.value, **kw) - self.whens = [(clone(x, **kw), clone(y, **kw)) for x, y in self.whens] - if self.else_ is not None: - self.else_ = clone(self.else_, **kw) - - def get_children(self, **kwargs): - if self.value is not None: - yield self.value - for x, y in self.whens: - yield x - yield y - if self.else_ is not None: - yield self.else_ - - def _cache_key(self, **kw): - return ( - ( - Case, - self.value._cache_key(**kw) - if self.value is not None - else None, - ) - + tuple( - (x._cache_key(**kw), y._cache_key(**kw)) for x, y in self.whens - ) - + ( - self.else_._cache_key(**kw) - if self.else_ is not None - else None, - ) - ) - @property def _from_objects(self): return list( @@ -2603,6 +2563,11 @@ class Cast(WrapsColumnExpression, ColumnElement): __visit_name__ = "cast" + _traverse_internals = [ + ("clause", InternalTraversal.dp_clauseelement), + ("typeclause", InternalTraversal.dp_clauseelement), + ] + def __init__(self, expression, type_): r"""Produce a ``CAST`` expression. @@ -2662,20 +2627,6 @@ class Cast(WrapsColumnExpression, ColumnElement): ) self.typeclause = TypeClause(self.type) - def _copy_internals(self, clone=_clone, **kw): - self.clause = clone(self.clause, **kw) - self.typeclause = clone(self.typeclause, **kw) - - def get_children(self, **kwargs): - return self.clause, self.typeclause - - def _cache_key(self, **kw): - return ( - Cast, - self.clause._cache_key(**kw), - self.typeclause._cache_key(**kw), - ) - @property def _from_objects(self): return self.clause._from_objects @@ -2685,7 +2636,7 @@ class Cast(WrapsColumnExpression, ColumnElement): return self.clause -class TypeCoerce(WrapsColumnExpression, ColumnElement): +class TypeCoerce(HasMemoized, WrapsColumnExpression, ColumnElement): """Represent a Python-side type-coercion wrapper. :class:`.TypeCoerce` supplies the :func:`.expression.type_coerce` @@ -2705,6 +2656,13 @@ class TypeCoerce(WrapsColumnExpression, ColumnElement): __visit_name__ = "type_coerce" + _traverse_internals = [ + ("clause", InternalTraversal.dp_clauseelement), + ("type", InternalTraversal.dp_type), + ] + + _memoized_property = util.group_expirable_memoized_property() + def __init__(self, expression, type_): r"""Associate a SQL expression with a particular type, without rendering ``CAST``. @@ -2773,21 +2731,11 @@ class TypeCoerce(WrapsColumnExpression, ColumnElement): roles.ExpressionElementRole, expression, type_=self.type ) - def _copy_internals(self, clone=_clone, **kw): - self.clause = clone(self.clause, **kw) - self.__dict__.pop("typed_expression", None) - - def get_children(self, **kwargs): - return (self.clause,) - - def _cache_key(self, **kw): - return (TypeCoerce, self.type._cache_key, self.clause._cache_key(**kw)) - @property def _from_objects(self): return self.clause._from_objects - @util.memoized_property + @_memoized_property def typed_expression(self): if isinstance(self.clause, BindParameter): bp = self.clause._clone() @@ -2806,6 +2754,11 @@ class Extract(ColumnElement): __visit_name__ = "extract" + _traverse_internals = [ + ("expr", InternalTraversal.dp_clauseelement), + ("field", InternalTraversal.dp_string), + ] + def __init__(self, field, expr, **kwargs): """Return a :class:`.Extract` construct. @@ -2818,15 +2771,6 @@ class Extract(ColumnElement): self.field = field self.expr = coercions.expect(roles.ExpressionElementRole, expr) - def _copy_internals(self, clone=_clone, **kw): - self.expr = clone(self.expr, **kw) - - def get_children(self, **kwargs): - return (self.expr,) - - def _cache_key(self, **kw): - return (Extract, self.field, self.expr._cache_key(**kw)) - @property def _from_objects(self): return self.expr._from_objects @@ -2847,18 +2791,11 @@ class _label_reference(ColumnElement): __visit_name__ = "label_reference" + _traverse_internals = [("element", InternalTraversal.dp_clauseelement)] + def __init__(self, element): self.element = element - def _copy_internals(self, clone=_clone, **kw): - self.element = clone(self.element, **kw) - - def _cache_key(self, **kw): - return (_label_reference, self.element._cache_key(**kw)) - - def get_children(self, **kwargs): - return [self.element] - @property def _from_objects(self): return () @@ -2867,6 +2804,8 @@ class _label_reference(ColumnElement): class _textual_label_reference(ColumnElement): __visit_name__ = "textual_label_reference" + _traverse_internals = [("element", InternalTraversal.dp_string)] + def __init__(self, element): self.element = element @@ -2874,9 +2813,6 @@ class _textual_label_reference(ColumnElement): def _text_clause(self): return TextClause._create_text(self.element) - def _cache_key(self, **kw): - return (_textual_label_reference, self.element) - class UnaryExpression(ColumnElement): """Define a 'unary' expression. @@ -2894,13 +2830,18 @@ class UnaryExpression(ColumnElement): __visit_name__ = "unary" + _traverse_internals = [ + ("element", InternalTraversal.dp_clauseelement), + ("operator", InternalTraversal.dp_operator), + ("modifier", InternalTraversal.dp_operator), + ] + def __init__( self, element, operator=None, modifier=None, type_=None, - negate=None, wraps_column_expression=False, ): self.operator = operator @@ -2909,7 +2850,6 @@ class UnaryExpression(ColumnElement): against=self.operator or self.modifier ) self.type = type_api.to_instance(type_) - self.negate = negate self.wraps_column_expression = wraps_column_expression @classmethod @@ -3135,37 +3075,13 @@ class UnaryExpression(ColumnElement): def _from_objects(self): return self.element._from_objects - def _copy_internals(self, clone=_clone, **kw): - self.element = clone(self.element, **kw) - - def _cache_key(self, **kw): - return ( - UnaryExpression, - self.element._cache_key(**kw), - self.operator, - self.modifier, - ) - - def get_children(self, **kwargs): - return (self.element,) - def _negate(self): - if self.negate is not None: - return UnaryExpression( - self.element, - operator=self.negate, - negate=self.operator, - modifier=self.modifier, - type_=self.type, - wraps_column_expression=self.wraps_column_expression, - ) - elif self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity: + if self.type._type_affinity is type_api.BOOLEANTYPE._type_affinity: return UnaryExpression( self.self_group(against=operators.inv), operator=operators.inv, type_=type_api.BOOLEANTYPE, wraps_column_expression=self.wraps_column_expression, - negate=None, ) else: return ClauseElement._negate(self) @@ -3286,15 +3202,6 @@ class AsBoolean(WrapsColumnExpression, UnaryExpression): # type: (Optional[Any]) -> ClauseElement return self - def _cache_key(self, **kw): - return ( - self.element._cache_key(**kw), - self.type._cache_key, - self.operator, - self.negate, - self.modifier, - ) - def _negate(self): if isinstance(self.element, (True_, False_)): return self.element._negate() @@ -3318,6 +3225,14 @@ class BinaryExpression(ColumnElement): __visit_name__ = "binary" + _traverse_internals = [ + ("left", InternalTraversal.dp_clauseelement), + ("right", InternalTraversal.dp_clauseelement), + ("operator", InternalTraversal.dp_operator), + ("negate", InternalTraversal.dp_operator), + ("modifiers", InternalTraversal.dp_plain_dict), + ] + _is_implicitly_boolean = True """Indicates that any database will know this is a boolean expression even if the database does not have an explicit boolean datatype. @@ -3360,20 +3275,6 @@ class BinaryExpression(ColumnElement): def _from_objects(self): return self.left._from_objects + self.right._from_objects - def _copy_internals(self, clone=_clone, **kw): - self.left = clone(self.left, **kw) - self.right = clone(self.right, **kw) - - def get_children(self, **kwargs): - return self.left, self.right - - def _cache_key(self, **kw): - return ( - BinaryExpression, - self.left._cache_key(**kw), - self.right._cache_key(**kw), - ) - def self_group(self, against=None): # type: (Optional[Any]) -> ClauseElement @@ -3406,6 +3307,12 @@ class Slice(ColumnElement): __visit_name__ = "slice" + _traverse_internals = [ + ("start", InternalTraversal.dp_plain_obj), + ("stop", InternalTraversal.dp_plain_obj), + ("step", InternalTraversal.dp_plain_obj), + ] + def __init__(self, start, stop, step): self.start = start self.stop = stop @@ -3417,9 +3324,6 @@ class Slice(ColumnElement): assert against is operator.getitem return self - def _cache_key(self, **kw): - return (Slice, self.start, self.stop, self.step) - class IndexExpression(BinaryExpression): """Represent the class of expressions that are like an "index" operation. @@ -3444,6 +3348,11 @@ class GroupedElement(ClauseElement): class Grouping(GroupedElement, ColumnElement): """Represent a grouping within a column expression""" + _traverse_internals = [ + ("element", InternalTraversal.dp_clauseelement), + ("type", InternalTraversal.dp_type), + ] + def __init__(self, element): self.element = element self.type = getattr(element, "type", type_api.NULLTYPE) @@ -3460,15 +3369,6 @@ class Grouping(GroupedElement, ColumnElement): def _label(self): return getattr(self.element, "_label", None) or self.anon_label - def _copy_internals(self, clone=_clone, **kw): - self.element = clone(self.element, **kw) - - def get_children(self, **kwargs): - return (self.element,) - - def _cache_key(self, **kw): - return (Grouping, self.element._cache_key(**kw)) - @property def _from_objects(self): return self.element._from_objects @@ -3501,6 +3401,14 @@ class Over(ColumnElement): __visit_name__ = "over" + _traverse_internals = [ + ("element", InternalTraversal.dp_clauseelement), + ("order_by", InternalTraversal.dp_clauseelement), + ("partition_by", InternalTraversal.dp_clauseelement), + ("range_", InternalTraversal.dp_plain_obj), + ("rows", InternalTraversal.dp_plain_obj), + ] + order_by = None partition_by = None @@ -3667,30 +3575,6 @@ class Over(ColumnElement): def type(self): return self.element.type - def get_children(self, **kwargs): - return [ - c - for c in (self.element, self.partition_by, self.order_by) - if c is not None - ] - - def _cache_key(self, **kw): - return ( - (Over,) - + tuple( - e._cache_key(**kw) if e is not None else None - for e in (self.element, self.partition_by, self.order_by) - ) - + (self.range_, self.rows) - ) - - def _copy_internals(self, clone=_clone, **kw): - self.element = clone(self.element, **kw) - if self.partition_by is not None: - self.partition_by = clone(self.partition_by, **kw) - if self.order_by is not None: - self.order_by = clone(self.order_by, **kw) - @property def _from_objects(self): return list( @@ -3723,6 +3607,11 @@ class WithinGroup(ColumnElement): __visit_name__ = "withingroup" + _traverse_internals = [ + ("element", InternalTraversal.dp_clauseelement), + ("order_by", InternalTraversal.dp_clauseelement), + ] + order_by = None def __init__(self, element, *order_by): @@ -3791,25 +3680,6 @@ class WithinGroup(ColumnElement): else: return self.element.type - def get_children(self, **kwargs): - return [c for c in (self.element, self.order_by) if c is not None] - - def _cache_key(self, **kw): - return ( - WithinGroup, - self.element._cache_key(**kw) - if self.element is not None - else None, - self.order_by._cache_key(**kw) - if self.order_by is not None - else None, - ) - - def _copy_internals(self, clone=_clone, **kw): - self.element = clone(self.element, **kw) - if self.order_by is not None: - self.order_by = clone(self.order_by, **kw) - @property def _from_objects(self): return list( @@ -3845,6 +3715,11 @@ class FunctionFilter(ColumnElement): __visit_name__ = "funcfilter" + _traverse_internals = [ + ("func", InternalTraversal.dp_clauseelement), + ("criterion", InternalTraversal.dp_clauseelement), + ] + criterion = None def __init__(self, func, *criterion): @@ -3932,23 +3807,6 @@ class FunctionFilter(ColumnElement): def type(self): return self.func.type - def get_children(self, **kwargs): - return [c for c in (self.func, self.criterion) if c is not None] - - def _copy_internals(self, clone=_clone, **kw): - self.func = clone(self.func, **kw) - if self.criterion is not None: - self.criterion = clone(self.criterion, **kw) - - def _cache_key(self, **kw): - return ( - FunctionFilter, - self.func._cache_key(**kw), - self.criterion._cache_key(**kw) - if self.criterion is not None - else None, - ) - @property def _from_objects(self): return list( @@ -3962,7 +3820,7 @@ class FunctionFilter(ColumnElement): ) -class Label(roles.LabeledColumnExprRole, ColumnElement): +class Label(HasMemoized, roles.LabeledColumnExprRole, ColumnElement): """Represents a column label (AS). Represent a label, as typically applied to any column-level @@ -3972,6 +3830,14 @@ class Label(roles.LabeledColumnExprRole, ColumnElement): __visit_name__ = "label" + _traverse_internals = [ + ("name", InternalTraversal.dp_anon_name), + ("_type", InternalTraversal.dp_type), + ("_element", InternalTraversal.dp_clauseelement), + ] + + _memoized_property = util.group_expirable_memoized_property() + def __init__(self, name, element, type_=None): """Return a :class:`Label` object for the given :class:`.ColumnElement`. @@ -4010,14 +3876,11 @@ class Label(roles.LabeledColumnExprRole, ColumnElement): def __reduce__(self): return self.__class__, (self.name, self._element, self._type) - def _cache_key(self, **kw): - return (Label, self.element._cache_key(**kw), self._resolve_label) - @util.memoized_property def _is_implicitly_boolean(self): return self.element._is_implicitly_boolean - @util.memoized_property + @_memoized_property def _allow_label_resolve(self): return self.element._allow_label_resolve @@ -4031,7 +3894,7 @@ class Label(roles.LabeledColumnExprRole, ColumnElement): self._type or getattr(self._element, "type", None) ) - @util.memoized_property + @_memoized_property def element(self): return self._element.self_group(against=operators.as_) @@ -4057,13 +3920,9 @@ class Label(roles.LabeledColumnExprRole, ColumnElement): def foreign_keys(self): return self.element.foreign_keys - def get_children(self, **kwargs): - return (self.element,) - def _copy_internals(self, clone=_clone, anonymize_labels=False, **kw): + self._reset_memoizations() self._element = clone(self._element, **kw) - self.__dict__.pop("element", None) - self.__dict__.pop("_allow_label_resolve", None) if anonymize_labels: self.name = self._resolve_label = _anonymous_label( "%%(%d %s)s" @@ -4124,6 +3983,13 @@ class ColumnClause(roles.LabeledColumnExprRole, Immutable, ColumnElement): __visit_name__ = "column" + _traverse_internals = [ + ("name", InternalTraversal.dp_string), + ("type", InternalTraversal.dp_type), + ("table", InternalTraversal.dp_clauseelement), + ("is_literal", InternalTraversal.dp_boolean), + ] + onupdate = default = server_default = server_onupdate = None _is_multiparam_column = False @@ -4254,14 +4120,6 @@ class ColumnClause(roles.LabeledColumnExprRole, Immutable, ColumnElement): table = property(_get_table, _set_table) - def _cache_key(self, **kw): - return ( - self.name, - self.table.name if self.table is not None else None, - self.is_literal, - self.type._cache_key, - ) - @_memoized_property def _from_objects(self): t = self.table @@ -4395,12 +4253,11 @@ class ColumnClause(roles.LabeledColumnExprRole, Immutable, ColumnElement): class CollationClause(ColumnElement): __visit_name__ = "collation" + _traverse_internals = [("collation", InternalTraversal.dp_string)] + def __init__(self, collation): self.collation = collation - def _cache_key(self, **kw): - return (CollationClause, self.collation) - class _IdentifiedClause(Executable, ClauseElement): |