]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- The functionality of result.rowcount is now disabled
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 11 Apr 2010 20:37:49 +0000 (16:37 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 11 Apr 2010 20:37:49 +0000 (16:37 -0400)
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.

CHANGES
lib/sqlalchemy/dialects/firebird/kinterbasdb.py
test/dialect/test_firebird.py

diff --git a/CHANGES b/CHANGES
index 5e44313e947ba1f8fe67395256237896c9aa5204..b8031869dbe94e0cf064f40ee945aff52329863d 100644 (file)
--- 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
index 9984d32a2895df6f72b5272249bd8308e999fd5a..890ba83fe6fc062de5ff1cbc34f8cad5fde86a90 100644 (file)
@@ -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')
index e8337003f59fd57805c2912e61164410ab5a6e85..a96a528be2061772d0a115d201053c2cf95b77b6 100644 (file)
@@ -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"), '%'),