Hi guys -
I'm not sure this is a bug or not, but if not it might be worth
adding a comment to the documentation.
Using the following schema:
table A:
pk
B_fk_1
B_fk_2
...
table B:
pk
info
After setting up A and B correctly (see attachment), and performing
cascade_mapper(A, B), looking at an instance of class A does not show
any B instances (nor is it unambiguous what they should be
called). There is, I note, a A.B entry, but with the value of
None. Looking at the engine log, it is obvious what is happening -
it is trying to find the B object which has a pk matching both B_fk_1
AND B_fk_2.
The documentation for cascade_mapper should be updated to either
state that it's not the right tool for this situation, or perhaps a
bug/feature request filed against cascade_mapper.
Incidentally, I am trying, as you can see from the attached larger
sample, to build up to a correctly specified mapper for this
testcase. This information may be in the docs already (if anyone
wants to submit chapter and verse...?).
Cheers!
--G
import sys
from sqlalchemy import *
engine = create_engine('sqlite', {'filename':':memory:'}, echo_uow=True)
info_table = Table('infos', engine,
Column('pk', Integer, primary_key=True),
Column('info', String))
rel_table = Table('rels', engine,
Column('pk', Integer, primary_key=True),
Column('info_fk_one', Integer, ForeignKey(info_table.c.pk),
nullable=False),
Column('info_fk_two', Integer, ForeignKey(info_table.c.pk),
nullable=False),
Column('rel_data', String))
info_table.create()
rel_table.create()
info_table.insert().execute(
{'pk':1, 'info':'pk_1_data'},
{'pk':2, 'info':'pk_2_data'},
{'pk':3, 'info':'pk_3_data'},
{'pk':4, 'info':'pk_4_data'},
{'pk':5, 'info':'pk_5_data'})
rel_table.insert().execute(
{'pk':1, 'info_fk_one':1, 'info_fk_two':3, 'rel_data':'1-1:3'},
{'pk':2, 'info_fk_one':2, 'info_fk_two':4, 'rel_data':'2-2:4'},
{'pk':3, 'info_fk_one':3, 'info_fk_two':5, 'rel_data':'3-3:5'})
# The raw SQL query we are trying to reproduce in SQLAlchemy
sql_result = engine.text("""
SELECT rels_1.pk, infos_1.info, infos_2.info, rels_1.rel_data
FROM rels AS rels_1, rels AS rels_2, infos AS infos_1, infos AS infos_2
WHERE rels_1.info_fk_one = infos_1.pk
AND rels_2.info_fk_two = infos_2.pk
AND rels_1.pk = rels_2.pk
""").execute().fetchall()
# Outputs:
# [(1, u'pk_1_data', u'pk_3_data', u'1-1:3'),
# (2, u'pk_2_data', u'pk_4_data', u'2-2:4'),
# (3, u'pk_3_data', u'pk_5_data', u'3-3:5')]
print 'The result from the SQL Query:\n' + repr(sql_result)
# Here's the same query, but as using SQLAlchemy tokens
info_t_2 = info_table.alias()
sas_result = select(
# First select the columns we wish to see, which are the same as the
ones in the above query.
[rel_table.c.pk, info_table.c.info, info_t_2.c.info,
rel_table.c.rel_data],
# Logically AND the following arguments together...
and_(
# Set the first relationship on our normal info_table
rel_table.c.info_fk_one == info_table.c.pk,
# Set the second relationship using the alias to tie together
the second foreign key relation
rel_table.c.info_fk_two == info_t_2.c.pk)
).execute().fetchall()
# Outputs:
# [(1, u'pk_1_data', u'pk_3_data', u'1-1:3'),
# (2, u'pk_2_data', u'pk_4_data', u'2-2:4'),
# (3, u'pk_3_data', u'pk_5_data', u'3-3:5')]
print 'The result from the SA SELECT Tokenized query:\n' + repr(sas_result)
assert repr(sql_result) == repr(sas_result)
print
# Cleanup a bit:
info_t_2 = None
# Here's the same query, but using SQLALchemy mappers:
# Create the classes to integrate the mappers with:
class Information(object):
pass
class Relationship(object):
pass
# Create the basic mappers, with no frills or modifications
Information.mapper = mapper(Information, info_table)
Relationship.mapper = mapper(Relationship, rel_table)
# Add the necessary properties
# Cascade mappers doesn't create any corresponding entries for the foreign keys
cascade_mappers(Relationship.mapper, Information.mapper)