diff options
| -rw-r--r-- | doc/build/changelog/unreleased_20/8705.rst | 31 | ||||
| -rw-r--r-- | doc/build/orm/queryguide/columns.rst | 8 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 257 | ||||
| -rw-r--r-- | test/ext/declarative/test_inheritance.py | 5 | ||||
| -rw-r--r-- | test/ext/test_hybrid.py | 24 | ||||
| -rw-r--r-- | test/ext/test_serializer.py | 9 | ||||
| -rw-r--r-- | test/orm/declarative/test_basic.py | 133 | ||||
| -rw-r--r-- | test/orm/declarative/test_mixin.py | 4 | ||||
| -rw-r--r-- | test/orm/declarative/test_typed_mapping.py | 4 | ||||
| -rw-r--r-- | test/orm/dml/test_update_delete_where.py | 20 | ||||
| -rw-r--r-- | test/orm/inheritance/test_basic.py | 60 | ||||
| -rw-r--r-- | test/orm/inheritance/test_relationship.py | 8 | ||||
| -rw-r--r-- | test/orm/test_core_compilation.py | 55 | ||||
| -rw-r--r-- | test/orm/test_deferred.py | 76 | ||||
| -rw-r--r-- | test/orm/test_mapper.py | 4 | ||||
| -rw-r--r-- | test/orm/test_query.py | 39 |
16 files changed, 534 insertions, 203 deletions
diff --git a/doc/build/changelog/unreleased_20/8705.rst b/doc/build/changelog/unreleased_20/8705.rst new file mode 100644 index 000000000..2c1965769 --- /dev/null +++ b/doc/build/changelog/unreleased_20/8705.rst @@ -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`. diff --git a/doc/build/orm/queryguide/columns.rst b/doc/build/orm/queryguide/columns.rst index f9d2816a4..29edca345 100644 --- a/doc/build/orm/queryguide/columns.rst +++ b/doc/build/orm/queryguide/columns.rst @@ -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,) diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 5d784498a..d5576fac5 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -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, " diff --git a/test/ext/declarative/test_inheritance.py b/test/ext/declarative/test_inheritance.py index d22090086..9efe08029 100644 --- a/test/ext/declarative/test_inheritance.py +++ b/test/ext/declarative/test_inheritance.py @@ -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", ) diff --git a/test/ext/test_hybrid.py b/test/ext/test_hybrid.py index 0cba8f3a1..252844a48 100644 --- a/test/ext/test_hybrid.py +++ b/test/ext/test_hybrid.py @@ -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={ diff --git a/test/ext/test_serializer.py b/test/ext/test_serializer.py index 76fd90fa8..e15ace2eb 100644 --- a/test/ext/test_serializer.py +++ b/test/ext/test_serializer.py @@ -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}, ) diff --git a/test/orm/declarative/test_basic.py b/test/orm/declarative/test_basic.py index 9f8e81d91..6959d06ac 100644 --- a/test/orm/declarative/test_basic.py +++ b/test/orm/declarative/test_basic.py @@ -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) diff --git a/test/orm/declarative/test_mixin.py b/test/orm/declarative/test_mixin.py index 4d438e68b..967958846 100644 --- a/test/orm/declarative/test_mixin.py +++ b/test/orm/declarative/test_mixin.py @@ -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 diff --git a/test/orm/declarative/test_typed_mapping.py b/test/orm/declarative/test_typed_mapping.py index 1b4be84d3..72fd4d84e 100644 --- a/test/orm/declarative/test_typed_mapping.py +++ b/test/orm/declarative/test_typed_mapping.py @@ -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( diff --git a/test/orm/dml/test_update_delete_where.py b/test/orm/dml/test_update_delete_where.py index ebaba191a..9c8809eef 100644 --- a/test/orm/dml/test_update_delete_where.py +++ b/test/orm/dml/test_update_delete_where.py @@ -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}], ), diff --git a/test/orm/inheritance/test_basic.py b/test/orm/inheritance/test_basic.py index 9d7b73e1c..0ba900798 100644 --- a/test/orm/inheritance/test_basic.py +++ b/test/orm/inheritance/test_basic.py @@ -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", diff --git a/test/orm/inheritance/test_relationship.py b/test/orm/inheritance/test_relationship.py index 779239015..925bf7dd0 100644 --- a/test/orm/inheritance/test_relationship.py +++ b/test/orm/inheritance/test_relationship.py @@ -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 " diff --git a/test/orm/test_core_compilation.py b/test/orm/test_core_compilation.py index efa4c773a..8fd22bdd0 100644 --- a/test/orm/test_core_compilation.py +++ b/test/orm/test_core_compilation.py @@ -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", ) diff --git a/test/orm/test_deferred.py b/test/orm/test_deferred.py index 1c5dadc01..5bd70ca7b 100644 --- a/test/orm/test_deferred.py +++ b/test/orm/test_deferred.py @@ -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", ) diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py index 0aa38ca54..e06908621 100644 --- a/test/orm/test_mapper.py +++ b/test/orm/test_mapper.py @@ -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, " diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 559f4ed9d..463009065 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -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", ) |
