Files
sqlalchemy/test/orm/test_loading.py
T
Mike Bayer 3916bfc9cc support "SELECT *" for ORM queries
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.

Fixes: #8235
Change-Id: Ic8e84491e14acdc8570704eadeaeaf6e16b1e870
2022-07-10 21:27:12 -04:00

240 lines
7.1 KiB
Python

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 GetFromIdentityTest(_fixtures.FixtureTest):
# 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
User, Order, Address = self.classes("User", "Order", "Address")
users, orders, addresses = self.tables("users", "orders", "addresses")
self.mapper_registry.map_imperatively(
User,
users,
properties={
"addresses": relationship(Address, lazy="joined"),
"orders": relationship(
Order, lazy="joined", order_by=orders.c.id
),
},
)
self.mapper_registry.map_imperatively(
Order,
orders,
properties={"address": relationship(Address, lazy="joined")},
)
self.mapper_registry.map_imperatively(Address, addresses)
s = fixture_session()
def go():
eq_(
User(
id=7,
orders=[
Order(id=1, address=Address(id=1)),
Order(id=3, address=Address(id=1)),
Order(id=5, address=None),
],
),
s.get(User, 7, populate_existing=True),
)
self.assert_sql_count(testing.db, go, 1)
class InstancesTest(_fixtures.FixtureTest):
run_setup_mappers = "once"
run_inserts = "once"
run_deletes = None
@classmethod
def setup_mappers(cls):
cls._setup_stock_mapping()
def test_cursor_close_w_failed_rowproc(self):
User = self.classes.User
s = fixture_session()
q = s.query(User)
ctx = q._compile_context()
cursor = mock.Mock()
ctx.compile_state._entities = [
mock.Mock(row_processor=mock.Mock(side_effect=Exception("boom")))
]
assert_raises(Exception, loading.instances, cursor, ctx)
assert cursor.close.called, "Cursor wasn't closed"
def test_row_proc_not_created(self):
User = self.classes.User
s = fixture_session()
q = s.query(User.id, User.name)
stmt = select(User.id)
assert_raises_message(
exc.NoSuchColumnError,
"Could not locate column in row for column 'users.name'",
q.from_statement(stmt).all,
)
class MergeResultTest(_fixtures.FixtureTest):
run_setup_mappers = "once"
run_inserts = "once"
run_deletes = None
@classmethod
def setup_mappers(cls):
cls._setup_stock_mapping()
def _fixture(self):
User = self.classes.User
s = fixture_session()
u1, u2, u3, u4 = (
User(id=1, name="u1"),
User(id=2, name="u2"),
User(id=7, name="u3"),
User(id=8, name="u4"),
)
s.query(User).filter(User.id.in_([7, 8])).all()
s.close()
return s, [u1, u2, u3, u4]
def test_single_entity_frozen(self):
s = fixture_session()
User = self.classes.User
stmt = select(User).where(User.id.in_([7, 8, 9])).order_by(User.id)
result = s.execute(stmt)
it = loading.merge_frozen_result(s, stmt, result.freeze())
eq_([x.id for x in it().scalars()], [7, 8, 9])
def test_single_column_frozen(self):
User = self.classes.User
s = fixture_session()
stmt = select(User.id).where(User.id.in_([7, 8, 9])).order_by(User.id)
result = s.execute(stmt)
it = loading.merge_frozen_result(s, stmt, result.freeze())
eq_([x.id for x in it()], [7, 8, 9])
def test_entity_col_mix_plain_tuple_frozen(self):
s = fixture_session()
User = self.classes.User
stmt = (
select(User, User.id)
.where(User.id.in_([7, 8, 9]))
.order_by(User.id)
)
result = s.execute(stmt)
it = loading.merge_frozen_result(s, stmt, result.freeze())
it = list(it())
eq_([(x.id, y) for x, y in it], [(7, 7), (8, 8), (9, 9)])
eq_(list(it[0]._mapping.keys()), ["User", "id"])