]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
reconcile Mapper properties ordering against mapped Table
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 24 Oct 2022 15:29:36 +0000 (11:29 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 25 Oct 2022 18:30:54 +0000 (14:30 -0400)
Changed a fundamental configuration behavior of :class:`.Mapper`, where
:class:`_schema.Column` objects that are explicitly present in the
:paramref:`_orm.Mapper.properties` dictionary, either directly or enclosed
within a mapper property object, will now be mapped within the order of how
they appear within the mapped :class:`.Table` (or other selectable) itself
(assuming they are in fact part of that table's list of columns), thereby
maintaining the same order of columns in the mapped selectable as is
instrumented on the mapped class, as well as what renders in an ORM SELECT
statement for that mapper. Previously (where "previously" means since
version 0.0.1), :class:`.Column` objects in the
:paramref:`_orm.Mapper.properties` dictionary would always be mapped first,
ahead of when the other columns in the mapped :class:`.Table` would be
mapped, causing a discrepancy in the order in which the mapper would
assign attributes to the mapped class as well as the order in which they
would render in statements.

The change most prominently takes place in the way that Declarative
assigns declared columns to the :class:`.Mapper`, specifically how
:class:`.Column` (or :func:`_orm.mapped_column`) objects are handled
when they have a DDL name that is explicitly different from the mapped
attribute name, as well as when constructs such as :func:`_orm.deferred`
etc. are used.   The new behavior will see the column ordering within
the mapped :class:`.Table` being the same order in which the attributes
are mapped onto the class, assigned within the :class:`.Mapper` itself,
and rendered in ORM statements such as SELECT statements, independent
of how the :class:`_schema.Column` was configured against the
:class:`.Mapper`.

Fixes: #8705
Change-Id: I95cc05061a97fe6b1654bab70e2f6da30f8f3bd3

16 files changed:
doc/build/changelog/unreleased_20/8705.rst [new file with mode: 0644]
doc/build/orm/queryguide/columns.rst
lib/sqlalchemy/orm/mapper.py
test/ext/declarative/test_inheritance.py
test/ext/test_hybrid.py
test/ext/test_serializer.py
test/orm/declarative/test_basic.py
test/orm/declarative/test_mixin.py
test/orm/declarative/test_typed_mapping.py
test/orm/dml/test_update_delete_where.py
test/orm/inheritance/test_basic.py
test/orm/inheritance/test_relationship.py
test/orm/test_core_compilation.py
test/orm/test_deferred.py
test/orm/test_mapper.py
test/orm/test_query.py

diff --git a/doc/build/changelog/unreleased_20/8705.rst b/doc/build/changelog/unreleased_20/8705.rst
new file mode 100644 (file)
index 0000000..2c19657
--- /dev/null
@@ -0,0 +1,31 @@
+.. change::
+    :tags: bug, orm, declarative
+    :tickets: 8705
+
+    Changed a fundamental configuration behavior of :class:`.Mapper`, where
+    :class:`_schema.Column` objects that are explicitly present in the
+    :paramref:`_orm.Mapper.properties` dictionary, either directly or enclosed
+    within a mapper property object, will now be mapped within the order of how
+    they appear within the mapped :class:`.Table` (or other selectable) itself
+    (assuming they are in fact part of that table's list of columns), thereby
+    maintaining the same order of columns in the mapped selectable as is
+    instrumented on the mapped class, as well as what renders in an ORM SELECT
+    statement for that mapper. Previously (where "previously" means since
+    version 0.0.1), :class:`.Column` objects in the
+    :paramref:`_orm.Mapper.properties` dictionary would always be mapped first,
+    ahead of when the other columns in the mapped :class:`.Table` would be
+    mapped, causing a discrepancy in the order in which the mapper would
+    assign attributes to the mapped class as well as the order in which they
+    would render in statements.
+
+    The change most prominently takes place in the way that Declarative
+    assigns declared columns to the :class:`.Mapper`, specifically how
+    :class:`.Column` (or :func:`_orm.mapped_column`) objects are handled
+    when they have a DDL name that is explicitly different from the mapped
+    attribute name, as well as when constructs such as :func:`_orm.deferred`
+    etc. are used.   The new behavior will see the column ordering within
+    the mapped :class:`.Table` being the same order in which the attributes
+    are mapped onto the class, assigned within the :class:`.Mapper` itself,
+    and rendered in ORM statements such as SELECT statements, independent
+    of how the :class:`_schema.Column` was configured against the
+    :class:`.Mapper`.
index f9d2816a4b208510879d8b5ecb284037178fedbf..29edca345ad2c2f0f0cc75aa7e30e9401f484e5b 100644 (file)
@@ -455,7 +455,7 @@ as deferred::
 
     >>> from sqlalchemy.orm import undefer
     >>> book = session.scalar(select(Book).where(Book.id == 2).options(undefer(Book.summary)))
-    {opensql}SELECT book.summary, book.id, book.owner_id, book.title
+    {opensql}SELECT book.id, book.owner_id, book.title, book.summary
     FROM book
     WHERE book.id = ?
     [...] (2,)
@@ -528,7 +528,7 @@ option, passing the string name of the group to be eagerly loaded::
     >>> book = session.scalar(
     ...     select(Book).where(Book.id == 2).options(undefer_group("book_attrs"))
     ... )
-    {opensql}SELECT book.summary, book.cover_photo, book.id, book.owner_id, book.title
+    {opensql}SELECT book.id, book.owner_id, book.title, book.summary, book.cover_photo
     FROM book
     WHERE book.id = ?
     [...] (2,)
@@ -547,7 +547,7 @@ columns can be undeferred at once, without using a group name, by indicating
 a wildcard::
 
     >>> book = session.scalar(select(Book).where(Book.id == 3).options(undefer("*")))
-    {opensql}SELECT book.summary, book.cover_photo, book.id, book.owner_id, book.title
+    {opensql}SELECT book.id, book.owner_id, book.title, book.summary, book.cover_photo
     FROM book
     WHERE book.id = ?
     [...] (3,)
@@ -607,7 +607,7 @@ Only by overridding their behavior at query time, typically using
     ...     .options(undefer("*"))
     ...     .execution_options(populate_existing=True)
     ... )
-    {opensql}SELECT book.summary, book.cover_photo, book.id, book.owner_id, book.title
+    {opensql}SELECT book.id, book.owner_id, book.title, book.summary, book.cover_photo
     FROM book
     WHERE book.id = ?
     [...] (2,)
index 5d784498a0e2ddf90b2814938a16e890dbb48c7d..d5576fac51ee5e13dbe47458ebcbb81ed7665ac6 100644 (file)
@@ -1618,7 +1618,6 @@ class Mapper(
 
     def _configure_properties(self) -> None:
 
-        # TODO: consider using DedupeColumnCollection
         self.columns = self.c = sql_base.ColumnCollection()  # type: ignore
 
         # object attribute names mapped to MapperProperty objects
@@ -1627,23 +1626,83 @@ class Mapper(
         # table columns mapped to MapperProperty
         self._columntoproperty = _ColumnMapping(self)
 
-        # load custom properties
+        explicit_col_props_by_column: Dict[
+            KeyedColumnElement[Any], Tuple[str, ColumnProperty[Any]]
+        ] = {}
+        explicit_col_props_by_key: Dict[str, ColumnProperty[Any]] = {}
+
+        # step 1: go through properties that were explicitly passed
+        # in the properties dictionary.  For Columns that are local, put them
+        # aside in a separate collection we will reconcile with the Table
+        # that's given.  For other properties, set them up in _props now.
         if self._init_properties:
-            for key, prop in self._init_properties.items():
-                self._configure_property(key, prop, False)
+            for key, prop_arg in self._init_properties.items():
+
+                if not isinstance(prop_arg, MapperProperty):
+                    possible_col_prop = self._make_prop_from_column(
+                        key, prop_arg
+                    )
+                else:
+                    possible_col_prop = prop_arg
+
+                # issue #8705.  if the explicit property is actually a
+                # Column that is local to the local Table, don't set it up
+                # in ._props yet, integrate it into the order given within
+                # the Table.
+                if isinstance(possible_col_prop, properties.ColumnProperty):
+                    given_col = possible_col_prop.columns[0]
+                    if self.local_table.c.contains_column(given_col):
+                        explicit_col_props_by_key[key] = possible_col_prop
+                        explicit_col_props_by_column[given_col] = (
+                            key,
+                            possible_col_prop,
+                        )
+                        continue
 
-        # pull properties from the inherited mapper if any.
+                self._configure_property(key, possible_col_prop, False)
+
+        # step 2: pull properties from the inherited mapper.  reconcile
+        # columns with those which are explicit above.  for properties that
+        # are only in the inheriting mapper, set them up as local props
         if self.inherits:
-            for key, prop in self.inherits._props.items():
-                if key not in self._props and not self._should_exclude(
-                    key, key, local=False, column=None
-                ):
-                    self._adapt_inherited_property(key, prop, False)
+            for key, inherited_prop in self.inherits._props.items():
+                if self._should_exclude(key, key, local=False, column=None):
+                    continue
+
+                incoming_prop = explicit_col_props_by_key.get(key)
+                if incoming_prop:
+
+                    new_prop = self._reconcile_prop_with_incoming_columns(
+                        key,
+                        inherited_prop,
+                        warn_only=False,
+                        incoming_prop=incoming_prop,
+                    )
+                    explicit_col_props_by_key[key] = new_prop
+                    explicit_col_props_by_column[incoming_prop.columns[0]] = (
+                        key,
+                        new_prop,
+                    )
+                elif key not in self._props:
+                    self._adapt_inherited_property(key, inherited_prop, False)
+
+        # step 3.  Iterate through all columns in the persist selectable.
+        # this includes not only columns in the local table / fromclause,
+        # but also those columns in the superclass table if we are joined
+        # inh or single inh mapper.  map these columns as well. additional
+        # reconciliation against inherited columns occurs here also.
 
-        # create properties for each column in the mapped table,
-        # for those columns which don't already map to a property
         for column in self.persist_selectable.columns:
-            if column in self._columntoproperty:
+
+            if column in explicit_col_props_by_column:
+                # column was explicitly passed to properties; configure
+                # it now in the order in which it corresponds to the
+                # Table / selectable
+                key, prop = explicit_col_props_by_column[column]
+                self._configure_property(key, prop, False)
+                continue
+
+            elif column in self._columntoproperty:
                 continue
 
             column_key = (self.column_prefix or "") + column.key
@@ -1913,7 +1972,9 @@ class Mapper(
         )
 
         if not isinstance(prop_arg, MapperProperty):
-            prop = self._property_from_column(key, prop_arg)
+            prop: MapperProperty[Any] = self._property_from_column(
+                key, prop_arg
+            )
         else:
             prop = prop_arg
 
@@ -2030,79 +2091,129 @@ class Mapper(
 
         return prop
 
+    def _make_prop_from_column(
+        self,
+        key: str,
+        column: Union[
+            Sequence[KeyedColumnElement[Any]], KeyedColumnElement[Any]
+        ],
+    ) -> ColumnProperty[Any]:
+
+        columns = util.to_list(column)
+        mapped_column = []
+        for c in columns:
+            mc = self.persist_selectable.corresponding_column(c)
+            if mc is None:
+                mc = self.local_table.corresponding_column(c)
+                if mc is not None:
+                    # if the column is in the local table but not the
+                    # mapped table, this corresponds to adding a
+                    # column after the fact to the local table.
+                    # [ticket:1523]
+                    self.persist_selectable._refresh_for_new_column(mc)
+                mc = self.persist_selectable.corresponding_column(c)
+                if mc is None:
+                    raise sa_exc.ArgumentError(
+                        "When configuring property '%s' on %s, "
+                        "column '%s' is not represented in the mapper's "
+                        "table. Use the `column_property()` function to "
+                        "force this column to be mapped as a read-only "
+                        "attribute." % (key, self, c)
+                    )
+            mapped_column.append(mc)
+        return properties.ColumnProperty(*mapped_column)
+
+    def _reconcile_prop_with_incoming_columns(
+        self,
+        key: str,
+        existing_prop: MapperProperty[Any],
+        warn_only: bool,
+        incoming_prop: Optional[ColumnProperty[Any]] = None,
+        single_column: Optional[KeyedColumnElement[Any]] = None,
+    ) -> ColumnProperty[Any]:
+
+        if incoming_prop and (
+            self.concrete
+            or not isinstance(existing_prop, properties.ColumnProperty)
+        ):
+            return incoming_prop
+
+        existing_column = existing_prop.columns[0]
+
+        if incoming_prop and existing_column in incoming_prop.columns:
+            return incoming_prop
+
+        if incoming_prop is None:
+            assert single_column is not None
+            incoming_column = single_column
+            equated_pair_key = (existing_prop.columns[0], incoming_column)
+        else:
+            assert single_column is None
+            incoming_column = incoming_prop.columns[0]
+            equated_pair_key = (incoming_column, existing_prop.columns[0])
+
+        if (
+            (
+                not self._inherits_equated_pairs
+                or (equated_pair_key not in self._inherits_equated_pairs)
+            )
+            and not existing_column.shares_lineage(incoming_column)
+            and existing_column is not self.version_id_col
+            and incoming_column is not self.version_id_col
+        ):
+            msg = (
+                "Implicitly combining column %s with column "
+                "%s under attribute '%s'.  Please configure one "
+                "or more attributes for these same-named columns "
+                "explicitly."
+                % (
+                    existing_prop.columns[-1],
+                    incoming_column,
+                    key,
+                )
+            )
+            if warn_only:
+                util.warn(msg)
+            else:
+                raise sa_exc.InvalidRequestError(msg)
+
+        # existing properties.ColumnProperty from an inheriting
+        # mapper. make a copy and append our column to it
+        # breakpoint()
+        new_prop = existing_prop.copy()
+
+        new_prop.columns.insert(0, incoming_column)
+        self._log(
+            "inserting column to existing list "
+            "in properties.ColumnProperty %s",
+            key,
+        )
+        return new_prop  # type: ignore
+
     @util.preload_module("sqlalchemy.orm.descriptor_props")
     def _property_from_column(
         self,
         key: str,
-        prop_arg: Union[KeyedColumnElement[Any], MapperProperty[Any]],
-    ) -> MapperProperty[Any]:
+        column: KeyedColumnElement[Any],
+    ) -> ColumnProperty[Any]:
         """generate/update a :class:`.ColumnProperty` given a
-        :class:`_schema.Column` object."""
+        :class:`_schema.Column` or other SQL expression object."""
+
         descriptor_props = util.preloaded.orm_descriptor_props
-        # we were passed a Column or a list of Columns;
-        # generate a properties.ColumnProperty
-        columns = util.to_list(prop_arg)
-        column = columns[0]
 
         prop = self._props.get(key)
 
         if isinstance(prop, properties.ColumnProperty):
-            if (
-                (
-                    not self._inherits_equated_pairs
-                    or (prop.columns[0], column)
-                    not in self._inherits_equated_pairs
-                )
-                and not prop.columns[0].shares_lineage(column)
-                and prop.columns[0] is not self.version_id_col
-                and column is not self.version_id_col
-            ):
-                warn_only = prop.parent is not self
-                msg = (
-                    "Implicitly combining column %s with column "
-                    "%s under attribute '%s'.  Please configure one "
-                    "or more attributes for these same-named columns "
-                    "explicitly." % (prop.columns[-1], column, key)
-                )
-                if warn_only:
-                    util.warn(msg)
-                else:
-                    raise sa_exc.InvalidRequestError(msg)
-
-            # existing properties.ColumnProperty from an inheriting
-            # mapper. make a copy and append our column to it
-            prop = prop.copy()
-            prop.columns.insert(0, column)
-            self._log(
-                "inserting column to existing list "
-                "in properties.ColumnProperty %s" % (key)
+            return self._reconcile_prop_with_incoming_columns(
+                key,
+                prop,
+                single_column=column,
+                warn_only=prop.parent is not self,
             )
-            return prop
         elif prop is None or isinstance(
             prop, descriptor_props.ConcreteInheritedProperty
         ):
-            mapped_column = []
-            for c in columns:
-                mc = self.persist_selectable.corresponding_column(c)
-                if mc is None:
-                    mc = self.local_table.corresponding_column(c)
-                    if mc is not None:
-                        # if the column is in the local table but not the
-                        # mapped table, this corresponds to adding a
-                        # column after the fact to the local table.
-                        # [ticket:1523]
-                        self.persist_selectable._refresh_for_new_column(mc)
-                    mc = self.persist_selectable.corresponding_column(c)
-                    if mc is None:
-                        raise sa_exc.ArgumentError(
-                            "When configuring property '%s' on %s, "
-                            "column '%s' is not represented in the mapper's "
-                            "table. Use the `column_property()` function to "
-                            "force this column to be mapped as a read-only "
-                            "attribute." % (key, self, c)
-                        )
-                mapped_column.append(mc)
-            return properties.ColumnProperty(*mapped_column)
+            return self._make_prop_from_column(key, column)
         else:
             raise sa_exc.ArgumentError(
                 "WARNING: when configuring property '%s' on %s, "
index d22090086b633ce44ef82305ec826280dd2de5e0..9efe0802966bbfebebb87730c7cbc3b8f5fe7d27 100644 (file)
@@ -975,8 +975,9 @@ class ConcreteExtensionConfigTest(
         session = Session()
         self.assert_compile(
             session.query(Document),
-            "SELECT pjoin.documenttype AS pjoin_documenttype, "
-            "pjoin.id AS pjoin_id, pjoin.type AS pjoin_type FROM "
+            "SELECT "
+            "pjoin.id AS pjoin_id, pjoin.documenttype AS pjoin_documenttype, "
+            "pjoin.type AS pjoin_type FROM "
             "(SELECT offers.id AS id, offers.documenttype AS documenttype, "
             "'offer' AS type FROM offers) AS pjoin",
         )
index 0cba8f3a15e0e295c6b3c6fed0ca5f2198942ea7..252844a487cd4f6719d303ede26d3cf0bf640a6c 100644 (file)
@@ -98,7 +98,7 @@ class PropertyComparatorTest(fixtures.TestBase, AssertsCompiledSQL):
         sess = fixture_session()
         self.assert_compile(
             sess.query(aliased(A)).filter_by(value="foo"),
-            "SELECT a_1.value AS a_1_value, a_1.id AS a_1_id "
+            "SELECT a_1.id AS a_1_id, a_1.value AS a_1_value "
             "FROM a AS a_1 WHERE upper(a_1.value) = upper(:upper_1)",
         )
 
@@ -467,7 +467,7 @@ class PropertyExpressionTest(fixtures.TestBase, AssertsCompiledSQL):
         sess = fixture_session()
         self.assert_compile(
             sess.query(A).filter_by(value="foo"),
-            "SELECT a.value AS a_value, a.id AS a_id "
+            "SELECT a.id AS a_id, a.value AS a_value "
             "FROM a WHERE foo(a.value) + bar(a.value) = :param_1",
         )
 
@@ -476,7 +476,7 @@ class PropertyExpressionTest(fixtures.TestBase, AssertsCompiledSQL):
         sess = fixture_session()
         self.assert_compile(
             sess.query(aliased(A)).filter_by(value="foo"),
-            "SELECT a_1.value AS a_1_value, a_1.id AS a_1_id "
+            "SELECT a_1.id AS a_1_id, a_1.value AS a_1_value "
             "FROM a AS a_1 WHERE foo(a_1.value) + bar(a_1.value) = :param_1",
         )
 
@@ -914,7 +914,7 @@ class MethodExpressionTest(fixtures.TestBase, AssertsCompiledSQL):
         sess = fixture_session()
         self.assert_compile(
             sess.query(A).filter(A.value(5) == "foo"),
-            "SELECT a.value AS a_value, a.id AS a_id "
+            "SELECT a.id AS a_id, a.value AS a_value "
             "FROM a WHERE foo(a.value, :foo_1) + :foo_2 = :param_1",
         )
 
@@ -924,7 +924,7 @@ class MethodExpressionTest(fixtures.TestBase, AssertsCompiledSQL):
         a1 = aliased(A)
         self.assert_compile(
             sess.query(a1).filter(a1.value(5) == "foo"),
-            "SELECT a_1.value AS a_1_value, a_1.id AS a_1_id "
+            "SELECT a_1.id AS a_1_id, a_1.value AS a_1_value "
             "FROM a AS a_1 WHERE foo(a_1.value, :foo_1) + :foo_2 = :param_1",
         )
 
@@ -1381,8 +1381,9 @@ class SpecialObjectTest(fixtures.TestBase, AssertsCompiledSQL):
 
         self.assert_compile(
             query,
-            "SELECT bank_account.balance AS bank_account_balance, "
-            "bank_account.id AS bank_account_id FROM bank_account "
+            "SELECT bank_account.id AS bank_account_id, "
+            "bank_account.balance AS bank_account_balance "
+            "FROM bank_account "
             "WHERE bank_account.balance = :balance_1",
             checkparams={"balance_1": Decimal("9886.110000")},
         )
@@ -1403,8 +1404,8 @@ class SpecialObjectTest(fixtures.TestBase, AssertsCompiledSQL):
         )
         self.assert_compile(
             query,
-            "SELECT bank_account.balance AS bank_account_balance, "
-            "bank_account.id AS bank_account_id "
+            "SELECT bank_account.id AS bank_account_id, "
+            "bank_account.balance AS bank_account_balance "
             "FROM bank_account "
             "WHERE :balance_1 * bank_account.balance > :param_1 "
             "AND :balance_2 * bank_account.balance < :param_2",
@@ -1426,8 +1427,9 @@ class SpecialObjectTest(fixtures.TestBase, AssertsCompiledSQL):
         )
         self.assert_compile(
             query,
-            "SELECT bank_account.balance AS bank_account_balance, "
-            "bank_account.id AS bank_account_id FROM bank_account "
+            "SELECT bank_account.id AS bank_account_id, "
+            "bank_account.balance AS bank_account_balance "
+            "FROM bank_account "
             "WHERE :balance_1 * bank_account.balance > "
             ":param_1 * :balance_2 * bank_account.balance",
             checkparams={
index 76fd90fa88faa70a459c9e17a209b399f8de42a1..e15ace2eb23252e88799e9489557263138e02542 100644 (file)
@@ -316,9 +316,9 @@ class ColumnPropertyWParamTest(
         # note in the original, the same bound parameter is used twice
         self.assert_compile(
             expr,
-            "SELECT test.some_id AS test_some_id, "
+            "SELECT "
             "CAST(left(test.some_id, :left_1) AS INTEGER) AS anon_1, "
-            "test.id AS test_id FROM test WHERE "
+            "test.id AS test_id, test.some_id AS test_some_id FROM test WHERE "
             "CAST(left(test.some_id, :left_1) AS INTEGER) = :param_1",
             checkparams={"left_1": 6, "param_1": 123456},
         )
@@ -328,9 +328,8 @@ class ColumnPropertyWParamTest(
         # the same value however
         self.assert_compile(
             expr2,
-            "SELECT test.some_id AS test_some_id, "
-            "CAST(left(test.some_id, :left_1) AS INTEGER) AS anon_1, "
-            "test.id AS test_id FROM test WHERE "
+            "SELECT CAST(left(test.some_id, :left_1) AS INTEGER) AS anon_1, "
+            "test.id AS test_id, test.some_id AS test_some_id FROM test WHERE "
             "CAST(left(test.some_id, :left_2) AS INTEGER) = :param_1",
             checkparams={"left_1": 6, "left_2": 6, "param_1": 123456},
         )
index 9f8e81d9193b976d6911716bbb48b0ecf5690d05..6959d06acdecb14900352e26ad3bddd4c188e938 100644 (file)
@@ -1,3 +1,5 @@
+import random
+
 import sqlalchemy as sa
 from sqlalchemy import CheckConstraint
 from sqlalchemy import event
@@ -7,6 +9,7 @@ from sqlalchemy import ForeignKeyConstraint
 from sqlalchemy import Index
 from sqlalchemy import inspect
 from sqlalchemy import Integer
+from sqlalchemy import select
 from sqlalchemy import String
 from sqlalchemy import testing
 from sqlalchemy import UniqueConstraint
@@ -27,6 +30,7 @@ from sqlalchemy.orm import descriptor_props
 from sqlalchemy.orm import exc as orm_exc
 from sqlalchemy.orm import joinedload
 from sqlalchemy.orm import mapped_column
+from sqlalchemy.orm import MappedColumn
 from sqlalchemy.orm import Mapper
 from sqlalchemy.orm import registry
 from sqlalchemy.orm import relationship
@@ -2588,3 +2592,132 @@ class DeclarativeMultiBaseTest(
 
         mt = MyTable(id=5)
         eq_(mt.id, 5)
+
+
+class NamedAttrOrderingTest(fixtures.TestBase):
+    """test for #8705"""
+
+    @testing.combinations(
+        "decl_base_fn",
+        "decl_base_base",
+        "classical_mapping",
+        argnames="mapping_style",
+    )
+    def test_ordering_of_attrs_cols_named_or_unnamed(self, mapping_style):
+        def make_name():
+            uppercase = random.randint(1, 3) == 1
+            name = "".join(
+                random.choice("abcdefghijklmnopqrstuvxyz")
+                for i in range(random.randint(4, 10))
+            )
+            if uppercase:
+                name = random.choice("ABCDEFGHIJKLMNOP") + name
+            return name
+
+        def make_column(assign_col_name):
+            use_key = random.randint(1, 3) == 1
+            use_name = random.randint(1, 3) == 1
+
+            args = []
+            kw = {}
+            name = col_name = make_name()
+
+            if use_name:
+                use_different_name = random.randint(1, 3) != 3
+                if use_different_name:
+                    col_name = make_name()
+
+                args.append(col_name)
+            elif assign_col_name:
+                args.append(col_name)
+
+            if use_key:
+                kw["key"] = name
+                expected_c_name = name
+            else:
+                expected_c_name = col_name
+
+            args.append(Integer)
+
+            if mapping_style.startswith("decl"):
+                use_mapped_column = random.randint(1, 2) == 1
+            else:
+                use_mapped_column = False
+
+            if use_mapped_column:
+                col = mapped_column(*args, **kw)
+            else:
+                col = Column(*args, **kw)
+
+            use_explicit_property = (
+                not use_mapped_column and random.randint(1, 6) == 1
+            )
+            if use_explicit_property:
+                col_prop = column_property(col)
+            else:
+                col_prop = col
+
+            return name, expected_c_name, col, col_prop
+
+        assign_col_name = mapping_style == "classical_mapping"
+
+        names = [
+            make_column(assign_col_name) for i in range(random.randint(10, 15))
+        ]
+        len_names = len(names)
+
+        pk_col = names[random.randint(0, len_names - 1)][2]
+        if isinstance(pk_col, MappedColumn):
+            pk_col.column.primary_key = True
+        else:
+            pk_col.primary_key = True
+
+        names_only = [name for name, _, _, _ in names]
+        col_names_only = [col_name for _, col_name, _, _ in names]
+        cols_only = [col for _, _, col, _ in names]
+
+        if mapping_style in ("decl_base_fn", "decl_base_base"):
+            if mapping_style == "decl_base_fn":
+                Base = declarative_base()
+            elif mapping_style == "decl_base_base":
+
+                class Base(DeclarativeBase):
+                    pass
+
+            else:
+                assert False
+
+            clsdict = {
+                "__tablename__": "new_table",
+            }
+            clsdict.update({name: colprop for name, _, _, colprop in names})
+
+            new_cls = type("NewCls", (Base,), clsdict)
+
+        elif mapping_style == "classical_mapping":
+
+            class new_cls:
+                pass
+
+            reg = registry()
+            t = Table("new_table", reg.metadata, *cols_only)
+
+            reg.map_imperatively(
+                new_cls,
+                t,
+                properties={
+                    key: colprop
+                    for key, col_name, col, colprop in names
+                    if col_name != key
+                },
+            )
+        else:
+            assert False
+
+        eq_(new_cls.__table__.c.keys(), col_names_only)
+        eq_(new_cls.__mapper__.attrs.keys(), names_only)
+        eq_(list(new_cls._sa_class_manager.keys()), names_only)
+        eq_([k for k in new_cls.__dict__ if not k.startswith("_")], names_only)
+
+        stmt = select(new_cls)
+        eq_(stmt.selected_columns.keys(), col_names_only)
index 4d438e68bedf422b7666f487a11d64587d472709..967958846814f431683d5f38c6c45412ad11a7f9 100644 (file)
@@ -2017,11 +2017,11 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
         s = fixture_session()
         self.assert_compile(
             s.query(A),
-            "SELECT a.x + :x_1 AS anon_1, a.x AS a_x, a.id AS a_id FROM a",
+            "SELECT a.x + :x_1 AS anon_1, a.id AS a_id, a.x AS a_x FROM a",
         )
         self.assert_compile(
             s.query(B),
-            "SELECT b.x + :x_1 AS anon_1, b.x AS b_x, b.id AS b_id FROM b",
+            "SELECT b.x + :x_1 AS anon_1, b.id AS b_id, b.x AS b_x FROM b",
         )
 
     @testing.requires.predictable_gc
index 1b4be84d38793283a662706fdeb36cdd0625b3f9..72fd4d84e9ba18a0155ab2d41f5759fd60b45c3a 100644 (file)
@@ -1004,7 +1004,7 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         self.assert_compile(select(User), "SELECT users.id FROM users")
         self.assert_compile(
             select(User).options(undefer(User.data)),
-            "SELECT users.data, users.id FROM users",
+            "SELECT users.id, users.data FROM users",
         )
 
     def test_deferred_kw(self, decl_base):
@@ -1017,7 +1017,7 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         self.assert_compile(select(User), "SELECT users.id FROM users")
         self.assert_compile(
             select(User).options(undefer(User.data)),
-            "SELECT users.data, users.id FROM users",
+            "SELECT users.id, users.data FROM users",
         )
 
     @testing.combinations(
index ebaba191ab9b6f3fbb9f36bf62401d2b82bb03af..9c8809eef952e08c9d1d76add7bef01fae574a89 100644 (file)
@@ -533,15 +533,15 @@ class UpdateDeleteTest(fixtures.MappedTest):
         to_assert = [
             # refresh john
             CompiledSQL(
-                "SELECT users.age_int AS users_age_int, "
-                "users.id AS users_id, users.name AS users_name FROM users "
+                "SELECT users.id AS users_id, users.name AS users_name, "
+                "users.age_int AS users_age_int FROM users "
                 "WHERE users.id = :pk_1",
                 [{"pk_1": 1}],
             ),
             # refresh jill
             CompiledSQL(
-                "SELECT users.age_int AS users_age_int, "
-                "users.id AS users_id, users.name AS users_name FROM users "
+                "SELECT users.id AS users_id, users.name AS users_name, "
+                "users.age_int AS users_age_int FROM users "
                 "WHERE users.id = :pk_1",
                 [{"pk_1": 3}],
             ),
@@ -551,8 +551,8 @@ class UpdateDeleteTest(fixtures.MappedTest):
             to_assert.append(
                 # refresh jane for partial attributes
                 CompiledSQL(
-                    "SELECT users.age_int AS users_age_int, "
-                    "users.name AS users_name FROM users "
+                    "SELECT users.name AS users_name, "
+                    "users.age_int AS users_age_int FROM users "
                     "WHERE users.id = :pk_1",
                     [{"pk_1": 4}],
                 )
@@ -677,15 +677,15 @@ class UpdateDeleteTest(fixtures.MappedTest):
         asserter.assert_(
             # refresh john
             CompiledSQL(
-                "SELECT users.age_int AS users_age_int, "
-                "users.id AS users_id, users.name AS users_name FROM users "
+                "SELECT users.id AS users_id, users.name AS users_name, "
+                "users.age_int AS users_age_int FROM users "
                 "WHERE users.id = :pk_1",
                 [{"pk_1": 1}],
             ),
             # refresh jill
             CompiledSQL(
-                "SELECT users.age_int AS users_age_int, "
-                "users.id AS users_id, users.name AS users_name FROM users "
+                "SELECT users.id AS users_id, users.name AS users_name, "
+                "users.age_int AS users_age_int FROM users "
                 "WHERE users.id = :pk_1",
                 [{"pk_1": 3}],
             ),
index 9d7b73e1cf829fc63e377ecdf9776b2318dda10c..0ba9007988732957695fec342a3815539dcf44d6 100644 (file)
@@ -32,6 +32,7 @@ from sqlalchemy.sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
 from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_raises_message
 from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_
@@ -2518,6 +2519,50 @@ class OverrideColKeyTest(fixtures.MappedTest):
         # PK col
         assert s2.id == s2.base_id != 15
 
+    def test_subclass_renames_superclass_col_single_inh(self, decl_base):
+        """tested as part of #8705.
+
+        The step where we configure columns mapped to specific keys must
+        take place even if the given column is already in _columntoproperty,
+        as would be the case if the superclass maps that column already.
+
+        """
+
+        class A(decl_base):
+            __tablename__ = "a"
+
+            id = Column(Integer, primary_key=True)
+            a_data = Column(String)
+
+        class B(A):
+            b_data = column_property(A.__table__.c.a_data)
+
+        is_(A.a_data.property.columns[0], A.__table__.c.a_data)
+        is_(B.a_data.property.columns[0], A.__table__.c.a_data)
+        is_(B.b_data.property.columns[0], A.__table__.c.a_data)
+
+    def test_column_setup_sanity_check(self, decl_base):
+        class A(decl_base):
+            __tablename__ = "a"
+
+            id = Column(Integer, primary_key=True)
+            a_data = Column(String)
+
+        class B(A):
+            __tablename__ = "b"
+            id = Column(Integer, ForeignKey("a.id"), primary_key=True)
+            b_data = Column(String)
+
+        is_(A.id.property.parent, inspect(A))
+        # overlapping cols get a new prop on the subclass, with cols merged
+        is_(B.id.property.parent, inspect(B))
+        eq_(B.id.property.columns, [B.__table__.c.id, A.__table__.c.id])
+
+        # totally independent cols remain w/ parent on the originating
+        # mapper
+        is_(B.a_data.property.parent, inspect(A))
+        is_(B.b_data.property.parent, inspect(B))
+
     def test_override_implicit(self):
         # this is originally [ticket:1111].
         # the pattern here is now disallowed by [ticket:1892]
@@ -2532,7 +2577,12 @@ class OverrideColKeyTest(fixtures.MappedTest):
             Base, base, properties={"id": base.c.base_id}
         )
 
-        def go():
+        with expect_raises_message(
+            sa_exc.InvalidRequestError,
+            "Implicitly combining column base.base_id with column "
+            "subtable.base_id under attribute 'id'.  Please configure one "
+            "or more attributes for these same-named columns explicitly.",
+        ):
             self.mapper_registry.map_imperatively(
                 Sub,
                 subtable,
@@ -2540,12 +2590,6 @@ class OverrideColKeyTest(fixtures.MappedTest):
                 properties={"id": subtable.c.base_id},
             )
 
-        # Sub mapper compilation needs to detect that "base.c.base_id"
-        # is renamed in the inherited mapper as "id", even though
-        # it has its own "id" property.  It then generates
-        # an exception in 0.7 due to the implicit conflict.
-        assert_raises(sa_exc.InvalidRequestError, go)
-
     def test_pk_fk_different(self):
         class Base:
             pass
@@ -2745,9 +2789,9 @@ class OptimizedLoadTest(fixtures.MappedTest):
             go,
             CompiledSQL(
                 "SELECT base.id AS base_id, sub.id AS sub_id, "
+                "base.data AS base_data, base.type AS base_type, "
                 "base.counter AS base_counter, "
                 "sub.subcounter AS sub_subcounter, "
-                "base.data AS base_data, base.type AS base_type, "
                 "sub.sub AS sub_sub, sub.subcounter2 AS sub_subcounter2 "
                 "FROM base LEFT OUTER JOIN sub ON base.id = sub.id "
                 "WHERE base.id = :pk_1",
index 779239015f949f20c8836f026bae50286e62d109..925bf7dd0ebf37cbe0d567010c54e03d0152f353 100644 (file)
@@ -2901,14 +2901,14 @@ class BetweenSubclassJoinWExtraJoinedLoad(
         ) if autoalias else nullcontext():
             self.assert_compile(
                 q,
-                "SELECT people.type AS people_type, engineers.id AS "
+                "SELECT engineers.id AS "
                 "engineers_id, "
-                "people.id AS people_id, "
+                "people.id AS people_id, people.type AS people_type, "
                 "engineers.primary_language AS engineers_primary_language, "
                 "engineers.manager_id AS engineers_manager_id, "
-                "people_1.type AS people_1_type, "
                 "managers_1.id AS managers_1_id, "
-                "people_1.id AS people_1_id, seen_1.id AS seen_1_id, "
+                "people_1.id AS people_1_id, people_1.type AS people_1_type, "
+                "seen_1.id AS seen_1_id, "
                 "seen_1.timestamp AS seen_1_timestamp, "
                 "seen_2.id AS seen_2_id, "
                 "seen_2.timestamp AS seen_2_timestamp "
index efa4c773a53dda0c49eba0f4f6ece59b8deae96d..8fd22bdd0902862c6855f22dfa2f55b2cc6038e0 100644 (file)
@@ -774,8 +774,8 @@ class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
         self.assert_compile(
             q,
             "SELECT anon_1.id "
-            "FROM (SELECT users.name AS name, "
-            "users.id AS id FROM users) AS anon_1",
+            "FROM (SELECT users.id AS id, users.name AS name "
+            "FROM users) AS anon_1",
         )
 
         # testing deferred opts separately for deterministic SQL generation
@@ -784,9 +784,9 @@ class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
 
         self.assert_compile(
             q,
-            "SELECT anon_1.name, anon_1.id "
-            "FROM (SELECT users.name AS name, "
-            "users.id AS id FROM users) AS anon_1",
+            "SELECT anon_1.id, anon_1.name "
+            "FROM (SELECT users.id AS id, users.name AS name "
+            "FROM users) AS anon_1",
         )
 
         q = select(u1).options(undefer(u1.name_upper))
@@ -794,8 +794,8 @@ class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
         self.assert_compile(
             q,
             "SELECT upper(anon_1.name) AS upper_1, anon_1.id "
-            "FROM (SELECT users.name AS name, "
-            "users.id AS id FROM users) AS anon_1",
+            "FROM (SELECT users.id AS id, users.name AS name "
+            "FROM users) AS anon_1",
         )
 
     def test_non_deferred_subq_one(self, non_deferred_fixture):
@@ -876,9 +876,9 @@ class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
 
         self.assert_compile(
             stmt,
-            "WITH RECURSIVE anon_1(name, id) AS "
-            "(SELECT users.name AS name, users.id AS id FROM users "
-            "UNION ALL SELECT users.name AS name, users.id AS id "
+            "WITH RECURSIVE anon_1(id, name) AS "
+            "(SELECT users.id AS id, users.name AS name FROM users "
+            "UNION ALL SELECT users.id AS id, users.name AS name "
             "FROM users JOIN anon_1 ON anon_1.id = users.id) "
             "SELECT users.id FROM users JOIN anon_1 ON users.id = anon_1.id",
         )
@@ -886,9 +886,9 @@ class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
         # testing deferred opts separately for deterministic SQL generation
         self.assert_compile(
             stmt.options(undefer(User.name_upper)),
-            "WITH RECURSIVE anon_1(name, id) AS "
-            "(SELECT users.name AS name, users.id AS id FROM users "
-            "UNION ALL SELECT users.name AS name, users.id AS id "
+            "WITH RECURSIVE anon_1(id, name) AS "
+            "(SELECT users.id AS id, users.name AS name FROM users "
+            "UNION ALL SELECT users.id AS id, users.name AS name "
             "FROM users JOIN anon_1 ON anon_1.id = users.id) "
             "SELECT upper(users.name) AS upper_1, users.id "
             "FROM users JOIN anon_1 ON users.id = anon_1.id",
@@ -896,11 +896,11 @@ class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
 
         self.assert_compile(
             stmt.options(undefer(User.name)),
-            "WITH RECURSIVE anon_1(name, id) AS "
-            "(SELECT users.name AS name, users.id AS id FROM users "
-            "UNION ALL SELECT users.name AS name, users.id AS id "
+            "WITH RECURSIVE anon_1(id, name) AS "
+            "(SELECT users.id AS id, users.name AS name FROM users "
+            "UNION ALL SELECT users.id AS id, users.name AS name "
             "FROM users JOIN anon_1 ON anon_1.id = users.id) "
-            "SELECT users.name, users.id "
+            "SELECT users.id, users.name "
             "FROM users JOIN anon_1 ON users.id = anon_1.id",
         )
 
@@ -919,12 +919,13 @@ class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
 
         self.assert_compile(
             select(u_alias),
-            "SELECT anon_1.id FROM ((SELECT users.name AS name, "
-            "users.id AS id FROM users "
-            "WHERE users.id = :id_1 UNION SELECT users.name AS name, "
-            "users.id AS id "
+            "SELECT anon_1.id FROM ((SELECT users.id AS id, "
+            "users.name AS name "
+            "FROM users "
+            "WHERE users.id = :id_1 UNION SELECT users.id AS id, "
+            "users.name AS name "
             "FROM users WHERE users.id = :id_2) "
-            "UNION SELECT users.name AS name, users.id AS id "
+            "UNION SELECT users.id AS id, users.name AS name "
             "FROM users WHERE users.id = :id_3) AS anon_1",
         )
 
@@ -949,12 +950,12 @@ class LoadersInSubqueriesTest(QueryTest, AssertsCompiledSQL):
 
         self.assert_compile(
             select(u_alias).options(undefer(u_alias.name)),
-            "SELECT anon_1.name, anon_1.id FROM "
-            "((SELECT users.name AS name, users.id AS id FROM users "
-            "WHERE users.id = :id_1 UNION SELECT users.name AS name, "
-            "users.id AS id "
+            "SELECT anon_1.id, anon_1.name FROM "
+            "((SELECT users.id AS id, users.name AS name FROM users "
+            "WHERE users.id = :id_1 UNION SELECT users.id AS id, "
+            "users.name AS name "
             "FROM users WHERE users.id = :id_2) "
-            "UNION SELECT users.name AS name, users.id AS id "
+            "UNION SELECT users.id AS id, users.name AS name "
             "FROM users WHERE users.id = :id_3) AS anon_1",
         )
 
index 1c5dadc01d02eb72545f63137a5da7b405863fb0..5bd70ca7bdafb6fb4c61720c8ad6a3988dc72457 100644 (file)
@@ -162,8 +162,9 @@ class DeferredTest(AssertsCompiledSQL, _fixtures.FixtureTest):
             )
             self.assert_compile(
                 select(Order).options(undefer_group("g1")),
-                "SELECT orders.isopen, orders.description, orders.id, "
-                "orders.user_id, orders.address_id FROM orders",
+                "SELECT orders.id, orders.user_id, orders.address_id, "
+                "orders.isopen, orders.description "
+                "FROM orders",
             )
         else:
             self.assert_compile(
@@ -583,11 +584,11 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
             go,
             [
                 (
-                    "SELECT orders.user_id AS orders_user_id, "
+                    "SELECT orders.id AS orders_id, "
+                    "orders.user_id AS orders_user_id, "
+                    "orders.address_id AS orders_address_id, "
                     "orders.description AS orders_description, "
-                    "orders.isopen AS orders_isopen, "
-                    "orders.id AS orders_id, "
-                    "orders.address_id AS orders_address_id "
+                    "orders.isopen AS orders_isopen "
                     "FROM orders ORDER BY orders.id",
                     {},
                 )
@@ -628,11 +629,12 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
             go,
             [
                 (
-                    "SELECT orders.user_id AS orders_user_id, "
-                    "orders.description AS orders_description, "
-                    "orders.isopen AS orders_isopen, "
+                    "SELECT "
                     "orders.id AS orders_id, "
-                    "orders.address_id AS orders_address_id "
+                    "orders.user_id AS orders_user_id, "
+                    "orders.address_id AS orders_address_id, "
+                    "orders.description AS orders_description, "
+                    "orders.isopen AS orders_isopen "
                     "FROM orders ORDER BY orders.id",
                     {},
                 )
@@ -673,11 +675,12 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
             go,
             [
                 (
-                    "SELECT orders.user_id AS orders_user_id, "
-                    "orders.description AS orders_description, "
-                    "orders.isopen AS orders_isopen, "
+                    "SELECT "
                     "orders.id AS orders_id, "
-                    "orders.address_id AS orders_address_id "
+                    "orders.user_id AS orders_user_id, "
+                    "orders.address_id AS orders_address_id, "
+                    "orders.description AS orders_description, "
+                    "orders.isopen AS orders_isopen "
                     "FROM orders ORDER BY orders.id",
                     {},
                 )
@@ -735,11 +738,11 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
                     {"id_1": 7},
                 ),
                 (
-                    "SELECT orders.user_id AS orders_user_id, "
-                    "orders.description "
-                    "AS orders_description, orders.isopen AS orders_isopen, "
-                    "orders.id AS orders_id, orders.address_id "
-                    "AS orders_address_id "
+                    "SELECT orders.id AS orders_id, "
+                    "orders.user_id AS orders_user_id, "
+                    "orders.address_id AS orders_address_id, "
+                    "orders.description AS orders_description, "
+                    "orders.isopen AS orders_isopen "
                     "FROM orders WHERE :param_1 = orders.user_id "
                     "ORDER BY orders.id",
                     {"param_1": 7},
@@ -798,11 +801,12 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
                     {"id_1": 7},
                 ),
                 (
-                    "SELECT orders.user_id AS orders_user_id, "
-                    "orders.description "
-                    "AS orders_description, orders.isopen AS orders_isopen, "
+                    "SELECT "
                     "orders.id AS orders_id, "
+                    "orders.user_id AS orders_user_id, "
                     "orders.address_id AS orders_address_id, "
+                    "orders.description AS orders_description, "
+                    "orders.isopen AS orders_isopen, "
                     "anon_1.users_id AS anon_1_users_id "
                     "FROM (SELECT users.id AS "
                     "users_id FROM users WHERE users.id = :id_1) AS anon_1 "
@@ -860,11 +864,12 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
             [
                 (
                     "SELECT users.id AS users_id, users.name AS users_name, "
+                    "orders_1.id AS orders_1_id, "
                     "orders_1.user_id AS orders_1_user_id, "
+                    "orders_1.address_id AS orders_1_address_id, "
                     "orders_1.description AS orders_1_description, "
-                    "orders_1.isopen AS orders_1_isopen, "
-                    "orders_1.id AS orders_1_id, orders_1.address_id AS "
-                    "orders_1_address_id FROM users "
+                    "orders_1.isopen AS orders_1_isopen "
+                    "FROM users "
                     "LEFT OUTER JOIN orders AS orders_1 ON users.id = "
                     "orders_1.user_id WHERE users.id = :id_1 "
                     "ORDER BY orders_1.id",
@@ -923,12 +928,13 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
             [
                 (
                     "SELECT users.id AS users_id, users.name AS users_name, "
-                    "orders_1.user_id AS orders_1_user_id, "
                     "lower(orders_1.description) AS lower_1, "
-                    "orders_1.isopen AS orders_1_isopen, "
                     "orders_1.id AS orders_1_id, "
+                    "orders_1.user_id AS orders_1_user_id, "
                     "orders_1.address_id AS orders_1_address_id, "
-                    "orders_1.description AS orders_1_description FROM users "
+                    "orders_1.description AS orders_1_description, "
+                    "orders_1.isopen AS orders_1_isopen "
+                    "FROM users "
                     "LEFT OUTER JOIN orders AS orders_1 ON users.id = "
                     "orders_1.user_id WHERE users.id = :id_1 "
                     "ORDER BY orders_1.id",
@@ -956,11 +962,12 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
         q = sess.query(Order).options(Load(Order).undefer("*"))
         self.assert_compile(
             q,
-            "SELECT orders.user_id AS orders_user_id, "
-            "orders.description AS orders_description, "
-            "orders.isopen AS orders_isopen, "
+            "SELECT "
             "orders.id AS orders_id, "
-            "orders.address_id AS orders_address_id "
+            "orders.user_id AS orders_user_id, "
+            "orders.address_id AS orders_address_id, "
+            "orders.description AS orders_description, "
+            "orders.isopen AS orders_isopen "
             "FROM orders",
         )
 
@@ -1322,9 +1329,8 @@ class DeferredOptionsTest(AssertsCompiledSQL, _fixtures.FixtureTest):
         )
         self.assert_compile(
             q,
-            "SELECT orders.description AS orders_description, "
-            "orders.id AS orders_id, "
-            "orders.user_id AS orders_user_id, "
+            "SELECT orders.id AS orders_id, orders.user_id AS orders_user_id, "
+            "orders.description AS orders_description, "
             "orders.isopen AS orders_isopen FROM orders",
         )
 
index 0aa38ca54d5ae25b8faf16f2ccc0572fa0bc9211..e0690862189a5d66fd01ffd73e1298c4e6bfce0a 100644 (file)
@@ -910,9 +910,9 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
                 self.assert_compile(
                     q,
                     "SELECT "
-                    "addresses_1.id AS addresses_1_id, "
                     "users_1.id AS users_1_id, "
                     "users_1.name AS users_1_name, "
+                    "addresses_1.id AS addresses_1_id, "
                     "addresses_1.user_id AS addresses_1_user_id, "
                     "addresses_1.email_address AS "
                     "addresses_1_email_address, "
@@ -928,9 +928,9 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
             self.assert_compile(
                 q,
                 "SELECT "
-                "addresses_1.id AS addresses_1_id, "
                 "users_1.id AS users_1_id, "
                 "users_1.name AS users_1_name, "
+                "addresses_1.id AS addresses_1_id, "
                 "addresses_1.user_id AS addresses_1_user_id, "
                 "addresses_1.email_address AS "
                 "addresses_1_email_address, "
index 559f4ed9d51900abdd95e1202b3a346871679a4e..463009065666b2429d26cc48f4f0902ca0f80b60 100644 (file)
@@ -4221,10 +4221,10 @@ class SetOpsWDeferredTest(QueryTest, AssertsCompiledSQL):
         self.assert_compile(
             stmt,
             "SELECT anon_1.users_id AS anon_1_users_id FROM "
-            "(SELECT users.name AS users_name, users.id AS users_id "
+            "(SELECT users.id AS users_id, users.name AS users_name "
             "FROM users WHERE users.id = :id_1 "
             "UNION "
-            "SELECT users.name AS users_name, users.id AS users_id FROM users "
+            "SELECT users.id AS users_id, users.name AS users_name FROM users "
             "WHERE users.id = :id_2) AS anon_1 ORDER BY anon_1.users_id",
         )
 
@@ -4253,12 +4253,13 @@ class SetOpsWDeferredTest(QueryTest, AssertsCompiledSQL):
         stmt = s1.union(s2).options(undefer(User.name)).order_by(User.id)
         self.assert_compile(
             stmt,
-            "SELECT anon_1.users_name AS anon_1_users_name, "
-            "anon_1.users_id AS anon_1_users_id FROM "
-            "(SELECT users.name AS users_name, users.id AS users_id "
+            "SELECT anon_1.users_id AS anon_1_users_id, "
+            "anon_1.users_name AS anon_1_users_name "
+            "FROM "
+            "(SELECT users.id AS users_id, users.name AS users_name "
             "FROM users WHERE users.id = :id_1 "
             "UNION "
-            "SELECT users.name AS users_name, users.id AS users_id "
+            "SELECT users.id AS users_id, users.name AS users_name "
             "FROM users WHERE users.id = :id_2) AS anon_1 "
             "ORDER BY anon_1.users_id",
         )
@@ -4298,14 +4299,15 @@ class SetOpsWDeferredTest(QueryTest, AssertsCompiledSQL):
             stmt,
             "SELECT anon_1.anon_2_users_id AS anon_1_anon_2_users_id "
             "FROM ("
-            "SELECT anon_2.users_name AS anon_2_users_name, "
-            "anon_2.users_id AS anon_2_users_id FROM "
-            "(SELECT users.name AS users_name, users.id AS users_id "
+            "SELECT anon_2.users_id AS anon_2_users_id, "
+            "anon_2.users_name AS anon_2_users_name "
+            "FROM "
+            "(SELECT users.id AS users_id, users.name AS users_name "
             "FROM users WHERE users.id = :id_1 UNION "
-            "SELECT users.name AS users_name, users.id AS users_id "
+            "SELECT users.id AS users_id, users.name AS users_name "
             "FROM users WHERE users.id = :id_2) AS anon_2 "
             "UNION "
-            "SELECT users.name AS users_name, users.id AS users_id FROM users "
+            "SELECT users.id AS users_id, users.name AS users_name FROM users "
             "WHERE users.id = :id_3) AS anon_1 "
             "ORDER BY anon_1.anon_2_users_id",
         )
@@ -4342,17 +4344,18 @@ class SetOpsWDeferredTest(QueryTest, AssertsCompiledSQL):
         )
         self.assert_compile(
             stmt,
-            "SELECT anon_1.anon_2_users_name AS anon_1_anon_2_users_name, "
-            "anon_1.anon_2_users_id AS anon_1_anon_2_users_id "
+            "SELECT anon_1.anon_2_users_id AS anon_1_anon_2_users_id, "
+            "anon_1.anon_2_users_name AS anon_1_anon_2_users_name "
             "FROM ("
-            "SELECT anon_2.users_name AS anon_2_users_name, "
-            "anon_2.users_id AS anon_2_users_id FROM "
-            "(SELECT users.name AS users_name, users.id AS users_id "
+            "SELECT anon_2.users_id AS anon_2_users_id, "
+            "anon_2.users_name AS anon_2_users_name "
+            "FROM "
+            "(SELECT users.id AS users_id, users.name AS users_name "
             "FROM users WHERE users.id = :id_1 UNION "
-            "SELECT users.name AS users_name, users.id AS users_id "
+            "SELECT users.id AS users_id, users.name AS users_name "
             "FROM users WHERE users.id = :id_2) AS anon_2 "
             "UNION "
-            "SELECT users.name AS users_name, users.id AS users_id FROM users "
+            "SELECT users.id AS users_id, users.name AS users_name FROM users "
             "WHERE users.id = :id_3) AS anon_1 "
             "ORDER BY anon_1.anon_2_users_id",
         )