mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-08 09:51:28 -04:00
8be3b096a6
Added the ability to create custom SQL constructs that can define new clauses within SELECT, INSERT, UPDATE, and DELETE statements without needing to modify the construction or compilation code of of :class:`.Select`, :class:`.Insert`, :class:`.Update`, or :class:`.Delete` directly. Support for testing these constructs, including caching support, is present along with an example test suite. The use case for these constructs is expected to be third party dialects for NewSQL or other novel styles of database that introduce new clauses to these statements. A new example suite is included which illustrates the ``QUALIFY`` SQL construct used by several NewSQL databases which includes a cachable implementation as well as a test suite. Since these extensions start to make it a bit crowded with how many kinds of "options" we have on statements, did some naming / documentation changes with existing constructs on Executable, in particular to distinguish ExecutableOption from SyntaxExtension. Fixes: #12195 Change-Id: I4a44ee5bbc3d8b1b640837680c09d25b1b7077af
171 lines
5.5 KiB
Python
171 lines
5.5 KiB
Python
import random
|
|
import unittest
|
|
|
|
from sqlalchemy import Column
|
|
from sqlalchemy import func
|
|
from sqlalchemy import Integer
|
|
from sqlalchemy import MetaData
|
|
from sqlalchemy import select
|
|
from sqlalchemy import Table
|
|
from sqlalchemy.testing import AssertsCompiledSQL
|
|
from sqlalchemy.testing import eq_
|
|
from sqlalchemy.testing import fixtures
|
|
from .qualify import qualify
|
|
|
|
qt_table = Table(
|
|
"qt",
|
|
MetaData(),
|
|
Column("i", Integer),
|
|
Column("p", Integer),
|
|
Column("o", Integer),
|
|
)
|
|
|
|
|
|
class QualifyCompileTest(AssertsCompiledSQL, fixtures.CacheKeySuite):
|
|
"""A sample test suite for the QUALIFY clause, making use of SQLAlchemy
|
|
testing utilities.
|
|
|
|
"""
|
|
|
|
__dialect__ = "default"
|
|
|
|
@fixtures.CacheKeySuite.run_suite_tests
|
|
def test_qualify_cache_key(self):
|
|
"""A cache key suite using the ``CacheKeySuite.run_suite_tests``
|
|
decorator.
|
|
|
|
This suite intends to test that the "_traverse_internals" structure
|
|
of the custom SQL construct covers all the structural elements of
|
|
the object. A decorated function should return a callable (e.g.
|
|
a lambda) which returns a list of SQL structures. The suite will
|
|
call upon this lambda multiple times, to make the same list of
|
|
SQL structures repeatedly. It then runs comparisons of the generated
|
|
cache key for each element in a particular list to all the other
|
|
elements in that same list, as well as other versions of the list.
|
|
|
|
The rules for this list are then as follows:
|
|
|
|
* Each element of the list should store a SQL structure that is
|
|
**structurally identical** each time, for a given position in the
|
|
list. Successive versions of this SQL structure will be compared
|
|
to previous ones in the same list position and they must be
|
|
identical.
|
|
|
|
* Each element of the list should store a SQL structure that is
|
|
**structurally different** from **all other** elements in the list.
|
|
Successive versions of this SQL structure will be compared to
|
|
other members in other list positions, and they must be different
|
|
each time.
|
|
|
|
* The SQL structures returned in the list should exercise all of the
|
|
structural features that are provided by the construct. This is
|
|
to ensure that two different structural elements generate a
|
|
different cache key and won't be mis-cached.
|
|
|
|
* Literal parameters like strings and numbers are **not** part of the
|
|
cache key itself since these are not "structural" elements; two
|
|
SQL structures that are identical can nonetheless have different
|
|
parameterized values. To better exercise testing that this variation
|
|
is not stored as part of the cache key, ``random`` functions like
|
|
``random.randint()`` or ``random.choice()`` can be used to generate
|
|
random literal values within a single element.
|
|
|
|
|
|
"""
|
|
|
|
def stmt0():
|
|
return select(qt_table)
|
|
|
|
def stmt1():
|
|
stmt = stmt0()
|
|
|
|
return stmt.ext(qualify(qt_table.c.p == random.choice([2, 6, 10])))
|
|
|
|
def stmt2():
|
|
stmt = stmt0()
|
|
|
|
return stmt.ext(
|
|
qualify(func.row_number().over(order_by=qt_table.c.o))
|
|
)
|
|
|
|
def stmt3():
|
|
stmt = stmt0()
|
|
|
|
return stmt.ext(
|
|
qualify(
|
|
func.row_number().over(
|
|
partition_by=qt_table.c.i, order_by=qt_table.c.o
|
|
)
|
|
)
|
|
)
|
|
|
|
return lambda: [stmt0(), stmt1(), stmt2(), stmt3()]
|
|
|
|
def test_query_one(self):
|
|
"""A compilation test. This makes use of the
|
|
``AssertsCompiledSQL.assert_compile()`` utility.
|
|
|
|
"""
|
|
|
|
stmt = select(qt_table).ext(
|
|
qualify(
|
|
func.row_number().over(
|
|
partition_by=qt_table.c.p, order_by=qt_table.c.o
|
|
)
|
|
== 1
|
|
)
|
|
)
|
|
|
|
self.assert_compile(
|
|
stmt,
|
|
"SELECT qt.i, qt.p, qt.o FROM qt QUALIFY row_number() "
|
|
"OVER (PARTITION BY qt.p ORDER BY qt.o) = :param_1",
|
|
)
|
|
|
|
def test_query_two(self):
|
|
"""A compilation test. This makes use of the
|
|
``AssertsCompiledSQL.assert_compile()`` utility.
|
|
|
|
"""
|
|
|
|
row_num = (
|
|
func.row_number()
|
|
.over(partition_by=qt_table.c.p, order_by=qt_table.c.o)
|
|
.label("row_num")
|
|
)
|
|
stmt = select(qt_table, row_num).ext(
|
|
qualify(row_num.as_reference() == 1)
|
|
)
|
|
|
|
self.assert_compile(
|
|
stmt,
|
|
"SELECT qt.i, qt.p, qt.o, row_number() OVER "
|
|
"(PARTITION BY qt.p ORDER BY qt.o) AS row_num "
|
|
"FROM qt QUALIFY row_num = :param_1",
|
|
)
|
|
|
|
def test_propagate_attrs(self):
|
|
"""ORM propagate test. this is an optional test that tests
|
|
apply_propagate_attrs, indicating when you pass ORM classes /
|
|
attributes to your construct, there's a dictionary called
|
|
``._propagate_attrs`` that gets carried along to the statement,
|
|
which marks it as an "ORM" statement.
|
|
|
|
"""
|
|
row_num = (
|
|
func.row_number().over(partition_by=qt_table.c.p).label("row_num")
|
|
)
|
|
row_num._propagate_attrs = {"foo": "bar"}
|
|
|
|
stmt = select(1).ext(qualify(row_num.as_reference() == 1))
|
|
|
|
eq_(stmt._propagate_attrs, {"foo": "bar"})
|
|
|
|
|
|
class QualifyCompileUnittest(QualifyCompileTest, unittest.TestCase):
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|