From aaefbb7e04c0b673d052c225e51749549f354f12 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 3 Aug 2006 00:28:57 +0000 Subject: [PATCH] - better check for ambiguous join conditions in sql.Join; propigates to a better error message in PropertyLoader (i.e. relation()/backref()) for when the join condition can't be reasonably determined. - sqlite creates ForeignKeyConstraint objects properly upon table reflection. --- CHANGES | 5 +++++ lib/sqlalchemy/databases/sqlite.py | 20 +++++++++++++++++--- lib/sqlalchemy/orm/mapper.py | 4 ++++ lib/sqlalchemy/orm/properties.py | 20 +++++++++++--------- lib/sqlalchemy/sql.py | 5 +++++ test/engine/reflection.py | 3 ++- 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index eb8032d913..487b7c5ba2 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,11 @@ if it was not loaded already - [ticket:257] - oracle boolean type - custom primary/secondary join conditions in a relation *will* be propigated to backrefs by default. specifying a backref() will override this behavior. +- better check for ambiguous join conditions in sql.Join; propigates to a +better error message in PropertyLoader (i.e. relation()/backref()) for when +the join condition can't be reasonably determined. +- sqlite creates ForeignKeyConstraint objects properly upon table +reflection. 0.2.6 - big overhaul to schema to allow truly composite primary and foreign diff --git a/lib/sqlalchemy/databases/sqlite.py b/lib/sqlalchemy/databases/sqlite.py index b68bf99a72..529486f46d 100644 --- a/lib/sqlalchemy/databases/sqlite.py +++ b/lib/sqlalchemy/databases/sqlite.py @@ -200,16 +200,30 @@ class SQLiteDialect(ansisql.ANSIDialect): raise exceptions.NoSuchTableError(table.name) c = connection.execute("PRAGMA foreign_key_list(" + table.name + ")", {}) + fks = {} while True: row = c.fetchone() if row is None: break - (tablename, localcol, remotecol) = (row[2], row[3], row[4]) - #print "row! " + repr(row) + (constraint_name, tablename, localcol, remotecol) = (row[0], row[2], row[3], row[4]) + try: + fk = fks[constraint_name] + except KeyError: + fk = ([],[]) + fks[constraint_name] = fk + + print "row! " + repr([key for key in row.keys()]), repr(row) # look up the table based on the given table's engine, not 'self', # since it could be a ProxyEngine remotetable = schema.Table(tablename, table.metadata, autoload=True, autoload_with=connection) - table.c[localcol].append_item(schema.ForeignKey(remotetable.c[remotecol])) + constrained_column = table.c[localcol].name + refspec = ".".join([tablename, remotecol]) + if constrained_column not in fk[0]: + fk[0].append(constrained_column) + if refspec not in fk[1]: + fk[1].append(refspec) + for name, value in fks.iteritems(): + table.append_item(schema.ForeignKeyConstraint(value[0], value[1])) # check for UNIQUE indexes c = connection.execute("PRAGMA index_list(" + table.name + ")", {}) unique_indexes = [] diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index c9c342dc92..4db2b40f21 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -474,6 +474,10 @@ class Mapper(object): else: return self + def common_parent(self, other): + """return true if the given mapper shares a common inherited parent as this mapper""" + return self.base_mapper() is other.base_mapper() + def isa(self, other): """return True if the given mapper inherits from this mapper""" m = other diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 2d2bf5fecb..f03feaa1c2 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -219,15 +219,17 @@ class PropertyLoader(mapper.MapperProperty): if self.secondaryjoin is not None and self.secondary is None: raise exceptions.ArgumentError("Property '" + self.key + "' specified with secondary join condition but no secondary argument") # if join conditions were not specified, figure them out based on foreign keys - if self.secondary is not None: - if self.secondaryjoin is None: - self.secondaryjoin = sql.join(self.mapper.unjoined_table, self.secondary).onclause - if self.primaryjoin is None: - self.primaryjoin = sql.join(self.parent.unjoined_table, self.secondary).onclause - else: - if self.primaryjoin is None: - self.primaryjoin = sql.join(self.parent.unjoined_table, self.target).onclause - + try: + if self.secondary is not None: + if self.secondaryjoin is None: + self.secondaryjoin = sql.join(self.mapper.unjoined_table, self.secondary).onclause + if self.primaryjoin is None: + self.primaryjoin = sql.join(self.parent.unjoined_table, self.secondary).onclause + else: + if self.primaryjoin is None: + self.primaryjoin = sql.join(self.parent.unjoined_table, self.target).onclause + except exceptions.ArgumentError, e: + raise exceptions.ArgumentError("Error determining primary and/or secondary join for relationship '%s' between mappers '%s' and '%s'. You should specify the 'primaryjoin' (and 'secondaryjoin', if there is an association table present) keyword arguments to the relation() function (or for backrefs, by specifying the backref using the backref() function with keyword arguments) to explicitly specify the join conditions. Nested error is \"%s\"" % (self.key, self.localparent, self.mapper, str(e))) # if the foreign key wasnt specified and theres no assocaition table, try to figure # out who is dependent on who. we dont need all the foreign keys represented in the join, # just one of them. diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py index 3650dd8b83..8ad3ced45b 100644 --- a/lib/sqlalchemy/sql.py +++ b/lib/sqlalchemy/sql.py @@ -1070,17 +1070,22 @@ class Join(FromClause): return column def _match_primaries(self, primary, secondary): crit = [] + constraints = util.Set() for fk in secondary.foreign_keys: if fk.references(primary): crit.append(primary.corresponding_column(fk.column) == fk.parent) + constraints.add(fk.constraint) self.foreignkey = fk.parent if primary is not secondary: for fk in primary.foreign_keys: if fk.references(secondary): crit.append(secondary.corresponding_column(fk.column) == fk.parent) + constraints.add(fk.constraint) self.foreignkey = fk.parent if len(crit) == 0: raise exceptions.ArgumentError("Cant find any foreign key relationships between '%s' and '%s'" % (primary.name, secondary.name)) +# elif len(constraints) > 1: +# raise exceptions.ArgumentError("Cant 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. %s" % (primary.name, secondary.name, constraints)) elif len(crit) == 1: return (crit[0]) else: diff --git a/test/engine/reflection.py b/test/engine/reflection.py index 7cb31fd77a..de2651bd2d 100644 --- a/test/engine/reflection.py +++ b/test/engine/reflection.py @@ -155,7 +155,8 @@ class ReflectionTest(PersistTest): self.assert_(and_(table.c.multi_id==table2.c.foo, table.c.multi_rev==table2.c.bar).compare(j.onclause)) finally: - meta.drop_all() + pass +# meta.drop_all() def testcheckfirst(self): meta = BoundMetaData(testbase.db) -- 2.47.2