summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-02-26 15:34:49 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2014-02-26 15:34:49 -0500
commit302ad6228a12fe5cb4c5d332e5bab65ed373bc01 (patch)
tree9e39551b168090a75c28c1d5ba45ef24cdbfc1da /test
parentbf67069d264cba3feed8a48614289d605ed61a55 (diff)
downloadsqlalchemy-302ad6228a12fe5cb4c5d332e5bab65ed373bc01.tar.gz
- Some changes to how the :attr:`.FromClause.c` collection behaves
when presented with duplicate columns. The behavior of emitting a warning and replacing the old column with the same name still remains to some degree; the replacement in particular is to maintain backwards compatibility. However, the replaced column still remains associated with the ``c`` collection now in a collection ``._all_columns``, which is used by constructs such as aliases and unions, to deal with the set of columns in ``c`` more towards what is actually in the list of columns rather than the unique set of key names. This helps with situations where SELECT statements with same-named columns are used in unions and such, so that the union can match the columns up positionally and also there's some chance of :meth:`.FromClause.corresponding_column` still being usable here (it can now return a column that is only in selectable.c._all_columns and not otherwise named). The new collection is underscored as we still need to decide where this list might end up. Theoretically it would become the result of iter(selectable.c), however this would mean that the length of the iteration would no longer match the length of keys(), and that behavior needs to be checked out. fixes #2974 - add a bunch more tests for ColumnCollection
Diffstat (limited to 'test')
-rw-r--r--test/base/test_utils.py157
-rw-r--r--test/sql/test_selectable.py35
2 files changed, 191 insertions, 1 deletions
diff --git a/test/base/test_utils.py b/test/base/test_utils.py
index 86e4b190a..e6ea06296 100644
--- a/test/base/test_utils.py
+++ b/test/base/test_utils.py
@@ -5,7 +5,7 @@ from sqlalchemy.testing import assert_raises, assert_raises_message, fixtures
from sqlalchemy.testing import eq_, is_, ne_, fails_if
from sqlalchemy.testing.util import picklers, gc_collect
from sqlalchemy.util import classproperty, WeakSequence, get_callable_argspec
-
+from sqlalchemy.sql import column
class KeyedTupleTest():
@@ -298,6 +298,161 @@ class ColumnCollectionTest(fixtures.TestBase):
assert (cc1 == cc2).compare(c1 == c2)
assert not (cc1 == cc3).compare(c2 == c3)
+ @testing.emits_warning("Column ")
+ def test_dupes_add(self):
+ cc = sql.ColumnCollection()
+
+ c1, c2a, c3, c2b = column('c1'), column('c2'), column('c3'), column('c2')
+
+ cc.add(c1)
+ cc.add(c2a)
+ cc.add(c3)
+ cc.add(c2b)
+
+ eq_(cc._all_columns, [c1, c2a, c3, c2b])
+
+ # for iter, c2a is replaced by c2b, ordering
+ # is maintained in that way. ideally, iter would be
+ # the same as the "_all_columns" collection.
+ eq_(list(cc), [c1, c2b, c3])
+
+ assert cc.contains_column(c2a)
+ assert cc.contains_column(c2b)
+
+ ci = cc.as_immutable()
+ eq_(ci._all_columns, [c1, c2a, c3, c2b])
+ eq_(list(ci), [c1, c2b, c3])
+
+ def test_replace(self):
+ cc = sql.ColumnCollection()
+
+ c1, c2a, c3, c2b = column('c1'), column('c2'), column('c3'), column('c2')
+
+ cc.add(c1)
+ cc.add(c2a)
+ cc.add(c3)
+
+ cc.replace(c2b)
+
+ eq_(cc._all_columns, [c1, c2b, c3])
+ eq_(list(cc), [c1, c2b, c3])
+
+ assert not cc.contains_column(c2a)
+ assert cc.contains_column(c2b)
+
+ ci = cc.as_immutable()
+ eq_(ci._all_columns, [c1, c2b, c3])
+ eq_(list(ci), [c1, c2b, c3])
+
+ def test_replace_key_matches(self):
+ cc = sql.ColumnCollection()
+
+ c1, c2a, c3, c2b = column('c1'), column('c2'), column('c3'), column('X')
+ c2b.key = 'c2'
+
+ cc.add(c1)
+ cc.add(c2a)
+ cc.add(c3)
+
+ cc.replace(c2b)
+
+ assert not cc.contains_column(c2a)
+ assert cc.contains_column(c2b)
+
+ eq_(cc._all_columns, [c1, c2b, c3])
+ eq_(list(cc), [c1, c2b, c3])
+
+ ci = cc.as_immutable()
+ eq_(ci._all_columns, [c1, c2b, c3])
+ eq_(list(ci), [c1, c2b, c3])
+
+ def test_replace_name_matches(self):
+ cc = sql.ColumnCollection()
+
+ c1, c2a, c3, c2b = column('c1'), column('c2'), column('c3'), column('c2')
+ c2b.key = 'X'
+
+ cc.add(c1)
+ cc.add(c2a)
+ cc.add(c3)
+
+ cc.replace(c2b)
+
+ assert not cc.contains_column(c2a)
+ assert cc.contains_column(c2b)
+
+ eq_(cc._all_columns, [c1, c2b, c3])
+ eq_(list(cc), [c1, c3, c2b])
+
+ ci = cc.as_immutable()
+ eq_(ci._all_columns, [c1, c2b, c3])
+ eq_(list(ci), [c1, c3, c2b])
+
+ def test_replace_no_match(self):
+ cc = sql.ColumnCollection()
+
+ c1, c2, c3, c4 = column('c1'), column('c2'), column('c3'), column('c4')
+ c4.key = 'X'
+
+ cc.add(c1)
+ cc.add(c2)
+ cc.add(c3)
+
+ cc.replace(c4)
+
+ assert cc.contains_column(c2)
+ assert cc.contains_column(c4)
+
+ eq_(cc._all_columns, [c1, c2, c3, c4])
+ eq_(list(cc), [c1, c2, c3, c4])
+
+ ci = cc.as_immutable()
+ eq_(ci._all_columns, [c1, c2, c3, c4])
+ eq_(list(ci), [c1, c2, c3, c4])
+
+ def test_dupes_extend(self):
+ cc = sql.ColumnCollection()
+
+ c1, c2a, c3, c2b = column('c1'), column('c2'), column('c3'), column('c2')
+
+ cc.add(c1)
+ cc.add(c2a)
+
+ cc.extend([c3, c2b])
+
+ eq_(cc._all_columns, [c1, c2a, c3, c2b])
+
+ # for iter, c2a is replaced by c2b, ordering
+ # is maintained in that way. ideally, iter would be
+ # the same as the "_all_columns" collection.
+ eq_(list(cc), [c1, c2b, c3])
+
+ assert cc.contains_column(c2a)
+ assert cc.contains_column(c2b)
+
+ ci = cc.as_immutable()
+ eq_(ci._all_columns, [c1, c2a, c3, c2b])
+ eq_(list(ci), [c1, c2b, c3])
+
+ def test_dupes_update(self):
+ cc = sql.ColumnCollection()
+
+ c1, c2a, c3, c2b = column('c1'), column('c2'), column('c3'), column('c2')
+
+ cc.add(c1)
+ cc.add(c2a)
+
+ cc.update([(c3.key, c3), (c2b.key, c2b)])
+
+ eq_(cc._all_columns, [c1, c2a, c3, c2b])
+
+ assert cc.contains_column(c2a)
+ assert cc.contains_column(c2b)
+
+ # for iter, c2a is replaced by c2b, ordering
+ # is maintained in that way. ideally, iter would be
+ # the same as the "_all_columns" collection.
+ eq_(list(cc), [c1, c2b, c3])
class LRUTest(fixtures.TestBase):
diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py
index 9617cfdf7..5d3d53b88 100644
--- a/test/sql/test_selectable.py
+++ b/test/sql/test_selectable.py
@@ -413,6 +413,41 @@ class SelectableTest(fixtures.TestBase, AssertsExecutionResults, AssertsCompiled
assert u2.corresponding_column(s1.c.col1) is u2.c.col1
assert u2.corresponding_column(s2.c.col1) is u2.c.col1
+ @testing.emits_warning("Column 'col1'")
+ def test_union_dupe_keys(self):
+ s1 = select([table1.c.col1, table1.c.col2, table2.c.col1])
+ s2 = select([table2.c.col1, table2.c.col2, table2.c.col3])
+ u1 = union(s1, s2)
+
+ assert u1.corresponding_column(s1.c._all_columns[0]) is u1.c._all_columns[0]
+ assert u1.corresponding_column(s2.c.col1) is u1.c._all_columns[0]
+ assert u1.corresponding_column(s1.c.col2) is u1.c.col2
+ assert u1.corresponding_column(s2.c.col2) is u1.c.col2
+
+ assert u1.corresponding_column(s2.c.col3) is u1.c._all_columns[2]
+
+ assert u1.corresponding_column(table2.c.col1) is u1.c._all_columns[2]
+ assert u1.corresponding_column(table2.c.col3) is u1.c._all_columns[2]
+
+ @testing.emits_warning("Column 'col1'")
+ def test_union_alias_dupe_keys(self):
+ s1 = select([table1.c.col1, table1.c.col2, table2.c.col1]).alias()
+ s2 = select([table2.c.col1, table2.c.col2, table2.c.col3])
+ u1 = union(s1, s2)
+
+ assert u1.corresponding_column(s1.c._all_columns[0]) is u1.c._all_columns[0]
+ assert u1.corresponding_column(s2.c.col1) is u1.c._all_columns[0]
+ assert u1.corresponding_column(s1.c.col2) is u1.c.col2
+ assert u1.corresponding_column(s2.c.col2) is u1.c.col2
+
+ assert u1.corresponding_column(s2.c.col3) is u1.c._all_columns[2]
+
+ # this differs from the non-alias test because table2.c.col1 is
+ # more directly at s2.c.col1 than it is s1.c.col1.
+ assert u1.corresponding_column(table2.c.col1) is u1.c._all_columns[0]
+ assert u1.corresponding_column(table2.c.col3) is u1.c._all_columns[2]
+
+
def test_select_union(self):
# like testaliasunion, but off a Select off the union.