import random
from sqlalchemy import exc
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import lambda_stmt
from sqlalchemy import String
from sqlalchemy import testing
from sqlalchemy import update
from sqlalchemy.future import select
from sqlalchemy.orm import aliased
from sqlalchemy.orm import relationship
from sqlalchemy.orm import selectinload
from sqlalchemy.orm import Session
from sqlalchemy.orm import subqueryload
from sqlalchemy.sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
from sqlalchemy.testing import assert_raises_message
from sqlalchemy.testing import AssertsCompiledSQL
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
from sqlalchemy.testing.fixtures import fixture_session
from sqlalchemy.testing.schema import Column
from sqlalchemy.testing.schema import Table
from .inheritance import _poly_fixtures
from .test_query import QueryTest
class LambdaTest(QueryTest, AssertsCompiledSQL):
__dialect__ = "default"
__backend__ = True
run_setup_mappers = None
@testing.fixture
def plain_fixture(self):
users, Address, addresses, User = (
self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User,
)
self.mapper_registry.map_imperatively(
User,
users,
properties={
"addresses": relationship(Address, back_populates="user")
},
)
self.mapper_registry.map_imperatively(
Address,
addresses,
properties={
"user": relationship(User, back_populates="addresses")
},
)
return User, Address
def test_user_cols_single_lambda(self, plain_fixture):
User, Address = plain_fixture
q = select(lambda: (User.id, User.name)).select_from(lambda: User)
self.assert_compile(q, "SELECT users.id, users.name FROM users")
def test_user_cols_single_lambda_query(self, plain_fixture):
User, Address = plain_fixture
s = fixture_session()
q = s.query(lambda: (User.id, User.name)).select_from(lambda: User)
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name FROM users",
)
def test_multiple_entities_single_lambda(self, plain_fixture):
User, Address = plain_fixture
q = select(lambda: (User, Address)).join(lambda: User.addresses)
self.assert_compile(
q,
"SELECT users.id, users.name, addresses.id AS id_1, "
"addresses.user_id, addresses.email_address "
"FROM users JOIN addresses ON users.id = addresses.user_id",
)
def test_cols_round_trip(self, plain_fixture):
User, Address = plain_fixture
s = Session(testing.db, future=True)
def query(names):
stmt = lambda_stmt(
lambda: select(User.name, Address.email_address)
.where(User.name.in_(names))
.join(User.addresses)
) + (lambda s: s.order_by(User.id, Address.id))
return s.execute(stmt)
def go1():
r1 = query(["ed"])
eq_(
r1.all(),
[
("ed", "ed@wood.com"),
("ed", "ed@bettyboop.com"),
("ed", "ed@lala.com"),
],
)
def go2():
r1 = query(["ed", "fred"])
eq_(
r1.all(),
[
("ed", "ed@wood.com"),
("ed", "ed@bettyboop.com"),
("ed", "ed@lala.com"),
("fred", "fred@fred.com"),
],
)
for i in range(5):
fn = random.choice([go1, go2])
fn()
@testing.combinations(
(True, True),
(True, False),
(False, False),
argnames="use_aliased,use_indirect_access",
)
def test_entity_round_trip(
self, plain_fixture, use_aliased, use_indirect_access
):
User, Address = plain_fixture
s = Session(testing.db, future=True)
if use_aliased:
if use_indirect_access:
def query(names):
class Foo:
def __init__(self):
self.u1 = aliased(User)
f1 = Foo()
stmt = lambda_stmt(
lambda: select(f1.u1)
.where(f1.u1.name.in_(names))
.options(selectinload(f1.u1.addresses)),
track_on=[f1.u1],
).add_criteria(
lambda s: s.order_by(f1.u1.id), track_on=[f1.u1]
)
return s.execute(stmt)
else:
def query(names):
u1 = aliased(User)
stmt = lambda_stmt(
lambda: select(u1)
.where(u1.name.in_(names))
.options(selectinload(u1.addresses))
) + (lambda s: s.order_by(u1.id))
return s.execute(stmt)
else:
def query(names):
stmt = lambda_stmt(
lambda: select(User)
.where(User.name.in_(names))
.options(selectinload(User.addresses))
) + (lambda s: s.order_by(User.id))
return s.execute(stmt)
def go1():
r1 = query(["ed"])
eq_(
r1.scalars().all(),
[User(name="ed", addresses=[Address(), Address(), Address()])],
)
def go2():
r1 = query(["ed", "fred"])
eq_(
r1.scalars().all(),
[
User(
name="ed", addresses=[Address(), Address(), Address()]
),
User(name="fred", addresses=[Address()]),
],
)
for i in range(5):
fn = random.choice([go1, go2])
self.assert_sql_count(testing.db, fn, 2)
def test_lambdas_rejected_in_options(self, plain_fixture):
User, Address = plain_fixture
assert_raises_message(
exc.ArgumentError,
"ExecutionOption Core or ORM object expected, got",
select(lambda: User).options,
lambda: subqueryload(User.addresses),
)
def test_subqueryload_internal_lambda(self, plain_fixture):
User, Address = plain_fixture
s = Session(testing.db, future=True)
def query(names):
stmt = (
select(lambda: User)
.where(lambda: User.name.in_(names))
.options(subqueryload(User.addresses))
.order_by(lambda: User.id)
)
return s.execute(stmt)
def go1():
r1 = query(["ed"])
eq_(
r1.scalars().all(),
[User(name="ed", addresses=[Address(), Address(), Address()])],
)
def go2():
r1 = query(["ed", "fred"])
eq_(
r1.scalars().all(),
[
User(
name="ed", addresses=[Address(), Address(), Address()]
),
User(name="fred", addresses=[Address()]),
],
)
for i in range(5):
fn = random.choice([go1, go2])
self.assert_sql_count(testing.db, fn, 2)
def test_subqueryload_external_lambda_caveats(self, plain_fixture):
User, Address = plain_fixture
s = Session(testing.db, future=True)
def query(names):
stmt = lambda_stmt(
lambda: select(User)
.where(User.name.in_(names))
.options(subqueryload(User.addresses))
) + (lambda s: s.order_by(User.id))
return s.execute(stmt)
def go1():
r1 = query(["ed"])
eq_(
r1.scalars().all(),
[User(name="ed", addresses=[Address(), Address(), Address()])],
)
def go2():
r1 = query(["ed", "fred"])
eq_(
r1.scalars().all(),
[
User(
name="ed", addresses=[Address(), Address(), Address()]
),
User(name="fred", addresses=[Address()]),
],
)
for i in range(5):
fn = random.choice([go1, go2])
with testing.expect_warnings(
'subqueryloader for "User.addresses" must invoke lambda '
r"callable at .*LambdaElement\(<code object <lambda> "
r".*test_lambdas.py.* in order to produce a new query, "
r"decreasing the efficiency of caching"
):
self.assert_sql_count(testing.db, fn, 2)
@testing.combinations(
lambda s, User, Address: s.query(lambda: User).join(lambda: Address),
lambda s, User, Address: s.query(lambda: User).join(
lambda: User.addresses
),
lambda s, User, Address: s.query(lambda: User).join(
lambda: Address, lambda: User.addresses
),
lambda s, User, Address: s.query(lambda: User).join(
Address, lambda: User.addresses
),
lambda s, User, Address: s.query(lambda: User).join(
lambda: Address, User.addresses
),
lambda User, Address: select(lambda: User)
.join(lambda: Address)
.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL),
lambda User, Address: select(lambda: User)
.join(lambda: User.addresses)
.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL),
lambda User, Address: select(lambda: User)
.join(lambda: Address, lambda: User.addresses)
.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL),
lambda User, Address: select(lambda: User)
.join(Address, lambda: User.addresses)
.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL),
lambda User, Address: select(lambda: User)
.join(lambda: Address, User.addresses)
.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL),
argnames="test_case",
)
def test_join_entity_arg(self, plain_fixture, test_case):
User, Address = plain_fixture
s = Session(testing.db, future=True)
stmt = testing.resolve_lambda(test_case, **locals())
self.assert_compile(
stmt,
"SELECT users.id AS users_id, users.name AS users_name "
"FROM users JOIN addresses ON users.id = addresses.user_id",
)
class PolymorphicTest(_poly_fixtures._Polymorphic):
run_setup_mappers = "once"
__dialect__ = "default"
def test_join_second_prop_lambda(self):
Company = self.classes.Company
Manager = self.classes.Manager
s = Session(future=True)
q = s.query(Company).join(lambda: Manager, lambda: Company.employees)
self.assert_compile(
q,
"SELECT companies.company_id AS companies_company_id, "
"companies.name AS companies_name FROM companies "
"JOIN (people JOIN managers ON people.person_id = "
"managers.person_id) ON companies.company_id = people.company_id",
)
class UpdateDeleteTest(fixtures.MappedTest):
__backend__ = True
run_setup_mappers = "once"
@classmethod
def define_tables(cls, metadata):
Table(
"users",
metadata,
Column(
"id", Integer, primary_key=True, test_needs_autoincrement=True
),
Column("name", String(32)),
Column("age_int", Integer),
)
Table(
"addresses",
metadata,
Column("id", Integer, primary_key=True),
Column("user_id", ForeignKey("users.id")),
)
@classmethod
def setup_classes(cls):
class User(cls.Comparable):
pass
class Address(cls.Comparable):
pass
@classmethod
def insert_data(cls, connection):
users = cls.tables.users
connection.execute(
users.insert(),
[
dict(id=1, name="john", age_int=25),
dict(id=2, name="jack", age_int=47),
dict(id=3, name="jill", age_int=29),
dict(id=4, name="jane", age_int=37),
],
)
@classmethod
def setup_mappers(cls):
User = cls.classes.User
users = cls.tables.users
Address = cls.classes.Address
addresses = cls.tables.addresses
cls.mapper_registry.map_imperatively(
User,
users,
properties={
"age": users.c.age_int,
"addresses": relationship(Address),
},
)
cls.mapper_registry.map_imperatively(Address, addresses)
def test_update(self):
User, Address = self.classes("User", "Address")
s = Session(testing.db, future=True)
def go(ids, values):
stmt = lambda_stmt(lambda: update(User).where(User.id.in_(ids)))
s.execute(
stmt,
values,
execution_options={"synchronize_session": "fetch"},
)
go([1, 2], {"name": "jack2"})
eq_(
s.execute(select(User.id, User.name).order_by(User.id)).all(),
[(1, "jack2"), (2, "jack2"), (3, "jill"), (4, "jane")],
)
go([3], {"name": "jane2"})
eq_(
s.execute(select(User.id, User.name).order_by(User.id)).all(),
[(1, "jack2"), (2, "jack2"), (3, "jane2"), (4, "jane")],
)