From: Mike Bayer Date: Thu, 21 Jul 2005 04:05:01 +0000 (+0000) Subject: (no commit message) X-Git-Tag: rel_0_1_0~863 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fa029a0446882ed5387c4998e2b90587b6e6b80b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git --- diff --git a/lib/sqlalchemy/databases/sqlite.py b/lib/sqlalchemy/databases/sqlite.py index e5577d7f3a..d989d1d663 100644 --- a/lib/sqlalchemy/databases/sqlite.py +++ b/lib/sqlalchemy/databases/sqlite.py @@ -57,6 +57,9 @@ class SQLiteSQLEngine(ansisql.ANSISQLEngine): def columnimpl(self, column): return SQLiteColumnImpl(column) + def reflecttable(self, table): + raise NotImplementedError() + class SQLiteColumnImpl(sql.ColumnSelectable): def _get_specification(self): coltype = self.column.type diff --git a/lib/sqlalchemy/mapper.py b/lib/sqlalchemy/mapper.py index 4f991be3cc..583c84a7d0 100644 --- a/lib/sqlalchemy/mapper.py +++ b/lib/sqlalchemy/mapper.py @@ -27,16 +27,23 @@ usermapper.select("user_id LIKE "%foo%") import sqlalchemy.sql as sql import sqlalchemy.schema as schema import sqlalchemy.engine as engine -import weakref, random +import sqlalchemy.util as util +import weakref, random, copy -__ALL__ = ['eagermapper', 'eagerloader', 'mapper', 'lazyloader', 'lazymapper', 'identitymap', 'globalidentity'] +__ALL__ = ['eagermapper', 'eagerloader', 'lazymapper', 'lazyloader', 'eagerload', 'lazyload', 'mapper', 'lazyloader', 'lazymapper', 'identitymap', 'globalidentity'] +def lazymapper(class_, selectable, whereclause, table = None, properties = None, **options): + return lazyloader(mapper(class_, selectable, table = table, properties = properties, isroot = False), whereclause, **options) + def eagermapper(class_, selectable, whereclause, table = None, properties = None, **options): return eagerloader(mapper(class_, selectable, table = table, properties = properties, isroot = False), whereclause, **options) def eagerloader(mapper, whereclause, **options): return EagerLoader(mapper, whereclause, **options) +def lazyloader(mapper, whereclause, **options): + return LazyLoader(mapper, whereclause, **options) + def mapper(class_, selectable, table = None, properties = None, identitymap = None, use_smart_properties = True, isroot = True): return Mapper(class_, selectable, table = table, properties = properties, identitymap = identitymap, use_smart_properties = use_smart_properties, isroot = isroot) @@ -45,6 +52,12 @@ def identitymap(): def globalidentity(): return _global_identitymap + +def eagerload(name): + return EagerOption(name) + +def lazyload(name): + return LazyOption(name) class Mapper(object): def __init__(self, class_, selectable, table = None, properties = None, identitymap = None, use_smart_properties = True, isroot = True): @@ -72,7 +85,12 @@ class Mapper(object): if isroot: self.init(self) + def set_property(self, key, prop): + self.props[key] = prop + prop.init(key, self, self.root) + def init(self, root): + self.root = root self.identitymap = root.identitymap [prop.init(key, self, root) for key, prop in self.props.iteritems()] @@ -92,6 +110,17 @@ class Mapper(object): def get(self, id): """returns an instance of the object based on the given ID.""" pass + + def compile(self, whereclause = None, **options): + """works like select, except returns the SQL statement object without + compiling or executing it""" + return self._compile(whereclause, **options) + + def options(self, *options): + mapper = copy.copy(self) + for option in options: + option.process(mapper) + return mapper def select(self, arg = None, **params): """selects instances of the object from the database. @@ -117,8 +146,11 @@ class Mapper(object): of the attribute, determines if the item is saved. if smart attributes are not being used, the item is saved unconditionally. """ - if not getattr(object, 'dirty', True): - return + if getattr(object, 'dirty', True): + pass + # do the save + for prop in self.props.values(): + prop.save(object, traverse, refetch) def remove(self, object, traverse = True): """removes the object. traverse indicates attached objects should be removed as well.""" @@ -149,11 +181,16 @@ class Mapper(object): tf = Mapper.TableFinder() selectable.accept_visitor(tf) return tf.table - - def _select_whereclause(self, whereclause = None, **params): + + def _compile(self, whereclause = None, **options): statement = sql.select([self.selectable], whereclause) for key, value in self.props.iteritems(): - value.setup(key, self.selectable, statement) + value.setup(key, self.selectable, statement, **options) + statement.use_labels = True + return statement + + def _select_whereclause(self, whereclause = None, **params): + statement = self._compile(whereclause) return self._select_statement(statement, **params) def _select_statement(self, statement, **params): @@ -186,7 +223,7 @@ class Mapper(object): # call further mapper properties on the row, to pull further # instances from the row and possibly populate this item. for key, prop in self.props.iteritems(): - prop.execute(instance, key, row, identitykey, localmap, exists) + prop.execute(instance, row, identitykey, localmap, exists) # now add to the result list, but we only want to add # to the result list uniquely, so get another identity map @@ -200,24 +237,32 @@ class Mapper(object): result.append(instance) - +class MapperOption: + def process(self, mapper): + raise NotImplementedError() + class MapperProperty: - def execute(self, instance, key, row, isduplicate): + def execute(self, instance, row, isduplicate): """called when the mapper receives a row. instance is the parent instance corresponding to the row. """ raise NotImplementedError() - def setup(self, key, primarytable, statement): - """called when a statement is being constructed.""" - pass + def setup(self, key, primarytable, statement, **options): + """called when a statement is being constructed. """ + return self def init(self, key, parent, root): """called when the MapperProperty is first attached to a new parent Mapper.""" pass - + def save(self, object, traverse, refetch): + pass + def delete(self, object): + pass + class ColumnProperty(MapperProperty): def __init__(self, column): self.column = column def init(self, key, parent, root): + self.key = key if root.use_smart_properties: self.use_smart = True if not hasattr(parent.class_, key): @@ -225,30 +270,44 @@ class ColumnProperty(MapperProperty): else: self.use_smart = False - def execute(self, instance, key, row, identitykey, localmap, isduplicate): + def execute(self, instance, row, identitykey, localmap, isduplicate): if not isduplicate: if self.use_smart: - instance.__dict__[key] = row[self.column.label] + instance.__dict__[self.key] = row[self.column.label] else: - setattr(instance, key, row[self.column.label]) + setattr(instance, self.key, row[self.column.label]) - -class EagerLoader(MapperProperty): + +class PropertyLoader(MapperProperty): def __init__(self, mapper, whereclause, **options): self.mapper = mapper self.whereclause = whereclause - + def init(self, key, parent, root): + self.key = key self.mapper.init(root) + + def save(self, object, traverse, refetch): + self.mapper.save(object, ) + def delete(self): + self.mapper.delete() - def setup(self, key, primarytable, statement): +class LazyLoader(PropertyLoader): + pass + +class EagerLoader(PropertyLoader): + def setup(self, key, primarytable, statement, **options): """add a left outer join to the statement thats being constructed""" targettable = self.mapper.selectable if statement.whereclause is not None: - #if the whereclause of the statement contains the table we eager load against, - # "aliasize" the whereclause into a new selectable unit - for target in [targettable]: # + self.whereclause._get_from_objects(): + # if the whereclause of the statement references tables that are also + # in the outer join we are constructing, then convert those objects to + # reference "aliases" of those tables so that their where condition does not interfere + # with ours + targets = util.Set([targettable] + self.whereclause._get_from_objects()) + del targets[primarytable] + for target in targets: aliasizer = Aliasizer(target, "aliased_" + target.name + "_" + hex(random.randint(0, 65535))[2:]) statement.whereclause.accept_visitor(aliasizer) statement.append_from(aliasizer.alias) @@ -262,16 +321,34 @@ class EagerLoader(MapperProperty): for key, value in self.mapper.props.iteritems(): value.setup(key, self.mapper.selectable, statement) - def execute(self, instance, key, row, identitykey, localmap, isduplicate): + def execute(self, instance, row, identitykey, localmap, isduplicate): """receive a row. tell our mapper to look for a new object instance in the row, and attach it to a list on the parent instance.""" try: - list = getattr(instance, key) + list = getattr(instance, self.key) except AttributeError: list = [] - setattr(instance, key, list) + setattr(instance, self.key, list) self.mapper._instance(row, localmap, list) +class EagerOption(MapperOption): + """an option that switches a PropertyLoader to be an EagerLoader""" + def __init__(self, key): + self.key = key + + def process(self, mapper): + oldprop = mapper.props[self.key] + mapper.set_property(self.key, EagerLoader(oldprop.mapper, oldprop.whereclause)) + +class LazyOption(MapperOption): + """an option that switches a PropertyLoader to be a LazyLoader""" + def __init__(self, key): + self.key = key + + def process(self, mapper): + oldprop = mapper.props[self.key] + mapper.set_property(self.key, LazyLoader(oldprop.mapper, oldprop.whereclause)) + class Aliasizer(sql.ClauseVisitor): def __init__(self, table, aliasname): self.table = table