diff options
Diffstat (limited to 'test/sql')
| -rw-r--r-- | test/sql/test_compare.py | 515 | ||||
| -rw-r--r-- | test/sql/test_external_traversal.py (renamed from test/sql/test_generative.py) | 1 | ||||
| -rw-r--r-- | test/sql/test_operators.py | 3 | ||||
| -rw-r--r-- | test/sql/test_selectable.py | 58 | ||||
| -rw-r--r-- | test/sql/test_utils.py | 2 |
5 files changed, 427 insertions, 152 deletions
diff --git a/test/sql/test_compare.py b/test/sql/test_compare.py index d48a8ed33..5d21960b7 100644 --- a/test/sql/test_compare.py +++ b/test/sql/test_compare.py @@ -32,6 +32,7 @@ from sqlalchemy.sql import operators from sqlalchemy.sql import True_ from sqlalchemy.sql import type_coerce from sqlalchemy.sql import visitors +from sqlalchemy.sql.base import HasCacheKey from sqlalchemy.sql.elements import _label_reference from sqlalchemy.sql.elements import _textual_label_reference from sqlalchemy.sql.elements import Annotated @@ -46,13 +47,13 @@ from sqlalchemy.sql.functions import FunctionElement from sqlalchemy.sql.functions import GenericFunction from sqlalchemy.sql.functions import ReturnTypeFromArgs from sqlalchemy.sql.selectable import _OffsetLimitParam +from sqlalchemy.sql.selectable import AliasedReturnsRows from sqlalchemy.sql.selectable import FromGrouping from sqlalchemy.sql.selectable import Selectable from sqlalchemy.sql.selectable import SelectStatementGrouping -from sqlalchemy.testing import assert_raises_message +from sqlalchemy.sql.visitors import InternalTraversal from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures -from sqlalchemy.testing import is_ from sqlalchemy.testing import is_false from sqlalchemy.testing import is_true from sqlalchemy.testing import ne_ @@ -63,8 +64,17 @@ meta = MetaData() meta2 = MetaData() table_a = Table("a", meta, Column("a", Integer), Column("b", String)) +table_b_like_a = Table("b2", meta, Column("a", Integer), Column("b", String)) + table_a_2 = Table("a", meta2, Column("a", Integer), Column("b", String)) +table_a_2_fs = Table( + "a", meta2, Column("a", Integer), Column("b", String), schema="fs" +) +table_a_2_bs = Table( + "a", meta2, Column("a", Integer), Column("b", String), schema="bs" +) + table_b = Table("b", meta, Column("a", Integer), Column("b", Integer)) table_c = Table("c", meta, Column("x", Integer), Column("y", Integer)) @@ -72,8 +82,18 @@ table_c = Table("c", meta, Column("x", Integer), Column("y", Integer)) table_d = Table("d", meta, Column("y", Integer), Column("z", Integer)) -class CompareAndCopyTest(fixtures.TestBase): +class MyEntity(HasCacheKey): + def __init__(self, name, element): + self.name = name + self.element = element + + _cache_key_traversal = [ + ("name", InternalTraversal.dp_string), + ("element", InternalTraversal.dp_clauseelement), + ] + +class CoreFixtures(object): # lambdas which return a tuple of ColumnElement objects. # must return at least two objects that should compare differently. # to test more varieties of "difference" additional objects can be added. @@ -100,11 +120,47 @@ class CompareAndCopyTest(fixtures.TestBase): text("select a, b, c from table").columns( a=Integer, b=String, c=Integer ), + text("select a, b, c from table where foo=:bar").bindparams( + bindparam("bar", Integer) + ), + text("select a, b, c from table where foo=:foo").bindparams( + bindparam("foo", Integer) + ), + text("select a, b, c from table where foo=:bar").bindparams( + bindparam("bar", String) + ), ), lambda: ( column("q") == column("x"), column("q") == column("y"), column("z") == column("x"), + column("z") + column("x"), + column("z") - column("x"), + column("x") - column("z"), + column("z") > column("x"), + # note these two are mathematically equivalent but for now they + # are considered to be different + column("z") >= column("x"), + column("x") <= column("z"), + column("q").between(5, 6), + column("q").between(5, 6, symmetric=True), + column("q").like("somstr"), + column("q").like("somstr", escape="\\"), + column("q").like("somstr", escape="X"), + ), + lambda: ( + table_a.c.a, + table_a.c.a._annotate({"orm": True}), + table_a.c.a._annotate({"orm": True})._annotate({"bar": False}), + table_a.c.a._annotate( + {"orm": True, "parententity": MyEntity("a", table_a)} + ), + table_a.c.a._annotate( + {"orm": True, "parententity": MyEntity("b", table_a)} + ), + table_a.c.a._annotate( + {"orm": True, "parententity": MyEntity("b", select([table_a]))} + ), ), lambda: ( cast(column("q"), Integer), @@ -226,6 +282,58 @@ class CompareAndCopyTest(fixtures.TestBase): .correlate_except(table_b), ), lambda: ( + select([table_a.c.a]).cte(), + select([table_a.c.a]).cte(recursive=True), + select([table_a.c.a]).cte(name="some_cte", recursive=True), + select([table_a.c.a]).cte(name="some_cte"), + select([table_a.c.a]).cte(name="some_cte").alias("other_cte"), + select([table_a.c.a]) + .cte(name="some_cte") + .union_all(select([table_a.c.a])), + select([table_a.c.a]) + .cte(name="some_cte") + .union_all(select([table_a.c.b])), + select([table_a.c.a]).lateral(), + select([table_a.c.a]).lateral(name="bar"), + table_a.tablesample(func.bernoulli(1)), + table_a.tablesample(func.bernoulli(1), seed=func.random()), + table_a.tablesample(func.bernoulli(1), seed=func.other_random()), + table_a.tablesample(func.hoho(1)), + table_a.tablesample(func.bernoulli(1), name="bar"), + table_a.tablesample( + func.bernoulli(1), name="bar", seed=func.random() + ), + ), + lambda: ( + select([table_a.c.a]), + select([table_a.c.a]).prefix_with("foo"), + select([table_a.c.a]).prefix_with("foo", dialect="mysql"), + select([table_a.c.a]).prefix_with("foo", dialect="postgresql"), + select([table_a.c.a]).prefix_with("bar"), + select([table_a.c.a]).suffix_with("bar"), + ), + lambda: ( + select([table_a_2.c.a]), + select([table_a_2_fs.c.a]), + select([table_a_2_bs.c.a]), + ), + lambda: ( + select([table_a.c.a]), + select([table_a.c.a]).with_hint(None, "some hint"), + select([table_a.c.a]).with_hint(None, "some other hint"), + select([table_a.c.a]).with_hint(table_a, "some hint"), + select([table_a.c.a]) + .with_hint(table_a, "some hint") + .with_hint(None, "some other hint"), + select([table_a.c.a]).with_hint(table_a, "some other hint"), + select([table_a.c.a]).with_hint( + table_a, "some hint", dialect_name="mysql" + ), + select([table_a.c.a]).with_hint( + table_a, "some hint", dialect_name="postgresql" + ), + ), + lambda: ( table_a.join(table_b, table_a.c.a == table_b.c.a), table_a.join( table_b, and_(table_a.c.a == table_b.c.a, table_a.c.b == 1) @@ -273,12 +381,202 @@ class CompareAndCopyTest(fixtures.TestBase): table("a", column("x"), column("y", Integer)), table("a", column("q"), column("y", Integer)), ), - lambda: ( - Table("a", MetaData(), Column("q", Integer), Column("b", String)), - Table("b", MetaData(), Column("q", Integer), Column("b", String)), - ), + lambda: (table_a, table_b), ] + def _complex_fixtures(): + def one(): + a1 = table_a.alias() + a2 = table_b_like_a.alias() + + stmt = ( + select([table_a.c.a, a1.c.b, a2.c.b]) + .where(table_a.c.b == a1.c.b) + .where(a1.c.b == a2.c.b) + .where(a1.c.a == 5) + ) + + return stmt + + def one_diff(): + a1 = table_b_like_a.alias() + a2 = table_a.alias() + + stmt = ( + select([table_a.c.a, a1.c.b, a2.c.b]) + .where(table_a.c.b == a1.c.b) + .where(a1.c.b == a2.c.b) + .where(a1.c.a == 5) + ) + + return stmt + + def two(): + inner = one().subquery() + + stmt = select([table_b.c.a, inner.c.a, inner.c.b]).select_from( + table_b.join(inner, table_b.c.b == inner.c.b) + ) + + return stmt + + def three(): + + a1 = table_a.alias() + a2 = table_a.alias() + ex = exists().where(table_b.c.b == a1.c.a) + + stmt = ( + select([a1.c.a, a2.c.a]) + .select_from(a1.join(a2, a1.c.b == a2.c.b)) + .where(ex) + ) + return stmt + + return [one(), one_diff(), two(), three()] + + fixtures.append(_complex_fixtures) + + +class CacheKeyFixture(object): + def _run_cache_key_fixture(self, fixture): + case_a = fixture() + case_b = fixture() + + for a, b in itertools.combinations_with_replacement( + range(len(case_a)), 2 + ): + if a == b: + a_key = case_a[a]._generate_cache_key() + b_key = case_b[b]._generate_cache_key() + eq_(a_key.key, b_key.key) + + for a_param, b_param in zip( + a_key.bindparams, b_key.bindparams + ): + assert a_param.compare(b_param, compare_values=False) + else: + a_key = case_a[a]._generate_cache_key() + b_key = case_b[b]._generate_cache_key() + + if a_key.key == b_key.key: + for a_param, b_param in zip( + a_key.bindparams, b_key.bindparams + ): + if not a_param.compare(b_param, compare_values=True): + break + else: + # this fails unconditionally since we could not + # find bound parameter values that differed. + # Usually we intended to get two distinct keys here + # so the failure will be more descriptive using the + # ne_() assertion. + ne_(a_key.key, b_key.key) + else: + ne_(a_key.key, b_key.key) + + # ClauseElement-specific test to ensure the cache key + # collected all the bound parameters + if isinstance(case_a[a], ClauseElement) and isinstance( + case_b[b], ClauseElement + ): + assert_a_params = [] + assert_b_params = [] + visitors.traverse_depthfirst( + case_a[a], {}, {"bindparam": assert_a_params.append} + ) + visitors.traverse_depthfirst( + case_b[b], {}, {"bindparam": assert_b_params.append} + ) + + # note we're asserting the order of the params as well as + # if there are dupes or not. ordering has to be deterministic + # and matches what a traversal would provide. + # regular traverse_depthfirst does produce dupes in cases like + # select([some_alias]). + # select_from(join(some_alias, other_table)) + # where a bound parameter is inside of some_alias. the + # cache key case is more minimalistic + eq_( + sorted(a_key.bindparams, key=lambda b: b.key), + sorted( + util.unique_list(assert_a_params), key=lambda b: b.key + ), + ) + eq_( + sorted(b_key.bindparams, key=lambda b: b.key), + sorted( + util.unique_list(assert_b_params), key=lambda b: b.key + ), + ) + + +class CacheKeyTest(CacheKeyFixture, CoreFixtures, fixtures.TestBase): + def test_cache_key(self): + for fixture in self.fixtures: + self._run_cache_key_fixture(fixture) + + def test_cache_key_unknown_traverse(self): + class Foobar1(ClauseElement): + _traverse_internals = [ + ("key", InternalTraversal.dp_anon_name), + ("type_", InternalTraversal.dp_unknown_structure), + ] + + def __init__(self, key, type_): + self.key = key + self.type_ = type_ + + f1 = Foobar1("foo", String()) + eq_(f1._generate_cache_key(), None) + + def test_cache_key_no_method(self): + class Foobar1(ClauseElement): + pass + + class Foobar2(ColumnElement): + pass + + # the None for cache key will prevent objects + # which contain these elements from being cached. + f1 = Foobar1() + eq_(f1._generate_cache_key(), None) + + f2 = Foobar2() + eq_(f2._generate_cache_key(), None) + + s1 = select([column("q"), Foobar2()]) + + eq_(s1._generate_cache_key(), None) + + def test_get_children_no_method(self): + class Foobar1(ClauseElement): + pass + + class Foobar2(ColumnElement): + pass + + f1 = Foobar1() + eq_(f1.get_children(), []) + + f2 = Foobar2() + eq_(f2.get_children(), []) + + def test_copy_internals_no_method(self): + class Foobar1(ClauseElement): + pass + + class Foobar2(ColumnElement): + pass + + f1 = Foobar1() + f2 = Foobar2() + + f1._copy_internals() + f2._copy_internals() + + +class CompareAndCopyTest(CoreFixtures, fixtures.TestBase): @classmethod def setup_class(cls): # TODO: we need to get dialects here somehow, perhaps in test_suite? @@ -293,7 +591,10 @@ class CompareAndCopyTest(fixtures.TestBase): cls for cls in class_hierarchy(ClauseElement) if issubclass(cls, (ColumnElement, Selectable)) - and "__init__" in cls.__dict__ + and ( + "__init__" in cls.__dict__ + or issubclass(cls, AliasedReturnsRows) + ) and not issubclass(cls, (Annotated)) and "orm" not in cls.__module__ and "compiler" not in cls.__module__ @@ -318,123 +619,16 @@ class CompareAndCopyTest(fixtures.TestBase): ): if a == b: is_true( - case_a[a].compare( - case_b[b], arbitrary_expression=True - ), + case_a[a].compare(case_b[b], compare_annotations=True), "%r != %r" % (case_a[a], case_b[b]), ) else: is_false( - case_a[a].compare( - case_b[b], arbitrary_expression=True - ), + case_a[a].compare(case_b[b], compare_annotations=True), "%r == %r" % (case_a[a], case_b[b]), ) - def test_cache_key(self): - def assert_params_append(assert_params): - def append(param): - if param._value_required_for_cache: - assert_params.append(param) - else: - is_(param.value, None) - - return append - - for fixture in self.fixtures: - case_a = fixture() - case_b = fixture() - - for a, b in itertools.combinations_with_replacement( - range(len(case_a)), 2 - ): - - assert_a_params = [] - assert_b_params = [] - - visitors.traverse_depthfirst( - case_a[a], - {}, - {"bindparam": assert_params_append(assert_a_params)}, - ) - visitors.traverse_depthfirst( - case_b[b], - {}, - {"bindparam": assert_params_append(assert_b_params)}, - ) - if assert_a_params: - assert_raises_message( - NotImplementedError, - "bindparams collection argument required ", - case_a[a]._cache_key, - ) - if assert_b_params: - assert_raises_message( - NotImplementedError, - "bindparams collection argument required ", - case_b[b]._cache_key, - ) - - if not assert_a_params and not assert_b_params: - if a == b: - eq_(case_a[a]._cache_key(), case_b[b]._cache_key()) - else: - ne_(case_a[a]._cache_key(), case_b[b]._cache_key()) - - def test_cache_key_gather_bindparams(self): - for fixture in self.fixtures: - case_a = fixture() - case_b = fixture() - - # in the "bindparams" case, the cache keys for bound parameters - # with only different values will be the same, but the params - # themselves are gathered into a collection. - for a, b in itertools.combinations_with_replacement( - range(len(case_a)), 2 - ): - a_params = {"bindparams": []} - b_params = {"bindparams": []} - if a == b: - a_key = case_a[a]._cache_key(**a_params) - b_key = case_b[b]._cache_key(**b_params) - eq_(a_key, b_key) - - if a_params["bindparams"]: - for a_param, b_param in zip( - a_params["bindparams"], b_params["bindparams"] - ): - assert a_param.compare(b_param) - else: - a_key = case_a[a]._cache_key(**a_params) - b_key = case_b[b]._cache_key(**b_params) - - if a_key == b_key: - for a_param, b_param in zip( - a_params["bindparams"], b_params["bindparams"] - ): - if not a_param.compare(b_param): - break - else: - assert False, "Bound parameters are all the same" - else: - ne_(a_key, b_key) - - assert_a_params = [] - assert_b_params = [] - visitors.traverse_depthfirst( - case_a[a], {}, {"bindparam": assert_a_params.append} - ) - visitors.traverse_depthfirst( - case_b[b], {}, {"bindparam": assert_b_params.append} - ) - - # note we're asserting the order of the params as well as - # if there are dupes or not. ordering has to be deterministic - # and matches what a traversal would provide. - eq_(a_params["bindparams"], assert_a_params) - eq_(b_params["bindparams"], assert_b_params) - def test_compare_col_identity(self): stmt1 = ( select([table_a.c.a, table_b.c.b]) @@ -473,8 +667,9 @@ class CompareAndCopyTest(fixtures.TestBase): assert case_a[0].compare(case_b[0]) - clone = case_a[0]._clone() - clone._copy_internals() + clone = visitors.replacement_traverse( + case_a[0], {}, lambda elem: None + ) assert clone.compare(case_b[0]) @@ -511,6 +706,37 @@ class CompareAndCopyTest(fixtures.TestBase): class CompareClausesTest(fixtures.TestBase): + def test_compare_metadata_tables(self): + # metadata Table objects cache on their own identity, not their + # structure. This is mainly to reduce the size of cache keys + # as well as reduce computational overhead, as Table objects have + # very large internal state and they are also generally global + # objects. + + t1 = Table("a", MetaData(), Column("q", Integer), Column("p", Integer)) + t2 = Table("a", MetaData(), Column("q", Integer), Column("p", Integer)) + + ne_(t1._generate_cache_key(), t2._generate_cache_key()) + + eq_(t1._generate_cache_key().key, (t1,)) + + def test_compare_adhoc_tables(self): + # non-metadata tables compare on their structure. these objects are + # not commonly used. + + # note this test is a bit redundant as we have a similar test + # via the fixtures also + t1 = table("a", Column("q", Integer), Column("p", Integer)) + t2 = table("a", Column("q", Integer), Column("p", Integer)) + t3 = table("b", Column("q", Integer), Column("p", Integer)) + t4 = table("a", Column("q", Integer), Column("x", Integer)) + + eq_(t1._generate_cache_key(), t2._generate_cache_key()) + + ne_(t1._generate_cache_key(), t3._generate_cache_key()) + ne_(t1._generate_cache_key(), t4._generate_cache_key()) + ne_(t3._generate_cache_key(), t4._generate_cache_key()) + def test_compare_comparison_associative(self): l1 = table_c.c.x == table_d.c.y @@ -521,6 +747,15 @@ class CompareClausesTest(fixtures.TestBase): is_true(l1.compare(l2)) is_false(l1.compare(l3)) + def test_compare_comparison_non_commutative_inverses(self): + l1 = table_c.c.x >= table_d.c.y + l2 = table_d.c.y < table_c.c.x + l3 = table_d.c.y <= table_c.c.x + + # we're not doing this kind of commutativity right now. + is_false(l1.compare(l2)) + is_false(l1.compare(l3)) + def test_compare_clauselist_associative(self): l1 = and_(table_c.c.x == table_d.c.y, table_c.c.y == table_d.c.z) @@ -624,3 +859,45 @@ class CompareClausesTest(fixtures.TestBase): use_proxies=True, ) ) + + def test_compare_annotated_clears_mapping(self): + t = table("t", column("x"), column("y")) + x_a = t.c.x._annotate({"foo": True}) + x_b = t.c.x._annotate({"foo": True}) + + is_true(x_a.compare(x_b, compare_annotations=True)) + is_false( + x_a.compare(x_b._annotate({"bar": True}), compare_annotations=True) + ) + + s1 = select([t.c.x])._annotate({"foo": True}) + s2 = select([t.c.x])._annotate({"foo": True}) + + is_true(s1.compare(s2, compare_annotations=True)) + + is_false( + s1.compare(s2._annotate({"bar": True}), compare_annotations=True) + ) + + def test_compare_annotated_wo_annotations(self): + t = table("t", column("x"), column("y")) + x_a = t.c.x._annotate({}) + x_b = t.c.x._annotate({"foo": True}) + + is_true(t.c.x.compare(x_a)) + is_true(x_b.compare(x_a)) + + is_true(x_a.compare(t.c.x)) + is_false(x_a.compare(t.c.y)) + is_false(t.c.y.compare(x_a)) + is_true((t.c.x == 5).compare(x_a == 5)) + is_false((t.c.y == 5).compare(x_a == 5)) + + s = select([t]).subquery() + x_p = s.c.x + is_false(x_a.compare(x_p)) + is_false(t.c.x.compare(x_p)) + x_p_a = x_p._annotate({}) + is_true(x_p_a.compare(x_p)) + is_true(x_p.compare(x_p_a)) + is_false(x_p_a.compare(x_a)) diff --git a/test/sql/test_generative.py b/test/sql/test_external_traversal.py index 8d347a522..8bfe5cf6f 100644 --- a/test/sql/test_generative.py +++ b/test/sql/test_external_traversal.py @@ -55,6 +55,7 @@ class TraversalTest(fixtures.TestBase, AssertsExecutionResults): # identity semantics. class A(ClauseElement): __visit_name__ = "a" + _traverse_internals = [] def __init__(self, expr): self.expr = expr diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index 637f1f8a5..06cfdc4b5 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -118,11 +118,14 @@ class DefaultColumnComparatorTest(fixtures.TestBase): ) ) + modifiers = operator(left, right).modifiers + assert operator(left, right).compare( BinaryExpression( coercions.expect(roles.WhereHavingRole, left), coercions.expect(roles.WhereHavingRole, right), operator, + modifiers=modifiers, ) ) diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index 2bc7ccc93..184e4a99c 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -1070,7 +1070,7 @@ class SelectableTest( s4 = s3.with_only_columns([table2.c.b]) self.assert_compile(s4, "SELECT t2.b FROM t2") - def test_from_list_warning_against_existing(self): + def test_from_list_against_existing_one(self): c1 = Column("c1", Integer) s = select([c1]) @@ -1081,7 +1081,7 @@ class SelectableTest( self.assert_compile(s, "SELECT t.c1 FROM t") - def test_from_list_recovers_after_warning(self): + def test_from_list_against_existing_two(self): c1 = Column("c1", Integer) c2 = Column("c2", Integer) @@ -1090,18 +1090,11 @@ class SelectableTest( # force a compile. eq_(str(s), "SELECT c1") - @testing.emits_warning() - def go(): - return Table("t", MetaData(), c1, c2) - - t = go() + t = Table("t", MetaData(), c1, c2) eq_(c1._from_objects, [t]) eq_(c2._from_objects, [t]) - # 's' has been baked. Can't afford - # not caching select._froms. - # hopefully the warning will clue the user self.assert_compile(s, "SELECT t.c1 FROM t") self.assert_compile(select([c1]), "SELECT t.c1 FROM t") self.assert_compile(select([c2]), "SELECT t.c2 FROM t") @@ -1124,6 +1117,26 @@ class SelectableTest( "foo", ) + def test_whereclause_adapted(self): + table1 = table("t1", column("a")) + + s1 = select([table1]).subquery() + + s2 = select([s1]).where(s1.c.a == 5) + + assert s2._whereclause.left.table is s1 + + ta = select([table1]).subquery() + + s3 = sql_util.ClauseAdapter(ta).traverse(s2) + + assert s1 not in s3._froms + + # these are new assumptions with the newer approach that + # actively swaps out whereclause and others + assert s3._whereclause.left.table is not s1 + assert s3._whereclause.left.table in s3._froms + class RefreshForNewColTest(fixtures.TestBase): def test_join_uninit(self): @@ -2241,25 +2254,6 @@ class AnnotationsTest(fixtures.TestBase): annot = obj._annotate({}) ne_(set([obj]), set([annot])) - def test_compare(self): - t = table("t", column("x"), column("y")) - x_a = t.c.x._annotate({}) - assert t.c.x.compare(x_a) - assert x_a.compare(t.c.x) - assert not x_a.compare(t.c.y) - assert not t.c.y.compare(x_a) - assert (t.c.x == 5).compare(x_a == 5) - assert not (t.c.y == 5).compare(x_a == 5) - - s = select([t]).subquery() - x_p = s.c.x - assert not x_a.compare(x_p) - assert not t.c.x.compare(x_p) - x_p_a = x_p._annotate({}) - assert x_p_a.compare(x_p) - assert x_p.compare(x_p_a) - assert not x_p_a.compare(x_a) - def test_proxy_set_iteration_includes_annotated(self): from sqlalchemy.schema import Column @@ -2542,13 +2536,13 @@ class AnnotationsTest(fixtures.TestBase): ): # the columns clause isn't changed at all assert sel._raw_columns[0].table is a1 - assert sel._froms[0] is sel._froms[1].left + assert sel._froms[0].element is sel._froms[1].left.element eq_(str(s), str(sel)) # when we are modifying annotations sets only - # partially, each element is copied unconditionally - # when encountered. + # partially, elements are copied uniquely based on id(). + # this is new as of 1.4, previously they'd be copied every time for sel in ( sql_util._deep_deannotate(s, {"foo": "bar"}), sql_util._deep_annotate(s, {"foo": "bar"}), diff --git a/test/sql/test_utils.py b/test/sql/test_utils.py index 988d5331e..48d6de6db 100644 --- a/test/sql/test_utils.py +++ b/test/sql/test_utils.py @@ -7,6 +7,6 @@ from sqlalchemy.testing import fixtures class MiscTest(fixtures.TestBase): def test_column_element_no_visit(self): class MyElement(ColumnElement): - pass + _traverse_internals = [] eq_(sql_util.find_tables(MyElement(), check_columns=True), []) |
