diff options
author | mike bayer <mike_mp@zzzcomputing.com> | 2021-05-06 21:12:48 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@ci3.zzzcomputing.com> | 2021-05-06 21:12:48 +0000 |
commit | c99c93fd64314c6384a1153c074ea39386892d05 (patch) | |
tree | b727e07f4f59b2b05bebfa9b82d4a06b8e3a9a7b /lib/sqlalchemy | |
parent | a965f0c99a0b02e918d0f63534a3b7c4c9926655 (diff) | |
parent | 6967b4502079e199b12f5eb307d10d27ec92d537 (diff) | |
download | sqlalchemy-c99c93fd64314c6384a1153c074ea39386892d05.tar.gz |
Merge "don't cache TypeDecorator by default"
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/dialects/mssql/information_schema.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/sqlite/pysqlite.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/sqltypes.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/traversals.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/type_api.py | 73 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/suite/test_types.py | 2 |
6 files changed, 85 insertions, 1 deletions
diff --git a/lib/sqlalchemy/dialects/mssql/information_schema.py b/lib/sqlalchemy/dialects/mssql/information_schema.py index c37920797..eb8f6db5b 100644 --- a/lib/sqlalchemy/dialects/mssql/information_schema.py +++ b/lib/sqlalchemy/dialects/mssql/information_schema.py @@ -25,6 +25,7 @@ ischema = MetaData() class CoerceUnicode(TypeDecorator): impl = Unicode + cache_ok = True def process_bind_param(self, value, dialect): if util.py2k and isinstance(value, util.binary_type): @@ -211,6 +212,7 @@ class IdentitySqlVariant(TypeDecorator): correct value as string. """ impl = Unicode + cache_ok = True def column_expression(self, colexpr): return cast(colexpr, Numeric) diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlite.py b/lib/sqlalchemy/dialects/sqlite/pysqlite.py index 20a4bb7ac..8df0037bf 100644 --- a/lib/sqlalchemy/dialects/sqlite/pysqlite.py +++ b/lib/sqlalchemy/dialects/sqlite/pysqlite.py @@ -312,6 +312,7 @@ same column, use a custom type that will check each row individually:: class MixedBinary(TypeDecorator): impl = String + cache_ok = True def process_result_value(self, value, dialect): if isinstance(value, str): diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 024b9f01e..4f8654afd 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -1780,6 +1780,7 @@ class PickleType(TypeDecorator): """ impl = LargeBinary + cache_ok = True def __init__( self, protocol=pickle.HIGHEST_PROTOCOL, pickler=None, comparator=None @@ -2027,6 +2028,7 @@ class Interval(Emulated, _AbstractInterval, TypeDecorator): impl = DateTime epoch = dt.datetime.utcfromtimestamp(0) + cache_ok = True def __init__(self, native=True, second_precision=None, day_precision=None): """Construct an Interval object. diff --git a/lib/sqlalchemy/sql/traversals.py b/lib/sqlalchemy/sql/traversals.py index f2099f191..e8a805285 100644 --- a/lib/sqlalchemy/sql/traversals.py +++ b/lib/sqlalchemy/sql/traversals.py @@ -152,7 +152,11 @@ class HasCacheKey(object): # efficient switch construct if meth is STATIC_CACHE_KEY: - result += (attrname, obj._static_cache_key) + sck = obj._static_cache_key + if sck is NO_CACHE: + anon_map[NO_CACHE] = True + return None + result += (attrname, sck) elif meth is ANON_NAME: elements = util.preloaded.sql_elements if isinstance(obj, elements._anonymous_label): diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py index 69cd3c5ca..47f6c3005 100644 --- a/lib/sqlalchemy/sql/type_api.py +++ b/lib/sqlalchemy/sql/type_api.py @@ -10,6 +10,7 @@ """ +from sqlalchemy.sql.traversals import NO_CACHE from . import operators from .base import SchemaEventTarget from .visitors import Traversible @@ -872,6 +873,8 @@ class TypeDecorator(SchemaEventTarget, TypeEngine): impl = types.Unicode + cache_ok = True + def process_bind_param(self, value, dialect): return "PREFIX:" + value @@ -887,6 +890,16 @@ class TypeDecorator(SchemaEventTarget, TypeEngine): given; in this case, the ``impl`` variable can reference ``TypeEngine`` as a placeholder. + The :attr:`.TypeDecorator.cache_ok` class-level flag indicates if this + custom :class:`.TypeDecorator` is safe to be used as part of a cache key. + This flag defaults to ``None`` which will initially generate a warning + when the SQL compiler attempts to generate a cache key for a statement + that uses this type. If the :class:`.TypeDecorator` is not guaranteed + to produce the same bind/result behavior and SQL generation + every time, this flag should be set to ``False``; otherwise if the + class produces the same behavior each time, it may be set to ``True``. + See :attr:`.TypeDecorator.cache_ok` for further notes on how this works. + Types that receive a Python type that isn't similar to the ultimate type used may want to define the :meth:`TypeDecorator.coerce_compared_value` method. This is used to give the expression system a hint when coercing @@ -946,6 +959,8 @@ class TypeDecorator(SchemaEventTarget, TypeEngine): class MyJsonType(TypeDecorator): impl = postgresql.JSON + cache_ok = True + def coerce_compared_value(self, op, value): return self.impl.coerce_compared_value(op, value) @@ -1002,6 +1017,47 @@ class TypeDecorator(SchemaEventTarget, TypeEngine): """ + cache_ok = None + """Indicate if statements using this :class:`.TypeDecorator` are "safe to + cache". + + The default value ``None`` will emit a warning and then not allow caching + of a statement which includes this type. Set to ``False`` to disable + statements using this type from being cached at all without a warning. + When set to ``True``, the object's class and selected elements from its + state will be used as part of the cache key, e.g.:: + + class MyType(TypeDecorator): + impl = String + + cache_ok = True + + def __init__(self, choices): + self.choices = tuple(choices) + self.internal_only = True + + The cache key for the above type would be equivalent to:: + + (<class '__main__.MyType'>, ('choices', ('a', 'b', 'c'))) + + The caching scheme will extract attributes from the type that correspond + to the names of parameters in the ``__init__()`` method. Above, the + "choices" attribute becomes part of the cache key but "internal_only" + does not, because there is no parameter named "internal_only". + + The requirements for cacheable elements is that they are hashable + and also that they indicate the same SQL rendered for expressions using + this type every time for a given cache value. + + .. versionadded:: 1.4.14 - added the ``cache_ok`` flag to allow + some configurability of caching for :class:`.TypeDecorator` classes. + + .. seealso:: + + :ref:`sql_caching` + + """ + class Comparator(TypeEngine.Comparator): """A :class:`.TypeEngine.Comparator` that is specific to :class:`.TypeDecorator`. @@ -1037,6 +1093,21 @@ class TypeDecorator(SchemaEventTarget, TypeEngine): {}, ) + @property + def _static_cache_key(self): + if self.cache_ok is None: + util.warn( + "TypeDecorator %r will not produce a cache key because " + "the ``cache_ok`` flag is not set to True. " + "Set this flag to True if this type object's " + "state is safe to use in a cache key, or False to " + "disable this warning." % self + ) + elif self.cache_ok is True: + return super(TypeDecorator, self)._static_cache_key + + return NO_CACHE + def _gen_dialect_impl(self, dialect): """ #todo @@ -1465,6 +1536,8 @@ class Variant(TypeDecorator): """ + cache_ok = True + def __init__(self, base, mapping): """Construct a new :class:`.Variant`. diff --git a/lib/sqlalchemy/testing/suite/test_types.py b/lib/sqlalchemy/testing/suite/test_types.py index ebcceaae7..3e54d87a4 100644 --- a/lib/sqlalchemy/testing/suite/test_types.py +++ b/lib/sqlalchemy/testing/suite/test_types.py @@ -288,6 +288,7 @@ class _DateFixture(_LiteralRoundTripFixture, fixtures.TestBase): def define_tables(cls, metadata): class Decorated(TypeDecorator): impl = cls.datatype + cache_ok = True Table( "date_table", @@ -477,6 +478,7 @@ class CastTypeDecoratorTest(_LiteralRoundTripFixture, fixtures.TestBase): def string_as_int(self): class StringAsInt(TypeDecorator): impl = String(50) + cache_ok = True def get_dbapi_type(self, dbapi): return dbapi.NUMBER |