diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-06-04 21:38:56 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-06-04 21:38:56 -0400 |
commit | 26ec0507be72d2e1a5abde8b7307012864a88a6b (patch) | |
tree | b83e68a0ee000059ff176d29f68706fb0a6da830 | |
parent | ada19275299f0105f4aaed5bbe0d373ea33feea6 (diff) | |
parent | 69e9574fefd5fbb4673c99ad476a00b03fe22318 (diff) | |
download | sqlalchemy-26ec0507be72d2e1a5abde8b7307012864a88a6b.tar.gz |
Merge branch 'ticket_2587'
Conflicts:
test/profiles.txt
test/sql/test_selectable.py
-rw-r--r-- | lib/sqlalchemy/dialects/sqlite/base.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/default.py | 5 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 44 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/util.py | 5 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 129 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 28 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/util.py | 33 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/visitors.py | 11 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/assertions.py | 15 | ||||
-rw-r--r-- | test/orm/inheritance/test_polymorphic_rel.py | 25 | ||||
-rw-r--r-- | test/orm/inheritance/test_relationship.py | 107 | ||||
-rw-r--r-- | test/orm/test_eager_relations.py | 32 | ||||
-rw-r--r-- | test/orm/test_joins.py | 111 | ||||
-rw-r--r-- | test/orm/test_mapper.py | 24 | ||||
-rw-r--r-- | test/orm/test_of_type.py | 60 | ||||
-rw-r--r-- | test/orm/test_subquery_relations.py | 23 | ||||
-rw-r--r-- | test/profiles.txt | 35 | ||||
-rw-r--r-- | test/sql/test_join_rewriting.py | 292 | ||||
-rw-r--r-- | test/sql/test_selectable.py | 34 |
20 files changed, 723 insertions, 299 deletions
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index 1ca8f4e64..c7e09b164 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -592,6 +592,7 @@ class SQLiteDialect(default.DefaultDialect): supports_empty_insert = False supports_cast = True supports_multivalues_insert = True + supports_right_nested_joins = False default_paramstyle = 'qmark' execution_ctx_cls = SQLiteExecutionContext diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 91869ab75..2ad7002c4 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -49,6 +49,8 @@ class DefaultDialect(interfaces.Dialect): postfetch_lastrowid = True implicit_returning = False + supports_right_nested_joins = True + supports_native_enum = False supports_native_boolean = False @@ -106,6 +108,7 @@ class DefaultDialect(interfaces.Dialect): def __init__(self, convert_unicode=False, encoding='utf-8', paramstyle=None, dbapi=None, implicit_returning=None, + supports_right_nested_joins=None, case_sensitive=True, label_length=None, **kwargs): @@ -130,6 +133,8 @@ 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 self.case_sensitive = case_sensitive diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index beae7aba0..39ed8d8bf 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -447,6 +447,8 @@ class Query(object): statement if self._params: stmt = stmt.params(self._params) + + # TODO: there's no tests covering effects of # the annotation not being there return stmt._annotate({'no_replacement_traverse': True}) @@ -1795,6 +1797,7 @@ class Query(object): right_entity, onclause, outerjoin, create_aliases, prop) + def _join_left_to_right(self, left, right, onclause, outerjoin, create_aliases, prop): """append a JOIN to the query's from clause.""" @@ -1814,10 +1817,21 @@ class Query(object): "are the same entity" % (left, right)) + l_info = inspect(left) + r_info = inspect(right) + + overlap = not create_aliases and \ + sql_util.selectables_overlap(l_info.selectable, + r_info.selectable) + if overlap and l_info.selectable is r_info.selectable: + raise sa_exc.InvalidRequestError( + "Can't join table/selectable '%s' to itself" % + l_info.selectable) + right, onclause = self._prepare_right_side( - right, onclause, + r_info, right, onclause, create_aliases, - prop) + prop, overlap) # if joining on a MapperProperty path, # track the path to prevent redundant joins @@ -1829,10 +1843,11 @@ class Query(object): else: self._joinpoint = {'_joinpoint_entity': right} - self._join_to_left(left, right, onclause, outerjoin) + self._join_to_left(l_info, left, right, onclause, outerjoin) - def _prepare_right_side(self, right, onclause, create_aliases, prop): - info = inspect(right) + def _prepare_right_side(self, r_info, right, onclause, create_aliases, + prop, overlap): + info = r_info right_mapper, right_selectable, right_is_aliased = \ getattr(info, 'mapper', None), \ @@ -1862,19 +1877,23 @@ class Query(object): (right_selectable.description, right_mapper.mapped_table.description)) - if not isinstance(right_selectable, expression.Alias): + if isinstance(right_selectable, expression.SelectBase): + # TODO: this isn't even covered now! right_selectable = right_selectable.alias() + need_adapter = True right = aliased(right_mapper, right_selectable) - need_adapter = True aliased_entity = right_mapper and \ not right_is_aliased and \ ( - right_mapper.with_polymorphic or isinstance( - right_mapper.mapped_table, - expression.Join) + right_mapper._with_polymorphic_selectable, + expression.Alias) + or + overlap # test for overlap: + # orm/inheritance/relationships.py + # SelfReferentialM2MTest ) if not need_adapter and (create_aliases or aliased_entity): @@ -1910,8 +1929,8 @@ class Query(object): return right, onclause - def _join_to_left(self, left, right, onclause, outerjoin): - info = inspect(left) + def _join_to_left(self, l_info, left, right, onclause, outerjoin): + info = l_info left_mapper = getattr(info, 'mapper', None) left_selectable = info.selectable @@ -1946,7 +1965,6 @@ class Query(object): clause = left_selectable assert clause is not None - try: clause = orm_join(clause, right, onclause, isouter=outerjoin) except sa_exc.ArgumentError as ae: diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 95fa28613..33377d3ec 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -17,7 +17,7 @@ from .. import sql, util, exc as sa_exc, schema from ..sql.util import ( ClauseAdapter, join_condition, _shallow_annotate, visit_binary_product, - _deep_deannotate, find_tables + _deep_deannotate, find_tables, selectables_overlap ) from ..sql import operators, expression, visitors from .interfaces import MANYTOMANY, MANYTOONE, ONETOMANY @@ -404,11 +404,7 @@ class JoinCondition(object): def _tables_overlap(self): """Return True if parent/child tables have some overlap.""" - return bool( - set(find_tables(self.parent_selectable)).intersection( - find_tables(self.child_selectable) - ) - ) + return selectables_overlap(self.parent_selectable, self.child_selectable) def _annotate_remote(self): """Annotate the primaryjoin and secondaryjoin diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index bd8228f2c..c21e7eace 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -493,6 +493,7 @@ class AliasedClass(object): """ def __init__(self, cls, alias=None, name=None, + flat=True, adapt_on_names=False, # TODO: None for default here? with_polymorphic_mappers=(), @@ -501,7 +502,7 @@ class AliasedClass(object): use_mapper_path=False): mapper = _class_to_mapper(cls) if alias is None: - alias = mapper._with_polymorphic_selectable.alias(name=name) + alias = mapper._with_polymorphic_selectable.alias(name=name, flat=flat) self._aliased_insp = AliasedInsp( self, mapper, @@ -837,7 +838,7 @@ def with_polymorphic(base, classes, selectable=False, _with_polymorphic_args(classes, selectable, innerjoin=innerjoin) if aliased: - selectable = selectable.alias() + selectable = selectable.alias(flat=True) return AliasedClass(base, selectable, with_polymorphic_mappers=mappers, diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 73b094053..dd2a6e08c 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1113,23 +1113,115 @@ class SQLCompiler(engine.Compiled): def get_crud_hint_text(self, table, text): return None + 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 the main one). + + """ + cloned = {} + column_translate = [{}] + + # TODO: should we be using isinstance() for this, + # as this whole system won't work for custom Join/Select + # subclasses where compilation routines + # call down to compiler.visit_join(), compiler.visit_select() + join_name = sql.Join.__visit_name__ + select_name = sql.Select.__visit_name__ + + 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.__visit_name__ is join_name and \ + isinstance(newelem.right, sql.FromGrouping): + + newelem._reset_exported() + newelem.left = visit(newelem.left, **kw) + + right = visit(newelem.right, **kw) + + selectable = sql.select( + [right.element], + use_labels=True).alias() + + for c in selectable.c: + c._label = c._key_label = c.name + translate_dict = dict( + zip(right.element.c, selectable.c) + ) + translate_dict[right.element.left] = selectable + translate_dict[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.__visit_name__ is select_name: + column_translate.append({}) + newelem._copy_internals(clone=visit, **kw) + 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): + inner_col = dict((c._key_label, c) for + c in transformed_select.inner_columns) + d = dict( + (inner_col[c._key_label], c) + for c in select.inner_columns + ) + for key, (name, objs, typ) in list(self.result_map.items()): + objs = tuple([d.get(col, col) for col in objs]) + self.result_map[key] = (name, objs, typ) + def visit_select(self, select, asfrom=False, parens=True, iswrapper=False, fromhints=None, compound_index=0, force_result_map=False, - positional_names=None, **kwargs): - entry = self.stack and self.stack[-1] or {} - - existingfroms = entry.get('from', None) - - froms = select._get_display_froms(existingfroms, asfrom=asfrom) - - correlate_froms = set(sql._from_objects(*froms)) + positional_names=None, + nested_join_translation=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, parens=parens, + iswrapper=iswrapper, fromhints=fromhints, + compound_index=compound_index, + force_result_map=force_result_map, + positional_names=positional_names, + nested_join_translation=True, **kwargs + ) - # TODO: might want to propagate existing froms for - # select(select(select)) where innermost select should correlate - # to outermost if existingfroms: correlate_froms = - # correlate_froms.union(existingfroms) + entry = self.stack and self.stack[-1] or {} populate_result_map = force_result_map or ( compound_index == 0 and ( @@ -1138,6 +1230,19 @@ class SQLCompiler(engine.Compiled): ) ) + if needs_nested_translation: + if populate_result_map: + self._transform_result_map_for_nested_joins( + select, transformed_select) + return text + + existingfroms = entry.get('from', None) + + froms = select._get_display_froms(existingfroms, asfrom=asfrom) + + correlate_froms = set(sql._from_objects(*froms)) + + self.stack.append({'from': correlate_froms, 'iswrapper': iswrapper}) diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 6dc134d98..f0c6134e5 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -795,7 +795,7 @@ def intersect_all(*selects, **kwargs): return CompoundSelect(CompoundSelect.INTERSECT_ALL, *selects, **kwargs) -def alias(selectable, name=None): +def alias(selectable, name=None, flat=False): """Return an :class:`.Alias` object. An :class:`.Alias` represents any :class:`.FromClause` @@ -2636,7 +2636,7 @@ class FromClause(Selectable): return Join(self, right, onclause, True) - def alias(self, name=None): + def alias(self, name=None, flat=False): """return an alias of this :class:`.FromClause`. This is shorthand for calling:: @@ -3980,7 +3980,7 @@ class Join(FromClause): def bind(self): return self.left.bind or self.right.bind - def alias(self, name=None): + def alias(self, name=None, flat=False): """return an alias of this :class:`.Join`. Used against a :class:`.Join` object, @@ -4008,7 +4008,17 @@ class Join(FromClause): aliases. """ - return self.select(use_labels=True, correlate=False).alias(name) + if flat: + assert name is None, "Can't send name argument with flat" + left_a, right_a = self.left.alias(flat=True), \ + self.right.alias(flat=True) + adapter = sqlutil.ClauseAdapter(left_a).\ + chain(sqlutil.ClauseAdapter(right_a)) + + return left_a.join(right_a, + adapter.traverse(self.onclause), isouter=self.isouter) + else: + return self.select(use_labels=True, correlate=False).alias(name) @property def _hide_froms(self): @@ -4138,7 +4148,7 @@ class CTE(Alias): self._restates = _restates super(CTE, self).__init__(selectable, name=name) - def alias(self, name=None): + def alias(self, name=None, flat=False): return CTE( self.original, name=name, @@ -4221,10 +4231,10 @@ class FromGrouping(FromClause): @property def foreign_keys(self): - # this could be - # self.element.foreign_keys - # see SelectableTest.test_join_condition - return set() + return self.element.foreign_keys + + def is_derived_from(self, element): + return self.element.is_derived_from(element) @property def _hide_froms(self): diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 91740dc16..6f4d27e1b 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -200,15 +200,28 @@ def clause_is_present(clause, search): """ - stack = [search] - while stack: - elem = stack.pop() + for elem in surface_selectables(search): if clause == elem: # use == here so that Annotated's compare return True - elif isinstance(elem, expression.Join): + else: + return False + +def surface_selectables(clause): + stack = [clause] + while stack: + elem = stack.pop() + yield elem + if isinstance(elem, expression.Join): stack.extend((elem.left, elem.right)) - return False +def selectables_overlap(left, right): + """Return True if left/right have some overlapping selectable""" + + return bool( + set(surface_selectables(left)).intersection( + surface_selectables(right) + ) + ) def bind_values(clause): """Return an ordered list of "bound" values in the given clause. @@ -797,8 +810,11 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor): def __init__(self, selectable, equivalents=None, include=None, exclude=None, include_fn=None, exclude_fn=None, - adapt_on_names=False): + adapt_on_names=False, + traverse_options=None): self.__traverse_options__ = {'stop_on': [selectable]} + if traverse_options: + self.__traverse_options__.update(traverse_options) self.selectable = selectable if include: assert not include_fn @@ -829,10 +845,11 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor): newcol = self.selectable.c.get(col.name) return newcol + magic_flag = False def replace(self, col): - if isinstance(col, expression.FromClause) and \ + if not self.magic_flag and isinstance(col, expression.FromClause) and \ self.selectable.is_derived_from(col): - return self.selectable + return self.selectable elif not isinstance(col, expression.ColumnElement): return None elif self.include_fn and not self.include_fn(col): diff --git a/lib/sqlalchemy/sql/visitors.py b/lib/sqlalchemy/sql/visitors.py index 62f46ab64..c5a45ffd4 100644 --- a/lib/sqlalchemy/sql/visitors.py +++ b/lib/sqlalchemy/sql/visitors.py @@ -30,6 +30,7 @@ import operator __all__ = ['VisitableType', 'Visitable', 'ClauseVisitor', 'CloningVisitor', 'ReplacingCloningVisitor', 'iterate', 'iterate_depthfirst', 'traverse_using', 'traverse', + 'traverse_depthfirst', 'cloned_traverse', 'replacement_traverse'] @@ -255,7 +256,11 @@ def cloned_traverse(obj, opts, visitors): """clone the given expression structure, allowing modifications by visitors.""" - cloned = util.column_dict() + + if "cloned" in opts: + cloned = opts['cloned'] + else: + cloned = util.column_dict() stop_on = util.column_set(opts.get('stop_on', [])) def clone(elem): @@ -281,10 +286,12 @@ def replacement_traverse(obj, opts, replace): cloned = util.column_dict() stop_on = util.column_set([id(x) for x in opts.get('stop_on', [])]) + unconditional = opts.get('unconditional', False) def clone(elem, **kw): if id(elem) in stop_on or \ - 'no_replacement_traverse' in elem._annotations: + (not unconditional + and 'no_replacement_traverse' in elem._annotations): return elem else: newelem = replace(elem) diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py index c04153961..96a8bc023 100644 --- a/lib/sqlalchemy/testing/assertions.py +++ b/lib/sqlalchemy/testing/assertions.py @@ -184,15 +184,20 @@ class AssertsCompiledSQL(object): allow_dialect_select=False): if use_default_dialect: dialect = default.DefaultDialect() - elif dialect == None and not allow_dialect_select: - dialect = getattr(self, '__dialect__', None) - if dialect == 'default': - dialect = default.DefaultDialect() - elif dialect is None: + elif allow_dialect_select: + dialect = None + else: + if dialect is None: + dialect = getattr(self, '__dialect__', None) + + if dialect is None: dialect = config.db.dialect + elif dialect == 'default': + dialect = default.DefaultDialect() elif isinstance(dialect, util.string_types): dialect = create_engine("%s://" % dialect).dialect + kw = {} if params is not None: kw['column_keys'] = list(params) diff --git a/test/orm/inheritance/test_polymorphic_rel.py b/test/orm/inheritance/test_polymorphic_rel.py index 8c1f22114..eecfb036b 100644 --- a/test/orm/inheritance/test_polymorphic_rel.py +++ b/test/orm/inheritance/test_polymorphic_rel.py @@ -37,6 +37,7 @@ class _PolymorphicTestBase(object): e1, e2, e3, b1, m1 = \ cls.e1, cls.e2, cls.e3, cls.b1, cls.m1 + def test_loads_at_once(self): """ Test that all objects load from the full query, when @@ -672,7 +673,17 @@ class _PolymorphicTestBase(object): expected) self.assert_sql_count(testing.db, go, 1) + def test_subqueryload_on_subclass(self): sess = create_session() + expected = [ + Engineer( + name="dilbert", + engineer_name="dilbert", + primary_language="java", + status="regular engineer", + machines=[ + Machine(name="IBM ThinkPad"), + Machine(name="IPhone")])] def go(): # test load People with subqueryload to engineers + machines eq_(sess.query(Person) @@ -726,6 +737,14 @@ class _PolymorphicTestBase(object): .join(Engineer.machines).all(), [c1, c2]) + def test_join_to_subclass_six_point_five(self): + sess = create_session() + eq_(sess.query(Company) + .join(people.join(engineers), 'employees') + .join(Engineer.machines) + .filter(Engineer.name == 'dilbert').all(), + [c1]) + def test_join_to_subclass_seven(self): sess = create_session() eq_(sess.query(Company) @@ -898,7 +917,8 @@ class _PolymorphicTestBase(object): .filter(Paperwork.description.like('%#%')).all(), [c1, c2]) - def test_explicit_polymorphic_join(self): + + def test_explicit_polymorphic_join_one(self): sess = create_session() # join from Company to Engineer; join condition formulated by @@ -910,6 +930,9 @@ class _PolymorphicTestBase(object): .filter(Engineer.engineer_name == 'vlad').one(), c2) + def test_explicit_polymorphic_join_two(self): + sess = create_session() + # same, using explicit join condition. Query.join() must # adapt the on clause here to match the subquery wrapped around # "people join engineers". diff --git a/test/orm/inheritance/test_relationship.py b/test/orm/inheritance/test_relationship.py index 809884f52..3f1eb849f 100644 --- a/test/orm/inheritance/test_relationship.py +++ b/test/orm/inheritance/test_relationship.py @@ -546,11 +546,10 @@ class SelfReferentialM2MTest(fixtures.MappedTest, AssertsCompiledSQL): "SELECT child2.id AS child2_id, parent.id AS parent_id, " "parent.cls AS parent_cls FROM secondary AS secondary_1, " "parent JOIN child2 ON parent.id = child2.id JOIN secondary AS " - "secondary_2 ON parent.id = secondary_2.left_id JOIN (SELECT " - "parent.id AS parent_id, parent.cls AS parent_cls, child1.id AS " - "child1_id FROM parent JOIN child1 ON parent.id = child1.id) AS " - "anon_1 ON anon_1.parent_id = secondary_2.right_id WHERE " - "anon_1.parent_id = secondary_1.right_id AND :param_1 = " + "secondary_2 ON parent.id = secondary_2.left_id JOIN " + "(parent AS parent_1 JOIN child1 AS child1_1 ON parent_1.id = child1_1.id) " + "ON parent_1.id = secondary_2.right_id WHERE " + "parent_1.id = secondary_1.right_id AND :param_1 = " "secondary_1.left_id", dialect=default.DefaultDialect() ) @@ -569,15 +568,14 @@ class SelfReferentialM2MTest(fixtures.MappedTest, AssertsCompiledSQL): self.assert_compile(q.limit(1).with_labels().statement, "SELECT anon_1.child1_id AS anon_1_child1_id, anon_1.parent_id " "AS anon_1_parent_id, anon_1.parent_cls AS anon_1_parent_cls, " - "anon_2.child2_id AS anon_2_child2_id, anon_2.parent_id AS " - "anon_2_parent_id, anon_2.parent_cls AS anon_2_parent_cls FROM " + "child2_1.id AS child2_1_id, parent_1.id AS " + "parent_1_id, parent_1.cls AS parent_1_cls FROM " "(SELECT child1.id AS child1_id, parent.id AS parent_id, " "parent.cls AS parent_cls FROM parent JOIN child1 ON parent.id = " "child1.id LIMIT :param_1) AS anon_1 LEFT OUTER JOIN secondary " "AS secondary_1 ON anon_1.parent_id = secondary_1.right_id LEFT " - "OUTER JOIN (SELECT parent.id AS parent_id, parent.cls AS " - "parent_cls, child2.id AS child2_id FROM parent JOIN child2 ON " - "parent.id = child2.id) AS anon_2 ON anon_2.parent_id = " + "OUTER JOIN (parent AS parent_1 JOIN child2 AS child2_1 ON " + "parent_1.id = child2_1.id) ON parent_1.id = " "secondary_1.left_id", {'param_1':1}, dialect=default.DefaultDialect()) @@ -1224,18 +1222,13 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): join(Sub2.ep1). join(Sub2.ep2), "SELECT parent.id AS parent_id, parent.data AS parent_data " - "FROM parent JOIN (SELECT base1.id AS base1_id, " - "base1.data AS base1_data, sub1.id AS sub1_id, " - "sub1.parent_id AS sub1_parent_id, sub1.subdata AS sub1_subdata " - "FROM base1 JOIN sub1 ON base1.id = sub1.id) AS anon_1 " - "ON parent.id = anon_1.sub1_parent_id JOIN " - "(SELECT base2.id AS base2_id, base2.base1_id AS base2_base1_id, " - "base2.data AS base2_data, sub2.id AS sub2_id, " - "sub2.subdata AS sub2_subdata FROM base2 JOIN sub2 " - "ON base2.id = sub2.id) AS anon_2 " - "ON anon_1.base1_id = anon_2.base2_base1_id " - "JOIN ep1 ON anon_2.base2_id = ep1.base2_id " - "JOIN ep2 ON anon_2.base2_id = ep2.base2_id" + "FROM parent JOIN (base1 JOIN sub1 ON base1.id = sub1.id) " + "ON parent.id = sub1.parent_id JOIN " + "(base2 JOIN sub2 " + "ON base2.id = sub2.id) " + "ON base1.id = base2.base1_id " + "JOIN ep1 ON base2.id = ep1.base2_id " + "JOIN ep2 ON base2.id = ep2.base2_id" ) def test_two(self): @@ -1248,16 +1241,11 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): s.query(Parent).join(Parent.sub1). join(s2a, Sub1.sub2), "SELECT parent.id AS parent_id, parent.data AS parent_data " - "FROM parent JOIN (SELECT base1.id AS base1_id, " - "base1.data AS base1_data, sub1.id AS sub1_id, " - "sub1.parent_id AS sub1_parent_id, sub1.subdata AS sub1_subdata " - "FROM base1 JOIN sub1 ON base1.id = sub1.id) AS anon_1 " - "ON parent.id = anon_1.sub1_parent_id JOIN " - "(SELECT base2.id AS base2_id, base2.base1_id AS base2_base1_id, " - "base2.data AS base2_data, sub2.id AS sub2_id, " - "sub2.subdata AS sub2_subdata FROM base2 JOIN sub2 " - "ON base2.id = sub2.id) AS anon_2 " - "ON anon_1.base1_id = anon_2.base2_base1_id" + "FROM parent JOIN (base1 JOIN sub1 ON base1.id = sub1.id) " + "ON parent.id = sub1.parent_id JOIN " + "(base2 AS base2_1 JOIN sub2 AS sub2_1 " + "ON base2_1.id = sub2_1.id) " + "ON base1.id = base2_1.base1_id" ) def test_three(self): @@ -1269,13 +1257,11 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): join(Sub2.ep1).\ join(Sub2.ep2), "SELECT base1.id AS base1_id, base1.data AS base1_data " - "FROM base1 JOIN (SELECT base2.id AS base2_id, base2.base1_id " - "AS base2_base1_id, base2.data AS base2_data, sub2.id AS sub2_id, " - "sub2.subdata AS sub2_subdata FROM base2 JOIN sub2 " - "ON base2.id = sub2.id) AS anon_1 ON base1.id = " - "anon_1.base2_base1_id " - "JOIN ep1 ON anon_1.base2_id = ep1.base2_id " - "JOIN ep2 ON anon_1.base2_id = ep2.base2_id" + "FROM base1 JOIN (base2 JOIN sub2 " + "ON base2.id = sub2.id) ON base1.id = " + "base2.base1_id " + "JOIN ep1 ON base2.id = ep1.base2_id " + "JOIN ep2 ON base2.id = ep2.base2_id" ) def test_four(self): @@ -1308,11 +1294,8 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): "sub2.subdata AS sub2_subdata " "FROM base2 JOIN sub2 ON base2.id = sub2.id " "JOIN " - "(SELECT base1.id AS base1_id, base1.data AS base1_data, " - "sub1.id AS sub1_id, sub1.parent_id AS sub1_parent_id, " - "sub1.subdata AS sub1_subdata " - "FROM base1 JOIN sub1 ON base1.id = sub1.id) AS anon_1 " - "ON anon_1.sub1_id = base2.base1_id " + "(base1 JOIN sub1 ON base1.id = sub1.id) " + "ON sub1.id = base2.base1_id " "JOIN ep1 ON base2.id = ep1.base2_id " "JOIN ep2 ON base2.id = ep2.base2_id" ) @@ -1352,28 +1335,22 @@ class SubClassToSubClassMultiTest(AssertsCompiledSQL, fixtures.MappedTest): join(Sub2.ep2), "SELECT anon_1.parent_id AS anon_1_parent_id, " "anon_1.parent_data AS anon_1_parent_data, " - "anon_1.anon_2_sub2_id AS anon_1_anon_2_sub2_id, " - "anon_1.anon_2_base2_id AS anon_1_anon_2_base2_id, " - "anon_1.anon_2_base2_base1_id AS anon_1_anon_2_base2_base1_id, " - "anon_1.anon_2_base2_data AS anon_1_anon_2_base2_data, " - "anon_1.anon_2_sub2_subdata AS anon_1_anon_2_sub2_subdata " + "anon_1.sub2_id AS anon_1_sub2_id, " + "anon_1.base2_id AS anon_1_base2_id, " + "anon_1.base2_base1_id AS anon_1_base2_base1_id, " + "anon_1.base2_data AS anon_1_base2_data, " + "anon_1.sub2_subdata AS anon_1_sub2_subdata " "FROM (SELECT parent.id AS parent_id, parent.data AS parent_data, " - "anon_2.sub2_id AS anon_2_sub2_id, " - "anon_2.base2_id AS anon_2_base2_id, " - "anon_2.base2_base1_id AS anon_2_base2_base1_id, " - "anon_2.base2_data AS anon_2_base2_data, " - "anon_2.sub2_subdata AS anon_2_sub2_subdata " - "FROM parent JOIN (SELECT base1.id AS base1_id, " - "base1.data AS base1_data, sub1.id AS sub1_id, " - "sub1.parent_id AS sub1_parent_id, sub1.subdata AS sub1_subdata " - "FROM base1 JOIN sub1 ON base1.id = sub1.id) AS anon_3 " - "ON parent.id = anon_3.sub1_parent_id JOIN " - "(SELECT base2.id AS base2_id, base2.base1_id AS base2_base1_id, " - "base2.data AS base2_data, sub2.id AS sub2_id, " + "sub2.id AS sub2_id, " + "base2.id AS base2_id, " + "base2.base1_id AS base2_base1_id, " + "base2.data AS base2_data, " "sub2.subdata AS sub2_subdata " - "FROM base2 JOIN sub2 ON base2.id = sub2.id) AS anon_2 " - "ON anon_3.base1_id = anon_2.base2_base1_id) AS anon_1 " - "JOIN ep1 ON anon_1.anon_2_base2_id = ep1.base2_id " - "JOIN ep2 ON anon_1.anon_2_base2_id = ep2.base2_id" + "FROM parent JOIN (base1 JOIN sub1 ON base1.id = sub1.id) " + "ON parent.id = sub1.parent_id JOIN " + "(base2 JOIN sub2 ON base2.id = sub2.id) " + "ON base1.id = base2.base1_id) AS anon_1 " + "JOIN ep1 ON anon_1.base2_id = ep1.base2_id " + "JOIN ep2 ON anon_1.base2_id = ep2.base2_id" ) diff --git a/test/orm/test_eager_relations.py b/test/orm/test_eager_relations.py index 54a106c5a..52f45a2d4 100644 --- a/test/orm/test_eager_relations.py +++ b/test/orm/test_eager_relations.py @@ -2630,16 +2630,15 @@ class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest, self.assert_compile( s.query(Director).options(joinedload('*')), "SELECT director.id AS director_id, persistent.id AS persistent_id, " - "director.name AS director_name, anon_1.movie_id AS anon_1_movie_id, " - "anon_1.persistent_id AS anon_1_persistent_id, " - "anon_1.movie_director_id AS anon_1_movie_director_id, " - "anon_1.movie_title AS anon_1_movie_title " + "director.name AS director_name, movie_1.id AS movie_1_id, " + "persistent_1.id AS persistent_1_id, " + "movie_1.director_id AS movie_1_director_id, " + "movie_1.title AS movie_1_title " "FROM persistent JOIN director ON persistent.id = director.id " "LEFT OUTER JOIN " - "(SELECT persistent.id AS persistent_id, movie.id AS movie_id, " - "movie.director_id AS movie_director_id, movie.title AS movie_title " - "FROM persistent JOIN movie ON persistent.id = movie.id) AS anon_1 " - "ON director.id = anon_1.movie_director_id" + "(persistent AS persistent_1 JOIN movie AS movie_1 " + "ON persistent_1.id = movie_1.id) " + "ON director.id = movie_1.director_id" ) def test_integrate(self): @@ -2702,15 +2701,12 @@ class CyclicalInheritingEagerTestThree(fixtures.DeclarativeMappedTest, sess.query(PersistentObject).options(joinedload(Director.other, join_depth=1)), "SELECT persistent.id AS persistent_id, director.id AS director_id, " "director.other_id AS director_other_id, " - "director.name AS director_name, anon_1.persistent_id AS " - "anon_1_persistent_id, anon_1.director_id AS anon_1_director_id, " - "anon_1.director_other_id AS anon_1_director_other_id, " - "anon_1.director_name AS anon_1_director_name " + "director.name AS director_name, persistent_1.id AS " + "persistent_1_id, director_1.id AS director_1_id, " + "director_1.other_id AS director_1_other_id, " + "director_1.name AS director_1_name " "FROM persistent LEFT OUTER JOIN director ON director.id = persistent.id " - "LEFT OUTER JOIN (SELECT persistent.id AS persistent_id, " - "director.id AS director_id, director.other_id AS director_other_id, " - "director.name AS director_name " - "FROM persistent LEFT OUTER JOIN director ON " - "director.id = persistent.id) " - "AS anon_1 ON director.other_id = anon_1.persistent_id" + "LEFT OUTER JOIN (persistent AS persistent_1 LEFT OUTER JOIN director AS director_1 ON " + "director_1.id = persistent_1.id) " + "ON director.other_id = persistent_1.id" ) diff --git a/test/orm/test_joins.py b/test/orm/test_joins.py index 2dac59150..cb9412e1d 100644 --- a/test/orm/test_joins.py +++ b/test/orm/test_joins.py @@ -203,15 +203,11 @@ class InheritedJoinTest(fixtures.MappedTest, AssertsCompiledSQL): self.assert_compile( sess.query(Company).join(Company.employees.of_type(Engineer)), - "SELECT companies.company_id AS companies_company_id, companies.name AS companies_name " - "FROM companies JOIN (SELECT people.person_id AS people_person_id, " - "people.company_id AS people_company_id, people.name AS people_name, " - "people.type AS people_type, engineers.person_id AS " - "engineers_person_id, engineers.status AS engineers_status, " - "engineers.engineer_name AS engineers_engineer_name, " - "engineers.primary_language AS engineers_primary_language " - "FROM people JOIN engineers ON people.person_id = engineers.person_id) AS " - "anon_1 ON companies.company_id = anon_1.people_company_id" + "SELECT companies.company_id AS companies_company_id, " + "companies.name AS companies_name " + "FROM companies JOIN " + "(people JOIN engineers ON people.person_id = engineers.person_id) " + "ON companies.company_id = people.company_id" , use_default_dialect = True ) @@ -259,7 +255,7 @@ class InheritedJoinTest(fixtures.MappedTest, AssertsCompiledSQL): , use_default_dialect=True ) - def test_explicit_polymorphic_join(self): + def test_explicit_polymorphic_join_one(self): Company, Engineer = self.classes.Company, self.classes.Engineer sess = create_session() @@ -268,35 +264,28 @@ class InheritedJoinTest(fixtures.MappedTest, AssertsCompiledSQL): sess.query(Company).join(Engineer).filter(Engineer.engineer_name=='vlad'), "SELECT companies.company_id AS companies_company_id, companies.name AS " "companies_name " - "FROM companies JOIN (SELECT people.person_id AS people_person_id, " - "people.company_id AS " - "people_company_id, people.name AS people_name, people.type AS people_type," - " engineers.person_id AS " - "engineers_person_id, engineers.status AS engineers_status, " - "engineers.engineer_name AS engineers_engineer_name, " - "engineers.primary_language AS engineers_primary_language " - "FROM people JOIN engineers ON people.person_id = engineers.person_id) " - "AS anon_1 ON " - "companies.company_id = anon_1.people_company_id " - "WHERE anon_1.engineers_engineer_name = :engineer_name_1" + "FROM companies JOIN (people JOIN engineers " + "ON people.person_id = engineers.person_id) " + "ON " + "companies.company_id = people.company_id " + "WHERE engineers.engineer_name = :engineer_name_1" , use_default_dialect=True ) + + def test_explicit_polymorphic_join_two(self): + Company, Engineer = self.classes.Company, self.classes.Engineer + + sess = create_session() self.assert_compile( sess.query(Company).join(Engineer, Company.company_id==Engineer.company_id). filter(Engineer.engineer_name=='vlad'), "SELECT companies.company_id AS companies_company_id, companies.name " "AS companies_name " - "FROM companies JOIN (SELECT people.person_id AS people_person_id, " - "people.company_id AS " - "people_company_id, people.name AS people_name, people.type AS " - "people_type, engineers.person_id AS " - "engineers_person_id, engineers.status AS engineers_status, " - "engineers.engineer_name AS engineers_engineer_name, " - "engineers.primary_language AS engineers_primary_language " - "FROM people JOIN engineers ON people.person_id = engineers.person_id) AS " - "anon_1 ON " - "companies.company_id = anon_1.people_company_id " - "WHERE anon_1.engineers_engineer_name = :engineer_name_1" + "FROM companies JOIN " + "(people JOIN engineers ON people.person_id = engineers.person_id) " + "ON " + "companies.company_id = people.company_id " + "WHERE engineers.engineer_name = :engineer_name_1" , use_default_dialect=True ) @@ -319,16 +308,10 @@ class InheritedJoinTest(fixtures.MappedTest, AssertsCompiledSQL): filter(Engineer.name=='dilbert'), "SELECT companies.company_id AS companies_company_id, companies.name AS " "companies_name " - "FROM companies JOIN (SELECT people.person_id AS people_person_id, " - "people.company_id AS " - "people_company_id, people.name AS people_name, people.type AS " - "people_type, engineers.person_id " - "AS engineers_person_id, engineers.status AS engineers_status, " - "engineers.engineer_name AS engineers_engineer_name, " - "engineers.primary_language AS engineers_primary_language FROM people " + "FROM companies JOIN (people " "JOIN engineers ON people.person_id = " - "engineers.person_id) AS anon_1 ON companies.company_id = " - "anon_1.people_company_id WHERE anon_1.people_name = :name_1" + "engineers.person_id) ON companies.company_id = " + "people.company_id WHERE people.name = :name_1" , use_default_dialect = True ) @@ -339,20 +322,14 @@ class InheritedJoinTest(fixtures.MappedTest, AssertsCompiledSQL): filter(Engineer.name=='dilbert').filter(Machine.name=='foo'), "SELECT companies.company_id AS companies_company_id, companies.name AS " "companies_name " - "FROM companies JOIN (SELECT people.person_id AS people_person_id, " - "people.company_id AS " - "people_company_id, people.name AS people_name, people.type AS people_type," - " engineers.person_id " - "AS engineers_person_id, engineers.status AS engineers_status, " - "engineers.engineer_name AS engineers_engineer_name, " - "engineers.primary_language AS engineers_primary_language FROM people " + "FROM companies JOIN (people " "JOIN engineers ON people.person_id = " - "engineers.person_id) AS anon_1 ON companies.company_id = " - "anon_1.people_company_id JOIN " + "engineers.person_id) ON companies.company_id = " + "people.company_id JOIN " "(SELECT machines.machine_id AS machine_id, machines.name AS name, " "machines.engineer_id AS engineer_id " - "FROM machines) AS anon_2 ON anon_1.engineers_person_id = anon_2.engineer_id " - "WHERE anon_1.people_name = :name_1 AND anon_2.name = :name_2" + "FROM machines) AS anon_1 ON engineers.person_id = anon_1.engineer_id " + "WHERE people.name = :name_1 AND anon_1.name = :name_2" , use_default_dialect = True ) @@ -1364,19 +1341,13 @@ class JoinTest(QueryTest, AssertsCompiledSQL): assert_raises_message( sa_exc.InvalidRequestError, - "Could not find a FROM clause to join from. Tried joining " - "to .*?, but got: " - "Can't find any foreign key relationships " - "between 'users' and 'users'.", + "Can't join table/selectable 'users' to itself", sess.query(users.c.id).join, User ) assert_raises_message( sa_exc.InvalidRequestError, - "Could not find a FROM clause to join from. Tried joining " - "to .*?, but got: " - "Can't find any foreign key relationships " - "between 'users' and 'users'.", + "Can't join table/selectable 'users' to itself", sess.query(users.c.id).select_from(users).join, User ) @@ -1522,16 +1493,22 @@ class JoinFromSelectableTest(fixtures.MappedTest, AssertsCompiledSQL): subq = sess.query(T2.t1_id, func.count(T2.id).label('count')).\ group_by(T2.t1_id).subquery() - # this query is wrong, but verifying behavior stays the same - # (or improves, like an error message) + assert_raises_message( + sa_exc.InvalidRequestError, + "Can't join table/selectable 'table1' to itself", + sess.query(T1.id, subq.c.count).join, T1, subq.c.t1_id == T1.id + ) + self.assert_compile( - sess.query(T1.id, subq.c.count).join(T1, subq.c.t1_id==T1.id), - "SELECT table1.id AS table1_id, anon_1.count AS anon_1_count FROM " - "(SELECT table2.t1_id AS t1_id, count(table2.id) AS count FROM " - "table2 GROUP BY table2.t1_id) AS anon_1, table1 JOIN table1 " - "ON anon_1.t1_id = table1.id" + sess.query(T1.id, subq.c.count).select_from(subq).\ + join(T1, subq.c.t1_id == T1.id), + "SELECT table1.id AS table1_id, anon_1.count AS anon_1_count " + "FROM (SELECT table2.t1_id AS t1_id, count(table2.id) AS count " + "FROM table2 GROUP BY table2.t1_id) AS anon_1 " + "JOIN table1 ON anon_1.t1_id = table1.id" ) + def test_mapped_select_to_mapped_explicit_left(self): T1, T2 = self.classes.T1, self.classes.T2 diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py index 19ff78004..ed09e72c1 100644 --- a/test/orm/test_mapper.py +++ b/test/orm/test_mapper.py @@ -582,20 +582,16 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL): self.assert_compile( q, "SELECT " - "anon_1.addresses_id AS anon_1_addresses_id, " - "anon_1.users_id AS anon_1_users_id, " - "anon_1.users_name AS anon_1_users_name, " - "anon_1.addresses_user_id AS anon_1_addresses_user_id, " - "anon_1.addresses_email_address AS " - "anon_1_addresses_email_address, " - "anon_1.users_name || :name_1 AS anon_2 " - "FROM addresses JOIN (SELECT users.id AS users_id, " - "users.name AS users_name, addresses.id AS addresses_id, " - "addresses.user_id AS addresses_user_id, " - "addresses.email_address AS addresses_email_address " - "FROM users JOIN addresses ON users.id = " - "addresses.user_id) AS anon_1 ON " - "anon_1.users_id = addresses.user_id" + "addresses_1.id AS addresses_1_id, " + "users_1.id AS users_1_id, " + "users_1.name AS users_1_name, " + "addresses_1.user_id AS addresses_1_user_id, " + "addresses_1.email_address AS " + "addresses_1_email_address, " + "users_1.name || :name_1 AS anon_1 " + "FROM addresses JOIN (users AS users_1 JOIN addresses AS addresses_1 ON users_1.id = " + "addresses_1.user_id) ON " + "users_1.id = addresses.user_id" ) def test_column_prop_deannotate(self): diff --git a/test/orm/test_of_type.py b/test/orm/test_of_type.py index 17ffebc3d..d002fd50f 100644 --- a/test/orm/test_of_type.py +++ b/test/orm/test_of_type.py @@ -69,12 +69,16 @@ class _PolymorphicTestBase(object): .filter(Engineer.primary_language == 'java').count(), 1) + def test_join_to_subclass_four(self): + sess = Session() # test [ticket:2093] eq_(sess.query(Company.company_id, Engineer) .join(Company.employees.of_type(Engineer)) .filter(Engineer.primary_language == 'java').count(), 1) + def test_join_to_subclass_five(self): + sess = Session() eq_(sess.query(Company) .join(Company.employees.of_type(Engineer)) .filter(Engineer.primary_language == 'java').count(), @@ -82,7 +86,7 @@ class _PolymorphicTestBase(object): def test_with_polymorphic_join_compile_one(self): sess = Session() - +# MARKMARK self.assert_compile( sess.query(Company).join( Company.employees.of_type( @@ -185,17 +189,19 @@ class _PolymorphicTestBase(object): ) self.assert_sql_count(testing.db, go, 3) + class PolymorphicPolymorphicTest(_PolymorphicTestBase, _PolymorphicPolymorphic): def _polymorphic_join_target(self, cls): from sqlalchemy.orm import class_mapper + from sqlalchemy.sql.expression import FromGrouping m, sel = class_mapper(Person)._with_polymorphic_args(cls) - sel = sel.alias() + sel = FromGrouping(sel.alias(flat=True)) comp_sel = sel.compile(dialect=default.DefaultDialect()) return \ comp_sel.process(sel, asfrom=True).replace("\n", "") + \ - " ON companies.company_id = anon_1.people_company_id" + " ON companies.company_id = people_1.company_id" class PolymorphicUnionsTest(_PolymorphicTestBase, _PolymorphicUnions): @@ -223,13 +229,14 @@ class PolymorphicAliasedJoinsTest(_PolymorphicTestBase, _PolymorphicAliasedJoins class PolymorphicJoinsTest(_PolymorphicTestBase, _PolymorphicJoins): def _polymorphic_join_target(self, cls): from sqlalchemy.orm import class_mapper + from sqlalchemy.sql.expression import FromGrouping - sel = class_mapper(Person)._with_polymorphic_selectable.alias() + sel = FromGrouping(class_mapper(Person)._with_polymorphic_selectable.alias(flat=True)) comp_sel = sel.compile(dialect=default.DefaultDialect()) return \ comp_sel.process(sel, asfrom=True).replace("\n", "") + \ - " ON companies.company_id = anon_1.people_company_id" + " ON companies.company_id = people_1.company_id" class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeMappedTest): @@ -448,6 +455,7 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM DataContainer.jobs.of_type(Job_P).\ any(Job_P.id < Job.id) ) + self.assert_compile(q, "SELECT job.id AS job_id, job.type AS job_type, " "job.container_id " @@ -455,11 +463,10 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM "FROM data_container " "JOIN job ON data_container.id = job.container_id " "WHERE EXISTS (SELECT 1 " - "FROM (SELECT job.id AS job_id, job.type AS job_type, " - "job.container_id AS job_container_id, " - "subjob.id AS subjob_id, subjob.attr AS subjob_attr " - "FROM job LEFT OUTER JOIN subjob ON job.id = subjob.id) AS anon_1 " - "WHERE data_container.id = anon_1.job_container_id AND job.id > anon_1.job_id)" + "FROM job AS job_1 LEFT OUTER JOIN subjob AS subjob_1 " + "ON job_1.id = subjob_1.id " + "WHERE data_container.id = job_1.container_id " + "AND job.id > job_1.id)" ) def test_any_walias(self): @@ -501,11 +508,10 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM self.assert_compile(q, "SELECT data_container.id AS data_container_id, " "data_container.name AS data_container_name " - "FROM data_container JOIN (SELECT job.id AS job_id, " - "job.type AS job_type, job.container_id AS job_container_id, " - "subjob.id AS subjob_id, subjob.attr AS subjob_attr " - "FROM job LEFT OUTER JOIN subjob ON job.id = subjob.id) " - "AS anon_1 ON data_container.id = anon_1.job_container_id") + "FROM data_container JOIN " + "(job AS job_1 LEFT OUTER JOIN subjob AS subjob_1 " + "ON job_1.id = subjob_1.id) " + "ON data_container.id = job_1.container_id") def test_join_wsubclass(self): ParentThing, DataContainer, Job, SubJob = \ @@ -524,11 +530,8 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM self.assert_compile(q, "SELECT data_container.id AS data_container_id, " "data_container.name AS data_container_name " - "FROM data_container JOIN (SELECT job.id AS job_id, " - "job.type AS job_type, job.container_id AS job_container_id, " - "subjob.id AS subjob_id, subjob.attr AS subjob_attr " - "FROM job JOIN subjob ON job.id = subjob.id) AS anon_1 " - "ON data_container.id = anon_1.job_container_id" + "FROM data_container JOIN (job JOIN subjob ON job.id = subjob.id) " + "ON data_container.id = job.container_id" ) def test_join_wpoly_innerjoin(self): @@ -545,11 +548,9 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM self.assert_compile(q, "SELECT data_container.id AS data_container_id, " "data_container.name AS data_container_name " - "FROM data_container JOIN (SELECT job.id AS job_id, " - "job.type AS job_type, job.container_id AS job_container_id, " - "subjob.id AS subjob_id, subjob.attr AS subjob_attr " - "FROM job JOIN subjob ON job.id = subjob.id) " - "AS anon_1 ON data_container.id = anon_1.job_container_id") + "FROM data_container JOIN " + "(job AS job_1 JOIN subjob AS subjob_1 ON job_1.id = subjob_1.id) " + "ON data_container.id = job_1.container_id") def test_join_walias(self): ParentThing, DataContainer, Job, SubJob = \ @@ -582,9 +583,8 @@ class SubclassRelationshipTest(testing.AssertsCompiledSQL, fixtures.DeclarativeM self.assert_compile(q, "SELECT data_container.id AS data_container_id, " "data_container.name AS data_container_name " - "FROM data_container JOIN (SELECT job.id AS job_id, " - "job.type AS job_type, job.container_id AS job_container_id, " - "subjob.id AS subjob_id, subjob.attr AS subjob_attr " - "FROM job LEFT OUTER JOIN subjob ON job.id = subjob.id) " - "AS anon_1 ON data_container.id = anon_1.job_container_id") + "FROM data_container JOIN " + "(job AS job_1 LEFT OUTER JOIN subjob AS subjob_1 " + "ON job_1.id = subjob_1.id) " + "ON data_container.id = job_1.container_id") diff --git a/test/orm/test_subquery_relations.py b/test/orm/test_subquery_relations.py index d493e0b7e..a6cc37691 100644 --- a/test/orm/test_subquery_relations.py +++ b/test/orm/test_subquery_relations.py @@ -1533,19 +1533,18 @@ class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest, q = ctx.attributes[('subquery', (inspect(Director), inspect(Director).attrs.movies))] self.assert_compile(q, - "SELECT anon_1.movie_id AS anon_1_movie_id, " - "anon_1.persistent_id AS anon_1_persistent_id, " - "anon_1.movie_director_id AS anon_1_movie_director_id, " - "anon_1.movie_title AS anon_1_movie_title, " - "anon_2.director_id AS anon_2_director_id FROM " - "(SELECT director.id AS director_id FROM persistent JOIN director " - "ON persistent.id = director.id) AS anon_2 " - "JOIN (SELECT persistent.id AS persistent_id, movie.id AS movie_id, " + "SELECT movie.id AS movie_id, persistent.id AS persistent_id, " "movie.director_id AS movie_director_id, " - "movie.title AS movie_title FROM persistent JOIN movie " - "ON persistent.id = movie.id) AS anon_1 " - "ON anon_2.director_id = anon_1.movie_director_id " - "ORDER BY anon_2.director_id") + "movie.title AS movie_title, " + "anon_1.director_id AS anon_1_director_id " + "FROM (SELECT director.id AS director_id " + "FROM persistent JOIN director " + "ON persistent.id = director.id) AS anon_1 " + "JOIN (persistent JOIN movie ON persistent.id = movie.id) " + "ON anon_1.director_id = movie.director_id " + "ORDER BY anon_1.director_id", + dialect="default" + ) def test_integrate(self): Director = self.classes.Director diff --git a/test/profiles.txt b/test/profiles.txt index 62924b895..4d8964639 100644 --- a/test/profiles.txt +++ b/test/profiles.txt @@ -1,15 +1,15 @@ # /mnt/hgfs/classic/dev/sqlalchemy/test/profiles.txt # This file is written out on a per-environment basis. -# For each test in aaa_profiling, the corresponding function and +# For each test in aaa_profiling, the corresponding function and # environment is located within this file. If it doesn't exist, # the test is skipped. -# If a callcount does exist, it is compared to what we received. +# If a callcount does exist, it is compared to what we received. # assertions are raised if the counts do not match. -# -# To add a new callcount test, apply the function_call_count -# decorator and re-run the tests using the --write-profiles +# +# To add a new callcount test, apply the function_call_count +# decorator and re-run the tests using the --write-profiles # option - this file will be rewritten including the new count. -# +# # TEST: test.aaa_profiling.test_compiler.CompileTest.test_insert @@ -109,51 +109,46 @@ test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_ # TEST: test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity -test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.6_sqlite_pysqlite_nocextensions 118319 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.6_sqlite_pysqlite_nocextensions 154319 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_cextensions 124069 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_mysql_mysqldb_nocextensions 126819 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_oracle_cx_oracle_nocextensions 128319 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_cextensions 116569 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_postgresql_psycopg2_nocextensions 119319 -test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_cextensions 115569 -test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_nocextensions 118319 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_cextensions 151569 +test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 2.7_sqlite_pysqlite_nocextensions 154319 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.2_postgresql_psycopg2_nocextensions 121790 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.2_sqlite_pysqlite_nocextensions 121822 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.3_oracle_cx_oracle_nocextensions 130792 test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.3_postgresql_psycopg2_nocextensions 121822 -test.aaa_profiling.test_orm.LoadManyToOneFromIdentityTest.test_many_to_one_load_no_identity 3.3_sqlite_pysqlite_nocextensions 121822 # TEST: test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.6_sqlite_pysqlite_nocextensions 19534 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.6_sqlite_pysqlite_nocextensions 21744 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_cextensions 19838 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_mysql_mysqldb_nocextensions 20098 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_oracle_cx_oracle_nocextensions 20152 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_cextensions 19237 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_postgresql_psycopg2_nocextensions 19467 -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_cextensions 19274 -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_nocextensions 19534 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_cextensions 21530 +test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 2.7_sqlite_pysqlite_nocextensions 21790 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.2_postgresql_psycopg2_nocextensions 20424 -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.2_sqlite_pysqlite_nocextensions 20447 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.3_oracle_cx_oracle_nocextensions 21244 test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.3_postgresql_psycopg2_nocextensions 20344 -test.aaa_profiling.test_orm.MergeBackrefsTest.test_merge_pending_with_all_pks 3.3_sqlite_pysqlite_nocextensions 20433 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_load -test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.6_sqlite_pysqlite_nocextensions 1221 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.6_sqlite_pysqlite_nocextensions 1521 test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_cextensions 1388 test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_mysql_mysqldb_nocextensions 1413 test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_oracle_cx_oracle_nocextensions 1349 test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_cextensions 1296 test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_postgresql_psycopg2_nocextensions 1321 -test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_cextensions 1196 -test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_nocextensions 1221 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_cextensions 1496 +test.aaa_profiling.test_orm.MergeTest.test_merge_load 2.7_sqlite_pysqlite_nocextensions 1521 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.2_postgresql_psycopg2_nocextensions 1332 -test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.2_sqlite_pysqlite_nocextensions 1243 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.3_oracle_cx_oracle_nocextensions 1366 test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.3_postgresql_psycopg2_nocextensions 1357 -test.aaa_profiling.test_orm.MergeTest.test_merge_load 3.3_sqlite_pysqlite_nocextensions 1243 # TEST: test.aaa_profiling.test_orm.MergeTest.test_merge_no_load diff --git a/test/sql/test_join_rewriting.py b/test/sql/test_join_rewriting.py new file mode 100644 index 000000000..5a9bdd1d3 --- /dev/null +++ b/test/sql/test_join_rewriting.py @@ -0,0 +1,292 @@ +from sqlalchemy import Table, Column, Integer, MetaData, ForeignKey, select +from sqlalchemy.testing import fixtures, AssertsCompiledSQL +from sqlalchemy import util +from sqlalchemy.engine import default +from sqlalchemy import testing + + +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')) + ) + +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) + ) + +class _JoinRewriteTestBase(AssertsCompiledSQL): + def _test(self, s, assert_): + self.assert_compile( + s, + assert_ + ) + + compiled = s.compile(dialect=self.__dialect__) + for key, col in zip([c.key for c in s.c], s.inner_columns): + key = key % compiled.anon_map + assert col in compiled.result_map[key][1] + + + 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__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 + ) + +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_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" + ) + +class JoinPlainTest(_JoinRewriteTestBase, fixtures.TestBase): + """test rendering of each join with normal nesting.""" + @util.classproperty + def __dialect__(cls): + dialect = default.DefaultDialect() + return dialect + + _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_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" + ) + +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__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_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" + ) + +class JoinExecTest(_JoinRewriteTestBase, fixtures.TestBase): + """invoke the SQL on the current backend to ensure compatibility""" + + _a_bc = _a_bc_comma_a1_selbc = _a__b_dc = 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_): + testing.db.execute(selectable) + + +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 + ) diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index 0c5cde9fb..2ac04dce3 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -776,12 +776,13 @@ class AnonLabelTest(fixtures.TestBase): c1 = literal_column('x') eq_(str(select([c1.label('y')])), "SELECT x AS y") -class JoinConditionTest(fixtures.TestBase, AssertsExecutionResults): +class JoinConditionTest(fixtures.TestBase, AssertsExecutionResults, AssertsCompiledSQL): def test_join_condition(self): m = MetaData() t1 = Table('t1', m, Column('id', Integer)) - t2 = Table('t2', m, Column('id', Integer), + t2 = Table('t2', m, + Column('id', Integer), Column('t1id', ForeignKey('t1.id'))) t3 = Table('t3', m, Column('id', Integer), @@ -793,6 +794,7 @@ class JoinConditionTest(fixtures.TestBase, AssertsExecutionResults): Column('t1id1', ForeignKey('t1.id')), Column('t1id2', ForeignKey('t1.id')), ) + t1t2 = t1.join(t2) t2t3 = t2.join(t3) @@ -841,21 +843,23 @@ class JoinConditionTest(fixtures.TestBase, AssertsExecutionResults): left.join(right).onclause ) + # these are right-nested joins + j = t1t2.join(t2t3) + assert j.onclause.compare(t2.c.id == t3.c.t2id) + self.assert_compile(j, + "t1 JOIN t2 ON t1.id = t2.t1id JOIN " + "(t2 JOIN t3 ON t2.id = t3.t2id) ON t2.id = t3.t2id") + + st2t3 = t2t3.select(use_labels=True) + j = t1t2.join(st2t3) + assert j.onclause.compare(t2.c.id == st2t3.c.t3_t2id) + self.assert_compile(j, + "t1 JOIN t2 ON t1.id = t2.t1id JOIN " + "(SELECT t2.id AS t2_id, t2.t1id AS t2_t1id, " + "t3.id AS t3_id, t3.t1id AS t3_t1id, t3.t2id AS t3_t2id " + "FROM t2 JOIN t3 ON t2.id = t3.t2id) ON t2.id = t3_t2id") - # TODO: this raises due to right side being "grouped", and no - # longer has FKs. Did we want to make FromGrouping friendlier - # ? - - assert_raises_message(exc.ArgumentError, - "Perhaps you meant to convert the right " - "side to a subquery using alias\(\)\?", - t1t2.join, t2t3) - assert_raises_message(exc.ArgumentError, - "Perhaps you meant to convert the right " - "side to a subquery using alias\(\)\?", - t1t2.join, t2t3.select(use_labels=True)) - def test_join_cond_no_such_unrelated_table(self): m = MetaData() |