summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql/util.py')
-rw-r--r--lib/sqlalchemy/sql/util.py78
1 files changed, 53 insertions, 25 deletions
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index 1b90a457f..74651a9d1 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -131,49 +131,77 @@ def adapt_criterion_to_null(crit, nulls):
return visitors.cloned_traverse(crit, {}, {'binary':visit_binary})
-def join_condition(a, b, ignore_nonexistent_tables=False):
- """create a join condition between two tables.
+def join_condition(a, b, ignore_nonexistent_tables=False, a_subset=None):
+ """create a join condition between two tables or selectables.
- ignore_nonexistent_tables=True allows a join condition to be
- determined between two tables which may contain references to
- other not-yet-defined tables. In general the NoSuchTableError
- raised is only required if the user is trying to join selectables
- across multiple MetaData objects (which is an extremely rare use
- case).
+ e.g.::
+
+ join_condition(tablea, tableb)
+
+ would produce an expression along the lines of::
+
+ tablea.c.id==tableb.c.tablea_id
+
+ The join is determined based on the foreign key relationships
+ between the two selectables. If there are multiple ways
+ to join, or no way to join, an error is raised.
+
+ :param ignore_nonexistent_tables: This flag will cause the
+ function to silently skip over foreign key resolution errors
+ due to nonexistent tables - the assumption is that these
+ tables have not yet been defined within an initialization process
+ and are not significant to the operation.
+
+ :param a_subset: An optional expression that is a sub-component
+ of ``a``. An attempt will be made to join to just this sub-component
+ first before looking at the full ``a`` construct, and if found
+ will be successful even if there are other ways to join to ``a``.
+ This allows the "right side" of a join to be passed thereby
+ providing a "natural join".
"""
crit = []
constraints = set()
- for fk in b.foreign_keys:
- try:
- col = fk.get_referent(a)
- except exc.NoReferencedTableError:
- if ignore_nonexistent_tables:
- continue
- else:
- raise
-
- if col is not None:
- crit.append(col == fk.parent)
- constraints.add(fk.constraint)
- if a is not b:
- for fk in a.foreign_keys:
+
+ for left in (a_subset, a):
+ if left is None:
+ continue
+ for fk in b.foreign_keys:
try:
- col = fk.get_referent(b)
+ col = fk.get_referent(left)
except exc.NoReferencedTableError:
if ignore_nonexistent_tables:
continue
else:
raise
-
+
if col is not None:
crit.append(col == fk.parent)
constraints.add(fk.constraint)
+ if left is not b:
+ for fk in left.foreign_keys:
+ try:
+ col = fk.get_referent(b)
+ except exc.NoReferencedTableError:
+ if ignore_nonexistent_tables:
+ continue
+ else:
+ raise
+ if col is not None:
+ crit.append(col == fk.parent)
+ constraints.add(fk.constraint)
+ if crit:
+ break
+
if len(crit) == 0:
+ if isinstance(b, expression._FromGrouping):
+ hint = " Perhaps you meant to convert the right side to a subquery using alias()?"
+ else:
+ hint = ""
raise exc.ArgumentError(
"Can't find any foreign key relationships "
- "between '%s' and '%s'" % (a.description, b.description))
+ "between '%s' and '%s'.%s" % (a.description, b.description, hint))
elif len(constraints) > 1:
raise exc.ArgumentError(
"Can't determine join between '%s' and '%s'; "