diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/exc.py | 7 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 71 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/util.py | 14 |
3 files changed, 74 insertions, 18 deletions
diff --git a/lib/sqlalchemy/exc.py b/lib/sqlalchemy/exc.py index 91ffc2811..f28bd8a07 100644 --- a/lib/sqlalchemy/exc.py +++ b/lib/sqlalchemy/exc.py @@ -25,6 +25,13 @@ class ArgumentError(SQLAlchemyError): """ +class NoForeignKeysError(ArgumentError): + """Raised when no foreign keys can be located between two selectables + during a join.""" + +class AmbiguousForeignKeysError(ArgumentError): + """Raised when more than one foreign key matching can be located + between two selectables during a join.""" class CircularDependencyError(SQLAlchemyError): """Raised by topological sorts when a circular dependency is detected. diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index edb7498e0..76e219efe 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -80,11 +80,11 @@ class JoinCondition(object): self._annotate_fks() self._annotate_remote() self._annotate_local() - self._determine_direction() self._setup_pairs() self._check_foreign_cols(self.primaryjoin, True) if self.secondaryjoin is not None: self._check_foreign_cols(self.secondaryjoin, False) + self._determine_direction() self._check_remote_side() self._log_joins() @@ -134,27 +134,68 @@ class JoinCondition(object): join_condition( self.child_selectable, self.secondary, - a_subset=self.child_local_selectable) + a_subset=self.child_local_selectable, + consider_as_foreign_keys=\ + self.consider_as_foreign_keys or None + ) if self.primaryjoin is None: self.primaryjoin = \ join_condition( self.parent_selectable, self.secondary, - a_subset=self.parent_local_selectable) + a_subset=self.parent_local_selectable, + consider_as_foreign_keys=\ + self.consider_as_foreign_keys or None + ) else: if self.primaryjoin is None: self.primaryjoin = \ join_condition( self.parent_selectable, self.child_selectable, - a_subset=self.parent_local_selectable) - except sa_exc.ArgumentError, e: - raise sa_exc.ArgumentError("Could not determine join " - "condition between parent/child tables on " - "relationship %s. Specify a 'primaryjoin' " - "expression. If 'secondary' is present, " - "'secondaryjoin' is needed as well." - % self.prop) + a_subset=self.parent_local_selectable, + consider_as_foreign_keys=\ + self.consider_as_foreign_keys or None + ) + except sa_exc.NoForeignKeysError, nfke: + if self.secondary is not None: + raise sa_exc.NoForeignKeysError("Could not determine join " + "condition between parent/child tables on " + "relationship %s - there are no foreign keys " + "linking these tables via secondary table '%s'. " + "Ensure that referencing columns are associated with a "\ + "ForeignKey or ForeignKeyConstraint, or specify 'primaryjoin' "\ + "and 'secondaryjoin' expressions." + % (self.prop, self.secondary)) + else: + raise sa_exc.NoForeignKeysError("Could not determine join " + "condition between parent/child tables on " + "relationship %s - there are no foreign keys " + "linking these tables. " + "Ensure that referencing columns are associated with a " + "ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' " + "expression." + % self.prop) + except sa_exc.AmbiguousForeignKeysError, afke: + if self.secondary is not None: + raise sa_exc.AmbiguousForeignKeysError("Could not determine join " + "condition between parent/child tables on " + "relationship %s - there are multiple foreign key " + "paths linking the tables via secondary table '%s'. " + "Specify the 'foreign_keys' " + "argument, providing a list of those columns which " + "should be counted as containing a foreign key reference " + "from the secondary table to each of the parent and child tables." + % (self.prop, self.secondary)) + else: + raise sa_exc.AmbiguousForeignKeysError("Could not determine join " + "condition between parent/child tables on " + "relationship %s - there are multiple foreign key " + "paths linking the tables. Specify the 'foreign_keys' " + "argument, providing a list of those columns which " + "should be counted as containing a foreign key reference " + "to the parent table." + % self.prop) @util.memoized_property def primaryjoin_reverse_remote(self): @@ -515,7 +556,7 @@ class JoinCondition(object): err = "Could not locate any simple equality expressions "\ "involving foreign key columns for %s join condition "\ "'%s' on relationship %s." % ( - primary and 'primaryjoin' or 'secondaryjoin', + primary and 'primary' or 'secondary', join_condition, self.prop ) @@ -529,12 +570,12 @@ class JoinCondition(object): else: err = "Could not locate any relevant foreign key columns "\ "for %s join condition '%s' on relationship %s." % ( - primary and 'primaryjoin' or 'secondaryjoin', + primary and 'primary' or 'secondary', join_condition, self.prop ) - err += "Ensure that referencing columns are associated with a "\ - "a ForeignKey or ForeignKeyConstraint, or are annotated "\ + err += " Ensure that referencing columns are associated with a "\ + "ForeignKey or ForeignKeyConstraint, or are annotated "\ "in the join condition with the foreign() annotation." raise sa_exc.ArgumentError(err) diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 2862e9af9..38d95dde5 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -284,8 +284,10 @@ def adapt_criterion_to_null(crit, nulls): return visitors.cloned_traverse(crit, {}, {'binary':visit_binary}) + def join_condition(a, b, ignore_nonexistent_tables=False, - a_subset=None): + a_subset=None, + consider_as_foreign_keys=None): """create a join condition between two tables or selectables. e.g.:: @@ -321,6 +323,9 @@ def join_condition(a, b, ignore_nonexistent_tables=False, for fk in sorted( b.foreign_keys, key=lambda fk:fk.parent._creation_order): + if consider_as_foreign_keys is not None and \ + fk.parent not in consider_as_foreign_keys: + continue try: col = fk.get_referent(left) except exc.NoReferenceError, nrte: @@ -336,6 +341,9 @@ def join_condition(a, b, ignore_nonexistent_tables=False, for fk in sorted( left.foreign_keys, key=lambda fk:fk.parent._creation_order): + if consider_as_foreign_keys is not None and \ + fk.parent not in consider_as_foreign_keys: + continue try: col = fk.get_referent(b) except exc.NoReferenceError, nrte: @@ -358,11 +366,11 @@ def join_condition(a, b, ignore_nonexistent_tables=False, "subquery using alias()?" else: hint = "" - raise exc.ArgumentError( + raise exc.NoForeignKeysError( "Can't find any foreign key relationships " "between '%s' and '%s'.%s" % (a.description, b.description, hint)) elif len(constraints) > 1: - raise exc.ArgumentError( + raise exc.AmbiguousForeignKeysError( "Can't determine join between '%s' and '%s'; " "tables have more than one foreign key " "constraint relationship between them. " |
