From: Mike Bayer Date: Mon, 12 Mar 2012 20:14:14 +0000 (-0700) Subject: - add __table_cls__ option to declarative, not publicized yet, is for the moment X-Git-Tag: rel_0_7_6~18 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6c03a8ddd366b62285e2671a25a429f7bff1d052;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - add __table_cls__ option to declarative, not publicized yet, is for the moment for the benefit of the test.lib.schema package. - use test.lib.schema.Table for the table within test.lib.fixtures.DeclarativeMappedTest - [bug] Removed the check for number of rows affected when doing a multi-delete against mapped objects. If an ON DELETE CASCADE exists between two rows, we can't get an accurate rowcount from the DBAPI; this particular count is not supported on most DBAPIs in any case, MySQLdb is the notable case where it is. [ticket:2403] --- diff --git a/CHANGES b/CHANGES index cc5f663484..3936964c4c 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,16 @@ CHANGES invokes common table expression support from the Core (see below). [ticket:1859] + - [bug] Removed the check for number of + rows affected when doing a multi-delete + against mapped objects. If an ON DELETE + CASCADE exists between two rows, we can't + get an accurate rowcount from the DBAPI; + this particular count is not supported + on most DBAPIs in any case, MySQLdb + is the notable case where it is. + [ticket:2403] + - [bug] Fixed bug whereby objects using attribute_mapped_collection or column_mapped_collection could not be diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 891130a48d..faf575da1c 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -1213,6 +1213,12 @@ def _as_declarative(cls, classname, dict_): del our_stuff[key] cols = sorted(cols, key=lambda c:c._creation_order) table = None + + if hasattr(cls, '__table_cls__'): + table_cls = util.unbound_method_to_callable(cls.__table_cls__) + else: + table_cls = Table + if '__table__' not in dict_: if tablename is not None: @@ -1230,7 +1236,7 @@ def _as_declarative(cls, classname, dict_): if autoload: table_kw['autoload'] = True - cls.__table__ = table = Table(tablename, cls.metadata, + cls.__table__ = table = table_cls(tablename, cls.metadata, *(tuple(cols) + tuple(args)), **table_kw) else: diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index 31c9891b82..55b9bf84a1 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -642,12 +642,10 @@ def _emit_delete_statements(base_mapper, uowtransaction, cached_connections, for connection, del_objects in delete.iteritems(): statement = base_mapper._memo(('delete', table), delete_stmt) - rows = -1 connection = cached_connections[connection] - if need_version_id and \ - not connection.dialect.supports_sane_multi_rowcount: + if need_version_id: # TODO: need test coverage for this [ticket:1761] if connection.dialect.supports_sane_rowcount: rows = 0 @@ -656,6 +654,12 @@ def _emit_delete_statements(base_mapper, uowtransaction, cached_connections, for params in del_objects: c = connection.execute(statement, params) rows += c.rowcount + if rows != len(del_objects): + raise orm_exc.StaleDataError( + "DELETE statement on table '%s' expected to " + "delete %d row(s); %d were matched." % + (table.description, len(del_objects), c.rowcount) + ) else: util.warn( "Dialect %s does not support deleted rowcount " @@ -664,16 +668,8 @@ def _emit_delete_statements(base_mapper, uowtransaction, cached_connections, stacklevel=12) connection.execute(statement, del_objects) else: - c = connection.execute(statement, del_objects) - if connection.dialect.supports_sane_multi_rowcount: - rows = c.rowcount + connection.execute(statement, del_objects) - if rows != -1 and rows != len(del_objects): - raise orm_exc.StaleDataError( - "DELETE statement on table '%s' expected to " - "delete %d row(s); %d were matched." % - (table.description, len(del_objects), c.rowcount) - ) def _finalize_insert_update_commands(base_mapper, uowtransaction, states_to_insert, states_to_update): diff --git a/test/lib/fixtures.py b/test/lib/fixtures.py index 03116fbc11..41a72c9a49 100644 --- a/test/lib/fixtures.py +++ b/test/lib/fixtures.py @@ -1,4 +1,5 @@ from test.lib import testing +from test.lib import schema from test.lib.testing import adict from test.lib.engines import drop_all_tables import sys @@ -325,10 +326,11 @@ class DeclarativeMappedTest(MappedTest): cls_registry[classname] = cls return DeclarativeMeta.__init__( cls, classname, bases, dict_) + class DeclarativeBasic(object): + __table_cls__ = schema.Table _DeclBase = declarative_base(metadata=cls.declarative_meta, - metaclass=FindFixtureDeclarative) - class DeclarativeBasic(BasicEntity): - pass + metaclass=FindFixtureDeclarative, + cls=DeclarativeBasic) cls.DeclarativeBasic = _DeclBase fn() if cls.declarative_meta.tables: diff --git a/test/orm/test_unitofwork.py b/test/orm/test_unitofwork.py index b0dbfe3907..baf7754b3e 100644 --- a/test/orm/test_unitofwork.py +++ b/test/orm/test_unitofwork.py @@ -567,6 +567,31 @@ class PassiveDeletesTest(fixtures.MappedTest): mapper(MyClass, mytable) assert_raises(sa.exc.SAWarning, sa.orm.configure_mappers) +class BatchDeleteIgnoresRowcountTest(fixtures.DeclarativeMappedTest): + __requires__ = ('foreign_keys',) + @classmethod + def setup_classes(cls): + class A(cls.DeclarativeBasic): + __tablename__ = 'A' + __table_args__ = dict(test_needs_fk=True) + id = Column(Integer, primary_key=True) + parent_id = Column(Integer, ForeignKey('A.id', ondelete='CASCADE')) + + def test_delete_both(self): + A = self.classes.A + session = Session(testing.db) + + a1, a2 = A(id=1),A(id=2, parent_id=1) + + session.add_all([a1, a2]) + session.flush() + + session.delete(a1) + session.delete(a2) + + # no issue with multi-row count here + session.flush() + class ExtraPassiveDeletesTest(fixtures.MappedTest): __requires__ = ('foreign_keys',)