Files
sqlalchemy/test/sql/test_select.py
T
Mike Bayer 3b4bbbb2a3 Create a real type for Tuple() and handle appropriately in compiler
Improved the :func:`_sql.tuple_` construct such that it behaves predictably
when used in a columns-clause context.  The SQL tuple is not supported as a
"SELECT" columns clause element on most backends; on those that do
(PostgreSQL, not surprisingly), the Python DBAPI does not have a "nested
type" concept so there are still challenges in fetching rows for such an
object. Use of :func:`_sql.tuple_` in a :func:`_sql.select` or
:class:`_orm.Query` will now raise a :class:`_exc.CompileError` at the
point at which the :func:`_sql.tuple_` object is seen as presenting itself
for fetching rows (i.e., if the tuple is in the columns clause of a
subquery, no error is raised).  For ORM use,the :class:`_orm.Bundle` object
is an explicit directive that a series of columns should be returned as a
sub-tuple per row and is suggested by the error message. Additionally ,the
tuple will now render with parenthesis in all contexts. Previously, the
parenthesization would not render in a columns context leading to
non-defined behavior.

As part of this change, Tuple receives a dedicated datatype
which appears to allow us the very desirable change of removing
the bindparam._expanding_in_types attribute as well as
ClauseList._tuple_values (which might already have not been
needed due to #4645).

Fixes: #5127
Change-Id: Iecafa0e0aac2f1f37ec8d0e1631d562611c90200
2020-08-17 11:29:51 -04:00

225 lines
6.9 KiB
Python

from sqlalchemy import Column
from sqlalchemy import exc
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import tuple_
from sqlalchemy.sql import column
from sqlalchemy.sql import table
from sqlalchemy.testing import assert_raises_message
from sqlalchemy.testing import AssertsCompiledSQL
from sqlalchemy.testing import fixtures
table1 = table(
"mytable",
column("myid", Integer),
column("name", String),
column("description", String),
)
table2 = table(
"myothertable", column("otherid", Integer), column("othername", String)
)
metadata = MetaData()
parent = Table(
"parent",
metadata,
Column("id", Integer, primary_key=True),
Column("data", String(50)),
)
child = Table(
"child",
metadata,
Column("id", Integer, primary_key=True),
Column("parent_id", ForeignKey("parent.id")),
Column("data", String(50)),
)
class FutureSelectTest(fixtures.TestBase, AssertsCompiledSQL):
__dialect__ = "default"
def test_legacy_calling_style_kw_only(self):
stmt = select(
whereclause=table1.c.myid == table2.c.otherid
).add_columns(table1.c.myid)
self.assert_compile(
stmt,
"SELECT mytable.myid FROM mytable, myothertable "
"WHERE mytable.myid = myothertable.otherid",
)
def test_legacy_calling_style_col_seq_only(self):
stmt = select([table1.c.myid]).where(table1.c.myid == table2.c.otherid)
self.assert_compile(
stmt,
"SELECT mytable.myid FROM mytable, myothertable "
"WHERE mytable.myid = myothertable.otherid",
)
def test_new_calling_style(self):
stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid)
self.assert_compile(
stmt,
"SELECT mytable.myid FROM mytable, myothertable "
"WHERE mytable.myid = myothertable.otherid",
)
def test_kw_triggers_old_style(self):
assert_raises_message(
exc.ArgumentError,
r"select\(\) construct created in legacy mode, "
"i.e. with keyword arguments",
select,
table1.c.myid,
whereclause=table1.c.myid == table2.c.otherid,
)
def test_join_nofrom_implicit_left_side_explicit_onclause(self):
stmt = select(table1).join(table2, table1.c.myid == table2.c.otherid)
self.assert_compile(
stmt,
"SELECT mytable.myid, mytable.name, mytable.description "
"FROM mytable JOIN myothertable "
"ON mytable.myid = myothertable.otherid",
)
def test_join_nofrom_explicit_left_side_explicit_onclause(self):
stmt = select(table1).join_from(
table1, table2, table1.c.myid == table2.c.otherid
)
self.assert_compile(
stmt,
"SELECT mytable.myid, mytable.name, mytable.description "
"FROM mytable JOIN myothertable "
"ON mytable.myid = myothertable.otherid",
)
def test_join_nofrom_implicit_left_side_implicit_onclause(self):
stmt = select(parent).join(child)
self.assert_compile(
stmt,
"SELECT parent.id, parent.data FROM parent JOIN child "
"ON parent.id = child.parent_id",
)
def test_join_nofrom_explicit_left_side_implicit_onclause(self):
stmt = select(parent).join_from(parent, child)
self.assert_compile(
stmt,
"SELECT parent.id, parent.data FROM parent JOIN child "
"ON parent.id = child.parent_id",
)
def test_join_froms_implicit_left_side_explicit_onclause(self):
stmt = (
select(table1)
.select_from(table1)
.join(table2, table1.c.myid == table2.c.otherid)
)
self.assert_compile(
stmt,
"SELECT mytable.myid, mytable.name, mytable.description "
"FROM mytable JOIN myothertable "
"ON mytable.myid = myothertable.otherid",
)
def test_join_froms_explicit_left_side_explicit_onclause(self):
stmt = (
select(table1)
.select_from(table1)
.join_from(table1, table2, table1.c.myid == table2.c.otherid)
)
self.assert_compile(
stmt,
"SELECT mytable.myid, mytable.name, mytable.description "
"FROM mytable JOIN myothertable "
"ON mytable.myid = myothertable.otherid",
)
def test_join_froms_implicit_left_side_implicit_onclause(self):
stmt = select(parent).select_from(parent).join(child)
self.assert_compile(
stmt,
"SELECT parent.id, parent.data FROM parent JOIN child "
"ON parent.id = child.parent_id",
)
def test_join_froms_explicit_left_side_implicit_onclause(self):
stmt = select(parent).select_from(parent).join_from(parent, child)
self.assert_compile(
stmt,
"SELECT parent.id, parent.data FROM parent JOIN child "
"ON parent.id = child.parent_id",
)
def test_joins_w_filter_by(self):
stmt = (
select(parent)
.filter_by(data="p1")
.join(child)
.filter_by(data="c1")
.join_from(table1, table2, table1.c.myid == table2.c.otherid)
.filter_by(otherid=5)
)
self.assert_compile(
stmt,
"SELECT parent.id, parent.data FROM parent JOIN child "
"ON parent.id = child.parent_id, mytable JOIN myothertable "
"ON mytable.myid = myothertable.otherid "
"WHERE parent.data = :data_1 AND child.data = :data_2 "
"AND myothertable.otherid = :otherid_1",
checkparams={"data_1": "p1", "data_2": "c1", "otherid_1": 5},
)
def test_filter_by_no_property(self):
assert_raises_message(
exc.InvalidRequestError,
'Entity namespace for "mytable" has no property "foo"',
select(table1).filter_by,
foo="bar",
)
def test_select_tuple_outer(self):
stmt = select(tuple_(table1.c.myid, table1.c.name))
assert_raises_message(
exc.CompileError,
r"Most backends don't support SELECTing from a tuple\(\) object. "
"If this is an ORM query, consider using the Bundle object.",
stmt.compile,
)
def test_select_tuple_subquery(self):
subq = select(
table1.c.name, tuple_(table1.c.myid, table1.c.name)
).subquery()
stmt = select(subq.c.name)
# if we aren't fetching it, then render it
self.assert_compile(
stmt,
"SELECT anon_1.name FROM (SELECT mytable.name AS name, "
"(mytable.myid, mytable.name) AS anon_2 FROM mytable) AS anon_1",
)