From a4477a53bb978a3eff401ce2105762371284eeac Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 8 Jan 2006 01:31:23 +0000 Subject: [PATCH] modified 'primarytable' idea and 'inheritance' to work better with improved relational algrebra stuff --- lib/sqlalchemy/mapping/mapper.py | 58 +++++++++++++++++----------- lib/sqlalchemy/mapping/properties.py | 49 +++++++++++------------ 2 files changed, 58 insertions(+), 49 deletions(-) diff --git a/lib/sqlalchemy/mapping/mapper.py b/lib/sqlalchemy/mapping/mapper.py index ee8f8ddc76..746ddb8b92 100644 --- a/lib/sqlalchemy/mapping/mapper.py +++ b/lib/sqlalchemy/mapping/mapper.py @@ -10,7 +10,7 @@ import sqlalchemy.schema as schema import sqlalchemy.engine as engine import sqlalchemy.util as util import objectstore - +import sys mapper_registry = {} @@ -35,7 +35,6 @@ class Mapper(object): self.copyargs = { 'class_':class_, 'table':table, - 'primarytable':primarytable, 'properties':properties or {}, 'primary_key':primary_key, 'is_primary':None, @@ -44,7 +43,10 @@ class Mapper(object): 'extension':extension, 'order_by':order_by } - + + if primarytable is not None: + sys.stderr.write("'primarytable' argument to mapper is deprecated\n") + if extension is None: self.extension = MapperExtension() else: @@ -55,19 +57,24 @@ class Mapper(object): self.order_by = order_by if not issubclass(class_, object): - raise "Class '%s' is not a new-style class" % class_.__name__ - - if inherits is not None: - primarytable = inherits.primarytable - # inherit_condition is optional since the join can figure it out - table = sql.join(table, inherits.table, inherit_condition) - + raise TypeError("Class '%s' is not a new-style class" % class_.__name__) + if isinstance(table, sql.Select): # some db's, noteably postgres, dont want to select from a select - # without an alias - self.table = table.alias(None) + # without an alias. also if we make our own alias internally, then + # the configured properties on the mapper are not matched against the alias + # we make, theres workarounds but it starts to get really crazy (its crazy enough + # the SQL that gets generated) so just require an alias + raise TypeError("Mapping against a Select object requires that it has a name. Use an alias to give it a name, i.e. s = select(...).alias('myselect')") else: self.table = table + + if inherits is not None: + self.primarytable = inherits.primarytable + # inherit_condition is optional since the join can figure it out + self.table = sql.join(table, inherits.table, inherit_condition) + else: + self.primarytable = self.table # locate all tables contained within the "table" passed in, which # may be a join or other construct @@ -75,13 +82,6 @@ class Mapper(object): self.table.accept_visitor(tf) self.tables = tf.tables - # determine "primary" table - if primarytable is None: - if len(self.tables) > 1: - raise "table contains multiple tables - specify primary table argument to Mapper" - self.primarytable = self.tables[0] - else: - self.primarytable = primarytable # determine primary key columns, either passed in, or get them from our set of tables self.pks_by_table = {} @@ -98,7 +98,7 @@ class Mapper(object): except KeyError: l = self.pks_by_table.setdefault(t, util.HashSet(ordered=True)) if not len(t.primary_key): - raise "Table " + t.name + " has no primary key columns. Specify primary_key argument to mapper." + raise ValueError("Table " + t.name + " has no primary key columns. Specify primary_key argument to mapper.") for k in t.primary_key: l.append(k) @@ -118,9 +118,17 @@ class Mapper(object): if properties is not None: for key, prop in properties.iteritems(): if sql.is_column(prop): + try: + prop = self.table._get_col_by_original(prop) + except KeyError: + raise ValueError("Column '%s' is not represented in mapper's table" % prop._label) self.columns[key] = prop prop = ColumnProperty(prop) elif isinstance(prop, list) and sql.is_column(prop[0]): + try: + prop = [self.table._get_col_by_original(p) for p in prop] + except KeyError, e: + raise ValueError("Column '%s' is not represented in mapper's table" % e.args[0]) self.columns[key] = prop[0] prop = ColumnProperty(*prop) self.props[key] = prop @@ -154,7 +162,7 @@ class Mapper(object): proplist.append(prop) self._get_clause = sql.and_() - for primary_key in self.pks_by_table[self.primarytable]: + for primary_key in self.pks_by_table[self.table]: self._get_clause.clauses.append(primary_key == sql.bindparam("pk_"+primary_key.key)) if ( @@ -248,22 +256,26 @@ class Mapper(object): return [result] + otherresults else: return result - + def get(self, *ident): """returns an instance of the object based on the given identifier, or None if not found. The *ident argument is a list of primary key columns in the order of the table def's primary key columns.""" key = objectstore.get_id_key(ident, self.class_, self.primarytable) #print "key: " + repr(key) + " ident: " + repr(ident) + return self._get(key, ident) + + def _get(self, key, ident=None): try: return objectstore.uow()._get(key) except KeyError: + if ident is None: + ident = key[2] i = 0 params = {} for primary_key in self.pks_by_table[self.primarytable]: params["pk_"+primary_key.key] = ident[i] i += 1 - print str(self._get_clause) try: return self.select(self._get_clause, params=params)[0] except IndexError: diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py index 2e1b7929d9..eb575d1dc4 100644 --- a/lib/sqlalchemy/mapping/properties.py +++ b/lib/sqlalchemy/mapping/properties.py @@ -66,10 +66,22 @@ class DeferredColumnProperty(ColumnProperty): def _copy(self): return DeferredColumnProperty(*self.columns) + def init(self, key, parent): + self.key = key + self.parent = parent + # establish a SmartProperty property manager on the object for this key, + # containing a callable to load in the attribute + if parent._is_primary_mapper(): + objectstore.uow().register_attribute(parent.class_, key, uselist=False, callable_=lambda i:self.setup_loader(i)) + def setup_loader(self, instance): def lazyload(): clause = sql.and_() - for primary_key in self.parent.pks_by_table[self.parent.primarytable]: + try: + pk = self.parent.pks_by_table[self.columns[0].table] + except KeyError: + pk = self.columns[0].table.primary_key + for primary_key in pk: attr = self.parent._getattrbycolumn(instance, primary_key) if not attr: return None @@ -96,14 +108,6 @@ class DeferredColumnProperty(ColumnProperty): def setup(self, key, statement, **options): pass - - def init(self, key, parent): - self.key = key - self.parent = parent - # establish a SmartProperty property manager on the object for this key, - # containing a callable to load in the attribute - if parent._is_primary_mapper(): - objectstore.uow().register_attribute(parent.class_, key, uselist=False, callable_=lambda i:self.setup_loader(i)) def execute(self, instance, row, identitykey, imap, isnew): if isnew: @@ -224,7 +228,8 @@ class PropertyLoader(MapperProperty): objectstore.uow().register_attribute(class_, key, uselist = self.uselist, deleteremoved = self.private, extension=self.attributeext) def _get_direction(self): - if self.parent.primarytable is self.target: +# print self.key, repr(self.parent.table.name), repr(self.parent.primarytable.name), repr(self.foreignkey.table.name) + if self.parent.table is self.target: if self.foreignkey.primary_key: return PropertyLoader.RIGHT else: @@ -233,7 +238,7 @@ class PropertyLoader(MapperProperty): return PropertyLoader.CENTER elif self.foreignkey.table == self.target: return PropertyLoader.LEFT - elif self.foreignkey.table == self.parent.primarytable: + elif self.foreignkey.table == self.parent.table: return PropertyLoader.RIGHT else: raise "Cant determine relation direction" @@ -297,7 +302,9 @@ class PropertyLoader(MapperProperty): self.primaryjoin.accept_visitor(processor) if self.secondaryjoin is not None: self.secondaryjoin.accept_visitor(processor) - + if len(self.syncrules) == 0: + raise "No syncrules generated for join criterion " + str(self.primaryjoin) + def get_criterion(self, key, value): """given a key/value pair, determines if this PropertyLoader's mapper contains a key of the given name in its property list, or if this PropertyLoader's association mapper, if any, @@ -362,17 +369,6 @@ class PropertyLoader(MapperProperty): if self.association is not None: # association object. our mapper should be dependent on both # the parent mapper and the association object mapper. - - # this seems to work, association->parent->self, then - # we process the child elements after the 'parent' save. but - # then the parent is dependent on the association which is - # somewhat arbitrary, might compete with some other dependency: - # uowcommit.register_dependency(self.association, self.parent) - # uowcommit.register_dependency(self.parent, self.mapper) - # #uowcommit.register_dependency(self.association, self.mapper) - # uowcommit.register_processor(self.parent, self, self.parent, False) - # uowcommit.register_processor(self.parent, self, self.parent, True) - # this is where we put the "stub" as a marker, so we get # association/parent->stub->self, then we process the child # elments after the 'stub' save, which is before our own @@ -599,8 +595,8 @@ class LazyLoader(PropertyLoader): # to possibly save a DB round trip if self.use_get: ident = [] - for primary_key in self.mapper.pks_by_table[self.mapper.primarytable]: - ident.append(params[self.mapper.primarytable.name + "_" + primary_key.key]) + for primary_key in self.mapper.pks_by_table[self.mapper.table]: + ident.append(params[self.mapper.table.name + "_" + primary_key.key]) return self.mapper.get(*ident) elif self.order_by is not False: order_by = self.order_by @@ -673,7 +669,8 @@ class EagerLoader(PropertyLoader): if self.secondaryjoin is not None: [self.to_alias.append(f) for f in self.secondaryjoin._get_from_objects()] try: - del self.to_alias[parent.primarytable] +# del self.to_alias[parent.primarytable] + del self.to_alias[parent.table] except KeyError: pass -- 2.47.2