From: Mike Bayer Date: Sun, 11 Apr 2010 20:37:49 +0000 (-0400) Subject: - The functionality of result.rowcount is now disabled X-Git-Tag: rel_0_6_0~23^2~15^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=47ef5ed5f72b9d65a48780ee561ae84e8b05becb;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - The functionality of result.rowcount is now disabled by default, and can be re-enabled using the 'enable_rowcount' flag with create_engine(), as well as the 'enable_rowcount' execution context flag on a per-execute basis. This because cursor.rowcount requires cursor access (can't be evaluated lazily since the result auto-closes) and also incurs an expensive round-trip. --- diff --git a/CHANGES b/CHANGES index 5e44313e94..b8031869db 100644 --- a/CHANGES +++ b/CHANGES @@ -134,7 +134,16 @@ CHANGES - STRING/FIXED_CHAR now convert to unicode natively. SQLAlchemy's String types then don't need to apply any kind of conversions. - + +- firebird + - The functionality of result.rowcount is now disabled + by default, and can be re-enabled using the 'enable_rowcount' + flag with create_engine(), as well as the 'enable_rowcount' + execution context flag on a per-execute basis. This because + cursor.rowcount requires cursor access (can't be evaluated + lazily since the result auto-closes) and also incurs an + expensive round-trip. + - examples - Updated attribute_shard.py example to use a more robust method of searching a Query for binary expressions which diff --git a/lib/sqlalchemy/dialects/firebird/kinterbasdb.py b/lib/sqlalchemy/dialects/firebird/kinterbasdb.py index 9984d32a28..890ba83fe6 100644 --- a/lib/sqlalchemy/dialects/firebird/kinterbasdb.py +++ b/lib/sqlalchemy/dialects/firebird/kinterbasdb.py @@ -13,21 +13,34 @@ The connection URL is of the form Kinterbasedb backend specific keyword arguments are: -type_conv - select the kind of mapping done on the types: by default SQLAlchemy +* type_conv - select the kind of mapping done on the types: by default SQLAlchemy uses 200 with Unicode, datetime and decimal support (see details__). -concurrency_level - set the backend policy with regards to threading issues: by default +* concurrency_level - set the backend policy with regards to threading issues: by default SQLAlchemy uses policy 1 (see details__). +* enable_rowcount - False by default, this enables the usage of "cursor.rowcount" with the + Kinterbasdb dialect. When disabled, SQLAlchemy's ResultProxy will + return -1 for result.rowcount. The rationale here is that Kinterbasdb + requires a second round trip to the database when .rowcount is called - + since SQLA's resultproxy automatically closes the cursor after a + non-result-returning statement, rowcount must be called, if at all, + before the result object is returned. The behavior can also be + controlled on a per-execution basis using the `enable_rowcount` + option with :meth:`execution_options()`:: + + conn = engine.connect().execution_options(enable_rowcount=True) + r = conn.execute(stmt) + print r.rowcount + __ http://sourceforge.net/projects/kinterbasdb __ http://firebirdsql.org/index.php?op=devel&sub=python __ http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_param_conv_dynamic_type_translation __ http://kinterbasdb.sourceforge.net/dist_docs/usage.html#special_issue_concurrency """ -from sqlalchemy.dialects.firebird.base import FBDialect, FBCompiler +from sqlalchemy.dialects.firebird.base import FBDialect, \ + FBCompiler, FBExecutionContext from sqlalchemy import util, types as sqltypes class _FBNumeric_kinterbasdb(sqltypes.Numeric): @@ -38,11 +51,21 @@ class _FBNumeric_kinterbasdb(sqltypes.Numeric): else: return value return process - + +class FBExecutionContext_kinterbasdb(FBExecutionContext): + @property + def rowcount(self): + if self.execution_options.get('enable_rowcount', + self.dialect.enable_rowcount): + return self.cursor.rowcount + else: + return -1 + class FBDialect_kinterbasdb(FBDialect): driver = 'kinterbasdb' supports_sane_rowcount = False supports_sane_multi_rowcount = False + execution_ctx_cls = FBExecutionContext_kinterbasdb supports_native_decimal = True @@ -54,12 +77,14 @@ class FBDialect_kinterbasdb(FBDialect): ) - def __init__(self, type_conv=200, concurrency_level=1, **kwargs): + def __init__(self, type_conv=200, concurrency_level=1, enable_rowcount=False, **kwargs): super(FBDialect_kinterbasdb, self).__init__(**kwargs) - + self.enable_rowcount = enable_rowcount self.type_conv = type_conv self.concurrency_level = concurrency_level - + if enable_rowcount: + self.supports_sane_rowcount = True + @classmethod def dbapi(cls): k = __import__('kinterbasdb') diff --git a/test/dialect/test_firebird.py b/test/dialect/test_firebird.py index e8337003f5..a96a528be2 100644 --- a/test/dialect/test_firebird.py +++ b/test/dialect/test_firebird.py @@ -265,6 +265,7 @@ class CompileTest(TestBase, AssertsCompiledSQL): class MiscTest(TestBase): __only_on__ = 'firebird' + @testing.provide_metadata def test_strlen(self): # On FB the length() function is implemented by an external # UDF, strlen(). Various SA tests fail because they pass a @@ -272,23 +273,49 @@ class MiscTest(TestBase): # the maximum string length the UDF was declared to accept). # This test checks that at least it works ok in other cases. - meta = MetaData(testing.db) - t = Table('t1', meta, + t = Table('t1', metadata, Column('id', Integer, Sequence('t1idseq'), primary_key=True), Column('name', String(10)) ) - meta.create_all() - try: - t.insert(values=dict(name='dante')).execute() - t.insert(values=dict(name='alighieri')).execute() - select([func.count(t.c.id)],func.length(t.c.name)==5).execute().first()[0] == 1 - finally: - meta.drop_all() + metadata.create_all() + t.insert(values=dict(name='dante')).execute() + t.insert(values=dict(name='alighieri')).execute() + select([func.count(t.c.id)],func.length(t.c.name)==5).execute().first()[0] == 1 def test_server_version_info(self): version = testing.db.dialect.server_version_info assert len(version) == 3, "Got strange version info: %s" % repr(version) + @testing.provide_metadata + def test_rowcount_flag(self): + engine = engines.testing_engine(options={'enable_rowcount':True}) + assert engine.dialect.supports_sane_rowcount + metadata.bind = engine + t = Table('t1', metadata, + Column('data', String(10)) + ) + metadata.create_all() + r = t.insert().execute({'data':'d1'}, {'data':'d2'}, {'data': 'd3'}) + r = t.update().where(t.c.data=='d2').values(data='d3').execute() + eq_(r.rowcount, 1) + r = t.delete().where(t.c.data == 'd3').execute() + eq_(r.rowcount, 2) + + r = t.delete().execution_options(enable_rowcount=False).execute() + eq_(r.rowcount, -1) + + + engine = engines.testing_engine(options={'enable_rowcount':False}) + assert not engine.dialect.supports_sane_rowcount + metadata.bind = engine + r = t.insert().execute({'data':'d1'}, {'data':'d2'}, {'data':'d3'}) + r = t.update().where(t.c.data=='d2').values(data='d3').execute() + eq_(r.rowcount, -1) + r = t.delete().where(t.c.data == 'd3').execute() + eq_(r.rowcount, -1) + r = t.delete().execution_options(enable_rowcount=True).execute() + eq_(r.rowcount, 1) + def test_percents_in_text(self): for expr, result in ( (text("select '%' from rdb$database"), '%'),