From: Mike Bayer Date: Sun, 28 Aug 2005 19:36:41 +0000 (+0000) Subject: (no commit message) X-Git-Tag: rel_0_1_0~827 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3d276a682e034abfca68567d1533e20d797efa6b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git --- diff --git a/lib/sqlalchemy/ansisql.py b/lib/sqlalchemy/ansisql.py index e0dcc58bd7..1782a29f08 100644 --- a/lib/sqlalchemy/ansisql.py +++ b/lib/sqlalchemy/ansisql.py @@ -68,12 +68,17 @@ class ANSICompiler(sql.Compiled): def get_whereclause(self, obj): return self.wheres.get(obj, None) - + def get_params(self, **params): """returns the bind params for this compiled object, with values overridden by those given in the **params dictionary""" d = {} - for key, value in params.iteritems(): + if self.bindparams is not None: + bindparams = self.bindparams.copy() + else: + bindparams = {} + bindparams.update(params) + for key, value in bindparams.iteritems(): try: b = self.binds[key] except KeyError: @@ -84,7 +89,7 @@ class ANSICompiler(sql.Compiled): d.setdefault(b.key, b.value) return d - + def visit_column(self, column): if column.table.name is None: self.strings[column] = column.name @@ -121,19 +126,18 @@ class ANSICompiler(sql.Compiled): result += " " + self.get_str(binary.right) if binary.parens: result = "(" + result + ")" - self.strings[binary] = result - + def visit_bindparam(self, bindparam): self.binds[bindparam.shortname] = bindparam - count = 1 key = bindparam.key - + + # redefine the generated name of the bind param in the case + # that we have multiple conflicting bind parameters. while self.binds.setdefault(key, bindparam) is not bindparam: key = "%s_%d" % (bindparam.key, count) count += 1 - self.strings[bindparam] = ":" + key def visit_alias(self, alias): diff --git a/lib/sqlalchemy/engine.py b/lib/sqlalchemy/engine.py index e6183c328f..7689c53e82 100644 --- a/lib/sqlalchemy/engine.py +++ b/lib/sqlalchemy/engine.py @@ -132,7 +132,7 @@ class SQLEngine(schema.SchemaEngine): if echo is True or self._echo: self.log(statement) - self.log(repr(parameters)) + self.log("here are the params: " + repr(parameters)) if connection is None: poolconn = self.connection() diff --git a/lib/sqlalchemy/mapper.py b/lib/sqlalchemy/mapper.py index d8b6caf465..c5d2e8c193 100644 --- a/lib/sqlalchemy/mapper.py +++ b/lib/sqlalchemy/mapper.py @@ -166,7 +166,7 @@ class Mapper(object): *options is a list of options directives, which include eagerload() and lazyload()""" hashkey = hash_key(self) + "->" + repr([hash_key(o) for o in options]) - print "HASHKEY: " + hashkey + #print "HASHKEY: " + hashkey try: return _mappers[hashkey] except KeyError: @@ -321,6 +321,8 @@ class Mapper(object): class MapperOption: + """describes a modification to a Mapper in the context of making a copy + of it. This is used to assist in the prototype pattern used by mapper.options().""" def process(self, mapper): raise NotImplementedError() @@ -328,7 +330,7 @@ class MapperOption: return repr(self) class MapperProperty: - """an element attached to a Mapper that describes the loading and population + """an element attached to a Mapper that describes and assists in the loading and saving of an attribute on an object instance.""" def execute(self, instance, row, identitykey, localmap, isduplicate): """called when the mapper receives a row. instance is the parent instance corresponding @@ -337,7 +339,8 @@ class MapperProperty: def hash_key(self): """describes this property and its instantiated arguments in such a way - as to uniquely identify the concept this MapperProperty represents""" + as to uniquely identify the concept this MapperProperty represents,within + a process.""" raise NotImplementedError() def setup(self, key, primarytable, statement, **options): @@ -347,15 +350,17 @@ class MapperProperty: 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): + """called when the instance is being saved""" pass - + def delete(self, object): + """called when the instance is being deleted""" pass class ColumnProperty(MapperProperty): - """describes an object attribute that corresponds to the value in a result set column.""" + """describes an object attribute that corresponds to a table column.""" def __init__(self, column): self.column = column @@ -379,27 +384,6 @@ class ColumnProperty(MapperProperty): setattr(instance, self.key, row[self.column.label]) -def hash_key(obj): - if obj is None: - return 'None' - else: - return obj.hash_key() - -def mapper_hash_key(class_, selectable, table = None, properties = None, identitymap = None, use_smart_properties = True, isroot = True, echo = None): - if properties is None: - properties = {} - return ( - "Mapper(%s, %s, table=%s, properties=%s, identitymap=%s, use_smart_properties=%s, echo=%s)" % ( - repr(class_), - hash_key(selectable), - hash_key(table), - repr(dict([(k, hash_key(p)) for k,p in properties.iteritems()])), - hash_key(identitymap), - repr(use_smart_properties), - repr(echo) - - ) - ) class PropertyLoader(MapperProperty): def __init__(self, mapper, secondary, primaryjoin, secondaryjoin): @@ -448,9 +432,9 @@ class LazyLoader(PropertyLoader): PropertyLoader.init(self, key, parent, root) if not hasattr(parent.class_, key): if not issubclass(parent.class_, object): - raise "LazyLoader can only be used with new-style classes, i.e. subclass object" + raise "LazyLoader can only be used with new-style classes" setattr(parent.class_, key, SmartProperty(key).property()) - + def setup(self, key, primarytable, statement, **options): if self.secondaryjoin is not None: self.lazywhere = sql.and_(self.primaryjoin, self.secondaryjoin) @@ -465,17 +449,37 @@ class LazyLoader(PropertyLoader): if not isduplicate: setattr(instance, self.key, LazyLoadInstance(self, row)) +class LazyIzer(sql.ClauseVisitor): + """converts an expression which refers to a table column into an + expression refers to a Bind Param, i.e. a specific value. + e.g. the clause 'WHERE tablea.foo=tableb.foo' becomes 'WHERE tablea.foo=:foo'. + this is used to turn a join expression into one useable by a lazy load + for a specific parent row.""" + + def __init__(self, table): + self.table = table + self.binds = {} + + def visit_binary(self, binary): + if isinstance(binary.left, schema.Column) and binary.left.table == self.table: + binary.left = self.binds.setdefault(self.table.name + "_" + binary.left.name, + sql.BindParamClause(self.table.name + "_" + binary.left.name, None, shortname = binary.left.name)) + + if isinstance(binary.right, schema.Column) and binary.right.table == self.table: + binary.right = self.binds.setdefault(self.table.name + "_" + binary.right.name, + sql.BindParamClause(self.table.name + "_" + binary.right.name, None, shortname = binary.right.name)) + class LazyLoadInstance(object): - """attached to a specific object instance to load related rows. this is implemetned - as a callable object, rather than a closure, to allow serialization of the target object""" + """attached to a specific object instance to load related rows.""" def __init__(self, lazyloader, row): self.params = {} for key, value in lazyloader.binds.iteritems(): self.params[key] = row[key] - # TODO: dont attach to the mapper, its huge. - # figure out some way to shrink this. + # TODO: this still sucks. the mapper points to tables, which point + # to dbengines, which cant be serialized, or are too huge to be serialized + # quickly, so an object with a lazyloader still cant really be serialized self.mapper = lazyloader.mapper - + self.lazywhere = lazyloader.lazywhere def __call__(self): return self.mapper.select(self.lazywhere, **self.params) @@ -535,7 +539,7 @@ class EagerLazySwitcher(MapperOption): def hash_key(self): return "EagerLazySwitcher(%s, %s)" % (repr(self.key), repr(self.toeager)) - + def process(self, mapper): oldprop = mapper.props[self.key] if self.toeager: @@ -545,6 +549,7 @@ class EagerLazySwitcher(MapperOption): mapper.set_property(self.key, class_(oldprop.mapper, oldprop.secondary, primaryjoin = oldprop.primaryjoin, secondaryjoin = oldprop.secondaryjoin)) class Aliasizer(sql.ClauseVisitor): + """converts a table instance within an expression to be an alias of that table.""" def __init__(self, table, aliasname): self.table = table self.alias = sql.alias(table, aliasname) @@ -556,19 +561,6 @@ class Aliasizer(sql.ClauseVisitor): if isinstance(binary.right, schema.Column) and binary.right.table == self.table: binary.right = self.alias.c[binary.right.name] -class LazyIzer(sql.ClauseVisitor): - def __init__(self, table): - self.table = table - self.binds = {} - - def visit_binary(self, binary): - if isinstance(binary.left, schema.Column) and binary.left.table == self.table: - binary.left = self.binds.setdefault(self.table.name + "_" + binary.left.name, - sql.BindParamClause(self.table.name + "_" + binary.left.name, None, shortname = binary.left.name)) - - if isinstance(binary.right, schema.Column) and binary.right.table == self.table: - binary.right = self.binds.setdefault(self.table.name + "_" + binary.right.name, - sql.BindParamClause(self.table.name + "_" + binary.right.name, None, shortname = binary.left.name)) class LazyRow(MapperProperty): def __init__(self, table, whereclause, **options): @@ -598,18 +590,11 @@ class SmartProperty(object): v = s.__dict__[self.key] except KeyError: raise AttributeError(self.key) - if isinstance(v, types.FunctionType): + if callable(v): s.__dict__[self.key] = v() return s.__dict__[self.key] return property(get_prop, set_prop, del_prop) -def match_primaries(primary, secondary): - pk = primary.primary_keys - if len(pk) == 1: - return (pk[0] == secondary.c[pk[0].name]) - else: - return sql.and_([pk == secondary.c[pk.name] for pk in primary.primary_keys]) - class IdentityMap(dict): def get_id_key(self, ident, class_, table, selectable): return (class_, table, tuple(ident)) @@ -620,5 +605,36 @@ class IdentityMap(dict): def hash_key(self): return "IdentityMap(%s)" % id(self) + +def hash_key(obj): + if obj is None: + return 'None' + else: + return obj.hash_key() + +def mapper_hash_key(class_, selectable, table = None, properties = None, identitymap = None, use_smart_properties = True, isroot = True, echo = None): + if properties is None: + properties = {} + return ( + "Mapper(%s, %s, table=%s, properties=%s, identitymap=%s, use_smart_properties=%s, echo=%s)" % ( + repr(class_), + hash_key(selectable), + hash_key(table), + repr(dict([(k, hash_key(p)) for k,p in properties.iteritems()])), + hash_key(identitymap), + repr(use_smart_properties), + repr(echo) + + ) + ) + +def match_primaries(primary, secondary): + pk = primary.primary_keys + if len(pk) == 1: + return (pk[0] == secondary.c[pk[0].name]) + else: + return sql.and_([pk == secondary.c[pk.name] for pk in primary.primary_keys]) + + _global_identitymap = IdentityMap() diff --git a/test/mapper.py b/test/mapper.py index 1e2eabd926..c44d8dcfc5 100644 --- a/test/mapper.py +++ b/test/mapper.py @@ -39,7 +39,7 @@ class AssertMixin(PersistTest): print repr(result) self.assert_list(result, class_, objects) def assert_list(self, result, class_, list): - for i in range(0, len(result)): + for i in range(0, len(list)): self.assert_row(class_, result[i], list[i]) def assert_row(self, class_, rowobj, desc): self.assert_(rowobj.__class__ is class_, "item class is not " + repr(class_)) diff --git a/test/select.py b/test/select.py index 1c2e4562cd..b55d414b88 100644 --- a/test/select.py +++ b/test/select.py @@ -286,6 +286,12 @@ EXISTS (select yay from foo where boo = lar)", FROM mytable, myothertable WHERE mytable.myid = myothertable.otherid AND mytable.name = :mytablename" ) + # check that the bind params sent along with a compile() call + # get preserved when the params are retreived later + s = select([table], table.c.id == bindparam('test')) + c = s.compile(bindparams = {'test' : 7}) + self.assert_(c.get_params() == {'test' : 7}) + def testcorrelatedsubquery(self): self.runtest( select([table], table.c.id == select([table2.c.id], table.c.name == table2.c.name)),