will be rendered into the SQL statement at execution time, rather than
being passed as separate parameters to the driver.
+ To create an :class:`.ExpandedState` instance, use the
+ :meth:`.SQLCompiler.construct_expanded_state` method on any
+ :class:`.SQLCompiler` instance.
+
"""
statement: str
- additional_parameters: _CoreSingleExecuteParams
+ """String SQL statement with parameters fully expanded"""
+
+ parameters: _CoreSingleExecuteParams
+ """Parameter dictionary with parameters fully expanded.
+
+ For a statement that uses named parameters, this dictionary will map
+ exactly to the names in the statement. For a statement that uses
+ positional parameters, the :attr:`.ExpandedState.positional_parameters`
+ will yield a tuple with the positional parameter set.
+
+ """
+
processors: Mapping[str, _BindProcessorType[Any]]
+ """mapping of bound value processors"""
+
positiontup: Optional[Sequence[str]]
+ """Sequence of string names indicating the order of positional
+ parameters"""
+
parameter_expansion: Mapping[str, List[str]]
+ """Mapping representing the intermediary link from original parameter
+ name to list of "expanded" parameter names, for those parameters that
+ were expanded."""
+
+ @property
+ def positional_parameters(self) -> Tuple[Any, ...]:
+ """Tuple of positional parameters, for statements that were compiled
+ using a positional paramstyle.
+
+ """
+ if self.positiontup is None:
+ raise exc.InvalidRequestError(
+ "statement does not use a positional paramstyle"
+ )
+ return tuple(self.parameters[key] for key in self.positiontup)
+
+ @property
+ def additional_parameters(self) -> _CoreSingleExecuteParams:
+ """synonym for :attr:`.ExpandedState.parameters`."""
+ return self.parameters
class _InsertManyValues(NamedTuple):
"""
whether to render out POSTCOMPILE params during the compile phase.
+ This attribute is used only for end-user invocation of stmt.compile();
+ it's never used for actual statement execution, where instead the
+ dialect internals access and render the internal postcompile structure
+ directly.
+
+ """
+
+ _post_compile_expanded_state: Optional[ExpandedState] = None
+ """When render_postcompile is used, the ``ExpandedState`` used to create
+ the "expanded" SQL is assigned here, and then used by the ``.params``
+ accessor and ``.construct_params()`` methods for their return values.
+
+ .. versionadded:: 2.0.0b5
+
"""
+ _pre_expanded_string: Optional[str] = None
+ """Stores the original string SQL before 'post_compile' is applied,
+ for cases where 'post_compile' were used.
+
+ """
+
+ _pre_expanded_positiontup: Optional[List[str]] = None
+
_insertmanyvalues: Optional[_InsertManyValues] = None
_insert_crud_params: Optional[crud._CrudParamSequence] = None
self._process_positional()
if self._render_postcompile:
- self._process_parameters_for_postcompile(_populate_self=True)
+ parameters = self.construct_params(
+ escape_names=False,
+ _no_postcompile=True,
+ )
+
+ self._process_parameters_for_postcompile(
+ parameters, _populate_self=True
+ )
@property
def insert_single_values_expr(self) -> Optional[str]:
def sql_compiler(self):
return self
+ def construct_expanded_state(
+ self,
+ params: Optional[_CoreSingleExecuteParams] = None,
+ escape_names: bool = True,
+ ) -> ExpandedState:
+ """Return a new :class:`.ExpandedState` for a given parameter set.
+
+ For queries that use "expanding" or other late-rendered parameters,
+ this method will provide for both the finalized SQL string as well
+ as the parameters that would be used for a particular parameter set.
+
+ .. versionadded:: 2.0.0b5
+
+ """
+ parameters = self.construct_params(
+ params,
+ escape_names=escape_names,
+ _no_postcompile=True,
+ )
+ return self._process_parameters_for_postcompile(
+ parameters,
+ )
+
def construct_params(
self,
params: Optional[_CoreSingleExecuteParams] = None,
escape_names: bool = True,
_group_number: Optional[int] = None,
_check: bool = True,
+ _no_postcompile: bool = False,
) -> _MutableCoreSingleExecuteParams:
"""return a dictionary of bind parameter keys and values"""
+ if self._render_postcompile and not _no_postcompile:
+ assert self._post_compile_expanded_state is not None
+ if not params:
+ return dict(self._post_compile_expanded_state.parameters)
+ else:
+ raise exc.InvalidRequestError(
+ "can't construct new parameters when render_postcompile "
+ "is used; the statement is hard-linked to the original "
+ "parameters. Use construct_expanded_state to generate a "
+ "new statement and parameters."
+ )
+
has_escaped_names = escape_names and bool(self.escaped_bind_names)
if extracted_parameters:
+
# related the bound parameters collected in the original cache key
# to those collected in the incoming cache key. They will not have
# matching names but they will line up positionally in the same
resolved_extracted = None
if params:
+
pd = {}
for bindparam, name in self.bind_names.items():
escaped_name = (
pd[escaped_name] = value_param.effective_value
else:
pd[escaped_name] = value_param.value
+
return pd
@util.memoized_instancemethod
def _process_parameters_for_postcompile(
self,
- parameters: Optional[_MutableCoreSingleExecuteParams] = None,
+ parameters: _MutableCoreSingleExecuteParams,
_populate_self: bool = False,
) -> ExpandedState:
"""handle special post compile parameters.
"""
- if parameters is None:
- parameters = self.construct_params(escape_names=False)
-
expanded_parameters = {}
- positiontup: Optional[List[str]]
+ new_positiontup: Optional[List[str]]
+
+ pre_expanded_string = self._pre_expanded_string
+ if pre_expanded_string is None:
+ pre_expanded_string = self.string
if self.positional:
- positiontup = []
+ new_positiontup = []
+
+ pre_expanded_positiontup = self._pre_expanded_positiontup
+ if pre_expanded_positiontup is None:
+ pre_expanded_positiontup = self.positiontup
+
else:
- positiontup = None
+ new_positiontup = pre_expanded_positiontup = None
processors = self._bind_processors
single_processors = cast(
numeric_positiontup: Optional[List[str]] = None
- if self.positional and self.positiontup is not None:
- names: Iterable[str] = self.positiontup
+ if self.positional and pre_expanded_positiontup is not None:
+ names: Iterable[str] = pre_expanded_positiontup
if self._numeric_binds:
numeric_positiontup = []
else:
numeric_positiontup.extend(
name for name, _ in to_update
)
- elif positiontup is not None:
+ elif new_positiontup is not None:
# to_update has escaped names, but that's ok since
# these are new names, that aren't in the
# escaped_bind_names dict.
- positiontup.extend(name for name, _ in to_update)
+ new_positiontup.extend(name for name, _ in to_update)
expanded_parameters[name] = [
expand_key for expand_key, _ in to_update
]
- elif positiontup is not None:
- positiontup.append(name)
+ elif new_positiontup is not None:
+ new_positiontup.append(name)
def process_expanding(m):
key = m.group(1)
return expr
statement = re.sub(
- self._post_compile_pattern, process_expanding, self.string
+ self._post_compile_pattern, process_expanding, pre_expanded_string
)
if numeric_positiontup is not None:
- assert positiontup is not None
+ assert new_positiontup is not None
param_pos = {
key: f"{self._numeric_binds_identifier_char}{num}"
for num, key in enumerate(
statement = self._pyformat_pattern.sub(
lambda m: param_pos[m.group(1)], statement
)
- positiontup.extend(numeric_positiontup)
+ new_positiontup.extend(numeric_positiontup)
expanded_state = ExpandedState(
statement,
parameters,
new_processors,
- positiontup,
+ new_positiontup,
expanded_parameters,
)
# this is for the "render_postcompile" flag, which is not
# otherwise used internally and is for end-user debugging and
# special use cases.
+ self._pre_expanded_string = pre_expanded_string
+ self._pre_expanded_positiontup = pre_expanded_positiontup
self.string = expanded_state.statement
- self._bind_processors.update(expanded_state.processors)
- self.positiontup = list(expanded_state.positiontup or ())
- self.post_compile_params = frozenset()
- for key in expanded_state.parameter_expansion:
- bind = self.binds.pop(key)
-
- if TYPE_CHECKING:
- assert bind.value is not None
-
- self.bind_names.pop(bind)
- for value, expanded_key in zip(
- bind.value, expanded_state.parameter_expansion[key]
- ):
- self.binds[expanded_key] = new_param = bind._with_value(
- value
- )
- self.bind_names[new_param] = expanded_key
+ self.positiontup = (
+ list(expanded_state.positiontup or ())
+ if self.positional
+ else None
+ )
+ self._post_compile_expanded_state = expanded_state
return expanded_state
"""
+from __future__ import annotations
+
import datetime
import decimal
+from typing import TYPE_CHECKING
from sqlalchemy import alias
from sqlalchemy import and_
from sqlalchemy.testing import expect_raises_message
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import is_
+from sqlalchemy.testing import is_none
from sqlalchemy.testing import is_true
from sqlalchemy.testing import mock
from sqlalchemy.testing import ne_
from sqlalchemy.testing.schema import pep435_enum
from sqlalchemy.types import UserDefinedType
+
+if TYPE_CHECKING:
+ from sqlalchemy import Select
+
+
table1 = table(
"mytable",
column("myid", Integer),
"WHERE t.x = '10') AS anon_1 UNION SELECT "
"(SELECT 1 FROM t WHERE t.x = '10') AS anon_1) AS anon_2",
)
- eq_(compiled.construct_params(), {"param_1": "10"})
+ eq_(compiled.construct_params(_no_postcompile=True), {"param_1": "10"})
def test_construct_params_repeated_postcompile_params_two(self):
"""test for :ticket:`6202` two - same param name used twice
"FROM t WHERE t.x = '10') AS anon_1 UNION SELECT "
"(SELECT 1 FROM t WHERE t.x = '10') AS anon_3) AS anon_2",
)
- eq_(compiled.construct_params(), {"param_1": "10"})
+ eq_(compiled.construct_params(_no_postcompile=True), {"param_1": "10"})
def test_construct_params_positional_plain_repeated(self):
t = table("t", column("x"))
"UNION SELECT (SELECT 1 FROM t WHERE t.x = %s AND t.x = '12') "
"AS anon_1) AS anon_2",
)
- eq_(compiled.construct_params(), {"param_1": "10", "param_2": "12"})
+ eq_(
+ compiled.construct_params(_no_postcompile=True),
+ {"param_1": "10", "param_2": "12"},
+ )
eq_(compiled.positiontup, ["param_1", "param_1"])
def test_tuple_clauselist_in(self):
table1.c.name.in_(bindparam(paramname, value=["a", "b"]))
)
- # NOTE: below the rendered params are just what
- # render_postcompile will do right now
- # if you run construct_params(). render_postcompile mode
- # is not actually used by the execution internals, it's for
- # user-facing compilation code. So this is likely a
- # current limitation of construct_params() which is not
- # doing the full blown postcompile; just assert that's
- # what it does for now. it likely should be corrected
- # to make more sense.
if paramstyle.qmark:
self.assert_compile(
stmt,
"SELECT mytable.myid FROM mytable "
"WHERE mytable.name IN (?, ?)",
- params={paramname: ["y", "z"]},
- checkpositional=(["y", "z"], ["y", "z"]),
+ params={paramname: ["y", "z", "q"]},
+ checkpositional=("y", "z", "q"),
dialect="sqlite",
render_postcompile=True,
)
stmt,
"SELECT mytable.myid FROM mytable "
"WHERE mytable.name IN (:1, :2)",
- params={paramname: ["y", "z"]},
- checkpositional=(["y", "z"], ["y", "z"]),
+ params={paramname: ["y", "z", "q"]},
+ checkpositional=("y", "z", "q"),
dialect=sqlite.dialect(paramstyle="numeric"),
render_postcompile=True,
)
"(:%s_1, :%s_2)" % (expected, expected),
params={paramname: ["y", "z"]},
checkparams={
- "%s_1" % expected: ["y", "z"],
- "%s_2" % expected: ["y", "z"],
+ "%s_1" % expected: "y",
+ "%s_2" % expected: "z",
},
dialect="default",
render_postcompile=True,
.where(table1.c.myid == 9)
).order_by("myid")
- # NOTE: below the rendered params are just what
- # render_postcompile will do right now
- # if you run construct_params(). render_postcompile mode
- # is not actually used by the execution internals, it's for
- # user-facing compilation code. So this is likely a
- # current limitation of construct_params() which is not
- # doing the full blown postcompile; just assert that's
- # what it does for now. it likely should be corrected
- # to make more sense.
-
if paramstyle.qmark:
self.assert_compile(
stmt,
"mytable.name IN (?)) "
"AND mytable.myid = ? ORDER BY myid",
params={"uname": ["y", "z"], "uname2": ["a"]},
- checkpositional=(
- ["y", "z"],
- ["y", "z"],
- ["a"],
- 8,
- ["y", "z"],
- ["y", "z"],
- ["a"],
- 9,
- ),
+ checkpositional=("y", "z", "a", 8, "y", "z", "a", 9),
dialect="sqlite",
render_postcompile=True,
)
"mytable.name IN (:5)) "
"AND mytable.myid = :2 ORDER BY myid",
params={"uname": ["y", "z"], "uname2": ["a"]},
- checkpositional=(8, 9, ["y", "z"], ["y", "z"], ["a"]),
+ checkpositional=(8, 9, "y", "z", "a"),
dialect=sqlite.dialect(paramstyle="numeric"),
render_postcompile=True,
)
"AND mytable.myid = :myid_2 ORDER BY myid",
params={"uname": ["y", "z"], "uname2": ["a"]},
checkparams={
- "uname": ["y", "z"],
- "uname2": ["a"],
- "uname_1": ["y", "z"],
- "uname_2": ["y", "z"],
- "uname2_1": ["a"],
"myid_1": 8,
"myid_2": 9,
+ "uname_1": "y",
+ "uname_2": "z",
+ "uname2_1": "a",
},
dialect="default",
render_postcompile=True,
)
+class CompileUXTest(fixtures.TestBase):
+ """tests focused on calling stmt.compile() directly, user cases"""
+
+ @testing.fixture
+ def render_postcompile_fixture(self):
+ return (
+ select(func.count(1))
+ .where(column("q") == "x")
+ .where(column("z").in_([1, 2, 3]))
+ .where(column("z_tuple").in_([(1, "a"), (2, "b"), (3, "c")]))
+ .where(
+ column("y").op("foobar")(
+ bindparam(
+ "key", value=[("1", "2"), ("3", "4")], expanding=True
+ )
+ )
+ )
+ )
+
+ def test_render_postcompile_default_stmt(self, render_postcompile_fixture):
+ stmt = render_postcompile_fixture
+
+ compiled = stmt.compile(compile_kwargs={"render_postcompile": True})
+ eq_ignore_whitespace(
+ compiled.string,
+ "SELECT count(:count_2) AS count_1 WHERE q = :q_1 AND z "
+ "IN (:z_1_1, :z_1_2, :z_1_3) AND z_tuple IN "
+ "((:z_tuple_1_1_1, :z_tuple_1_1_2), "
+ "(:z_tuple_1_2_1, :z_tuple_1_2_2), "
+ "(:z_tuple_1_3_1, :z_tuple_1_3_2)) "
+ "AND (y foobar ((:key_1_1, :key_1_2), (:key_2_1, :key_2_2)))",
+ )
+
+ def test_render_postcompile_named_parameters(
+ self, render_postcompile_fixture
+ ):
+ stmt = render_postcompile_fixture
+
+ compiled = stmt.compile(compile_kwargs={"render_postcompile": True})
+ is_none(compiled.positiontup)
+ eq_(
+ compiled.construct_params(),
+ {
+ "count_2": 1,
+ "q_1": "x",
+ "z_1_1": 1,
+ "z_1_2": 2,
+ "z_1_3": 3,
+ "z_tuple_1_1_1": 1,
+ "z_tuple_1_1_2": "a",
+ "z_tuple_1_2_1": 2,
+ "z_tuple_1_2_2": "b",
+ "z_tuple_1_3_1": 3,
+ "z_tuple_1_3_2": "c",
+ "key_1_1": "1",
+ "key_1_2": "2",
+ "key_2_1": "3",
+ "key_2_2": "4",
+ },
+ )
+
+ def test_render_postcompile_no_new_params(
+ self, render_postcompile_fixture
+ ):
+ stmt = render_postcompile_fixture
+
+ compiled = stmt.compile(compile_kwargs={"render_postcompile": True})
+ params = {"q_1": "g"}
+ with expect_raises_message(
+ exc.InvalidRequestError,
+ "can't construct new parameters when render_postcompile is used; "
+ "the statement is hard-linked to the original parameters.",
+ ):
+ compiled.construct_params(params)
+
+ @testing.variation("render_postcompile", [True, False])
+ def test_new_expanded_state_no_params(
+ self, render_postcompile_fixture: Select, render_postcompile
+ ):
+ stmt = render_postcompile_fixture
+
+ compiled = stmt.compile(
+ compile_kwargs={"render_postcompile": render_postcompile}
+ )
+ is_none(compiled.positiontup)
+
+ es = compiled.construct_expanded_state()
+
+ is_none(compiled.positiontup)
+
+ eq_ignore_whitespace(
+ es.statement,
+ "SELECT count(:count_2) AS count_1 WHERE q = :q_1 AND z "
+ "IN (:z_1_1, :z_1_2, :z_1_3) AND z_tuple IN "
+ "((:z_tuple_1_1_1, :z_tuple_1_1_2), "
+ "(:z_tuple_1_2_1, :z_tuple_1_2_2), "
+ "(:z_tuple_1_3_1, :z_tuple_1_3_2)) "
+ "AND (y foobar ((:key_1_1, :key_1_2), (:key_2_1, :key_2_2)))",
+ )
+
+ eq_(
+ es.parameters,
+ {
+ "count_2": 1,
+ "q_1": "x",
+ "z_1_1": 1,
+ "z_1_2": 2,
+ "z_1_3": 3,
+ "z_tuple_1_1_1": 1,
+ "z_tuple_1_1_2": "a",
+ "z_tuple_1_2_1": 2,
+ "z_tuple_1_2_2": "b",
+ "z_tuple_1_3_1": 3,
+ "z_tuple_1_3_2": "c",
+ "key_1_1": "1",
+ "key_1_2": "2",
+ "key_2_1": "3",
+ "key_2_2": "4",
+ },
+ )
+
+ @testing.variation("render_postcompile", [True, False])
+ @testing.variation("positional", [True, False])
+ def test_accessor_no_params(self, render_postcompile, positional):
+ stmt = select(column("q"))
+
+ positional_dialect = default.DefaultDialect(
+ paramstyle="qmark" if positional else "pyformat"
+ )
+ compiled = stmt.compile(
+ dialect=positional_dialect,
+ compile_kwargs={"render_postcompile": render_postcompile},
+ )
+ if positional:
+ eq_(compiled.positiontup, [])
+ else:
+ is_none(compiled.positiontup)
+ eq_(compiled.params, {})
+ eq_(compiled.construct_params(), {})
+
+ es = compiled.construct_expanded_state()
+ if positional:
+ eq_(es.positiontup, [])
+ eq_(es.positional_parameters, ())
+ else:
+ is_none(es.positiontup)
+ with expect_raises_message(
+ exc.InvalidRequestError,
+ "statement does not use a positional paramstyle",
+ ):
+ es.positional_parameters
+ eq_(es.parameters, {})
+
+ eq_ignore_whitespace(
+ es.statement,
+ "SELECT q",
+ )
+
+ @testing.variation("render_postcompile", [True, False])
+ def test_new_expanded_state_new_params(
+ self, render_postcompile_fixture: Select, render_postcompile
+ ):
+ stmt = render_postcompile_fixture
+
+ compiled = stmt.compile(
+ compile_kwargs={"render_postcompile": render_postcompile}
+ )
+ is_none(compiled.positiontup)
+
+ es = compiled.construct_expanded_state(
+ {
+ "z_tuple_1": [("q", "z", "p"), ("g", "h", "i")],
+ "key": ["a", "b"],
+ }
+ )
+ is_none(compiled.positiontup)
+
+ eq_ignore_whitespace(
+ es.statement,
+ "SELECT count(:count_2) AS count_1 WHERE q = :q_1 AND z IN "
+ "(:z_1_1, :z_1_2, :z_1_3) AND z_tuple IN "
+ "((:z_tuple_1_1_1, :z_tuple_1_1_2, :z_tuple_1_1_3), "
+ "(:z_tuple_1_2_1, :z_tuple_1_2_2, :z_tuple_1_2_3)) AND "
+ "(y foobar (:key_1, :key_2))",
+ )
+
+ eq_(
+ es.parameters,
+ {
+ "count_2": 1,
+ "q_1": "x",
+ "z_1_1": 1,
+ "z_1_2": 2,
+ "z_1_3": 3,
+ "z_tuple_1_1_1": "q",
+ "z_tuple_1_1_2": "z",
+ "z_tuple_1_1_3": "p",
+ "z_tuple_1_2_1": "g",
+ "z_tuple_1_2_2": "h",
+ "z_tuple_1_2_3": "i",
+ "key_1": "a",
+ "key_2": "b",
+ },
+ )
+
+ @testing.variation("render_postcompile", [True, False])
+ @testing.variation("paramstyle", ["qmark", "numeric"])
+ def test_new_expanded_state_new_positional_params(
+ self,
+ render_postcompile_fixture: Select,
+ render_postcompile,
+ paramstyle,
+ ):
+ stmt = render_postcompile_fixture
+ positional_dialect = default.DefaultDialect(paramstyle=paramstyle.name)
+
+ compiled = stmt.compile(
+ dialect=positional_dialect,
+ compile_kwargs={"render_postcompile": render_postcompile},
+ )
+
+ if render_postcompile:
+ eq_(
+ compiled.positiontup,
+ [
+ "count_2",
+ "q_1",
+ "z_1_1",
+ "z_1_2",
+ "z_1_3",
+ "z_tuple_1_1_1",
+ "z_tuple_1_1_2",
+ "z_tuple_1_2_1",
+ "z_tuple_1_2_2",
+ "z_tuple_1_3_1",
+ "z_tuple_1_3_2",
+ "key_1_1",
+ "key_1_2",
+ "key_2_1",
+ "key_2_2",
+ ],
+ )
+ else:
+ eq_(
+ compiled.positiontup,
+ ["count_2", "q_1", "z_1", "z_tuple_1", "key"],
+ )
+ es = compiled.construct_expanded_state(
+ {
+ "z_tuple_1": [("q", "z", "p"), ("g", "h", "i")],
+ "key": ["a", "b"],
+ }
+ )
+ if paramstyle.qmark:
+ eq_ignore_whitespace(
+ es.statement,
+ "SELECT count(?) AS count_1 WHERE q = ? "
+ "AND z IN (?, ?, ?) AND "
+ "z_tuple IN ((?, ?, ?), (?, ?, ?)) AND (y foobar (?, ?))",
+ )
+ elif paramstyle.numeric:
+ eq_ignore_whitespace(
+ es.statement,
+ "SELECT count(:1) AS count_1 WHERE q = :2 AND z IN "
+ "(:3, :4, :5) AND z_tuple "
+ "IN ((:6, :7, :8), (:9, :10, :11)) AND (y foobar (:12, :13))",
+ )
+ else:
+ paramstyle.fail()
+
+ eq_(
+ es.parameters,
+ {
+ "count_2": 1,
+ "q_1": "x",
+ "z_1_1": 1,
+ "z_1_2": 2,
+ "z_1_3": 3,
+ "z_tuple_1_1_1": "q",
+ "z_tuple_1_1_2": "z",
+ "z_tuple_1_1_3": "p",
+ "z_tuple_1_2_1": "g",
+ "z_tuple_1_2_2": "h",
+ "z_tuple_1_2_3": "i",
+ "key_1": "a",
+ "key_2": "b",
+ },
+ )
+ eq_(
+ es.positiontup,
+ [
+ "count_2",
+ "q_1",
+ "z_1_1",
+ "z_1_2",
+ "z_1_3",
+ "z_tuple_1_1_1",
+ "z_tuple_1_1_2",
+ "z_tuple_1_1_3",
+ "z_tuple_1_2_1",
+ "z_tuple_1_2_2",
+ "z_tuple_1_2_3",
+ "key_1",
+ "key_2",
+ ],
+ )
+ eq_(
+ es.positional_parameters,
+ (1, "x", 1, 2, 3, "q", "z", "p", "g", "h", "i", "a", "b"),
+ )
+
+ def test_render_postcompile_positional_parameters(
+ self, render_postcompile_fixture
+ ):
+ stmt = render_postcompile_fixture
+
+ positional_dialect = default.DefaultDialect(paramstyle="qmark")
+ compiled = stmt.compile(
+ dialect=positional_dialect,
+ compile_kwargs={"render_postcompile": True},
+ )
+ eq_(
+ compiled.construct_params(),
+ {
+ "count_2": 1,
+ "q_1": "x",
+ "z_1_1": 1,
+ "z_1_2": 2,
+ "z_1_3": 3,
+ "z_tuple_1_1_1": 1,
+ "z_tuple_1_1_2": "a",
+ "z_tuple_1_2_1": 2,
+ "z_tuple_1_2_2": "b",
+ "z_tuple_1_3_1": 3,
+ "z_tuple_1_3_2": "c",
+ "key_1_1": "1",
+ "key_1_2": "2",
+ "key_2_1": "3",
+ "key_2_2": "4",
+ },
+ )
+ eq_(
+ compiled.positiontup,
+ [
+ "count_2",
+ "q_1",
+ "z_1_1",
+ "z_1_2",
+ "z_1_3",
+ "z_tuple_1_1_1",
+ "z_tuple_1_1_2",
+ "z_tuple_1_2_1",
+ "z_tuple_1_2_2",
+ "z_tuple_1_3_1",
+ "z_tuple_1_3_2",
+ "key_1_1",
+ "key_1_2",
+ "key_2_1",
+ "key_2_2",
+ ],
+ )
+
+
class UnsupportedTest(fixtures.TestBase):
def test_unsupported_element_str_visit_name(self):
from sqlalchemy.sql.expression import ClauseElement