On Jan 31, 2011, at 4:27 AM, Martijn Moeling wrote:

> Hi,
> 
> First I need to let you know I do everything declarative......
> 
> To make things understandable I have chosen to use objects a bit more close 
> to real-life.
> 
> 
> There is a base object Person with a one-to-many relationship to the table 
> addresses:
> 
> class Person(Base):
> __tablename__         =       "person"
> Id                                    = Column(Integer, primary_key=True)
> .....
> Addresses           = relation('Address',primaryjoin='Address.Person_Id == 
> Person.Id', cascade="all")
> .... 
> 
> Address is a polymorphic base type:
> 
> class Address(Base):
>       __tablename__           = "adresses"
>       Id                                      = Column(Integer, 
> primary_key=True)
>       discriminator                   = Column(Unicode(20))   
>       __mapper_args__ = {'Polymorphic_on': discriminator}
>       ....
>       ...
> 
> for a lot of address types to be appended to Person.Addresses like:
> 
> class EmailAddress(Address):
>     __tablename__       = "emailaddresses"
>     __mapper_args__     = {'polymorphic_identity' : u'emailaddress'} 
>     Id                  = Column(Integer,ForeignKey('addresses.Id'), 
> primary_key=True)
>     Value               = Column(Unicode(100))
> 
> class MSNAddress(Address):
>     __tablename__       = "msnaddresses"
>     __mapper_args__     = {'polymorphic_identity' : u'msnaddress'} 
>     Id                  = Column(Integer,ForeignKey('addresses.Id'), 
> primary_key=True)
> 
> class PostalAddress(Address):
>     __tablename__       = "postaladdresses"
>     Country             = Column(Unicode(2),primary_key=True)                 
>                               # Should "hold" the polymorphic Identity for 
> subclass like "NL","D","UK", "US"
> 
>     __mapper_args__     = {'polymorphic_identity' : u'postaladdress'}    
>                               # cannot add {'Polymorphic_on' : Counrty} to 
> this.... see below in text what happens if I do
> 
>     Id                  = Column(Integer,ForeignKey('addresses.Id'), 
> primary_key=True)
> 
> 
> 
> 
> Now I want to make say a Dutch postaladdress, a german postaladdress.
> The obvious way to do so is make the base class Polymorphic again but it will 
> "overwrite" the __mapper_args__ of the base class Address
> 
> class NLPostalAddress(PostalAddress):
>     __tablename__       = "nlpostaladdress"
>     __mapper_args__     = {'polymorphic_identity' : u'NL'} # will "overwrite" 
> the polymorphic Identity of PostalAddress base class
>     Id                  = Column(Integer,ForeignKey('postaladdresses.Id'), 
> primary_key=True)
>     Street              = Column(Unicode(100))
>     HouseNumber               = Column(Unicode(10))
>     ...
>     ...
> 
> 
> Person.Addresses.append(NLPostalAddress(Street = "somestreet", HouseNum 
> ......)) works but with: Address. discriminator stays empty. and 
> PostallAddress. Country will become 'NL'. 
> Which is logical (look at the comments in the "code") since the 
> polymorphic_on : Country makes the mapper "forget" it was polymorphic on 
> "Address.discriminator"
> 
> PostalAddress.Id makes the ID part work well......
> 
> Is there a way to "nest" polymorph classes in some way so the above works... 
> 
> One solution might be Concrete tables but I have not been able to get that 
> working either? since that would involve a "polymorphic union" I might be 
> missing something there... but I do not really understand the documentation 
> (I'm still using 0.5.8, upgrading is possible)
> 
> Again this is a "fake" data model to make my point
> 
> Any suggestions?

The first thing to be aware of is that a Query against Address is going to emit 
a SELECT statement against the "addresses" table only.  As rows come in, the 
"polymorphic_on" column is examined, and the resulting class, Address, 
EmailAddress, NLPostalAddress, is determined from this value.   So already, 
having a second discriminator column in a sub-table will not work at this 
level, assuming you would like to query polymorphically against the base 
"Address" class.  The reason mappers expect one and only one "polymorphic_on" 
column is because the value in this column represents the ultimate "type" of 
the object.    The restriction being worked with here is that the resulting 
class is based on a single column in the result set.     

An object in any strongly typed language has only one "type" - the scheme here 
attempts to define the "type" of each row in terms of a derivation of two 
different "supertypes" - "discriminator" and "country".    There's a way to get 
the mapper to see this, but it is more awkward.

It is possible to have a polymorphic mapper query from a special selectable 
called the "with_polymorphic" selectable- in this case, you'd create one that 
queries against "addresses" and left outer joins to "postaladdress", creates a 
column that is a concantenation between  "discriminator" and "country", then 
the mapper receives that.   This is the kind of thing that you need to have 
Table objects declared outside of the class definitions (though you could still 
use declarative with __table__) in order to accomplish.

So if you really wanted to see that I could show you, but it's not as practical 
of a solution - even simple queries against Address become nested select 
statements with a sub-performing OUTER JOIN built in.  You'd probably need to 
have Table metadata defined outside of your declarative classes for best 
results (there might be ways around that but I'd have to scratch my head for 
awhile).  "Country" and "discriminator" would be plain mapped columns that 
you'd need to populate yourself (here there are also ways to automate this 
population).   Selectables at each level of inheritance often need to be 
defined, e.g. if you wanted to query from EmailAddress, PostalAddress.   It's 
entirely customizable but once you go there you need to tell it everything.   
The documentation doesn't get into it very much since its an exotic use case.

The other approach would be just to consider the "discriminator" column on 
"Address", to be the ultimate type of object being returned, not just one of 
its supertypes.   This just means you're storing a discriminator in a form like 
"emailaddress_nl", "emailaddress_de".   Since this column encapsulates 
information from both "discrminator" and "country" it is by definition 
denormalized.  But it would offer the simplest configuration and the best 
performance.

The "two discriminator" case is interesting though and I'll see if I can think 
of some recipes that make it work, with restrictions.

If you want to limit which entities you can select from in the first place, 
i.e. every Query is only against EmailAddress or PostalAddress, never Address, 
that would also make it easier to come up with a solution.


> 
> 
> Martijn
> 
> 
> 
> 
> 
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To post to this group, send email to sqlalchemy@googlegroups.com.
> To unsubscribe from this group, send email to 
> sqlalchemy+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/sqlalchemy?hl=en.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to