From 79843330ae315f2572765d5d95158e7f0fe280c2 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 23 Dec 2005 05:19:48 +0000 Subject: [PATCH] mapper, when updating, only SET's those columns that have changed. this also allows "deferred" column properties to remain untouched by a save operation if they werent affected. --- lib/sqlalchemy/mapping/mapper.py | 38 ++++++++++++++++++++-------- lib/sqlalchemy/mapping/properties.py | 2 ++ test/mapper.py | 18 ++++++++++--- test/objectstore.py | 28 ++++++++++---------- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/lib/sqlalchemy/mapping/mapper.py b/lib/sqlalchemy/mapping/mapper.py index 9f7cc7177c..df308a1a22 100644 --- a/lib/sqlalchemy/mapping/mapper.py +++ b/lib/sqlalchemy/mapping/mapper.py @@ -413,7 +413,7 @@ class Mapper(object): t = sql.text(text, engine=self.primarytable.engine) return self.instances(t.execute(**params)) - def _getattrbycolumn(self, obj, column): + def _getpropbycolumn(self, column): try: prop = self.columntoproperty[column.original] except KeyError: @@ -422,8 +422,11 @@ class Mapper(object): raise "Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop)) except KeyError: raise "No column %s.%s is configured on mapper %s..." % (column.table.name, column.name, str(self)) - - return prop[0].getattr(obj) + return prop[0] + + def _getattrbycolumn(self, obj, column): + prop = self._getpropbycolumn(column) + return prop.getattr(obj) def _setattrbycolumn(self, obj, column, value): self.columntoproperty[column.original][0].setattr(obj, value) @@ -453,9 +456,11 @@ class Mapper(object): # print "SAVE_OBJ we are " + hash_key(self) + " obj: " + obj.__class__.__name__ + repr(id(obj)) params = {} - if not hasattr(obj, "_instance_key"): + isinsert = not hasattr(obj, "_instance_key") + if isinsert: self.extension.before_insert(self, obj) + hasdata = False for col in table.columns: #if col.primary_key: if pk.has_key(col): @@ -468,10 +473,20 @@ class Mapper(object): if value is not None: params[col.key] = value else: - params[col.key] = self._getattrbycolumn(obj, col) + if not isinsert: + prop = self._getpropbycolumn(col) + history = prop.get_history(obj, passive=True) + if history: + a = history.added_items() + if len(a): + params[col.key] = a[0] + hasdata = True + else: + params[col.key] = self._getattrbycolumn(obj, col) - if hasattr(obj, "_instance_key"): - update.append(params) + if not isinsert: + if hasdata: + update.append(params) else: insert.append((obj, params)) uow.register_saved_object(obj) @@ -481,9 +496,12 @@ class Mapper(object): for col in self.pks_by_table[table]: clause.clauses.append(col == sql.bindparam(col.table.name + "_" + col.key)) statement = table.update(clause) - c = statement.execute(*update) - if table.engine.supports_sane_rowcount() and c.rowcount != len(update): - raise "ConcurrencyError - updated rowcount %d does not match number of objects updated %d" % (c.cursor.rowcount, len(update)) + rows = 0 + for rec in update: + c = statement.execute(rec) + rows += c.cursor.rowcount + if table.engine.supports_sane_rowcount() and rows != len(update): + raise "ConcurrencyError - updated rowcount %d does not match number of objects updated %d" % (rows, len(update)) if len(insert): import sys statement = table.insert() diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py index b7b59508ef..fc2460e4ab 100644 --- a/lib/sqlalchemy/mapping/properties.py +++ b/lib/sqlalchemy/mapping/properties.py @@ -38,6 +38,8 @@ class ColumnProperty(MapperProperty): return getattr(object, self.key, None) def setattr(self, object, value): setattr(object, self.key, value) + def get_history(self, obj, passive=False): + return objectstore.global_attributes.get_history(obj, self.key, passive=passive) def hash_key(self): return "ColumnProperty(%s)" % repr([hash_key(c) for c in self.columns]) diff --git a/test/mapper.py b/test/mapper.py index fca87518ce..d07a6488b9 100644 --- a/test/mapper.py +++ b/test/mapper.py @@ -215,9 +215,19 @@ class DeferredTest(MapperSuperTest): print o2.description self.assert_sql(db, go, [ - ("SELECT orders.order_id AS orders_order_id, orders.user_id AS orders_user_id, orders.isopen AS orders_isopen FROM orders ORDER BY orders.oid", {}), + ("SELECT orders.order_id AS orders_order_id, orders.user_id AS orders_user_id, orders.isopen AS orders_isopen FROM orders ORDER BY orders.%s" % orders.rowid_column.key, {}), ("SELECT orders.description AS orders_description FROM orders WHERE orders.order_id = :orders_order_id", {'orders_order_id':3}) ]) + + def testsave(self): + m = mapper(Order, orders, properties={ + 'description':deferred(orders.c.description) + }) + + l = m.select() + o2 = l[2] + o2.isopen = 1 + objectstore.commit() def testgroup(self): """tests deferred load with a group""" @@ -233,7 +243,7 @@ class DeferredTest(MapperSuperTest): o2 = l[2] print o2.opened, o2.description, o2.userident self.assert_sql(db, go, [ - ("SELECT orders.order_id AS orders_order_id FROM orders ORDER BY orders.oid", {}), + ("SELECT orders.order_id AS orders_order_id FROM orders ORDER BY orders.%s" % orders.rowid_column.key, {}), ("SELECT orders.user_id AS orders_user_id, orders.description AS orders_description, orders.isopen AS orders_isopen FROM orders WHERE orders.order_id = :orders_order_id", {'orders_order_id':3}) ]) @@ -245,7 +255,7 @@ class DeferredTest(MapperSuperTest): l = m2.select() print l[2].user_id self.assert_sql(db, go, [ - ("SELECT orders.order_id AS orders_order_id, orders.description AS orders_description, orders.isopen AS orders_isopen FROM orders ORDER BY orders.oid", {}), + ("SELECT orders.order_id AS orders_order_id, orders.description AS orders_description, orders.isopen AS orders_isopen FROM orders ORDER BY orders.%s" % orders.rowid_column.key, {}), ("SELECT orders.user_id AS orders_user_id FROM orders WHERE orders.order_id = :orders_order_id", {'orders_order_id':3}) ]) objectstore.clear() @@ -255,7 +265,7 @@ class DeferredTest(MapperSuperTest): l = m3.select() print l[3].user_id self.assert_sql(db, go, [ - ("SELECT orders.order_id AS orders_order_id, orders.user_id AS orders_user_id, orders.description AS orders_description, orders.isopen AS orders_isopen FROM orders ORDER BY orders.oid", {}), + ("SELECT orders.order_id AS orders_order_id, orders.user_id AS orders_user_id, orders.description AS orders_description, orders.isopen AS orders_isopen FROM orders ORDER BY orders.%s" % orders.rowid_column.key, {}), ]) def testdeepoptions(self): diff --git a/test/objectstore.py b/test/objectstore.py index 9fbce16372..9da6437d3f 100644 --- a/test/objectstore.py +++ b/test/objectstore.py @@ -350,12 +350,14 @@ class SaveTest(AssertMixin): {'user_name': 'imnewlyadded'} ), ( - "UPDATE email_addresses SET user_id=:user_id, email_address=:email_address WHERE email_addresses.address_id = :email_addresses_address_id", - lambda: [ - {'email_address': 'imnew@foo.bar', 'user_id': objects[2].user.user_id, 'email_addresses_address_id': objects[2].address_id}, - {'email_address': 'adsd5@llala.net', 'user_id': objects[3].user.user_id, 'email_addresses_address_id': objects[3].address_id} - ] - ) + "UPDATE email_addresses SET email_address=:email_address WHERE email_addresses.address_id = :email_addresses_address_id", + lambda: [{'email_address': 'imnew@foo.bar', 'email_addresses_address_id': objects[2].address_id}] + ), + ( + "UPDATE email_addresses SET user_id=:user_id WHERE email_addresses.address_id = :email_addresses_address_id", + lambda: [{'user_id': objects[3].user.user_id, 'email_addresses_address_id': objects[3].address_id}] + ), + ]) l = sql.select([users, addresses], sql.and_(users.c.user_id==addresses.c.address_id, addresses.c.address_id==a.address_id)).execute() self.echo( repr(l.fetchone().row)) @@ -469,11 +471,11 @@ class SaveTest(AssertMixin): [{'users_user_id': u2.user_id, 'user_name': 'user2modified'}] ), ( - "UPDATE email_addresses SET user_id=:user_id, email_address=:email_address WHERE email_addresses.address_id = :email_addresses_address_id", - [ - {'email_address': 'emailaddress3', 'user_id': u1.user_id, 'email_addresses_address_id': a3.address_id}, - {'email_address': 'emailaddress1', 'user_id': None, 'email_addresses_address_id': a1.address_id} - ] + "UPDATE email_addresses SET user_id=:user_id WHERE email_addresses.address_id = :email_addresses_address_id", + [{'user_id': u1.user_id, 'email_addresses_address_id': a3.address_id}] + ), + ("UPDATE email_addresses SET user_id=:user_id WHERE email_addresses.address_id = :email_addresses_address_id", + [{'user_id': None, 'email_addresses_address_id': a1.address_id}] ) ]) @@ -582,8 +584,8 @@ class SaveTest(AssertMixin): objects[5].keywords.append(k) self.assert_sql(db, lambda:objectstore.commit(), [ ( - "UPDATE items SET order_id=:order_id, item_name=:item_name WHERE items.item_id = :items_item_id", - [{'item_name': 'item4updated', 'order_id': None, 'items_item_id': objects[4].item_id}] + "UPDATE items SET item_name=:item_name WHERE items.item_id = :items_item_id", + [{'item_name': 'item4updated', 'items_item_id': objects[4].item_id}] ), ( "INSERT INTO keywords (name) VALUES (:name)", -- 2.47.2