Fixed issue where unpickling of a :class:`_schema.Column` or other
:class:`_sql.ColumnElement` would fail to restore the correct "comparator"
object, which is used to generate SQL expressions specific to the type
object.
Fixes: #10213
Change-Id: I74e805024bcc0d93d549bd94757c2865b3117d72
(cherry picked from commit
9d2b83740ad5c700b28cf4ca7807c09c7338c36a)
--- /dev/null
+.. change::
+ :tags: bug, sql
+ :tickets: 10213
+ :versions: 2.0.20
+
+ Fixed issue where unpickling of a :class:`_schema.Column` or other
+ :class:`_sql.ColumnElement` would fail to restore the correct "comparator"
+ object, which is used to generate SQL expressions specific to the type
+ object.
else:
return comparator_factory(self)
+ def __setstate__(self, state):
+ self.__dict__.update(state)
+
def __getattr__(self, key):
try:
return getattr(self.comparator, key)
return op, self.type
+ # note: this reduce is needed for tests to pass under python 2.
+ # it does not appear to apply to python 3. It has however been
+ # modified to accommodate issue #10213. In SQLA 2 this reduce
+ # has been removed.
def __reduce__(self):
- return _reconstitute_comparator, (self.expr,)
+ return _reconstitute_comparator, (self.expr, self.expr.type)
hashable = True
"""Flag, if False, means values from this type aren't hashable.
return self.impl.comparator_factory
-def _reconstitute_comparator(expression):
- return expression.comparator
+def _reconstitute_comparator(expression, type_=None):
+ # changed for #10213, added type_ argument.
+ # for previous pickles, keep type_ optional
+ if type_ is None:
+ return expression.comparator
+
+ comparator_factory = type_.comparator_factory
+ return comparator_factory(expression)
def to_instance(typeobj, *arg, **kw):
import datetime
import operator
+import pickle
from sqlalchemy import and_
from sqlalchemy import between
from sqlalchemy.types import Indexable
from sqlalchemy.types import JSON
from sqlalchemy.types import MatchType
+from sqlalchemy.types import NullType
from sqlalchemy.types import TypeDecorator
from sqlalchemy.types import TypeEngine
from sqlalchemy.types import UserDefinedType
clause = tuple_(1, 2, 3)
eq_(str(clause), str(util.pickle.loads(util.pickle.dumps(clause))))
+ @testing.combinations(Integer(), String(), JSON(), argnames="typ")
+ @testing.variation("eval_first", [True, False])
+ def test_pickle_comparator(self, typ, eval_first):
+ """test #10213"""
+
+ table1 = Table("t", MetaData(), Column("x", typ))
+ t1 = table1.c.x
+
+ if eval_first:
+ t1.comparator
+
+ t1p = pickle.loads(pickle.dumps(table1.c.x))
+
+ is_not(t1p.comparator.__class__, NullType.Comparator)
+ is_(t1.comparator.__class__, t1p.comparator.__class__)
+
@testing.combinations(
(operator.lt, "<", ">"),
(operator.gt, ">", "<"),