summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2021-05-06 21:12:48 +0000
committerGerrit Code Review <gerrit@ci3.zzzcomputing.com>2021-05-06 21:12:48 +0000
commitc99c93fd64314c6384a1153c074ea39386892d05 (patch)
treeb727e07f4f59b2b05bebfa9b82d4a06b8e3a9a7b /lib/sqlalchemy
parenta965f0c99a0b02e918d0f63534a3b7c4c9926655 (diff)
parent6967b4502079e199b12f5eb307d10d27ec92d537 (diff)
downloadsqlalchemy-c99c93fd64314c6384a1153c074ea39386892d05.tar.gz
Merge "don't cache TypeDecorator by default"
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/mssql/information_schema.py2
-rw-r--r--lib/sqlalchemy/dialects/sqlite/pysqlite.py1
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py2
-rw-r--r--lib/sqlalchemy/sql/traversals.py6
-rw-r--r--lib/sqlalchemy/sql/type_api.py73
-rw-r--r--lib/sqlalchemy/testing/suite/test_types.py2
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