Mike Bayer avatar Mike Bayer committed 42fa28e

- 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).

Comments (0)

Files changed (3)

     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

lib/sqlalchemy/orm/util.py

     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():
         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():

test/orm/inheritance/test_basic.py

 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
         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"
+        )
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.