]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- polymorphic_union() gets a "cast_nulls" option,
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 12 May 2011 16:47:09 +0000 (12:47 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 12 May 2011 16:47:09 +0000 (12:47 -0400)
    disables the usage of CAST when it renders
    the labeled NULL columns.  [ticket:1502]

  - polymorphic_union() renders the columns in their
    original table order, as according to the first
    table/selectable in the list of polymorphic
    unions in which they appear.  (which is itself
    an unordered mapping unless you pass an OrderedDict).

CHANGES
lib/sqlalchemy/orm/util.py
test/orm/inheritance/test_basic.py

diff --git a/CHANGES b/CHANGES
index 621a366ec2254a01eb9a3cc55e176f3922bda269..ec90b8bd87ec81baa423126f5956eb758d64df23 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -77,6 +77,16 @@ CHANGES
     reference the correct mapper.  [ticket:2163].
     Also in 0.6.8.
 
+  - polymorphic_union() gets a "cast_nulls" option,
+    disables the usage of CAST when it renders
+    the labeled NULL columns.  [ticket:1502]
+
+  - polymorphic_union() renders the columns in their
+    original table order, as according to the first
+    table/selectable in the list of polymorphic
+    unions in which they appear.  (which is itself 
+    an unordered mapping unless you pass an OrderedDict).
+
 - sql
   - Changed the handling in determination of join
     conditions such that foreign key errors are
index ecdf804bd9298c2edf63daabcaf6af29c93aa711..8448b545c40264c54f1ab0d56cf0849f211a93de 100644 (file)
@@ -65,14 +65,26 @@ def _validator_events(desc, key, validator):
     event.listen(desc, 'append', append, raw=True, retval=True)
     event.listen(desc, 'set', set_, raw=True, retval=True)
 
-def polymorphic_union(table_map, typecolname, aliasname='p_union'):
+def polymorphic_union(table_map, typecolname, aliasname='p_union', cast_nulls=True):
     """Create a ``UNION`` statement used by a polymorphic mapper.
 
     See  :ref:`concrete_inheritance` for an example of how
     this is used.
+    
+    :param table_map: mapping of polymorphic identities to 
+     :class:`.Table` objects.
+    :param typecolname: string name of a "discriminator" column, which will be 
+     derived from the query, producing the polymorphic identity for each row.  If
+     ``None``, no polymorphic discriminator is generated.
+    :param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()` 
+     construct generated.
+    :param cast_nulls: if True, non-existent columns, which are represented as labeled
+     NULLs, will be passed into CAST.   This is a legacy behavior that is problematic
+     on some backends such as Oracle - in which case it can be set to False.
+
     """
 
-    colnames = set()
+    colnames = util.OrderedSet()
     colnamemaps = {}
     types = {}
     for key in table_map.keys():
@@ -95,7 +107,10 @@ def polymorphic_union(table_map, typecolname, aliasname='p_union'):
         try:
             return colnamemaps[table][name]
         except KeyError:
-            return sql.cast(sql.null(), types[name]).label(name)
+            if cast_nulls:
+                return sql.cast(sql.null(), types[name]).label(name)
+            else:
+                return sql.type_coerce(sql.null(), types[name]).label(name)
 
     result = []
     for type, table in table_map.iteritems():
index ee408373a6267bd6f62790b9bf0a1e57acd19b37..ce1d8028d46bfe1acbb9af9e230f252078c1af7f 100644 (file)
@@ -5,7 +5,7 @@ from sqlalchemy import exc as sa_exc, util
 from sqlalchemy.orm import *
 from sqlalchemy.orm import exc as orm_exc, attributes
 from test.lib.assertsql import AllOf, CompiledSQL
-
+from sqlalchemy.sql import table, column
 from test.lib import testing, engines
 from test.lib import fixtures
 from test.orm import _fixtures
@@ -1816,4 +1816,60 @@ class DeleteOrphanTest(fixtures.MappedTest):
         sess.add(s1)
         assert_raises(sa_exc.DBAPIError, sess.flush)
 
+class PolymorphicUnionTest(fixtures.TestBase, testing.AssertsCompiledSQL):
+    __dialect__ = 'default'
+
+    def _fixture(self):
+        t1 = table('t1', column('c1', Integer), 
+                        column('c2', Integer), 
+                        column('c3', Integer))
+        t2 = table('t2', column('c1', Integer), column('c2', Integer), 
+                                column('c3', Integer), 
+                                column('c4', Integer))
+        t3 = table('t3', column('c1', Integer), 
+                                column('c3', Integer), 
+                                column('c5', Integer))
+        return t1, t2, t3
+
+    def test_type_col_present(self):
+        t1, t2, t3 = self._fixture()
+        self.assert_compile(
+            polymorphic_union(
+                util.OrderedDict([("a", t1), ("b", t2), ("c", t3)]),
+                'q1'
+            ),
+            "SELECT t1.c1, t1.c2, t1.c3, CAST(NULL AS INTEGER) AS c4, "
+            "CAST(NULL AS INTEGER) AS c5, 'a' AS q1 FROM t1 UNION ALL "
+            "SELECT t2.c1, t2.c2, t2.c3, t2.c4, CAST(NULL AS INTEGER) AS c5, "
+            "'b' AS q1 FROM t2 UNION ALL SELECT t3.c1, "
+            "CAST(NULL AS INTEGER) AS c2, t3.c3, CAST(NULL AS INTEGER) AS c4, "
+            "t3.c5, 'c' AS q1 FROM t3"
+        )
+
+    def test_type_col_non_present(self):
+        t1, t2, t3 = self._fixture()
+        self.assert_compile(
+            polymorphic_union(
+                util.OrderedDict([("a", t1), ("b", t2), ("c", t3)]),
+                None
+            ),
+            "SELECT t1.c1, t1.c2, t1.c3, CAST(NULL AS INTEGER) AS c4, "
+            "CAST(NULL AS INTEGER) AS c5 FROM t1 UNION ALL SELECT t2.c1, "
+            "t2.c2, t2.c3, t2.c4, CAST(NULL AS INTEGER) AS c5 FROM t2 "
+            "UNION ALL SELECT t3.c1, CAST(NULL AS INTEGER) AS c2, t3.c3, "
+            "CAST(NULL AS INTEGER) AS c4, t3.c5 FROM t3"
+        )
+
+    def test_no_cast_null(self):
+        t1, t2, t3 = self._fixture()
+        self.assert_compile(
+            polymorphic_union(
+                util.OrderedDict([("a", t1), ("b", t2), ("c", t3)]),
+                'q1', cast_nulls=False
+            ),
+            "SELECT t1.c1, t1.c2, t1.c3, NULL AS c4, NULL AS c5, 'a' AS q1 "
+            "FROM t1 UNION ALL SELECT t2.c1, t2.c2, t2.c3, t2.c4, NULL AS c5, "
+            "'b' AS q1 FROM t2 UNION ALL SELECT t3.c1, NULL AS c2, t3.c3, "
+            "NULL AS c4, t3.c5, 'c' AS q1 FROM t3"
+        )