--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 8235
+
+ A :func:`_sql.select` construct that is passed a sole '*' argument for
+ ``SELECT *``, either via string, :func:`_sql.text`, or
+ :func:`_sql.literal_column`, will be interpreted as a Core-level SQL
+ statement rather than as an ORM level statement. This is so that the ``*``,
+ when expanded to match any number of columns, will result in all columns
+ returned in the result. the ORM- level interpretation of
+ :func:`_sql.select` needs to know the names and types of all ORM columns up
+ front which can't be achieved when ``'*'`` is used.
+
+ If ``'*`` is used amongst other expressions simultaneously with an ORM
+ statement, an error is raised as this can't be interpreted correctly by the
+ ORM.
("_set_base_alias", InternalTraversal.dp_boolean),
("_for_refresh_state", InternalTraversal.dp_boolean),
("_render_for_subquery", InternalTraversal.dp_boolean),
+ ("_is_star", InternalTraversal.dp_boolean),
]
# set to True by default from Query._statement_20(), to indicate
_set_base_alias = False
_for_refresh_state = False
_render_for_subquery = False
+ _is_star = False
current_path = _path_registry
load_options = execution_options.get(
"_sa_orm_load_options", QueryContext.default_load_options
)
+ if compile_state.compile_options._is_star:
+ return result
querycontext = QueryContext(
compile_state,
self._for_update_arg = query._for_update_arg
+ if self.compile_options._is_star and (len(self._entities) != 1):
+ raise sa_exc.CompileError(
+ "Can't generate ORM query that includes multiple expressions "
+ "at the same time as '*'; query for '*' alone if present"
+ )
for entity in self._entities:
entity.setup_compile_state(self)
self.raw_column_index = raw_column_index
self.translate_raw_column = raw_column_index is not None
+ if column._is_star:
+ compile_state.compile_options += {"_is_star": True}
+
if not is_current_entities or column._is_text_clause:
self._label_name = None
else:
from sqlalchemy import exc
+from sqlalchemy import literal
+from sqlalchemy import literal_column
from sqlalchemy import select
from sqlalchemy import testing
+from sqlalchemy import text
from sqlalchemy.orm import loading
from sqlalchemy.orm import relationship
from sqlalchemy.testing import mock
from sqlalchemy.testing.assertions import assert_raises
from sqlalchemy.testing.assertions import assert_raises_message
from sqlalchemy.testing.assertions import eq_
+from sqlalchemy.testing.assertions import expect_raises_message
from sqlalchemy.testing.fixtures import fixture_session
from . import _fixtures
# class LoadOnIdentTest(_fixtures.FixtureTest):
+class SelectStarTest(_fixtures.FixtureTest):
+ run_setup_mappers = "once"
+ run_inserts = "once"
+ run_deletes = None
+
+ @classmethod
+ def setup_mappers(cls):
+ cls._setup_stock_mapping()
+
+ @testing.combinations(
+ "plain", "text", "literal_column", argnames="exprtype"
+ )
+ @testing.combinations("core", "orm", argnames="coreorm")
+ def test_single_star(self, exprtype, coreorm):
+ """test for #8235"""
+ User, Address = self.classes("User", "Address")
+
+ if exprtype == "plain":
+ star = "*"
+ elif exprtype == "text":
+ star = text("*")
+ elif exprtype == "literal_column":
+ star = literal_column("*")
+ else:
+ assert False
+
+ stmt = (
+ select(star)
+ .select_from(User)
+ .join(Address)
+ .where(User.id == 7)
+ .order_by(User.id, Address.id)
+ )
+
+ s = fixture_session()
+
+ if coreorm == "core":
+ result = s.connection().execute(stmt)
+ elif coreorm == "orm":
+ result = s.execute(stmt)
+ else:
+ assert False
+
+ eq_(result.all(), [(7, "jack", 1, 7, "jack@bean.com")])
+
+ @testing.combinations(
+ "plain", "text", "literal_column", argnames="exprtype"
+ )
+ @testing.combinations(
+ lambda User, star: (star, User.id),
+ lambda User, star: (star, User),
+ lambda User, star: (User.id, star),
+ lambda User, star: (User, star),
+ lambda User, star: (literal("some text"), star),
+ lambda User, star: (star, star),
+ lambda User, star: (star, text("some text")),
+ argnames="testcase",
+ )
+ def test_no_star_orm_combinations(self, exprtype, testcase):
+ """test for #8235"""
+ User = self.classes.User
+
+ if exprtype == "plain":
+ star = "*"
+ elif exprtype == "text":
+ star = text("*")
+ elif exprtype == "literal_column":
+ star = literal_column("*")
+ else:
+ assert False
+
+ args = testing.resolve_lambda(testcase, User=User, star=star)
+ stmt = select(*args).select_from(User)
+
+ s = fixture_session()
+
+ with expect_raises_message(
+ exc.CompileError,
+ r"Can't generate ORM query that includes multiple expressions "
+ r"at the same time as '\*';",
+ ):
+ s.execute(stmt)
+
+
class InstanceProcessorTest(_fixtures.FixtureTest):
def test_state_no_load_path_comparison(self):
# test issue #5110