I'm trying to make a patch to make join work with many-to-many relationships via an association table. This works for something like: a join b, but is having ambiguous column name problems if you attempt to do something like: a join b join c.
Patch attached as well as a test script illustrating what works and what not.
Any ideas Mike? Would this be useful?
--
---------------------------------------------------------------------------------------------------
John S. Yang
from sqlalchemy import *
db = engine.create_engine('sqlite://filename=:memory:', echo=True, default_ordering=True)
a = Table('a', db,
Column('id', Integer, Sequence('a_id_seq', optional=True), primary_key=True),
Column('name', String(30), nullable=False),
)
b = Table('b', db,
Column('id', Integer, Sequence('b_id_seq', optional=True), primary_key=True),
Column('name', String(30)),
)
c = Table('c', db,
Column('id', Integer, Sequence('c_id_seq', optional=True), primary_key=True),
Column('name', String(30)),
)
a_x_b = Table('a_x_b', db,
Column('a_id', Integer, ForeignKey('a.id')),
Column('b_id', Integer, ForeignKey('b.id')),
)
a_x_c = Table('a_x_c', db,
Column('a_id', Integer, ForeignKey('a.id')),
Column('c_id', Integer, ForeignKey('c.id')),
)
for t in [a, b, c, a_x_b, a_x_c]:
t.create()
# This works
a.join(b, middle=a_x_b).select().execute()
# This not works, complains about:
# ...
# File "/usr/lib/python2.4/site-packages/SQLAlchemy-0.1.3-py2.4.egg/sqlalchemy/engine.py", line 649, in _execute
# raise exceptions.SQLError(statement, parameters, e)
# sqlalchemy.exceptions.SQLError: (OperationalError) no such column: a_x_b.b_id
a.join(b, middle=a_x_b).join(c, middle=a_x_c).select().execute()
Index: lib/sqlalchemy/sql.py
===================================================================
--- lib/sqlalchemy/sql.py (revision 1102)
+++ lib/sqlalchemy/sql.py (working copy)
@@ -835,14 +835,17 @@
class Join(FromClause):
- def __init__(self, left, right, onclause=None, isouter = False):
+ def __init__(self, left, right, onclause=None, isouter = False,
middle=None):
self.left = left
self.right = right
self.id = self.left.id + "_" + self.right.id
# TODO: if no onclause, do NATURAL JOIN
if onclause is None:
- self.onclause = self._match_primaries(left, right)
+ if middle:
+ self.onclause = self._match_association(left, right, middle)
+ else:
+ self.onclause = self._match_primaries(left, right)
else:
self.onclause = onclause
self.isouter = isouter
@@ -858,6 +861,20 @@
if column.foreign_key:
self._foreign_keys.append(column.foreign_key)
return column
+ def _match_association(self, left, right, middle):
+ crit = []
+ for fk in middle.foreign_keys:
+ if fk.references(left):
+ crit.append(left._get_col_by_original(fk.column) == fk.parent)
+ if fk.references(right):
+ crit.append(right._get_col_by_original(fk.column) == fk.parent)
+ if len(crit) == 0:
+ raise ArgumentError("Cant find many-to-many relationships between
'%s' and '%s' with association '%s'" % (primary.name, secondary.name,
middle.name))
+ elif len(crit) == 1:
+ return (crit[0])
+ else:
+ return and_(*crit)
+
def _match_primaries(self, primary, secondary):
crit = []
for fk in secondary.foreign_keys:
Index: lib/sqlalchemy/engine.py
===================================================================
--- lib/sqlalchemy/engine.py (revision 1102)
+++ lib/sqlalchemy/engine.py (working copy)
@@ -687,7 +687,7 @@
def __init__(self, key):
self.key = key
def convert_result_value(self, arg, engine):
- raise InvalidRequestError("Ambiguous column name '%s' in result
set! try 'use_labels' option on select statement." % (self.key))
+ raise exceptions.InvalidRequestError("Ambiguous column name '%s'
in result set! try 'use_labels' option on select statement." % (self.key))
def __init__(self, cursor, engine, typemap = None):
"""ResultProxy objects are constructed via the execute() method on
SQLEngine."""

