summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-05-02 01:02:23 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-05-02 01:02:23 +0000
commite3460573d037e27592995277a19840be13457828 (patch)
tree90fbd43479edb50e2cdc12f40fa90bc5368ed846 /lib/sqlalchemy
parentc407a608c38a8d483e77fb950b21995b16fa05f7 (diff)
downloadsqlalchemy-e3460573d037e27592995277a19840be13457828.tar.gz
- factored out the logic used by Join to create its join condition
- With declarative, joined table inheritance mappers use a slightly relaxed function to create the "inherit condition" to the parent table, so that other foreign keys to not-yet-declared Table objects don't trigger an error.
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/exceptions.py4
-rw-r--r--lib/sqlalchemy/ext/declarative.py9
-rw-r--r--lib/sqlalchemy/orm/mapper.py2
-rw-r--r--lib/sqlalchemy/schema.py2
-rw-r--r--lib/sqlalchemy/sql/expression.py32
-rw-r--r--lib/sqlalchemy/sql/util.py58
6 files changed, 74 insertions, 33 deletions
diff --git a/lib/sqlalchemy/exceptions.py b/lib/sqlalchemy/exceptions.py
index a21a06b49..747a519f6 100644
--- a/lib/sqlalchemy/exceptions.py
+++ b/lib/sqlalchemy/exceptions.py
@@ -64,7 +64,9 @@ class AssertionError(SQLAlchemyError):
class NoSuchColumnError(KeyError, SQLAlchemyError):
"""Raised by ``RowProxy`` when a nonexistent column is requested from a row."""
-
+
+class NoSuchTableError(InvalidRequestError):
+ """Raised by ``ForeignKey`` when the referred ``Table`` cannot be located."""
class DisconnectionError(SQLAlchemyError):
"""Raised within ``Pool`` when a disconnect is detected on a raw DB-API connection.
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py
index 5bdd9652e..d736736e9 100644
--- a/lib/sqlalchemy/ext/declarative.py
+++ b/lib/sqlalchemy/ext/declarative.py
@@ -187,6 +187,7 @@ from sqlalchemy.orm import synonym as _orm_synonym, mapper, comparable_property
from sqlalchemy.orm.interfaces import MapperProperty
from sqlalchemy.orm.properties import PropertyLoader, ColumnProperty
from sqlalchemy import util, exceptions
+from sqlalchemy.sql import util as sql_util
__all__ = ['declarative_base', 'synonym_for', 'comparable_using',
@@ -241,7 +242,13 @@ class DeclarativeMeta(type):
if 'inherits' not in mapper_args:
inherits = cls.__mro__[1]
inherits = cls._decl_class_registry.get(inherits.__name__, None)
- mapper_args['inherits'] = inherits
+ if inherits:
+ mapper_args['inherits'] = inherits
+ if not mapper_args.get('concrete', False) and table:
+ # figure out the inherit condition with relaxed rules about nonexistent tables,
+ # to allow for ForeignKeys to not-yet-defined tables (since we know for sure that our parent
+ # table is defined within the same MetaData)
+ mapper_args['inherit_condition'] = sql_util.join_condition(inherits.__table__, table, ignore_nonexistent_tables=True)
if hasattr(cls, '__mapper_cls__'):
mapper_cls = util.unbound_method_to_callable(cls.__mapper_cls__)
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index b1d749d6f..15044ba34 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -424,7 +424,7 @@ class Mapper(object):
# figure out inherit condition from our table to the immediate table
# of the inherited mapper, not its full table which could pull in other
# stuff we dont want (allows test/inheritance.InheritTest4 to pass)
- self.inherit_condition = sql.join(self.inherits.local_table, self.local_table).onclause
+ self.inherit_condition = sqlutil.join_condition(self.inherits.local_table, self.local_table)
self.mapped_table = sql.join(self.inherits.mapped_table, self.local_table, self.inherit_condition)
fks = util.to_set(self.inherit_foreign_keys)
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index 666d64d93..5ca9573ad 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -782,7 +782,7 @@ class ForeignKey(SchemaItem):
else:
(schema,tname,colname) = m.group(1,2,3)
if _get_table_key(tname, schema) not in parenttable.metadata:
- raise exceptions.InvalidRequestError(
+ raise exceptions.NoSuchTableError(
"Could not find table '%s' with which to generate a "
"foreign key" % tname)
table = Table(tname, parenttable.metadata,
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 94c145613..1143bf8aa 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -2308,34 +2308,10 @@ class Join(FromClause):
return self.left, self.right, self.onclause
def __match_primaries(self, primary, secondary):
- crit = []
- constraints = util.Set()
- for fk in secondary.foreign_keys:
- col = fk.get_referent(primary)
- if col:
- crit.append(col == fk.parent)
- constraints.add(fk.constraint)
- if primary is not secondary:
- for fk in primary.foreign_keys:
- col = fk.get_referent(secondary)
- if col:
- crit.append(col == fk.parent)
- constraints.add(fk.constraint)
- if len(crit) == 0:
- raise exceptions.ArgumentError(
- "Can't find any foreign key relationships "
- "between '%s' and '%s'" % (primary.description, secondary.description))
- elif len(constraints) > 1:
- raise exceptions.ArgumentError(
- "Can't determine join between '%s' and '%s'; "
- "tables have more than one foreign key "
- "constraint relationship between them. "
- "Please specify the 'onclause' of this "
- "join explicitly." % (primary.description, secondary.description))
- elif len(crit) == 1:
- return (crit[0])
- else:
- return and_(*crit)
+ global sql_util
+ if not sql_util:
+ from sqlalchemy.sql import util as sql_util
+ return sql_util.join_condition(primary, secondary)
def select(self, whereclause=None, fold_equivalents=False, **kwargs):
"""Create a ``Select`` from this ``Join``.
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index dd29cb42b..0c85cbe4a 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -56,7 +56,63 @@ def find_columns(clause):
visitors.traverse(clause, visit_column=visit_column)
return cols
-
+def join_condition(a, b, ignore_nonexistent_tables=False):
+ """create a join condition between two tables.
+
+ 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).
+
+ """
+ crit = []
+ constraints = util.Set()
+ for fk in b.foreign_keys:
+ try:
+ col = fk.get_referent(a)
+ except exceptions.NoSuchTableError:
+ if ignore_nonexistent_tables:
+ continue
+ else:
+ raise
+
+ if col:
+ crit.append(col == fk.parent)
+ constraints.add(fk.constraint)
+
+ if a is not b:
+ for fk in a.foreign_keys:
+ try:
+ col = fk.get_referent(b)
+ except exceptions.NoSuchTableError:
+ if ignore_nonexistent_tables:
+ continue
+ else:
+ raise
+
+ if col:
+ crit.append(col == fk.parent)
+ constraints.add(fk.constraint)
+
+ if len(crit) == 0:
+ raise exceptions.ArgumentError(
+ "Can't find any foreign key relationships "
+ "between '%s' and '%s'" % (a.description, b.description))
+ elif len(constraints) > 1:
+ raise exceptions.ArgumentError(
+ "Can't determine join between '%s' and '%s'; "
+ "tables have more than one foreign key "
+ "constraint relationship between them. "
+ "Please specify the 'onclause' of this "
+ "join explicitly." % (a.description, b.description))
+ elif len(crit) == 1:
+ return (crit[0])
+ else:
+ return and_(*crit)
+
+
def reduce_columns(columns, *clauses):
"""given a list of columns, return a 'reduced' set based on natural equivalents.