from sqlalchemy import *
import sys

# this example illustrates how to create a relationship to a list of objects,
# where each object in the list has a different type.  The typed objects will
# extend from a common base class, although this same approach can be used
# with 

db = create_engine('sqlite://', echo=True, echo_uow=False)
#db = create_engine('postgres://user=scott&password=tiger&host=127.0.0.1&database=test', echo=True, echo_uow=False)

# a table to store companies
companies = Table('companies', db, 
   Column('company_id', Integer, primary_key=True),
   Column('name', String(50))).create()

# we will define an inheritance relationship between the table "people" and "engineers",
# and a second inheritance relationship between the table "people" and "managers"
people = Table('people', db, 
   Column('person_id', Integer, primary_key=True),
   Column('company_id', Integer, ForeignKey('companies.company_id')),
   Column('name', String(50))).create()
   
engineers = Table('engineers', db, 
   Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True),
   Column('special_description', String(50))).create()
   
managers = Table('managers', db, 
   Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True),
   Column('description', String(50))).create()

  
# create our classes.  The Engineer and Manager classes extend from Person.
class Person(object):
    def __repr__(self):
        return "Ordinary person %s" % self.name
class Engineer(Person):
    def __repr__(self):
        return "Engineer %s, description %s" % (self.name, self.special_description)
class Manager(Person):
    def __repr__(self):
        return "Manager %s, description %s" % (self.name, self.description)
class Company(object):
    def __repr__(self):
        return "Company %s" % self.name

# next we assign Person mappers.  Since these are the first mappers we are
# creating for these classes, they automatically become the "primary mappers", which
# define the dependency relationships between the classes, so we do a straight
# inheritance setup, i.e. no modifications to how objects are loaded or anything like that.
assign_mapper(Person, people)
assign_mapper(Engineer, engineers, inherits=Person.mapper)
assign_mapper(Manager, managers, inherits=Person.mapper)

# next, we define a query that is going to load Managers and Engineers in one shot.
# we will use a UNION ALL with an extra hardcoded column to indicate the type of object.
# this can also be done via several LEFT OUTER JOINS but a UNION is more appropriate
# since they are distinct result sets.
# The select() statement is also given an alias 'pjoin', since the mapper requires
# that all Selectables have a name.  
#
# TECHNIQUE - when you want to load a certain set of objects from a in one query, all the
# columns in the Selectable must have unique names.  Dont worry about mappers at this point,
# just worry about making a query where if you were to view the results, you could tell everything
# you need to know from each row how to construct an object instance from it.  this is the
# essence of "resultset-based-mapping", which is the core ideology of SQLAlchemy.
#
person_join = select(
                [people, managers.c.description,column("'manager'").label('type')], 
                people.c.person_id==managers.c.person_id).union_all(
            select(
            [people, engineers.c.special_description.label('description'), column("'engineer'").label('type')],
            people.c.person_id==engineers.c.person_id)).alias('pjoin')
            

# lets print out what this Selectable looks like.  The mapper is going to take the selectable and
# Select off of it, with the flag "use_labels" which indicates to prefix column names with the table
# name.  So here is what our mapper will see:
print "Person selectable:", str(person_join.select(use_labels=True)), "\n"


# MapperExtension object.
class PersonLoader(MapperExtension):
    def create_instance(self, mapper, row, imap, class_):
        if row['pjoin_type'] =='engineer':
            e = Engineer()
            e.special_description = row['pjoin_description']
            return e
        elif row['pjoin_type'] =='manager':
            return Manager()
        else:
            return Person()
ext = PersonLoader()

# set up the polymorphic mapper, which maps the person_join we set up to
# the Person class, using an instance of PersonLoader.  
people_mapper = mapper(Person, person_join, extension=ext)

# create a mapper for Company.  the 'employees' relationship points to 
# our new people_mapper. 
#
# the dependency relationships which take effect on commit (i.e. the order of 
# inserts/deletes) will be established against the Person class's primary 
# mapper, and when the Engineer and 
# Manager objects are found in the 'employees' list, the primary mappers
# for those subclasses will register 
# themselves as dependent on the Person mapper's save operations.
# (translation: it'll work)
# TODO: get the eager loading to work (the compound select alias doesnt like being aliased itself)
assign_mapper(Company, companies, properties={
    'employees': relation(people_mapper, lazy=False, private=True)
})

c = Company(name='company1')
c.employees.append(Manager(name='pointy haired boss', description='manager1'))
c.employees.append(Engineer(name='dilbert', special_description='engineer1'))
c.employees.append(Engineer(name='wally', special_description='engineer2'))
c.employees.append(Manager(name='jsmith', description='manager2'))
objectstore.commit()

objectstore.clear()

c = Company.get(1)
for e in c.employees:
    print e, e._instance_key

print "\n"

dilbert = Engineer.mapper.get_by(name='dilbert')
dilbert.special_description = 'hes dibert!'
objectstore.commit()

objectstore.clear()
c = Company.get(1)
for e in c.employees:
    print e, e._instance_key

objectstore.delete(c)
objectstore.commit()


managers.drop()
engineers.drop()
people.drop()
companies.drop()
