summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_14/4719.rst14
-rw-r--r--doc/build/changelog/unreleased_14/4877.rst11
-rw-r--r--lib/sqlalchemy/dialects/mssql/base.py19
-rw-r--r--lib/sqlalchemy/engine/default.py1
-rw-r--r--lib/sqlalchemy/engine/result.py73
-rw-r--r--lib/sqlalchemy/orm/query.py47
-rw-r--r--lib/sqlalchemy/orm/strategy_options.py7
-rw-r--r--lib/sqlalchemy/sql/compiler.py29
-rw-r--r--lib/sqlalchemy/sql/elements.py9
-rw-r--r--lib/sqlalchemy/sql/selectable.py5
-rw-r--r--lib/sqlalchemy/sql/util.py4
-rw-r--r--test/orm/test_deprecations.py232
-rw-r--r--test/orm/test_froms.py174
-rw-r--r--test/orm/test_mapper.py8
-rw-r--r--test/orm/test_pickled.py2
-rw-r--r--test/orm/test_query.py110
-rw-r--r--test/profiles.txt8
-rw-r--r--test/sql/test_compiler.py39
-rw-r--r--test/sql/test_deprecations.py505
-rw-r--r--test/sql/test_resultset.py179
-rw-r--r--test/sql/test_text.py10
21 files changed, 1160 insertions, 326 deletions
diff --git a/doc/build/changelog/unreleased_14/4719.rst b/doc/build/changelog/unreleased_14/4719.rst
new file mode 100644
index 000000000..d0a5cca91
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/4719.rst
@@ -0,0 +1,14 @@
+.. change::
+ :tags: bug, orm
+ :tickets: 4719
+
+ Calling the :meth:`.Query.instances` method without passing a
+ :class:`.QueryContext` is deprecated. The original use case for this was
+ that a :class:`.Query` could yield ORM objects when given only the entities
+ to be selected as well as a DBAPI cursor object. However, for this to work
+ correctly there is essential metadata that is passed from a SQLAlchemy
+ :class:`.ResultProxy` that is derived from the mapped column expressions,
+ which comes originally from the :class:`.QueryContext`. To retrieve ORM
+ results from arbitrary SELECT statements, the :meth:`.Query.from_statement`
+ method should be used.
+
diff --git a/doc/build/changelog/unreleased_14/4877.rst b/doc/build/changelog/unreleased_14/4877.rst
new file mode 100644
index 000000000..4a81e021e
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/4877.rst
@@ -0,0 +1,11 @@
+.. change::
+ :tags: bug, engine
+ :tickets: 4877
+
+ Deprecated the behavior by which a :class:`.Column` can be used as the key
+ in a result set row lookup, when that :class:`.Column` is not part of the
+ SQL selectable that is being selected; that is, it is only matched on name.
+ A deprecation warning is now emitted for this case. Various ORM use
+ cases, such as those involving :func:`.text` constructs, have been improved
+ so that this fallback logic is avoided in most cases.
+
diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py
index d4d303d5d..94c2cbe6d 100644
--- a/lib/sqlalchemy/dialects/mssql/base.py
+++ b/lib/sqlalchemy/dialects/mssql/base.py
@@ -1783,6 +1783,11 @@ class MSSQLCompiler(compiler.SQLCompiler):
return super(MSSQLCompiler, self).visit_binary(binary, **kwargs)
def returning_clause(self, stmt, returning_cols):
+ # SQL server returning clause requires that the columns refer to
+ # the virtual table names "inserted" or "deleted". Here, we make
+ # a simple alias of our table with that name, and then adapt the
+ # columns we have from the list of RETURNING columns to that new name
+ # so that they render as "inserted.<colname>" / "deleted.<colname>".
if self.isinsert or self.isupdate:
target = stmt.table.alias("inserted")
@@ -1791,9 +1796,21 @@ class MSSQLCompiler(compiler.SQLCompiler):
adapter = sql_util.ClauseAdapter(target)
+ # adapter.traverse() takes a column from our target table and returns
+ # the one that is linked to the "inserted" / "deleted" tables. So in
+ # order to retrieve these values back from the result (e.g. like
+ # row[column]), tell the compiler to also add the original unadapted
+ # column to the result map. Before #4877, these were (unknowingly)
+ # falling back using string name matching in the result set which
+ # necessarily used an expensive KeyError in order to match.
+
columns = [
self._label_select_column(
- None, adapter.traverse(c), True, False, {}
+ None,
+ adapter.traverse(c),
+ True,
+ False,
+ {"result_map_targets": (c,)},
)
for c in expression._select_iterables(returning_cols)
]
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index 8d9962c59..f66f06415 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -681,6 +681,7 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
compiled._result_columns,
compiled._ordered_columns,
compiled._textual_ordered_columns,
+ compiled._loose_column_name_matching,
)
self.unicode_statement = util.text_type(compiled)
diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py
index 90c884f94..af5303658 100644
--- a/lib/sqlalchemy/engine/result.py
+++ b/lib/sqlalchemy/engine/result.py
@@ -254,12 +254,15 @@ class ResultMetaData(object):
result_columns,
cols_are_ordered,
textual_ordered,
+ loose_column_name_matching,
) = context.result_column_struct
num_ctx_cols = len(result_columns)
else:
result_columns = (
cols_are_ordered
- ) = num_ctx_cols = textual_ordered = False
+ ) = (
+ num_ctx_cols
+ ) = loose_column_name_matching = textual_ordered = False
# merge cursor.description with the column info
# present in the compiled structure, if any
@@ -270,6 +273,7 @@ class ResultMetaData(object):
num_ctx_cols,
cols_are_ordered,
textual_ordered,
+ loose_column_name_matching,
)
self._keymap = {}
@@ -388,6 +392,7 @@ class ResultMetaData(object):
num_ctx_cols,
cols_are_ordered,
textual_ordered,
+ loose_column_name_matching,
):
"""Merge a cursor.description with compiled result column information.
@@ -482,7 +487,10 @@ class ResultMetaData(object):
# compiled SQL with a mismatch of description cols
# vs. compiled cols, or textual w/ unordered columns
raw_iterator = self._merge_cols_by_name(
- context, cursor_description, result_columns
+ context,
+ cursor_description,
+ result_columns,
+ loose_column_name_matching,
)
else:
# no compiled SQL, just a raw string
@@ -587,13 +595,18 @@ class ResultMetaData(object):
yield idx, colname, mapped_type, coltype, obj, untranslated
- def _merge_cols_by_name(self, context, cursor_description, result_columns):
+ def _merge_cols_by_name(
+ self,
+ context,
+ cursor_description,
+ result_columns,
+ loose_column_name_matching,
+ ):
dialect = context.dialect
case_sensitive = dialect.case_sensitive
match_map = self._create_description_match_map(
- result_columns, case_sensitive
+ result_columns, case_sensitive, loose_column_name_matching
)
-
self.matched_on_name = True
for (
idx,
@@ -622,7 +635,10 @@ class ResultMetaData(object):
@classmethod
def _create_description_match_map(
- cls, result_columns, case_sensitive=True
+ cls,
+ result_columns,
+ case_sensitive=True,
+ loose_column_name_matching=False,
):
"""when matching cursor.description to a set of names that are present
in a Compiled object, as is the case with TextualSelect, get all the
@@ -631,22 +647,29 @@ class ResultMetaData(object):
d = {}
for elem in result_columns:
- key, rec = (
- elem[RM_RENDERED_NAME],
- (elem[RM_NAME], elem[RM_OBJECTS], elem[RM_TYPE]),
- )
+ key = elem[RM_RENDERED_NAME]
if not case_sensitive:
key = key.lower()
if key in d:
- # conflicting keyname, just double up the list
- # of objects. this will cause an "ambiguous name"
- # error if an attempt is made by the result set to
- # access.
+ # conflicting keyname - just add the column-linked objects
+ # to the existing record. if there is a duplicate column
+ # name in the cursor description, this will allow all of those
+ # objects to raise an ambiguous column error
e_name, e_obj, e_type = d[key]
- d[key] = e_name, e_obj + rec[1], e_type
+ d[key] = e_name, e_obj + elem[RM_OBJECTS], e_type
else:
- d[key] = rec
-
+ d[key] = (elem[RM_NAME], elem[RM_OBJECTS], elem[RM_TYPE])
+
+ if loose_column_name_matching:
+ # when using a textual statement with an unordered set
+ # of columns that line up, we are expecting the user
+ # to be using label names in the SQL that match to the column
+ # expressions. Enable more liberal matching for this case;
+ # duplicate keys that are ambiguous will be fixed later.
+ for r_key in elem[RM_OBJECTS]:
+ d.setdefault(
+ r_key, (elem[RM_NAME], elem[RM_OBJECTS], elem[RM_TYPE])
+ )
return d
def _key_fallback(self, key, raiseerr=True):
@@ -688,6 +711,22 @@ class ResultMetaData(object):
break
else:
result = None
+ if result is not None:
+ if result[MD_OBJECTS] is _UNPICKLED:
+ util.warn_deprecated(
+ "Retreiving row values using Column objects from a "
+ "row that was unpickled is deprecated; adequate "
+ "state cannot be pickled for this to be efficient. "
+ "This usage will raise KeyError in a future release."
+ )
+ else:
+ util.warn_deprecated(
+ "Retreiving row values using Column objects with only "
+ "matching names as keys is deprecated, and will raise "
+ "KeyError in a future release; only Column "
+ "objects that are explicitly part of the statement "
+ "object should be used."
+ )
if result is None:
if raiseerr:
raise exc.NoSuchColumnError(
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 0480bfb01..92f9ee952 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -3159,12 +3159,6 @@ class Query(Generative):
"""
statement = coercions.expect(roles.SelectStatementRole, statement)
-
- # TODO: coercions above should have this handled
- assert isinstance(
- statement, (expression.TextClause, expression.SelectBase)
- )
-
self._statement = statement
def first(self):
@@ -3395,21 +3389,21 @@ class Query(Generative):
]
]
- def instances(self, cursor, __context=None):
- """Given a ResultProxy cursor as returned by connection.execute(),
- return an ORM result as an iterator.
+ def instances(self, result_proxy, context=None):
+ """Return an ORM result given a :class:`.ResultProxy` and
+ :class:`.QueryContext`.
- e.g.::
-
- result = engine.execute("select * from users")
- for u in session.query(User).instances(result):
- print u
"""
- context = __context
if context is None:
+ util.warn_deprecated(
+ "Using the Query.instances() method without a context "
+ "is deprecated and will be disallowed in a future release. "
+ "Please make use of :meth:`.Query.from_statement` "
+ "for linking ORM results to arbitrary select constructs."
+ )
context = QueryContext(self)
- return loading.instances(self, cursor, context)
+ return loading.instances(self, result_proxy, context)
def merge_result(self, iterator, load=True):
"""Merge a result into this :class:`.Query` object's Session.
@@ -3824,6 +3818,22 @@ class Query(Generative):
context = QueryContext(self)
if context.statement is not None:
+ if isinstance(context.statement, expression.TextClause):
+ # setup for all entities, including contains_eager entities.
+ for entity in self._entities:
+ entity.setup_context(self, context)
+ context.statement = expression.TextualSelect(
+ context.statement,
+ context.primary_columns,
+ positional=False,
+ )
+ else:
+ # allow TextualSelect with implicit columns as well
+ # as select() with ad-hoc columns, see test_query::TextTest
+ self._from_obj_alias = sql.util.ColumnAdapter(
+ context.statement, adapt_on_names=True
+ )
+
return context
context.labels = not for_statement or self._with_labels
@@ -4603,7 +4613,9 @@ class _ColumnEntity(_QueryEntity):
if ("fetch_column", self) in context.attributes:
column = context.attributes[("fetch_column", self)]
else:
- column = query._adapt_clause(self.column, False, True)
+ column = self.column
+ if query._from_obj_alias:
+ column = query._from_obj_alias.columns[column]
if column._annotations:
# annotated columns perform more slowly in compiler and
@@ -4697,6 +4709,7 @@ class QueryContext(object):
self.statement = query._statement.apply_labels()
else:
self.statement = query._statement
+ self.order_by = None
else:
self.statement = None
self.from_clause = query._from_obj
diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py
index c50b7d041..00c91dab5 100644
--- a/lib/sqlalchemy/orm/strategy_options.py
+++ b/lib/sqlalchemy/orm/strategy_options.py
@@ -1012,6 +1012,13 @@ def contains_eager(loadopt, attr, alias=None):
if not isinstance(alias, str):
info = inspect(alias)
alias = info.selectable
+ else:
+ util.warn_deprecated(
+ "Passing a string name for the 'alias' argument to "
+ "'contains_eager()` is deprecated, and will not work in a "
+ "future release. Please use a sqlalchemy.alias() or "
+ "sqlalchemy.orm.aliased() construct."
+ )
elif getattr(attr, "_of_type", None):
ot = inspect(attr._of_type)
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 5e432a74c..1381e734c 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -490,6 +490,13 @@ class SQLCompiler(Compiled):
True unless using an unordered TextualSelect.
"""
+ _loose_column_name_matching = False
+ """tell the result object that the SQL staement is textual, wants to match
+ up to Column objects, and may be using the ._label in the SELECT rather
+ than the base name.
+
+ """
+
_numeric_binds = False
"""
True if paramstyle is "numeric". This paramstyle is trickier than
@@ -799,6 +806,7 @@ class SQLCompiler(Compiled):
within_label_clause=False,
within_columns_clause=False,
render_label_as_label=None,
+ result_map_targets=(),
**kw
):
# only render labels within the columns clause
@@ -820,7 +828,7 @@ class SQLCompiler(Compiled):
add_to_result_map(
labelname,
label.name,
- (label, labelname) + label._alt_names,
+ (label, labelname) + label._alt_names + result_map_targets,
label.type,
)
@@ -847,7 +855,12 @@ class SQLCompiler(Compiled):
)
def visit_column(
- self, column, add_to_result_map=None, include_table=True, **kwargs
+ self,
+ column,
+ add_to_result_map=None,
+ include_table=True,
+ result_map_targets=(),
+ **kwargs
):
name = orig_name = column.name
if name is None:
@@ -859,7 +872,10 @@ class SQLCompiler(Compiled):
if add_to_result_map is not None:
add_to_result_map(
- name, orig_name, (column, name, column.key), column.type
+ name,
+ orig_name,
+ (column, name, column.key, column._label) + result_map_targets,
+ column.type,
)
if is_literal:
@@ -948,6 +964,13 @@ class SQLCompiler(Compiled):
self._ordered_columns = (
self._textual_ordered_columns
) = taf.positional
+
+ # enable looser result column matching when the SQL text links to
+ # Column objects by name only
+ self._loose_column_name_matching = not taf.positional and bool(
+ taf.column_args
+ )
+
for c in taf.column_args:
self.process(
c,
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 3045cb84e..8ee157b6f 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -4278,7 +4278,13 @@ class ColumnClause(roles.LabeledColumnExprRole, Immutable, ColumnElement):
label = quoted_name(label, t.name.quote)
# ensure the label name doesn't conflict with that
- # of an existing column
+ # of an existing column. note that this implies that any
+ # Column must **not** set up its _label before its parent table
+ # has all of its other Column objects set up. There are several
+ # tables in the test suite which will fail otherwise; example:
+ # table "owner" has columns "name" and "owner_name". Therefore
+ # column owner.name cannot use the label "owner_name", it has
+ # to be "owner_name_1".
if label in t.c:
_label = label
counter = 1
@@ -4339,7 +4345,6 @@ class ColumnClause(roles.LabeledColumnExprRole, Immutable, ColumnElement):
c._proxies = [self]
if selectable._is_clone_of is not None:
c._is_clone_of = selectable._is_clone_of.columns.get(c.key)
-
return c.key, c
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index b41a77622..33ba95717 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -4449,7 +4449,10 @@ class TextualSelect(SelectBase):
def __init__(self, text, columns, positional=False):
self.element = text
- self.column_args = columns
+ # convert for ORM attributes->columns, etc
+ self.column_args = [
+ coercions.expect(roles.ColumnsClauseRole, c) for c in columns
+ ]
self.positional = positional
@SelectBase._memoized_property
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index 780cdc7b2..5aeed0c1c 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -798,9 +798,7 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
if newcol is not None:
return newcol
if self.adapt_on_names and newcol is None:
- # TODO: this should be changed to .exported_columns if and
- # when we need to be able to adapt a plain Select statement
- newcol = self.selectable.c.get(col.name)
+ newcol = self.selectable.exported_columns.get(col.name)
return newcol
def replace(self, col):
diff --git a/test/orm/test_deprecations.py b/test/orm/test_deprecations.py
index f70566c8c..76cfb8667 100644
--- a/test/orm/test_deprecations.py
+++ b/test/orm/test_deprecations.py
@@ -19,6 +19,7 @@ from sqlalchemy.orm import column_property
from sqlalchemy.orm import comparable_property
from sqlalchemy.orm import composite
from sqlalchemy.orm import configure_mappers
+from sqlalchemy.orm import contains_alias
from sqlalchemy.orm import contains_eager
from sqlalchemy.orm import create_session
from sqlalchemy.orm import defer
@@ -55,6 +56,7 @@ from sqlalchemy.util.compat import pypy
from . import _fixtures
from .inheritance import _poly_fixtures
from .test_options import PathTest as OptionsPathTest
+from .test_query import QueryTest
from .test_transaction import _LocalFixture
@@ -2976,3 +2978,233 @@ class NonPrimaryMapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
addresses,
non_primary=True,
)
+
+
+class InstancesTest(QueryTest, AssertsCompiledSQL):
+ def test_from_alias_one(self):
+ User, addresses, users = (
+ self.classes.User,
+ self.tables.addresses,
+ self.tables.users,
+ )
+
+ query = (
+ users.select(users.c.id == 7)
+ .union(users.select(users.c.id > 7))
+ .alias("ulist")
+ .outerjoin(addresses)
+ .select(
+ use_labels=True, order_by=[text("ulist.id"), addresses.c.id]
+ )
+ )
+ sess = create_session()
+ q = sess.query(User)
+
+ # note this has multiple problems because we aren't giving Query
+ # the statement where it would be able to create an adapter
+ def go():
+ with testing.expect_deprecated(
+ r"Using the Query.instances\(\) method without a context",
+ "Retreiving row values using Column objects with only "
+ "matching names",
+ ):
+ result = list(
+ q.options(
+ contains_alias("ulist"), contains_eager("addresses")
+ ).instances(query.execute())
+ )
+ assert self.static.user_address_result == result
+
+ self.assert_sql_count(testing.db, go, 1)
+
+ def test_contains_eager(self):
+ users, addresses, User = (
+ self.tables.users,
+ self.tables.addresses,
+ self.classes.User,
+ )
+
+ sess = create_session()
+
+ selectquery = users.outerjoin(addresses).select(
+ users.c.id < 10,
+ use_labels=True,
+ order_by=[users.c.id, addresses.c.id],
+ )
+ q = sess.query(User)
+
+ def go():
+ with testing.expect_deprecated(
+ r"Using the Query.instances\(\) method without a context"
+ ):
+ result = list(
+ q.options(contains_eager("addresses")).instances(
+ selectquery.execute()
+ )
+ )
+ assert self.static.user_address_result[0:3] == result
+
+ self.assert_sql_count(testing.db, go, 1)
+
+ sess.expunge_all()
+
+ def go():
+ with testing.expect_deprecated(
+ r"Using the Query.instances\(\) method without a context"
+ ):
+ result = list(
+ q.options(contains_eager(User.addresses)).instances(
+ selectquery.execute()
+ )
+ )
+ assert self.static.user_address_result[0:3] == result
+
+ self.assert_sql_count(testing.db, go, 1)
+
+ def test_contains_eager_string_alias(self):
+ addresses, users, User = (
+ self.tables.addresses,
+ self.tables.users,
+ self.classes.User,
+ )
+
+ sess = create_session()
+ q = sess.query(User)
+
+ adalias = addresses.alias("adalias")
+ selectquery = users.outerjoin(adalias).select(
+ use_labels=True, order_by=[users.c.id, adalias.c.id]
+ )
+
+ # note this has multiple problems because we aren't giving Query
+ # the statement where it would be able to create an adapter
+ def go():
+ with testing.expect_deprecated(
+ r"Using the Query.instances\(\) method without a context",
+ r"Passing a string name for the 'alias' argument to "
+ r"'contains_eager\(\)` is deprecated",
+ "Retreiving row values using Column objects with only "
+ "matching names",
+ ):
+ result = list(
+ q.options(
+ contains_eager("addresses", alias="adalias")
+ ).instances(selectquery.execute())
+ )
+ assert self.static.user_address_result == result
+
+ self.assert_sql_count(testing.db, go, 1)
+
+ def test_contains_eager_aliased_instances(self):
+ addresses, users, User = (
+ self.tables.addresses,
+ self.tables.users,
+ self.classes.User,
+ )
+
+ sess = create_session()
+ q = sess.query(User)
+
+ adalias = addresses.alias("adalias")
+ selectquery = users.outerjoin(adalias).select(
+ use_labels=True, order_by=[users.c.id, adalias.c.id]
+ )
+
+ # note this has multiple problems because we aren't giving Query
+ # the statement where it would be able to create an adapter
+ def go():
+ with testing.expect_deprecated(
+ r"Using the Query.instances\(\) method without a context"
+ ):
+ result = list(
+ q.options(
+ contains_eager("addresses", alias=adalias)
+ ).instances(selectquery.execute())
+ )
+ assert self.static.user_address_result == result
+
+ self.assert_sql_count(testing.db, go, 1)
+
+ def test_contains_eager_multi_string_alias(self):
+ orders, items, users, order_items, User = (
+ self.tables.orders,
+ self.tables.items,
+ self.tables.users,
+ self.tables.order_items,
+ self.classes.User,
+ )
+
+ sess = create_session()
+ q = sess.query(User)
+
+ oalias = orders.alias("o1")
+ ialias = items.alias("i1")
+ query = (
+ users.outerjoin(oalias)
+ .outerjoin(order_items)
+ .outerjoin(ialias)
+ .select(use_labels=True)
+ .order_by(users.c.id, oalias.c.id, ialias.c.id)
+ )
+
+ # test using string alias with more than one level deep
+ def go():
+ with testing.expect_deprecated(
+ r"Using the Query.instances\(\) method without a context",
+ r"Passing a string name for the 'alias' argument to "
+ r"'contains_eager\(\)` is deprecated",
+ "Retreiving row values using Column objects with only "
+ "matching names",
+ ):
+ result = list(
+ q.options(
+ contains_eager("orders", alias="o1"),
+ contains_eager("orders.items", alias="i1"),
+ ).instances(query.execute())
+ )
+ assert self.static.user_order_result == result
+
+ self.assert_sql_count(testing.db, go, 1)
+
+ def test_contains_eager_multi_alias(self):
+ orders, items, users, order_items, User = (
+ self.tables.orders,
+ self.tables.items,
+ self.tables.users,
+ self.tables.order_items,
+ self.classes.User,
+ )
+
+ sess = create_session()
+ q = sess.query(User)
+
+ oalias = orders.alias("o1")
+ ialias = items.alias("i1")
+ query = (
+ users.outerjoin(oalias)
+ .outerjoin(order_items)
+ .outerjoin(ialias)
+ .select(use_labels=True)
+ .order_by(users.c.id, oalias.c.id, ialias.c.id)
+ )
+
+ # test using Alias with more than one level deep
+
+ # new way:
+ # from sqlalchemy.orm.strategy_options import Load
+ # opt = Load(User).contains_eager('orders', alias=oalias).
+ # contains_eager('items', alias=ialias)
+
+ def go():
+ with testing.expect_deprecated(
+ r"Using the Query.instances\(\) method without a context"
+ ):
+ result = list(
+ q.options(
+ contains_eager("orders", alias=oalias),
+ contains_eager("orders.items", alias=ialias),
+ ).instances(query.execute())
+ )
+ assert self.static.user_order_result == result
+
+ self.assert_sql_count(testing.db, go, 1)
diff --git a/test/orm/test_froms.py b/test/orm/test_froms.py
index efa45affa..736e27c14 100644
--- a/test/orm/test_froms.py
+++ b/test/orm/test_froms.py
@@ -852,35 +852,6 @@ class AddEntityEquivalenceTest(fixtures.MappedTest, AssertsCompiledSQL):
class InstancesTest(QueryTest, AssertsCompiledSQL):
- def test_from_alias_one(self):
- User, addresses, users = (
- self.classes.User,
- self.tables.addresses,
- self.tables.users,
- )
-
- query = (
- users.select(users.c.id == 7)
- .union(users.select(users.c.id > 7))
- .alias("ulist")
- .outerjoin(addresses)
- .select(
- use_labels=True, order_by=[text("ulist.id"), addresses.c.id]
- )
- )
- sess = create_session()
- q = sess.query(User)
-
- def go():
- result = list(
- q.options(
- contains_alias("ulist"), contains_eager("addresses")
- ).instances(query.execute())
- )
- assert self.static.user_address_result == result
-
- self.assert_sql_count(testing.db, go, 1)
-
def test_from_alias_two(self):
User, addresses, users = (
self.classes.User,
@@ -972,12 +943,8 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
self.assert_sql_count(testing.db, go, 1)
- def test_contains_eager(self):
- users, addresses, User = (
- self.tables.users,
- self.tables.addresses,
- self.classes.User,
- )
+ def test_contains_eager_one(self):
+ addresses, User = (self.tables.addresses, self.classes.User)
sess = create_session()
@@ -1005,7 +972,15 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
assert self.static.user_address_result == q.all()
self.assert_sql_count(testing.db, go, 1)
- sess.expunge_all()
+
+ def test_contains_eager_two(self):
+ users, addresses, User = (
+ self.tables.users,
+ self.tables.addresses,
+ self.classes.User,
+ )
+
+ sess = create_session()
adalias = addresses.alias()
q = (
@@ -1019,37 +994,23 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
eq_(self.static.user_address_result, q.all())
self.assert_sql_count(testing.db, go, 1)
- sess.expunge_all()
+
+ def test_contains_eager_four(self):
+ users, addresses, User = (
+ self.tables.users,
+ self.tables.addresses,
+ self.classes.User,
+ )
+
+ sess = create_session()
selectquery = users.outerjoin(addresses).select(
users.c.id < 10,
use_labels=True,
order_by=[users.c.id, addresses.c.id],
)
- q = sess.query(User)
-
- def go():
- result = list(
- q.options(contains_eager("addresses")).instances(
- selectquery.execute()
- )
- )
- assert self.static.user_address_result[0:3] == result
- self.assert_sql_count(testing.db, go, 1)
-
- sess.expunge_all()
-
- def go():
- result = list(
- q.options(contains_eager(User.addresses)).instances(
- selectquery.execute()
- )
- )
- assert self.static.user_address_result[0:3] == result
-
- self.assert_sql_count(testing.db, go, 1)
- sess.expunge_all()
+ q = sess.query(User)
def go():
result = (
@@ -1061,58 +1022,6 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
self.assert_sql_count(testing.db, go, 1)
- def test_contains_eager_string_alias(self):
- addresses, users, User = (
- self.tables.addresses,
- self.tables.users,
- self.classes.User,
- )
-
- sess = create_session()
- q = sess.query(User)
-
- adalias = addresses.alias("adalias")
- selectquery = users.outerjoin(adalias).select(
- use_labels=True, order_by=[users.c.id, adalias.c.id]
- )
-
- # string alias name
- def go():
- result = list(
- q.options(
- contains_eager("addresses", alias="adalias")
- ).instances(selectquery.execute())
- )
- assert self.static.user_address_result == result
-
- self.assert_sql_count(testing.db, go, 1)
-
- def test_contains_eager_aliased_instances(self):
- addresses, users, User = (
- self.tables.addresses,
- self.tables.users,
- self.classes.User,
- )
-
- sess = create_session()
- q = sess.query(User)
-
- adalias = addresses.alias("adalias")
- selectquery = users.outerjoin(adalias).select(
- use_labels=True, order_by=[users.c.id, adalias.c.id]
- )
-
- # expression.Alias object
- def go():
- result = list(
- q.options(
- contains_eager("addresses", alias=adalias)
- ).instances(selectquery.execute())
- )
- assert self.static.user_address_result == result
-
- self.assert_sql_count(testing.db, go, 1)
-
def test_contains_eager_aliased(self):
User, Address = self.classes.User, self.classes.Address
@@ -1132,40 +1041,6 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
self.assert_sql_count(testing.db, go, 1)
- def test_contains_eager_multi_string_alias(self):
- orders, items, users, order_items, User = (
- self.tables.orders,
- self.tables.items,
- self.tables.users,
- self.tables.order_items,
- self.classes.User,
- )
-
- sess = create_session()
- q = sess.query(User)
-
- oalias = orders.alias("o1")
- ialias = items.alias("i1")
- query = (
- users.outerjoin(oalias)
- .outerjoin(order_items)
- .outerjoin(ialias)
- .select(use_labels=True)
- .order_by(users.c.id, oalias.c.id, ialias.c.id)
- )
-
- # test using string alias with more than one level deep
- def go():
- result = list(
- q.options(
- contains_eager("orders", alias="o1"),
- contains_eager("orders.items", alias="i1"),
- ).instances(query.execute())
- )
- assert self.static.user_order_result == result
-
- self.assert_sql_count(testing.db, go, 1)
-
def test_contains_eager_multi_alias(self):
orders, items, users, order_items, User = (
self.tables.orders,
@@ -1200,7 +1075,7 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
q.options(
contains_eager("orders", alias=oalias),
contains_eager("orders.items", alias=ialias),
- ).instances(query.execute())
+ ).from_statement(query)
)
assert self.static.user_order_result == result
@@ -2098,7 +1973,7 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL):
use_labels=True, order_by=[users.c.id, addresses.c.id]
)
eq_(
- list(sess.query(User, Address).instances(selectquery.execute())),
+ list(sess.query(User, Address).from_statement(selectquery)),
expected,
)
sess.expunge_all()
@@ -2262,8 +2137,9 @@ class MixedEntitiesTest(QueryTest, AssertsCompiledSQL):
sess.expunge_all()
# TODO: figure out why group_by(users) doesn't work here
+ count = func.count(addresses.c.id).label("count")
s = (
- select([users, func.count(addresses.c.id).label("count")])
+ select([users, count])
.select_from(users.outerjoin(addresses))
.group_by(*[c for c in users.c])
.order_by(User.id)
diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py
index 93346b32f..2622ea640 100644
--- a/test/orm/test_mapper.py
+++ b/test/orm/test_mapper.py
@@ -2256,8 +2256,10 @@ class OptionsTest(_fixtures.FixtureTest):
# session's identity map)
r = users.select().order_by(users.c.id).execute()
+ ctx = sess.query(User)._compile_context()
+
def go():
- result = list(sess.query(User).instances(r))
+ result = list(sess.query(User).instances(r, ctx))
eq_(result, self.static.user_address_result)
self.sql_count_(4, go)
@@ -2353,8 +2355,10 @@ class OptionsTest(_fixtures.FixtureTest):
# then assert the data, which will launch 6 more lazy loads
r = users.select().execute()
+ ctx = sess.query(User)._compile_context()
+
def go():
- result = list(sess.query(User).instances(r))
+ result = list(sess.query(User).instances(r, ctx))
eq_(result, self.static.user_all_result)
self.assert_sql_count(testing.db, go, 6)
diff --git a/test/orm/test_pickled.py b/test/orm/test_pickled.py
index 399c881ac..72a68c42b 100644
--- a/test/orm/test_pickled.py
+++ b/test/orm/test_pickled.py
@@ -793,8 +793,6 @@ class TupleLabelTest(_fixtures.FixtureTest):
eq_(row.User, row[0])
eq_(row.orders, row[1])
- # test here that first col is not labeled, only
- # one name in keys, matches correctly
for row in sess.query(User.name + "hoho", User.name):
eq_(list(row.keys()), ["name"])
eq_(row[0], row.name + "hoho")
diff --git a/test/orm/test_query.py b/test/orm/test_query.py
index bcd13e6e2..7c4811dc7 100644
--- a/test/orm/test_query.py
+++ b/test/orm/test_query.py
@@ -36,6 +36,7 @@ from sqlalchemy.orm import attributes
from sqlalchemy.orm import backref
from sqlalchemy.orm import Bundle
from sqlalchemy.orm import column_property
+from sqlalchemy.orm import contains_eager
from sqlalchemy.orm import create_session
from sqlalchemy.orm import defer
from sqlalchemy.orm import joinedload
@@ -63,6 +64,7 @@ from sqlalchemy.testing.assertions import expect_warnings
from sqlalchemy.testing.assertsql import CompiledSQL
from sqlalchemy.testing.schema import Column
from sqlalchemy.testing.schema import Table
+from sqlalchemy.util import collections_abc
from test.orm import _fixtures
@@ -99,7 +101,7 @@ class OnlyReturnTuplesTest(QueryTest):
def test_single_entity_true(self):
User = self.classes.User
row = create_session().query(User).only_return_tuples(True).first()
- assert isinstance(row, tuple)
+ assert isinstance(row, collections_abc.Sequence)
def test_multiple_entity_false(self):
User = self.classes.User
@@ -109,7 +111,7 @@ class OnlyReturnTuplesTest(QueryTest):
.only_return_tuples(False)
.first()
)
- assert isinstance(row, tuple)
+ assert isinstance(row, collections_abc.Sequence)
def test_multiple_entity_true(self):
User = self.classes.User
@@ -119,7 +121,7 @@ class OnlyReturnTuplesTest(QueryTest):
.only_return_tuples(True)
.first()
)
- assert isinstance(row, tuple)
+ assert isinstance(row, collections_abc.Sequence)
class RowTupleTest(QueryTest):
@@ -1762,7 +1764,9 @@ class ExpressionTest(QueryTest, AssertsCompiledSQL):
)
result = list(
- session.query(User).instances(s.execute(emailad="jack@bean.com"))
+ session.query(User)
+ .params(emailad="jack@bean.com")
+ .from_statement(s)
)
eq_([User(id=7)], result)
@@ -4168,7 +4172,7 @@ class HintsTest(QueryTest, AssertsCompiledSQL):
class TextTest(QueryTest, AssertsCompiledSQL):
__dialect__ = "default"
- def test_fulltext(self):
+ def test_needs_text(self):
User = self.classes.User
assert_raises_message(
@@ -4178,6 +4182,9 @@ class TextTest(QueryTest, AssertsCompiledSQL):
"select * from users order by id",
)
+ def test_select_star(self):
+ User = self.classes.User
+
eq_(
create_session()
.query(User)
@@ -4195,6 +4202,97 @@ class TextTest(QueryTest, AssertsCompiledSQL):
None,
)
+ def test_columns_mismatched(self):
+ # test that columns using column._label match, as well as that
+ # ordering doesn't matter
+ User = self.classes.User
+
+ s = create_session()
+ q = s.query(User).from_statement(
+ text(
+ "select name, 27 as foo, id as users_id from users order by id"
+ )
+ )
+ eq_(
+ q.all(),
+ [
+ User(id=7, name="jack"),
+ User(id=8, name="ed"),
+ User(id=9, name="fred"),
+ User(id=10, name="chuck"),
+ ],
+ )
+
+ def test_columns_multi_table_uselabels(self):
+ # test that columns using column._label match, as well as that
+ # ordering doesn't matter.
+ User = self.classes.User
+ Address = self.classes.Address
+
+ s = create_session()
+ q = s.query(User, Address).from_statement(
+ text(
+ "select users.name AS users_name, users.id AS users_id, "
+ "addresses.id AS addresses_id FROM users JOIN addresses "
+ "ON users.id = addresses.user_id WHERE users.id=8 "
+ "ORDER BY addresses.id"
+ )
+ )
+
+ eq_(
+ q.all(),
+ [
+ (User(id=8), Address(id=2)),
+ (User(id=8), Address(id=3)),
+ (User(id=8), Address(id=4)),
+ ],
+ )
+
+ def test_columns_multi_table_uselabels_contains_eager(self):
+ # test that columns using column._label match, as well as that
+ # ordering doesn't matter.
+ User = self.classes.User
+ Address = self.classes.Address
+
+ s = create_session()
+ q = (
+ s.query(User)
+ .from_statement(
+ text(
+ "select users.name AS users_name, users.id AS users_id, "
+ "addresses.id AS addresses_id FROM users JOIN addresses "
+ "ON users.id = addresses.user_id WHERE users.id=8 "
+ "ORDER BY addresses.id"
+ )
+ )
+ .options(contains_eager(User.addresses))
+ )
+
+ def go():
+ r = q.all()
+ eq_(r[0].addresses, [Address(id=2), Address(id=3), Address(id=4)])
+
+ self.assert_sql_count(testing.db, go, 1)
+
+ def test_other_eager_loads(self):
+ # this is new in 1.4. with textclause, we build up column loaders
+ # normally, so that eager loaders also get installed. previously,
+ # _compile_context() didn't build up column loaders and attempted
+ # to get them after the fact.
+ User = self.classes.User
+
+ s = create_session()
+ q = (
+ s.query(User)
+ .from_statement(text("select * from users ORDER BY users.id"))
+ .options(subqueryload(User.addresses))
+ )
+
+ def go():
+ eq_(q.all(), self.static.user_address_result)
+
+ self.assert_sql_count(testing.db, go, 2)
+
def test_whereclause(self):
User = self.classes.User
@@ -4231,7 +4329,7 @@ class TextTest(QueryTest, AssertsCompiledSQL):
"id in (:id1, :id2)",
)
- def test_as_column(self):
+ def test_plain_textual_column(self):
User = self.classes.User
s = create_session()
diff --git a/test/profiles.txt b/test/profiles.txt
index 980b47634..721ce9677 100644
--- a/test/profiles.txt
+++ b/test/profiles.txt
@@ -999,10 +999,10 @@ test.aaa_profiling.test_resultset.ResultSetTest.test_unicode 3.7_sqlite_pysqlite
# TEST: test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_cextensions 6412,322,3969,13151,1340,2187,2770
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6429,322,3969,13149,1341,2187,2766
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 3.7_postgresql_psycopg2_dbapiunicode_cextensions 6177,306,3889,12597,1233,2133,2650
-test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 3.7_postgresql_psycopg2_dbapiunicode_nocextensions 6260,306,3969,13203,1344,2151,2840
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_cextensions 6412,322,4242,13151,1340,2187,2770
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 2.7_postgresql_psycopg2_dbapiunicode_nocextensions 6429,322,4242,13149,1341,2187,2766
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 3.7_postgresql_psycopg2_dbapiunicode_cextensions 6177,306,4162,12597,1233,2133,2650
+test.aaa_profiling.test_zoomark.ZooMarkTest.test_invocation 3.7_postgresql_psycopg2_dbapiunicode_nocextensions 6260,306,4242,13203,1344,2151,2840
# TEST: test.aaa_profiling.test_zoomark_orm.ZooMarkTest.test_invocation
diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py
index cda22a5ac..36e9cd33b 100644
--- a/test/sql/test_compiler.py
+++ b/test/sql/test_compiler.py
@@ -4466,8 +4466,8 @@ class ResultMapTest(fixtures.TestBase):
eq_(
comp._create_result_map(),
{
- "a": ("a", (t.c.a, "a", "a"), t.c.a.type),
- "b": ("b", (t.c.b, "b", "b"), t.c.b.type),
+ "a": ("a", (t.c.a, "a", "a", "t_a"), t.c.a.type),
+ "b": ("b", (t.c.b, "b", "b", "t_b"), t.c.b.type),
},
)
@@ -4478,7 +4478,7 @@ class ResultMapTest(fixtures.TestBase):
comp = stmt.compile()
eq_(
comp._create_result_map(),
- {"a": ("a", (t.c.a, "a", "a"), t.c.a.type)},
+ {"a": ("a", (t.c.a, "a", "a", "t_a"), t.c.a.type)},
)
def test_compound_only_top_populates(self):
@@ -4487,7 +4487,7 @@ class ResultMapTest(fixtures.TestBase):
comp = stmt.compile()
eq_(
comp._create_result_map(),
- {"a": ("a", (t.c.a, "a", "a"), t.c.a.type)},
+ {"a": ("a", (t.c.a, "a", "a", "t_a"), t.c.a.type)},
)
def test_label_plus_element(self):
@@ -4500,7 +4500,7 @@ class ResultMapTest(fixtures.TestBase):
eq_(
comp._create_result_map(),
{
- "a": ("a", (t.c.a, "a", "a"), t.c.a.type),
+ "a": ("a", (t.c.a, "a", "a", "t_a"), t.c.a.type),
"bar": ("bar", (l1, "bar"), l1.type),
"anon_1": (
tc.anon_label,
@@ -4541,7 +4541,7 @@ class ResultMapTest(fixtures.TestBase):
comp = stmt.compile(dialect=postgresql.dialect())
eq_(
comp._create_result_map(),
- {"a": ("a", (aint, "a", "a"), aint.type)},
+ {"a": ("a", (aint, "a", "a", "t2_a"), aint.type)},
)
def test_insert_from_select(self):
@@ -4557,7 +4557,7 @@ class ResultMapTest(fixtures.TestBase):
comp = stmt.compile(dialect=postgresql.dialect())
eq_(
comp._create_result_map(),
- {"a": ("a", (aint, "a", "a"), aint.type)},
+ {"a": ("a", (aint, "a", "a", "t2_a"), aint.type)},
)
def test_nested_api(self):
@@ -4596,12 +4596,22 @@ class ResultMapTest(fixtures.TestBase):
{
"otherid": (
"otherid",
- (table2.c.otherid, "otherid", "otherid"),
+ (
+ table2.c.otherid,
+ "otherid",
+ "otherid",
+ "myothertable_otherid",
+ ),
table2.c.otherid.type,
),
"othername": (
"othername",
- (table2.c.othername, "othername", "othername"),
+ (
+ table2.c.othername,
+ "othername",
+ "othername",
+ "myothertable_othername",
+ ),
table2.c.othername.type,
),
"k1": ("k1", (1, 2, 3), int_),
@@ -4612,18 +4622,23 @@ class ResultMapTest(fixtures.TestBase):
{
"myid": (
"myid",
- (table1.c.myid, "myid", "myid"),
+ (table1.c.myid, "myid", "myid", "mytable_myid"),
table1.c.myid.type,
),
"k2": ("k2", (3, 4, 5), int_),
"name": (
"name",
- (table1.c.name, "name", "name"),
+ (table1.c.name, "name", "name", "mytable_name"),
table1.c.name.type,
),
"description": (
"description",
- (table1.c.description, "description", "description"),
+ (
+ table1.c.description,
+ "description",
+ "description",
+ "mytable_description",
+ ),
table1.c.description.type,
),
},
diff --git a/test/sql/test_deprecations.py b/test/sql/test_deprecations.py
index a75de3f11..bac0a7413 100644
--- a/test/sql/test_deprecations.py
+++ b/test/sql/test_deprecations.py
@@ -2,12 +2,13 @@
from sqlalchemy import alias
from sqlalchemy import bindparam
-from sqlalchemy import Column
+from sqlalchemy import CHAR
from sqlalchemy import column
from sqlalchemy import create_engine
from sqlalchemy import exc
from sqlalchemy import ForeignKey
from sqlalchemy import func
+from sqlalchemy import INT
from sqlalchemy import Integer
from sqlalchemy import join
from sqlalchemy import literal_column
@@ -16,11 +17,11 @@ from sqlalchemy import null
from sqlalchemy import select
from sqlalchemy import sql
from sqlalchemy import String
-from sqlalchemy import Table
from sqlalchemy import table
from sqlalchemy import testing
from sqlalchemy import text
from sqlalchemy import util
+from sqlalchemy import VARCHAR
from sqlalchemy.engine import default
from sqlalchemy.schema import DDL
from sqlalchemy.sql import coercions
@@ -35,8 +36,12 @@ from sqlalchemy.testing import AssertsCompiledSQL
from sqlalchemy.testing import engines
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import in_
from sqlalchemy.testing import is_true
from sqlalchemy.testing import mock
+from sqlalchemy.testing import not_in_
+from sqlalchemy.testing.schema import Column
+from sqlalchemy.testing.schema import Table
class DeprecationWarningsTest(fixtures.TestBase):
@@ -730,7 +735,7 @@ class TextTest(fixtures.TestBase, AssertsCompiledSQL):
{
"myid": (
"myid",
- (table1.c.myid, "myid", "myid"),
+ (table1.c.myid, "myid", "myid", "mytable_myid"),
table1.c.myid.type,
)
},
@@ -993,7 +998,7 @@ class TextualSelectTest(fixtures.TestBase, AssertsCompiledSQL):
{
"myid": (
"myid",
- (table1.c.myid, "myid", "myid"),
+ (table1.c.myid, "myid", "myid", "mytable_myid"),
table1.c.myid.type,
)
},
@@ -1124,3 +1129,495 @@ class DeprecatedAppendMethTest(fixtures.TestBase, AssertsCompiledSQL):
with self._expect_deprecated("Select", "from", "select_from"):
stmt.append_from(t1.join(t2, t1.c.q == t2.c.q))
self.assert_compile(stmt, "SELECT t1.q FROM t1 JOIN t2 ON t1.q = t2.q")
+
+
+class KeyTargetingTest(fixtures.TablesTest):
+ run_inserts = "once"
+ run_deletes = None
+ __backend__ = True
+
+ @classmethod
+ def define_tables(cls, metadata):
+ Table(
+ "keyed1",
+ metadata,
+ Column("a", CHAR(2), key="b"),
+ Column("c", CHAR(2), key="q"),
+ )
+ Table("keyed2", metadata, Column("a", CHAR(2)), Column("b", CHAR(2)))
+ Table("keyed3", metadata, Column("a", CHAR(2)), Column("d", CHAR(2)))
+ Table("keyed4", metadata, Column("b", CHAR(2)), Column("q", CHAR(2)))
+ Table("content", metadata, Column("t", String(30), key="type"))
+ Table("bar", metadata, Column("ctype", String(30), key="content_type"))
+
+ if testing.requires.schemas.enabled:
+ Table(
+ "wschema",
+ metadata,
+ Column("a", CHAR(2), key="b"),
+ Column("c", CHAR(2), key="q"),
+ schema=testing.config.test_schema,
+ )
+
+ @classmethod
+ def insert_data(cls):
+ cls.tables.keyed1.insert().execute(dict(b="a1", q="c1"))
+ cls.tables.keyed2.insert().execute(dict(a="a2", b="b2"))
+ cls.tables.keyed3.insert().execute(dict(a="a3", d="d3"))
+ cls.tables.keyed4.insert().execute(dict(b="b4", q="q4"))
+ cls.tables.content.insert().execute(type="t1")
+
+ if testing.requires.schemas.enabled:
+ cls.tables[
+ "%s.wschema" % testing.config.test_schema
+ ].insert().execute(dict(b="a1", q="c1"))
+
+ def test_column_label_overlap_fallback(self):
+ content, bar = self.tables.content, self.tables.bar
+ row = testing.db.execute(
+ select([content.c.type.label("content_type")])
+ ).first()
+
+ not_in_(content.c.type, row)
+ not_in_(bar.c.content_type, row)
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(sql.column("content_type"), row)
+
+ row = testing.db.execute(
+ select([func.now().label("content_type")])
+ ).first()
+ not_in_(content.c.type, row)
+ not_in_(bar.c.content_type, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(sql.column("content_type"), row)
+
+ def test_columnclause_schema_column_one(self):
+ keyed2 = self.tables.keyed2
+
+ # this is addressed by [ticket:2932]
+ # ColumnClause._compare_name_for_result allows the
+ # columns which the statement is against to be lightweight
+ # cols, which results in a more liberal comparison scheme
+ a, b = sql.column("a"), sql.column("b")
+ stmt = select([a, b]).select_from(table("keyed2"))
+ row = testing.db.execute(stmt).first()
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(keyed2.c.a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(keyed2.c.b, row)
+
+ def test_columnclause_schema_column_two(self):
+ keyed2 = self.tables.keyed2
+
+ a, b = sql.column("a"), sql.column("b")
+ stmt = select([keyed2.c.a, keyed2.c.b])
+ row = testing.db.execute(stmt).first()
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(b, row)
+
+ def test_columnclause_schema_column_three(self):
+ keyed2 = self.tables.keyed2
+
+ # originally addressed by [ticket:2932], however liberalized
+ # Column-targeting rules are deprecated
+
+ a, b = sql.column("a"), sql.column("b")
+ stmt = text("select a, b from keyed2").columns(a=CHAR, b=CHAR)
+ row = testing.db.execute(stmt).first()
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(keyed2.c.a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(keyed2.c.b, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(b, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names",
+ "The SelectBase.c and SelectBase.columns",
+ ):
+ in_(stmt.c.a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names",
+ "The SelectBase.c and SelectBase.columns",
+ ):
+ in_(stmt.c.b, row)
+
+ def test_columnclause_schema_column_four(self):
+ keyed2 = self.tables.keyed2
+
+ # this is also addressed by [ticket:2932]
+
+ a, b = sql.column("keyed2_a"), sql.column("keyed2_b")
+ stmt = text("select a AS keyed2_a, b AS keyed2_b from keyed2").columns(
+ a, b
+ )
+ row = testing.db.execute(stmt).first()
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(keyed2.c.a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(keyed2.c.b, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names",
+ "The SelectBase.c and SelectBase.columns",
+ ):
+ in_(stmt.c.keyed2_a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names",
+ "The SelectBase.c and SelectBase.columns",
+ ):
+ in_(stmt.c.keyed2_b, row)
+
+ def test_columnclause_schema_column_five(self):
+ keyed2 = self.tables.keyed2
+
+ # this is also addressed by [ticket:2932]
+
+ stmt = text("select a AS keyed2_a, b AS keyed2_b from keyed2").columns(
+ keyed2_a=CHAR, keyed2_b=CHAR
+ )
+ row = testing.db.execute(stmt).first()
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(keyed2.c.a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(keyed2.c.b, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names",
+ "The SelectBase.c and SelectBase.columns",
+ ):
+ in_(stmt.c.keyed2_a, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names",
+ "The SelectBase.c and SelectBase.columns",
+ ):
+ in_(stmt.c.keyed2_b, row)
+
+
+class ResultProxyTest(fixtures.TablesTest):
+ __backend__ = True
+
+ @classmethod
+ def define_tables(cls, metadata):
+ Table(
+ "users",
+ metadata,
+ Column(
+ "user_id", INT, primary_key=True, test_needs_autoincrement=True
+ ),
+ Column("user_name", VARCHAR(20)),
+ test_needs_acid=True,
+ )
+ Table(
+ "addresses",
+ metadata,
+ Column(
+ "address_id",
+ Integer,
+ primary_key=True,
+ test_needs_autoincrement=True,
+ ),
+ Column("user_id", Integer, ForeignKey("users.user_id")),
+ Column("address", String(30)),
+ test_needs_acid=True,
+ )
+
+ Table(
+ "users2",
+ metadata,
+ Column("user_id", INT, primary_key=True),
+ Column("user_name", VARCHAR(20)),
+ test_needs_acid=True,
+ )
+
+ @classmethod
+ def insert_data(cls):
+ users = cls.tables.users
+
+ with testing.db.connect() as conn:
+ conn.execute(
+ users.insert(),
+ dict(user_id=1, user_name="john"),
+ dict(user_id=2, user_name="jack"),
+ )
+
+ def test_column_accessor_textual_select(self):
+ users = self.tables.users
+
+ # this will create column() objects inside
+ # the select(), these need to match on name anyway
+ r = testing.db.execute(
+ select([column("user_id"), column("user_name")])
+ .select_from(table("users"))
+ .where(text("user_id=2"))
+ ).first()
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ eq_(r[users.c.user_id], 2)
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ eq_(r[users.c.user_name], "jack")
+
+ def test_column_accessor_basic_text(self):
+ users = self.tables.users
+
+ r = testing.db.execute(
+ text("select * from users where user_id=2")
+ ).first()
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ eq_(r[users.c.user_id], 2)
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ eq_(r[users.c.user_name], "jack")
+
+ @testing.provide_metadata
+ def test_column_label_overlap_fallback(self):
+ content = Table("content", self.metadata, Column("type", String(30)))
+ bar = Table("bar", self.metadata, Column("content_type", String(30)))
+ self.metadata.create_all(testing.db)
+ testing.db.execute(content.insert().values(type="t1"))
+
+ row = testing.db.execute(content.select(use_labels=True)).first()
+ in_(content.c.type, row)
+ not_in_(bar.c.content_type, row)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(sql.column("content_type"), row)
+
+ row = testing.db.execute(
+ select([content.c.type.label("content_type")])
+ ).first()
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(content.c.type, row)
+
+ not_in_(bar.c.content_type, row)
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(sql.column("content_type"), row)
+
+ row = testing.db.execute(
+ select([func.now().label("content_type")])
+ ).first()
+
+ not_in_(content.c.type, row)
+
+ not_in_(bar.c.content_type, row)
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ in_(sql.column("content_type"), row)
+
+ def test_pickled_rows(self):
+ users = self.tables.users
+ addresses = self.tables.addresses
+ with testing.db.connect() as conn:
+ conn.execute(users.delete())
+ conn.execute(
+ users.insert(),
+ {"user_id": 7, "user_name": "jack"},
+ {"user_id": 8, "user_name": "ed"},
+ {"user_id": 9, "user_name": "fred"},
+ )
+
+ for pickle in False, True:
+ for use_labels in False, True:
+ result = (
+ users.select(use_labels=use_labels)
+ .order_by(users.c.user_id)
+ .execute()
+ .fetchall()
+ )
+
+ if pickle:
+ result = util.pickle.loads(util.pickle.dumps(result))
+
+ if pickle:
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "from a row that was unpickled"
+ ):
+ eq_(result[0][users.c.user_id], 7)
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "from a row that was unpickled"
+ ):
+ eq_(result[0][users.c.user_name], "jack")
+
+ if not pickle or use_labels:
+ assert_raises(
+ exc.NoSuchColumnError,
+ lambda: result[0][addresses.c.user_id],
+ )
+ else:
+ # test with a different table. name resolution is
+ # causing 'user_id' to match when use_labels wasn't used.
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "from a row that was unpickled"
+ ):
+ eq_(result[0][addresses.c.user_id], 7)
+
+ assert_raises(
+ exc.NoSuchColumnError,
+ lambda: result[0][addresses.c.address_id],
+ )
+
+
+class PositionalTextTest(fixtures.TablesTest):
+ run_inserts = "once"
+ run_deletes = None
+ __backend__ = True
+
+ @classmethod
+ def define_tables(cls, metadata):
+ Table(
+ "text1",
+ metadata,
+ Column("a", CHAR(2)),
+ Column("b", CHAR(2)),
+ Column("c", CHAR(2)),
+ Column("d", CHAR(2)),
+ )
+
+ @classmethod
+ def insert_data(cls):
+ cls.tables.text1.insert().execute(
+ [dict(a="a1", b="b1", c="c1", d="d1")]
+ )
+
+ def test_anon_aliased_overlapping(self):
+ text1 = self.tables.text1
+
+ c1 = text1.c.a.label(None)
+ c2 = text1.alias().c.a
+ c3 = text1.alias().c.a.label(None)
+ c4 = text1.c.a.label(None)
+
+ stmt = text("select a, b, c, d from text1").columns(c1, c2, c3, c4)
+ result = testing.db.execute(stmt)
+ row = result.first()
+
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ eq_(row[text1.c.a], "a1")
+
+ def test_anon_aliased_unique(self):
+ text1 = self.tables.text1
+
+ c1 = text1.c.a.label(None)
+ c2 = text1.alias().c.c
+ c3 = text1.alias().c.b
+ c4 = text1.alias().c.d.label(None)
+
+ stmt = text("select a, b, c, d from text1").columns(c1, c2, c3, c4)
+ result = testing.db.execute(stmt)
+ row = result.first()
+
+ eq_(row[c1], "a1")
+ eq_(row[c2], "b1")
+ eq_(row[c3], "c1")
+ eq_(row[c4], "d1")
+
+ # key fallback rules still match this to a column
+ # unambiguously based on its name
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ eq_(row[text1.c.a], "a1")
+
+ # key fallback rules still match this to a column
+ # unambiguously based on its name
+ with testing.expect_deprecated(
+ "Retreiving row values using Column objects "
+ "with only matching names"
+ ):
+ eq_(row[text1.c.d], "d1")
+
+ # text1.c.b goes nowhere....because we hit key fallback
+ # but the text1.c.b doesn't derive from text1.c.c
+ assert_raises_message(
+ exc.NoSuchColumnError,
+ "Could not locate column in row for column 'text1.b'",
+ lambda: row[text1.c.b],
+ )
diff --git a/test/sql/test_resultset.py b/test/sql/test_resultset.py
index 36d442ed7..35353671c 100644
--- a/test/sql/test_resultset.py
+++ b/test/sql/test_resultset.py
@@ -26,6 +26,7 @@ from sqlalchemy import VARCHAR
from sqlalchemy.engine import default
from sqlalchemy.engine import result as _result
from sqlalchemy.engine import Row
+from sqlalchemy.sql.selectable import TextualSelect
from sqlalchemy.testing import assert_raises
from sqlalchemy.testing import assert_raises_message
from sqlalchemy.testing import assertions
@@ -192,29 +193,16 @@ class ResultProxyTest(fixtures.TablesTest):
row = testing.db.execute(content.select(use_labels=True)).first()
in_(content.c.type, row)
not_in_(bar.c.content_type, row)
- in_(sql.column("content_type"), row)
-
- row = testing.db.execute(
- select([content.c.type.label("content_type")])
- ).first()
- in_(content.c.type, row)
-
- not_in_(bar.c.content_type, row)
-
- in_(sql.column("content_type"), row)
row = testing.db.execute(
select([func.now().label("content_type")])
).first()
- not_in_(content.c.type, row)
+ not_in_(content.c.type, row)
not_in_(bar.c.content_type, row)
- in_(sql.column("content_type"), row)
-
def test_pickled_rows(self):
users = self.tables.users
- addresses = self.tables.addresses
users.insert().execute(
{"user_id": 7, "user_name": "jack"},
@@ -246,26 +234,10 @@ class ResultProxyTest(fixtures.TablesTest):
eq_(list(result[0].keys()), ["user_id", "user_name"])
eq_(result[0][0], 7)
- eq_(result[0][users.c.user_id], 7)
- eq_(result[0][users.c.user_name], "jack")
-
- if not pickle or use_labels:
- assert_raises(
- exc.NoSuchColumnError,
- lambda: result[0][addresses.c.user_id],
- )
- else:
- # test with a different table. name resolution is
- # causing 'user_id' to match when use_labels wasn't used.
- eq_(result[0][addresses.c.user_id], 7)
assert_raises(
exc.NoSuchColumnError, lambda: result[0]["fake key"]
)
- assert_raises(
- exc.NoSuchColumnError,
- lambda: result[0][addresses.c.address_id],
- )
def test_column_error_printing(self):
result = testing.db.execute(select([1]))
@@ -350,11 +322,9 @@ class ResultProxyTest(fixtures.TablesTest):
eq_(r.user_id, 2)
eq_(r["user_id"], 2)
- eq_(r[users.c.user_id], 2)
eq_(r.user_name, "jack")
eq_(r["user_name"], "jack")
- eq_(r[users.c.user_name], "jack")
def test_column_accessor_textual_select(self):
users = self.tables.users
@@ -373,11 +343,9 @@ class ResultProxyTest(fixtures.TablesTest):
eq_(r.user_id, 2)
eq_(r["user_id"], 2)
- eq_(r[users.c.user_id], 2)
eq_(r.user_name, "jack")
eq_(r["user_name"], "jack")
- eq_(r[users.c.user_name], "jack")
def test_column_accessor_dotted_union(self):
users = self.tables.users
@@ -849,6 +817,81 @@ class ResultProxyTest(fixtures.TablesTest):
set([True]),
)
+ def test_loose_matching_one(self):
+ users = self.tables.users
+ addresses = self.tables.addresses
+
+ with testing.db.connect() as conn:
+ conn.execute(users.insert(), {"user_id": 1, "user_name": "john"})
+ conn.execute(
+ addresses.insert(),
+ {"address_id": 1, "user_id": 1, "address": "email"},
+ )
+
+ # use some column labels in the SELECT
+ result = conn.execute(
+ TextualSelect(
+ text(
+ "select users.user_name AS users_user_name, "
+ "users.user_id AS user_id, "
+ "addresses.address_id AS address_id "
+ "FROM users JOIN addresses "
+ "ON users.user_id = addresses.user_id "
+ "WHERE users.user_id=1 "
+ ),
+ [
+ users.c.user_id,
+ users.c.user_name,
+ addresses.c.address_id,
+ ],
+ positional=False,
+ )
+ )
+ row = result.first()
+ eq_(row[users.c.user_id], 1)
+ eq_(row[users.c.user_name], "john")
+
+ def test_loose_matching_two(self):
+ users = self.tables.users
+ addresses = self.tables.addresses
+
+ with testing.db.connect() as conn:
+ # MARKMARK
+ conn.execute(users.insert(), {"user_id": 1, "user_name": "john"})
+ conn.execute(
+ addresses.insert(),
+ {"address_id": 1, "user_id": 1, "address": "email"},
+ )
+
+ # use some column labels in the SELECT
+ result = conn.execute(
+ TextualSelect(
+ text(
+ "select users.user_name AS users_user_name, "
+ "users.user_id AS user_id, "
+ "addresses.user_id "
+ "FROM users JOIN addresses "
+ "ON users.user_id = addresses.user_id "
+ "WHERE users.user_id=1 "
+ ),
+ [users.c.user_id, users.c.user_name, addresses.c.user_id],
+ positional=False,
+ )
+ )
+ row = result.first()
+
+ assert_raises_message(
+ exc.InvalidRequestError,
+ "Ambiguous column name",
+ lambda: row[users.c.user_id],
+ )
+ assert_raises_message(
+ exc.InvalidRequestError,
+ "Ambiguous column name",
+ lambda: row[addresses.c.user_id],
+ )
+ eq_(row[users.c.user_name], "john")
+
def test_ambiguous_column_by_col_plus_label(self):
users = self.tables.users
@@ -1363,79 +1406,37 @@ class KeyTargetingTest(fixtures.TablesTest):
assert_raises(KeyError, lambda: row["keyed2_c"])
assert_raises(KeyError, lambda: row["keyed2_q"])
- def test_column_label_overlap_fallback(self):
- content, bar = self.tables.content, self.tables.bar
- row = testing.db.execute(
- select([content.c.type.label("content_type")])
- ).first()
-
- not_in_(content.c.type, row)
- not_in_(bar.c.content_type, row)
-
- in_(sql.column("content_type"), row)
-
- row = testing.db.execute(
- select([func.now().label("content_type")])
- ).first()
- not_in_(content.c.type, row)
- not_in_(bar.c.content_type, row)
- in_(sql.column("content_type"), row)
-
- def test_column_label_overlap_fallback_2(self):
- content, bar = self.tables.content, self.tables.bar
- row = testing.db.execute(content.select(use_labels=True)).first()
- in_(content.c.type, row)
- not_in_(bar.c.content_type, row)
- not_in_(sql.column("content_type"), row)
-
def test_columnclause_schema_column_one(self):
- keyed2 = self.tables.keyed2
-
- # this is addressed by [ticket:2932]
- # ColumnClause._compare_name_for_result allows the
- # columns which the statement is against to be lightweight
- # cols, which results in a more liberal comparison scheme
+ # originally addressed by [ticket:2932], however liberalized
+ # Column-targeting rules are deprecated
a, b = sql.column("a"), sql.column("b")
stmt = select([a, b]).select_from(table("keyed2"))
row = testing.db.execute(stmt).first()
- in_(keyed2.c.a, row)
- in_(keyed2.c.b, row)
in_(a, row)
in_(b, row)
def test_columnclause_schema_column_two(self):
keyed2 = self.tables.keyed2
- a, b = sql.column("a"), sql.column("b")
stmt = select([keyed2.c.a, keyed2.c.b])
row = testing.db.execute(stmt).first()
in_(keyed2.c.a, row)
in_(keyed2.c.b, row)
- in_(a, row)
- in_(b, row)
def test_columnclause_schema_column_three(self):
- keyed2 = self.tables.keyed2
-
# this is also addressed by [ticket:2932]
- a, b = sql.column("a"), sql.column("b")
stmt = text("select a, b from keyed2").columns(a=CHAR, b=CHAR)
row = testing.db.execute(stmt).first()
- in_(keyed2.c.a, row)
- in_(keyed2.c.b, row)
- in_(a, row)
- in_(b, row)
in_(stmt.selected_columns.a, row)
in_(stmt.selected_columns.b, row)
def test_columnclause_schema_column_four(self):
- keyed2 = self.tables.keyed2
-
- # this is also addressed by [ticket:2932]
+ # originally addressed by [ticket:2932], however liberalized
+ # Column-targeting rules are deprecated
a, b = sql.column("keyed2_a"), sql.column("keyed2_b")
stmt = text("select a AS keyed2_a, b AS keyed2_b from keyed2").columns(
@@ -1443,16 +1444,12 @@ class KeyTargetingTest(fixtures.TablesTest):
)
row = testing.db.execute(stmt).first()
- in_(keyed2.c.a, row)
- in_(keyed2.c.b, row)
in_(a, row)
in_(b, row)
in_(stmt.selected_columns.keyed2_a, row)
in_(stmt.selected_columns.keyed2_b, row)
def test_columnclause_schema_column_five(self):
- keyed2 = self.tables.keyed2
-
# this is also addressed by [ticket:2932]
stmt = text("select a AS keyed2_a, b AS keyed2_b from keyed2").columns(
@@ -1460,8 +1457,6 @@ class KeyTargetingTest(fixtures.TablesTest):
)
row = testing.db.execute(stmt).first()
- in_(keyed2.c.a, row)
- in_(keyed2.c.b, row)
in_(stmt.selected_columns.keyed2_a, row)
in_(stmt.selected_columns.keyed2_b, row)
@@ -1577,14 +1572,6 @@ class PositionalTextTest(fixtures.TablesTest):
eq_(row[c3], "c1")
eq_(row[c4], "d1")
- # key fallback rules still match this to a column
- # unambiguously based on its name
- eq_(row[text1.c.a], "a1")
-
- # key fallback rules still match this to a column
- # unambiguously based on its name
- eq_(row[text1.c.d], "d1")
-
# text1.c.b goes nowhere....because we hit key fallback
# but the text1.c.b doesn't derive from text1.c.c
assert_raises_message(
@@ -1610,10 +1597,6 @@ class PositionalTextTest(fixtures.TablesTest):
eq_(row[c3], "c1")
eq_(row[c4], "d1")
- # key fallback rules still match this to a column
- # unambiguously based on its name
- eq_(row[text1.c.a], "a1")
-
def test_anon_aliased_name_conflict(self):
text1 = self.tables.text1
diff --git a/test/sql/test_text.py b/test/sql/test_text.py
index bec56f2f7..6af2cffcf 100644
--- a/test/sql/test_text.py
+++ b/test/sql/test_text.py
@@ -427,12 +427,12 @@ class AsFromTest(fixtures.TestBase, AssertsCompiledSQL):
{
"id": (
"id",
- (t.selected_columns.id, "id", "id"),
+ (t.selected_columns.id, "id", "id", "id"),
t.selected_columns.id.type,
),
"name": (
"name",
- (t.selected_columns.name, "name", "name"),
+ (t.selected_columns.name, "name", "name", "name"),
t.selected_columns.name.type,
),
},
@@ -447,12 +447,12 @@ class AsFromTest(fixtures.TestBase, AssertsCompiledSQL):
{
"id": (
"id",
- (t.selected_columns.id, "id", "id"),
+ (t.selected_columns.id, "id", "id", "id"),
t.selected_columns.id.type,
),
"name": (
"name",
- (t.selected_columns.name, "name", "name"),
+ (t.selected_columns.name, "name", "name", "name"),
t.selected_columns.name.type,
),
},
@@ -474,7 +474,7 @@ class AsFromTest(fixtures.TestBase, AssertsCompiledSQL):
{
"myid": (
"myid",
- (table1.c.myid, "myid", "myid"),
+ (table1.c.myid, "myid", "myid", "mytable_myid"),
table1.c.myid.type,
)
},