From: Mike Bayer Date: Mon, 7 Oct 2019 19:25:27 +0000 (-0400) Subject: Drop right-nested join rewriting X-Git-Tag: rel_1_4_0b1~693^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a0d94763f8369939ae6b731d6b599b5edb05d37e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Drop right-nested join rewriting Dropped support for right-nested join rewriting to support old SQLite versions prior to 3.7.16, released in 2013. It is expected that all modern Python versions among those now supported should all include much newer versions of SQLite. Fixes: #4895 Change-Id: I7f0cfc2b7d988ff147b9a4c6d5e2adec87e27029 --- diff --git a/doc/build/changelog/migration_14.rst b/doc/build/changelog/migration_14.rst index bd8eca4377..15b6bcafec 100644 --- a/doc/build/changelog/migration_14.rst +++ b/doc/build/changelog/migration_14.rst @@ -782,3 +782,36 @@ runs operations on a new transaction. The "test harness" pattern described at :ref:`session_external_transaction` is the common place for this to occur. The new behavior is described in the errors page at :ref:`error_8s2a`. + + +Dialect Changes +=============== + +.. _change_4895: + +Removed "join rewriting" logic from SQLite dialect; updated imports +------------------------------------------------------------------- + +Dropped support for right-nested join rewriting to support old SQLite +versions prior to 3.7.16, released in 2013. It is not expected that +any modern Python versions rely upon this limitation. + +The behavior was first introduced in 0.9 and was part of the larger change of +allowing for right nested joins as described at :ref:`feature_joins_09`. +However the SQLite workaround produced many regressions in the 2013-2014 +period due to its complexity. In 2016, the dialect was modified so that the +join rewriting logic would only occur for SQLite verisons prior to 3.7.16 after +bisection was used to identify where SQLite fixed its support for this +construct, and no further issues were reported against the behavior (even +though some bugs were found internally). It is now anticipated that there +are little to no Python builds for Python 2.7 or 3.4 and above (the supported +Python versions) which would include a SQLite version prior to 3.7.17, and +the behavior is only necessary only in more complex ORM joining scenarios. +A warning is now emitted if the installed SQLite version is older than +3.7.16. + +In related changes, the module imports for SQLite no longer attempt to +import the "pysqlite2" driver on Python 3 as this driver does not exist +on Python 3; a very old warning for old pysqlite2 versions is also dropped. + +:ticket:`4895` diff --git a/doc/build/changelog/unreleased_14/4895.rst b/doc/build/changelog/unreleased_14/4895.rst new file mode 100644 index 0000000000..873b11fa9f --- /dev/null +++ b/doc/build/changelog/unreleased_14/4895.rst @@ -0,0 +1,13 @@ +.. change:: + :tags: change, sqlite + :tickets: 4895 + + Dropped support for right-nested join rewriting to support old SQLite + versions prior to 3.7.16, released in 2013. It is expected that + all modern Python versions among those now supported should all include + much newer versions of SQLite. + + .. seealso:: + + :ref:`change_4895` + diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index defb64ec05..1bb7bd4fca 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -1467,9 +1467,15 @@ class SQLiteDialect(default.DefaultDialect): self.native_datetime = native_datetime if self.dbapi is not None: - self.supports_right_nested_joins = ( - self.dbapi.sqlite_version_info >= (3, 7, 16) - ) + if self.dbapi.sqlite_version_info < (3, 7, 16): + util.warn( + "SQLite version %s is older than 3.7.16, and will not " + "support right nested joins, as are sometimes used in " + "more complex ORM scenarios. SQLAlchemy 1.4 and above " + "no longer tries to rewrite these joins." + % (self.dbapi.sqlite_version_info,) + ) + self._broken_dotted_colnames = self.dbapi.sqlite_version_info < ( 3, 10, diff --git a/lib/sqlalchemy/dialects/sqlite/pysqlite.py b/lib/sqlalchemy/dialects/sqlite/pysqlite.py index 4eca7ae318..89254eef13 100644 --- a/lib/sqlalchemy/dialects/sqlite/pysqlite.py +++ b/lib/sqlalchemy/dialects/sqlite/pysqlite.py @@ -18,19 +18,8 @@ r""" Driver ------ -When using Python 2.5 and above, the built in ``sqlite3`` driver is -already installed and no additional installation is needed. Otherwise, -the ``pysqlite2`` driver needs to be present. This is the same driver as -``sqlite3``, just with a different name. - -The ``pysqlite2`` driver will be loaded first, and if not found, ``sqlite3`` -is loaded. This allows an explicitly installed pysqlite driver to take -precedence over the built in one. As with all dialects, a specific -DBAPI module may be provided to :func:`~sqlalchemy.create_engine()` to control -this explicitly:: - - from sqlite3 import dbapi2 as sqlite - e = create_engine('sqlite+pysqlite:///file.db', module=sqlite) +The ``sqlite3`` Python DBAPI is standard on all modern Python versions; +for cPython and Pypy, no additional installation is necessary. Connect Strings @@ -379,30 +368,18 @@ class SQLiteDialect_pysqlite(SQLiteDialect): driver = "pysqlite" - def __init__(self, uri=False, **kwargs): - SQLiteDialect.__init__(self, **kwargs) - - if self.dbapi is not None: - sqlite_ver = self.dbapi.version_info - if sqlite_ver < (2, 1, 3): - util.warn( - ( - "The installed version of pysqlite2 (%s) is out-dated " - "and will cause errors in some cases. Version 2.1.3 " - "or greater is recommended." - ) - % ".".join([str(subver) for subver in sqlite_ver]) - ) - @classmethod def dbapi(cls): - try: - from pysqlite2 import dbapi2 as sqlite - except ImportError: + if util.py2k: try: - from sqlite3 import dbapi2 as sqlite # try 2.5+ stdlib name. - except ImportError as e: - raise e + from pysqlite2 import dbapi2 as sqlite + except ImportError: + try: + from sqlite3 import dbapi2 as sqlite + except ImportError as e: + raise e + else: + from sqlite3 import dbapi2 as sqlite return sqlite @classmethod diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index f66f064156..79b8622d59 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -65,7 +65,6 @@ class DefaultDialect(interfaces.Dialect): postfetch_lastrowid = True implicit_returning = False - supports_right_nested_joins = True cte_follows_insert = False supports_native_enum = False @@ -201,7 +200,6 @@ class DefaultDialect(interfaces.Dialect): paramstyle=None, dbapi=None, implicit_returning=None, - supports_right_nested_joins=None, case_sensitive=True, supports_native_boolean=None, empty_in_strategy="static", @@ -232,8 +230,6 @@ class DefaultDialect(interfaces.Dialect): self.positional = self.paramstyle in ("qmark", "format", "numeric") self.identifier_preparer = self.preparer(self) self.type_compiler = self.type_compiler(self) - if supports_right_nested_joins is not None: - self.supports_right_nested_joins = supports_right_nested_joins if supports_native_boolean is not None: self.supports_native_boolean = supports_native_boolean self.case_sensitive = case_sensitive diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 1381e734c9..320c7b7821 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -2049,136 +2049,6 @@ class SQLCompiler(Compiled): def get_statement_hint_text(self, hint_texts): return " ".join(hint_texts) - def _transform_select_for_nested_joins(self, select): - """Rewrite any "a JOIN (b JOIN c)" expression as - "a JOIN (select * from b JOIN c) AS anon", to support - databases that can't parse a parenthesized join correctly - (i.e. sqlite < 3.7.16). - - """ - cloned = {} - column_translate = [{}] - created = set() - - def visit(element, **kw): - if element in column_translate[-1]: - return column_translate[-1][element] - - elif element in cloned: - return cloned[element] - - newelem = cloned[element] = element._clone() - if ( - newelem._is_from_clause - and newelem._is_join - and isinstance(newelem.right, selectable.FromGrouping) - ): - - newelem._reset_exported() - newelem.left = visit(newelem.left, **kw) - - right = visit(newelem.right, **kw) - - selectable_ = selectable.Select( - [right.element], use_labels=True - ).alias() - created.add(selectable_) - created.update(selectable_.c) - - for c in selectable_.c: - c._key_label = c.key - c._label = c.name - - translate_dict = dict( - zip(newelem.right.element.c, selectable_.c) - ) - - # translating from both the old and the new - # because different select() structures will lead us - # to traverse differently - translate_dict[right.element.left] = selectable_ - translate_dict[right.element.right] = selectable_ - translate_dict[newelem.right.element.left] = selectable_ - translate_dict[newelem.right.element.right] = selectable_ - - # propagate translations that we've gained - # from nested visit(newelem.right) outwards - # to the enclosing select here. this happens - # only when we have more than one level of right - # join nesting, i.e. "a JOIN (b JOIN (c JOIN d))" - for k, v in list(column_translate[-1].items()): - if v in translate_dict: - # remarkably, no current ORM tests (May 2013) - # hit this condition, only test_join_rewriting - # does. - column_translate[-1][k] = translate_dict[v] - - column_translate[-1].update(translate_dict) - - newelem.right = selectable_ - - newelem.onclause = visit(newelem.onclause, **kw) - - elif newelem._is_from_container: - # if we hit an Alias, CompoundSelect or ScalarSelect, put a - # marker in the stack. - kw["transform_clue"] = "select_container" - newelem._copy_internals(clone=visit, **kw) - elif newelem._is_returns_rows and newelem._is_select_statement: - barrier_select = ( - kw.get("transform_clue", None) == "select_container" - ) - # if we're still descended from an - # Alias/CompoundSelect/ScalarSelect, we're - # in a FROM clause, so start with a new translate collection - if barrier_select: - column_translate.append({}) - kw["transform_clue"] = "inside_select" - if not newelem._is_select_container: - froms = newelem.froms - newelem._raw_columns = list(newelem.selected_columns) - newelem._from_obj.update(froms) - newelem._reset_memoizations() - newelem._copy_internals(clone=visit, **kw) - if barrier_select: - del column_translate[-1] - else: - newelem._copy_internals(clone=visit, **kw) - - return newelem - - return visit(select) - - def _transform_result_map_for_nested_joins( - self, select, transformed_select - ): - self._result_columns[:] = [ - result_rec - if col is tcol - else ( - result_rec[0], - name, - tuple([col if obj is tcol else obj for obj in result_rec[2]]), - result_rec[3], - ) - for result_rec, (name, col), (tname, tcol) in zip( - self._result_columns, - select._columns_plus_names, - transformed_select._columns_plus_names, - ) - ] - - # TODO: it's not anticipated that we need to correct anon_map - # however if we do, this is what it looks like: - # for (name, col), (tname, tcol) in zip( - # select._columns_plus_names, - # transformed_select._columns_plus_names, - # ): - # if isinstance(name, elements._anonymous_label) and name != tname: - # m1 = re.match(r"^%\((\d+ .+?)\)s$", name) - # m2 = re.match(r"^%\((\d+ .+?)\)s$", tname) - # self.anon_map[m1.group(1)] = self.anon_map[m2.group(1)] - _default_stack_entry = util.immutabledict( [("correlate_froms", frozenset()), ("asfrom_froms", frozenset())] ) @@ -2214,32 +2084,11 @@ class SQLCompiler(Compiled): asfrom=False, fromhints=None, compound_index=0, - nested_join_translation=False, select_wraps_for=None, lateral=False, **kwargs ): - needs_nested_translation = ( - select.use_labels - and not nested_join_translation - and not self.stack - and not self.dialect.supports_right_nested_joins - ) - - if needs_nested_translation: - transformed_select = self._transform_select_for_nested_joins( - select - ) - text = self.visit_select( - transformed_select, - asfrom=asfrom, - fromhints=fromhints, - compound_index=compound_index, - nested_join_translation=True, - **kwargs - ) - toplevel = not self.stack entry = self._default_stack_entry if toplevel else self.stack[-1] @@ -2258,13 +2107,6 @@ class SQLCompiler(Compiled): if not populate_result_map and "add_to_result_map" in kwargs: del kwargs["add_to_result_map"] - if needs_nested_translation: - if populate_result_map: - self._transform_result_map_for_nested_joins( - select, transformed_select - ) - return text - froms = self._setup_select_stack(select, entry, asfrom, lateral) column_clause_args = kwargs.copy() diff --git a/test/dialect/test_sqlite.py b/test/dialect/test_sqlite.py index 23d22d6fff..7d9c751755 100644 --- a/test/dialect/test_sqlite.py +++ b/test/dialect/test_sqlite.py @@ -592,6 +592,17 @@ class DialectTest(fixtures.TestBase, AssertsExecutionResults): __only_on__ = "sqlite" + def test_3_7_16_warning(self): + with expect_warnings( + r"SQLite version \(3, 2, 8\) is older than 3.7.16, and " + "will not support right nested joins" + ): + sqlite.dialect( + dbapi=mock.Mock( + version_info=(2, 6, 0), sqlite_version_info=(3, 2, 8) + ) + ) + def test_extra_reserved_words(self): """Tests reserved words in identifiers. diff --git a/test/sql/test_join_rewriting.py b/test/sql/test_join_rewriting.py deleted file mode 100644 index e713f5d73b..0000000000 --- a/test/sql/test_join_rewriting.py +++ /dev/null @@ -1,838 +0,0 @@ -"""These tests are all about the "join rewriting" feature built -to support SQLite's lack of right-nested joins. SQLite as of -version 3.7.16 no longer has this limitation. - -""" -from sqlalchemy import Column -from sqlalchemy import exists -from sqlalchemy import ForeignKey -from sqlalchemy import Integer -from sqlalchemy import literal_column -from sqlalchemy import MetaData -from sqlalchemy import select -from sqlalchemy import Table -from sqlalchemy import testing -from sqlalchemy import union -from sqlalchemy import util -from sqlalchemy.engine import default -from sqlalchemy.sql import elements -from sqlalchemy.testing import AssertsCompiledSQL -from sqlalchemy.testing import eq_ -from sqlalchemy.testing import fixtures - -m = MetaData() - - -a = Table("a", m, Column("id", Integer, primary_key=True)) - -b = Table( - "b", - m, - Column("id", Integer, primary_key=True), - Column("a_id", Integer, ForeignKey("a.id")), -) - -b_a = Table("b_a", m, Column("id", Integer, primary_key=True)) - -b1 = Table( - "b1", - m, - Column("id", Integer, primary_key=True), - Column("a_id", Integer, ForeignKey("a.id")), -) - -b2 = Table( - "b2", - m, - Column("id", Integer, primary_key=True), - Column("a_id", Integer, ForeignKey("a.id")), -) - -a_to_b = Table( - "a_to_b", - m, - Column("a_id", Integer, ForeignKey("a.id")), - Column("b_id", Integer, ForeignKey("b.id")), -) - -c = Table( - "c", - m, - Column("id", Integer, primary_key=True), - Column("b_id", Integer, ForeignKey("b.id")), -) - -d = Table( - "d", - m, - Column("id", Integer, primary_key=True), - Column("c_id", Integer, ForeignKey("c.id")), -) - -e = Table("e", m, Column("id", Integer, primary_key=True)) - -f = Table( - "f", - m, - Column("id", Integer, primary_key=True), - Column("a_id", ForeignKey("a.id")), -) - -b_key = Table("b_key", m, Column("id", Integer, primary_key=True, key="bid")) - -a_to_b_key = Table( - "a_to_b_key", - m, - Column("aid", Integer, ForeignKey("a.id")), - Column("bid", Integer, ForeignKey("b_key.bid")), -) - - -class _JoinRewriteTestBase(AssertsCompiledSQL): - def _test(self, s, assert_): - self.assert_compile(s, assert_) - - compiled = s.compile(dialect=self.__dialect__) - - for rec, (name, col) in zip( - compiled._result_columns, s._columns_plus_names - ): - assert col in set(rec[2]) - if ( - not isinstance(name, elements._anonymous_label) - and name is not None - ): - eq_(rec[1], name) - - _a_bkeyselect_bkey = "" - - def test_a_bkeyselect_bkey(self): - assoc = a_to_b_key.select().alias() - j1 = assoc.join(b_key) - j2 = a.join(j1) - - s = select([a, b_key], use_labels=True).select_from(j2) - self._test(s, self._a_bkeyselect_bkey) - - def test_a_bc(self): - j1 = b.join(c) - j2 = a.join(j1) - - # TODO: if we remove 'b' or 'c', shouldn't we get just - # the subset of cols from anon_1 ? - - # TODO: do this test also with individual cols, things change - # lots based on how you go with this - - s = ( - select([a, b, c], use_labels=True) - .select_from(j2) - .where(b.c.id == 2) - .where(c.c.id == 3) - .order_by(a.c.id, b.c.id, c.c.id) - ) - - self._test(s, self._a_bc) - - def test_a_bc_preserve_dupes(self): - j1 = b.join(c) - j2 = a.join(j1) - - s = ( - select( - [a.c.id, b.c.id, b.c.a_id, c, b.c.a_id, c.c.b_id], - use_labels=True, - ) - .select_from(j2) - .where(b.c.id == 2) - .where(c.c.id == 3) - .order_by(a.c.id, b.c.id, c.c.id) - ) - - self._test(s, self._a_bc_wdupes) - - def test_a_bc_preserve_dupes_anon_map(self): - j1 = b.join(c) - j2 = a.join(j1) - - s = ( - select( - [a.c.id, b.c.id, b.c.a_id, c, b.c.a_id, c.c.b_id], - use_labels=True, - ) - .select_from(j2) - .where(b.c.id == 2) - .where(c.c.id == 3) - ) - - # the anon_map needs to be preserved after the transform - # as the labels are going to be referred to outside of the query - subq = s.subquery() - s2 = ( - select([literal_column("1")]) - .select_from(subq) - .where(subq.c[5] == subq.c[6]) - ) - - self._test(s2, self._a_bc_wdupes_anon_map) - - def test_a_bkeyassoc(self): - j1 = b_key.join(a_to_b_key) - j2 = a.join(j1) - - s = select([a, b_key.c.bid], use_labels=True).select_from(j2) - - self._test(s, self._a_bkeyassoc) - - def test_a_bkeyassoc_aliased(self): - bkey_alias = b_key.alias() - a_to_b_key_alias = a_to_b_key.alias() - - j1 = bkey_alias.join(a_to_b_key_alias) - j2 = a.join(j1) - - s = select([a, bkey_alias.c.bid], use_labels=True).select_from(j2) - - self._test(s, self._a_bkeyassoc_aliased) - - def test_a__b_dc(self): - j1 = c.join(d) - j2 = b.join(j1) - j3 = a.join(j2) - - s = ( - select([a, b, c, d], use_labels=True) - .select_from(j3) - .where(b.c.id == 2) - .where(c.c.id == 3) - .where(d.c.id == 4) - .order_by(a.c.id, b.c.id, c.c.id, d.c.id) - ) - - self._test(s, self._a__b_dc) - - def test_a_bc_comma_a1_selbc(self): - # test here we're emulating is - # test.orm.inheritance.test_polymorphic_rel: - # PolymorphicJoinsTest.test_multi_join - j1 = b.join(c) - j2 = b.join(c).select(use_labels=True).alias() - j3 = a.join(j1) - a_a = a.alias() - j4 = a_a.join(j2) - - s = ( - select([a, a_a, b, c, j2], use_labels=True) - .select_from(j3) - .select_from(j4) - .order_by(j2.c.b_id) - ) - - self._test(s, self._a_bc_comma_a1_selbc) - - def test_a_atobalias_balias_c_w_exists(self): - a_to_b_alias = a_to_b.alias() - b_alias = b.alias() - - j1 = a_to_b_alias.join(b_alias) - j2 = a.outerjoin(j1, a.c.id == a_to_b_alias.c.a_id) - - # TODO: if we put straight a_to_b_alias here, - # it fails to alias the columns clause. - s = select( - [ - a, - a_to_b_alias.c.a_id, - a_to_b_alias.c.b_id, - b_alias.c.id, - b_alias.c.a_id, - exists() - .select_from(c) - .where(c.c.b_id == b_alias.c.id) - .label(None), - ], - use_labels=True, - ).select_from(j2) - - self._test(s, self._a_atobalias_balias_c_w_exists) - - def test_a_atobalias_balias(self): - a_to_b_alias = a_to_b.alias() - b_alias = b.alias() - - j1 = a_to_b_alias.join(b_alias) - j2 = a.outerjoin(j1, a.c.id == a_to_b_alias.c.a_id) - - s = select([a, a_to_b_alias, b_alias], use_labels=True).select_from(j2) - - self._test(s, self._a_atobalias_balias) - - def test_b_ab1_union_b_ab2(self): - j1 = a.join(b1) - j2 = a.join(b2) - - b_j1 = b.join(j1) - b_j2 = b.join(j2) - - s = ( - union( - select([b_j1], use_labels=True), - select([b_j2], use_labels=True), - ) - .subquery() - .select(use_labels=True) - ) - - self._test(s, self._b_ab1_union_c_ab2) - - def test_b_a_id_double_overlap_annotated(self): - # test issue #3057 - # this involves annotations so try to loop those in. - j1 = b.join(b_a, b.c.id == b_a.c.id) - annot = [ - b.c.id._annotate({}), - b.c.a_id._annotate({}), - b_a.c.id._annotate({}), - ] - - s = select(annot).select_from(j1).apply_labels().alias() - - s = select(list(s.c)).apply_labels() - - self._test(s, self._b_a_id_double_overlap_annotated) - - def test_f_b1a_where_in_b2a(self): - # test issue #3130 - b1a = a.join(b1) - b2a = a.join(b2) - subq = select([b2.c.id]).select_from(b2a) - s = select([f]).select_from(f.join(b1a)).where(b1.c.id.in_(subq)) - - s = s.apply_labels() - self._test(s, self._f_b1a_where_in_b2a) - - def test_anon_scalar_subqueries(self): - s1 = select([1]).scalar_subquery() - s2 = select([2]).scalar_subquery() - - s = select([s1, s2]).apply_labels() - self._test(s, self._anon_scalar_subqueries) - - -class JoinRewriteTest(_JoinRewriteTestBase, fixtures.TestBase): - - """test rendering of each join with right-nested rewritten as - aliased SELECT statements..""" - - @util.classproperty - def __dialect__(cls): - dialect = default.DefaultDialect() - dialect.supports_right_nested_joins = False - return dialect - - _a__b_dc = ( - "SELECT a.id AS a_id, anon_1.b_id AS b_id, " - "anon_1.b_a_id AS b_a_id, anon_1.c_id AS c_id, " - "anon_1.c_b_id AS c_b_id, anon_1.d_id AS d_id, " - "anon_1.d_c_id AS d_c_id " - "FROM a JOIN (SELECT b.id AS b_id, b.a_id AS b_a_id, " - "anon_2.c_id AS c_id, anon_2.c_b_id AS c_b_id, " - "anon_2.d_id AS d_id, anon_2.d_c_id AS d_c_id " - "FROM b JOIN (SELECT c.id AS c_id, c.b_id AS c_b_id, " - "d.id AS d_id, d.c_id AS d_c_id " - "FROM c JOIN d ON c.id = d.c_id) AS anon_2 " - "ON b.id = anon_2.c_b_id) AS anon_1 ON a.id = anon_1.b_a_id " - "WHERE anon_1.b_id = :id_1 AND anon_1.c_id = :id_2 AND " - "anon_1.d_id = :id_3 " - "ORDER BY a.id, anon_1.b_id, anon_1.c_id, anon_1.d_id" - ) - - _a_bc = ( - "SELECT a.id AS a_id, anon_1.b_id AS b_id, " - "anon_1.b_a_id AS b_a_id, anon_1.c_id AS c_id, " - "anon_1.c_b_id AS c_b_id FROM a JOIN " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, c.b_id AS c_b_id " - "FROM b JOIN c ON b.id = c.b_id) AS anon_1 " - "ON a.id = anon_1.b_a_id " - "WHERE anon_1.b_id = :id_1 AND anon_1.c_id = :id_2 " - "ORDER BY a.id, anon_1.b_id, anon_1.c_id" - ) - - _a_bc_wdupes = ( - "SELECT a.id AS a_id, anon_1.b_id AS b_id, anon_1.b_a_id AS b_a_id, " - "anon_1.c_id AS c_id, anon_1.c_b_id AS c_b_id, " - "anon_1.b_a_id AS b_a_id__1, anon_1.c_b_id AS c_b_id__1 " - "FROM a JOIN " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, c.id AS c_id, " - "c.b_id AS c_b_id " - "FROM b JOIN c ON b.id = c.b_id) AS anon_1 ON a.id = anon_1.b_a_id " - "WHERE anon_1.b_id = :id_1 AND anon_1.c_id = :id_2 " - "ORDER BY a.id, anon_1.b_id, anon_1.c_id" - ) - - _a_bc_wdupes_anon_map = ( - "SELECT 1 FROM (SELECT a.id AS a_id, b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, c.b_id AS c_b_id, b.a_id AS b_a_id__1, " - "c.b_id AS c_b_id__1 FROM a JOIN (b JOIN c ON b.id = c.b_id) " - "ON a.id = b.a_id WHERE b.id = :id_1 AND c.id = :id_2) AS anon_1 " - "WHERE anon_1.b_a_id = anon_1.c_b_id" - ) - - _a_bc_comma_a1_selbc = ( - "SELECT a.id AS a_id, a_1.id AS a_1_id, anon_1.b_id AS b_id, " - "anon_1.b_a_id AS b_a_id, anon_1.c_id AS c_id, " - "anon_1.c_b_id AS c_b_id, anon_2.b_id AS anon_2_b_id, " - "anon_2.b_a_id AS anon_2_b_a_id, anon_2.c_id AS anon_2_c_id, " - "anon_2.c_b_id AS anon_2_c_b_id FROM a " - "JOIN (SELECT b.id AS b_id, b.a_id AS b_a_id, c.id AS c_id, " - "c.b_id AS c_b_id FROM b JOIN c ON b.id = c.b_id) AS anon_1 " - "ON a.id = anon_1.b_a_id, " - "a AS a_1 JOIN " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, c.b_id AS c_b_id " - "FROM b JOIN c ON b.id = c.b_id) AS anon_2 " - "ON a_1.id = anon_2.b_a_id ORDER BY anon_2.b_id" - ) - - _a_bkeyassoc = ( - "SELECT a.id AS a_id, anon_1.b_key_id AS b_key_id " - "FROM a JOIN " - "(SELECT b_key.id AS b_key_id, a_to_b_key.aid AS a_to_b_key_aid, " - "a_to_b_key.bid AS a_to_b_key_bid FROM b_key " - "JOIN a_to_b_key ON b_key.id = a_to_b_key.bid) AS anon_1 " - "ON a.id = anon_1.a_to_b_key_aid" - ) - - _a_bkeyassoc_aliased = ( - "SELECT a.id AS a_id, anon_1.b_key_1_id AS b_key_1_id " - "FROM a JOIN (SELECT b_key_1.id AS b_key_1_id, " - "a_to_b_key_1.aid AS a_to_b_key_1_aid, " - "a_to_b_key_1.bid AS a_to_b_key_1_bid FROM b_key AS b_key_1 " - "JOIN a_to_b_key AS a_to_b_key_1 ON b_key_1.id = a_to_b_key_1.bid) AS " - "anon_1 ON a.id = anon_1.a_to_b_key_1_aid" - ) - - _a_bkeyselect_bkey = ( - "SELECT a.id AS a_id, anon_1.b_key_id AS b_key_id " - "FROM a JOIN (SELECT anon_2.aid AS anon_2_aid, " - "anon_2.bid AS anon_2_bid, " - "b_key.id AS b_key_id " - "FROM (SELECT a_to_b_key.aid AS aid, a_to_b_key.bid AS bid " - "FROM a_to_b_key) AS anon_2 " - "JOIN b_key ON b_key.id = anon_2.bid) AS anon_1 " - "ON a.id = anon_1.anon_2_aid" - ) - - _a_atobalias_balias_c_w_exists = ( - "SELECT a.id AS a_id, " - "anon_1.a_to_b_1_a_id AS a_to_b_1_a_id, " - "anon_1.a_to_b_1_b_id AS a_to_b_1_b_id, " - "anon_1.b_1_id AS b_1_id, anon_1.b_1_a_id AS b_1_a_id, " - "EXISTS (SELECT * FROM c WHERE c.b_id = anon_1.b_1_id) AS anon_2 " - "FROM a LEFT OUTER JOIN (SELECT a_to_b_1.a_id AS a_to_b_1_a_id, " - "a_to_b_1.b_id AS a_to_b_1_b_id, b_1.id AS b_1_id, " - "b_1.a_id AS b_1_a_id " - "FROM a_to_b AS a_to_b_1 " - "JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) AS anon_1 " - "ON a.id = anon_1.a_to_b_1_a_id" - ) - - _a_atobalias_balias = ( - "SELECT a.id AS a_id, anon_1.a_to_b_1_a_id AS a_to_b_1_a_id, " - "anon_1.a_to_b_1_b_id AS a_to_b_1_b_id, anon_1.b_1_id AS b_1_id, " - "anon_1.b_1_a_id AS b_1_a_id FROM a LEFT OUTER JOIN " - "(SELECT a_to_b_1.a_id AS a_to_b_1_a_id, " - "a_to_b_1.b_id AS a_to_b_1_b_id, " - "b_1.id AS b_1_id, b_1.a_id AS b_1_a_id FROM a_to_b AS a_to_b_1 " - "JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) AS anon_1 " - "ON a.id = anon_1.a_to_b_1_a_id" - ) - - _b_ab1_union_c_ab2 = ( - "SELECT anon_1.b_id AS anon_1_b_id, anon_1.b_a_id AS anon_1_b_a_id, " - "anon_1.a_id AS anon_1_a_id, anon_1.b1_id AS anon_1_b1_id, " - "anon_1.b1_a_id AS anon_1_b1_a_id FROM " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, anon_2.a_id AS a_id, " - "anon_2.b1_id AS b1_id, anon_2.b1_a_id AS b1_a_id " - "FROM b JOIN (SELECT a.id AS a_id, b1.id AS b1_id, b1.a_id AS b1_a_id " - "FROM a JOIN b1 ON a.id = b1.a_id) AS anon_2 ON anon_2.a_id = b.a_id " - "UNION " - "SELECT b.id AS b_id, b.a_id AS b_a_id, anon_3.a_id AS a_id, " - "anon_3.b2_id AS b2_id, anon_3.b2_a_id AS b2_a_id " - "FROM b JOIN (SELECT a.id AS a_id, b2.id AS b2_id, b2.a_id AS b2_a_id " - "FROM a JOIN b2 ON a.id = b2.a_id) AS anon_3 ON anon_3.a_id = b.a_id) " - "AS anon_1" - ) - - _b_a_id_double_overlap_annotated = ( - "SELECT anon_1.b_id AS anon_1_b_id, anon_1.b_a_id AS anon_1_b_a_id, " - "anon_1.b_a_id_1 AS anon_1_b_a_id_1 " - "FROM (SELECT b.id AS b_id, b.a_id AS b_a_id, b_a.id AS b_a_id_1 " - "FROM b JOIN b_a ON b.id = b_a.id) AS anon_1" - ) - - _f_b1a_where_in_b2a = ( - "SELECT f.id AS f_id, f.a_id AS f_a_id " - "FROM f JOIN (SELECT a.id AS a_id, b1.id AS b1_id, b1.a_id AS b1_a_id " - "FROM a JOIN b1 ON a.id = b1.a_id) AS anon_1 ON anon_1.a_id = f.a_id " - "WHERE anon_1.b1_id IN (SELECT b2.id " - "FROM a JOIN b2 ON a.id = b2.a_id)" - ) - - _anon_scalar_subqueries = ( - "SELECT (SELECT 1) AS anon_1, (SELECT 2) AS anon_2" - ) - - -class JoinPlainTest(_JoinRewriteTestBase, fixtures.TestBase): - - """test rendering of each join with normal nesting.""" - - @util.classproperty - def __dialect__(cls): - dialect = default.DefaultDialect() - return dialect - - _a_bkeyselect_bkey = ( - "SELECT a.id AS a_id, b_key.id AS b_key_id FROM a JOIN " - "((SELECT a_to_b_key.aid AS aid, a_to_b_key.bid AS bid " - "FROM a_to_b_key) AS anon_1 JOIN b_key ON b_key.id = anon_1.bid) " - "ON a.id = anon_1.aid" - ) - - _a__b_dc = ( - "SELECT a.id AS a_id, b.id AS b_id, " - "b.a_id AS b_a_id, c.id AS c_id, " - "c.b_id AS c_b_id, d.id AS d_id, " - "d.c_id AS d_c_id " - "FROM a JOIN (b JOIN (c JOIN d ON c.id = d.c_id) " - "ON b.id = c.b_id) ON a.id = b.a_id " - "WHERE b.id = :id_1 AND c.id = :id_2 AND " - "d.id = :id_3 " - "ORDER BY a.id, b.id, c.id, d.id" - ) - - _a_bc = ( - "SELECT a.id AS a_id, b.id AS b_id, " - "b.a_id AS b_a_id, c.id AS c_id, " - "c.b_id AS c_b_id FROM a JOIN " - "(b JOIN c ON b.id = c.b_id) " - "ON a.id = b.a_id " - "WHERE b.id = :id_1 AND c.id = :id_2 " - "ORDER BY a.id, b.id, c.id" - ) - - _a_bc_wdupes = ( - "SELECT a.id AS a_id, b.id AS b_id, b.a_id AS b_a_id, c.id AS c_id, " - "c.b_id AS c_b_id, b.a_id AS b_a_id__1, c.b_id AS c_b_id__1 " - "FROM a JOIN " - "(b JOIN c ON b.id = c.b_id) " - "ON a.id = b.a_id " - "WHERE b.id = :id_1 AND c.id = :id_2 " - "ORDER BY a.id, b.id, c.id" - ) - - _a_bc_wdupes_anon_map = ( - "SELECT 1 FROM (SELECT a.id AS a_id, b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, c.b_id AS c_b_id, b.a_id AS b_a_id__1, " - "c.b_id AS c_b_id__1 " - "FROM a JOIN (b JOIN c ON b.id = c.b_id) ON a.id = b.a_id " - "WHERE b.id = :id_1 AND c.id = :id_2) AS anon_1 " - "WHERE anon_1.b_a_id = anon_1.c_b_id" - ) - - _a_bc_comma_a1_selbc = ( - "SELECT a.id AS a_id, a_1.id AS a_1_id, b.id AS b_id, " - "b.a_id AS b_a_id, c.id AS c_id, " - "c.b_id AS c_b_id, anon_1.b_id AS anon_1_b_id, " - "anon_1.b_a_id AS anon_1_b_a_id, anon_1.c_id AS anon_1_c_id, " - "anon_1.c_b_id AS anon_1_c_b_id FROM a " - "JOIN (b JOIN c ON b.id = c.b_id) " - "ON a.id = b.a_id, " - "a AS a_1 JOIN " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, c.b_id AS c_b_id " - "FROM b JOIN c ON b.id = c.b_id) AS anon_1 " - "ON a_1.id = anon_1.b_a_id ORDER BY anon_1.b_id" - ) - - _a_bkeyassoc = ( - "SELECT a.id AS a_id, b_key.id AS b_key_id " - "FROM a JOIN " - "(b_key JOIN a_to_b_key ON b_key.id = a_to_b_key.bid) " - "ON a.id = a_to_b_key.aid" - ) - - _a_bkeyassoc_aliased = ( - "SELECT a.id AS a_id, b_key_1.id AS b_key_1_id FROM a " - "JOIN (b_key AS b_key_1 JOIN a_to_b_key AS a_to_b_key_1 " - "ON b_key_1.id = a_to_b_key_1.bid) ON a.id = a_to_b_key_1.aid" - ) - - _a_atobalias_balias_c_w_exists = ( - "SELECT a.id AS a_id, a_to_b_1.a_id AS a_to_b_1_a_id, " - "a_to_b_1.b_id AS a_to_b_1_b_id, b_1.id AS b_1_id, " - "b_1.a_id AS b_1_a_id, " - "EXISTS (SELECT * FROM c WHERE c.b_id = b_1.id) AS anon_1 " - "FROM a LEFT OUTER JOIN " - "(a_to_b AS a_to_b_1 JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) " - "ON a.id = a_to_b_1.a_id" - ) - - _a_atobalias_balias = ( - "SELECT a.id AS a_id, a_to_b_1.a_id AS a_to_b_1_a_id, " - "a_to_b_1.b_id AS a_to_b_1_b_id, b_1.id AS b_1_id, " - "b_1.a_id AS b_1_a_id " - "FROM a LEFT OUTER JOIN (a_to_b AS a_to_b_1 " - "JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) ON a.id = a_to_b_1.a_id" - ) - - _b_ab1_union_c_ab2 = ( - "SELECT anon_1.b_id AS anon_1_b_id, anon_1.b_a_id AS anon_1_b_a_id, " - "anon_1.a_id AS anon_1_a_id, anon_1.b1_id AS anon_1_b1_id, " - "anon_1.b1_a_id AS anon_1_b1_a_id FROM " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, a.id AS a_id, " - "b1.id AS b1_id, " - "b1.a_id AS b1_a_id FROM b " - "JOIN (a JOIN b1 ON a.id = b1.a_id) ON a.id = b.a_id " - "UNION " - "SELECT b.id AS b_id, b.a_id AS b_a_id, a.id AS a_id, b2.id AS b2_id, " - "b2.a_id AS b2_a_id FROM b " - "JOIN (a JOIN b2 ON a.id = b2.a_id) ON a.id = b.a_id) AS anon_1" - ) - - _b_a_id_double_overlap_annotated = ( - "SELECT anon_1.b_id AS anon_1_b_id, anon_1.b_a_id AS anon_1_b_a_id, " - "anon_1.b_a_id_1 AS anon_1_b_a_id_1 FROM " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, b_a.id AS b_a_id_1 " - "FROM b JOIN b_a ON b.id = b_a.id) AS anon_1" - ) - - _f_b1a_where_in_b2a = ( - "SELECT f.id AS f_id, f.a_id AS f_a_id " - "FROM f JOIN (a JOIN b1 ON a.id = b1.a_id) ON a.id = f.a_id " - "WHERE b1.id IN (SELECT b2.id " - "FROM a JOIN b2 ON a.id = b2.a_id)" - ) - - _anon_scalar_subqueries = ( - "SELECT (SELECT 1) AS anon_1, (SELECT 2) AS anon_2" - ) - - -class JoinNoUseLabelsTest(_JoinRewriteTestBase, fixtures.TestBase): - @util.classproperty - def __dialect__(cls): - dialect = default.DefaultDialect() - dialect.supports_right_nested_joins = False - return dialect - - def _test(self, s, assert_): - s.use_labels = False - self.assert_compile(s, assert_) - - _a_bkeyselect_bkey = ( - "SELECT a.id, b_key.id FROM a JOIN ((SELECT a_to_b_key.aid AS aid, " - "a_to_b_key.bid AS bid FROM a_to_b_key) AS anon_1 " - "JOIN b_key ON b_key.id = anon_1.bid) ON a.id = anon_1.aid" - ) - - _a__b_dc = ( - "SELECT a.id, b.id, " - "b.a_id, c.id, " - "c.b_id, d.id, " - "d.c_id " - "FROM a JOIN (b JOIN (c JOIN d ON c.id = d.c_id) " - "ON b.id = c.b_id) ON a.id = b.a_id " - "WHERE b.id = :id_1 AND c.id = :id_2 AND " - "d.id = :id_3 " - "ORDER BY a.id, b.id, c.id, d.id" - ) - - _a_bc = ( - "SELECT a.id, b.id, " - "b.a_id, c.id, " - "c.b_id FROM a JOIN " - "(b JOIN c ON b.id = c.b_id) " - "ON a.id = b.a_id " - "WHERE b.id = :id_1 AND c.id = :id_2 " - "ORDER BY a.id, b.id, c.id" - ) - - _a_bc_wdupes = ( - "SELECT a.id, b.id, b.a_id, c.id, c.b_id, b.a_id, c.b_id " - "FROM a JOIN " - "(b JOIN c ON b.id = c.b_id) " - "ON a.id = b.a_id " - "WHERE b.id = :id_1 AND c.id = :id_2 " - "ORDER BY a.id, b.id, c.id" - ) - - _a_bc_wdupes_anon_map = ( - "SELECT 1 FROM (SELECT a.id AS a_id, b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, c.b_id AS c_b_id, b.a_id AS b_a_id__1, " - "c.b_id AS c_b_id__1 " - "FROM a JOIN (b JOIN c ON b.id = c.b_id) ON a.id = b.a_id " - "WHERE b.id = :id_1 AND c.id = :id_2) AS anon_1 " - "WHERE anon_1.b_a_id = anon_1.c_b_id" - ) - - _a_bc_comma_a1_selbc = ( - "SELECT a.id, a_1.id, b.id, " - "b.a_id, c.id, " - "c.b_id, anon_1.b_id, " - "anon_1.b_a_id, anon_1.c_id, " - "anon_1.c_b_id FROM a " - "JOIN (b JOIN c ON b.id = c.b_id) " - "ON a.id = b.a_id, " - "a AS a_1 JOIN " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, c.b_id AS c_b_id " - "FROM b JOIN c ON b.id = c.b_id) AS anon_1 " - "ON a_1.id = anon_1.b_a_id ORDER BY anon_1.b_id" - ) - - _a_bkeyassoc = ( - "SELECT a.id, b_key.id FROM a JOIN (b_key JOIN a_to_b_key " - "ON b_key.id = a_to_b_key.bid) ON a.id = a_to_b_key.aid" - ) - - _a_bkeyassoc_aliased = ( - "SELECT a.id, b_key_1.id FROM a JOIN (b_key AS b_key_1 " - "JOIN a_to_b_key AS a_to_b_key_1 ON b_key_1.id = a_to_b_key_1.bid) " - "ON a.id = a_to_b_key_1.aid" - ) - - _a_atobalias_balias_c_w_exists = ( - "SELECT a.id, a_to_b_1.a_id, a_to_b_1.b_id, b_1.id, b_1.a_id, " - "EXISTS (SELECT * FROM c WHERE c.b_id = b_1.id) AS anon_1 " - "FROM a LEFT OUTER JOIN " - "(a_to_b AS a_to_b_1 JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) " - "ON a.id = a_to_b_1.a_id" - ) - - _a_atobalias_balias = ( - "SELECT a.id, a_to_b_1.a_id, a_to_b_1.b_id, b_1.id, b_1.a_id " - "FROM a LEFT OUTER JOIN (a_to_b AS a_to_b_1 " - "JOIN b AS b_1 ON b_1.id = a_to_b_1.b_id) ON a.id = a_to_b_1.a_id" - ) - - _b_ab1_union_c_ab2 = ( - "SELECT anon_1.b_id, anon_1.b_a_id, anon_1.a_id, anon_1.b1_id, " - "anon_1.b1_a_id " - "FROM (SELECT b.id AS b_id, b.a_id AS b_a_id, a.id AS a_id, " - "b1.id AS b1_id, b1.a_id AS b1_a_id " - "FROM b JOIN (a JOIN b1 ON a.id = b1.a_id) ON a.id = b.a_id " - "UNION " - "SELECT b.id AS b_id, b.a_id AS b_a_id, a.id AS a_id, b2.id AS b2_id, " - "b2.a_id AS b2_a_id " - "FROM b JOIN (a JOIN b2 ON a.id = b2.a_id) ON a.id = b.a_id) AS anon_1" - ) - - _b_a_id_double_overlap_annotated = ( - "SELECT anon_1.b_id, anon_1.b_a_id, anon_1.b_a_id_1 FROM " - "(SELECT b.id AS b_id, b.a_id AS b_a_id, b_a.id AS b_a_id_1 " - "FROM b JOIN b_a ON b.id = b_a.id) AS anon_1" - ) - - _f_b1a_where_in_b2a = ( - "SELECT f.id, f.a_id " - "FROM f JOIN (a JOIN b1 ON a.id = b1.a_id) ON a.id = f.a_id " - "WHERE b1.id IN (SELECT b2.id " - "FROM a JOIN b2 ON a.id = b2.a_id)" - ) - - _anon_scalar_subqueries = ( - "SELECT (SELECT 1) AS anon_1, (SELECT 2) AS anon_2" - ) - - -class JoinExecTest(_JoinRewriteTestBase, fixtures.TestBase): - - """invoke the SQL on the current backend to ensure compatibility""" - - __backend__ = True - - _a_bc = ( - _a_bc_wdupes - ) = ( - _a_bc_wdupes_anon_map - ) = ( - _a_bc_comma_a1_selbc - ) = ( - _a__b_dc - ) = ( - _a_bkeyassoc - ) = ( - _a_bkeyassoc_aliased - ) = ( - _a_atobalias_balias_c_w_exists - ) = ( - _a_atobalias_balias - ) = ( - _b_ab1_union_c_ab2 - ) = ( - _b_a_id_double_overlap_annotated - ) = _f_b1a_where_in_b2a = _anon_scalar_subqueries = None - - @classmethod - def setup_class(cls): - m.create_all(testing.db) - - @classmethod - def teardown_class(cls): - m.drop_all(testing.db) - - def _test(self, selectable, assert_): - result = testing.db.execute(selectable) - result.close() - for col in selectable.inner_columns: - assert col in result._metadata._keymap - - @testing.skip_if("oracle", "oracle's cranky") - @testing.skip_if( - "mssql", "can't query EXISTS in the columns " "clause w/o subquery" - ) - def test_a_atobalias_balias_c_w_exists(self): - super(JoinExecTest, self).test_a_atobalias_balias_c_w_exists() - - @testing.only_on( - "sqlite", - "non-standard aliasing rules used at the moment, " - "possibly fix this or add another test that uses " - "cross-compatible aliasing", - ) - def test_b_ab1_union_b_ab2(self): - super(JoinExecTest, self).test_b_ab1_union_b_ab2() - - -class DialectFlagTest(fixtures.TestBase, AssertsCompiledSQL): - def test_dialect_flag(self): - d1 = default.DefaultDialect(supports_right_nested_joins=True) - d2 = default.DefaultDialect(supports_right_nested_joins=False) - - j1 = b.join(c) - j2 = a.join(j1) - - s = select([a, b, c], use_labels=True).select_from(j2) - - self.assert_compile( - s, - "SELECT a.id AS a_id, b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, " - "c.b_id AS c_b_id FROM a JOIN (b JOIN c ON b.id = c.b_id) " - "ON a.id = b.a_id", - dialect=d1, - ) - self.assert_compile( - s, - "SELECT a.id AS a_id, anon_1.b_id AS b_id, " - "anon_1.b_a_id AS b_a_id, " - "anon_1.c_id AS c_id, anon_1.c_b_id AS c_b_id " - "FROM a JOIN (SELECT b.id AS b_id, b.a_id AS b_a_id, " - "c.id AS c_id, " - "c.b_id AS c_b_id FROM b JOIN c ON b.id = c.b_id) AS anon_1 " - "ON a.id = anon_1.b_a_id", - dialect=d2, - )