]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- added "autocommit=True" kwarg to select() and text(),
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 31 Jan 2008 17:48:22 +0000 (17:48 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 31 Jan 2008 17:48:22 +0000 (17:48 +0000)
as well as generative autocommit() method on select();
for statements which modify the database through some
user-defined means other than the usual INSERT/UPDATE/
DELETE etc., this flag will enable "autocommit" behavior
during execution if no transaction is in progress
[ticket:915]

CHANGES
lib/sqlalchemy/engine/default.py
lib/sqlalchemy/sql/expression.py
test/engine/transaction.py

diff --git a/CHANGES b/CHANGES
index 97f480eb9f37f1b750e4a925c6b7d23d44f41d77..5ef21fa2468506523af546c99d82720fcb6cd8fd 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,14 @@ CHANGES
       
     - cast() accepts text('something') and other non-literal
       operands properly [ticket:962]
+
+    - added "autocommit=True" kwarg to select() and text(),
+      as well as generative autocommit() method on select(); 
+      for statements which modify the database through some 
+      user-defined means other than the usual INSERT/UPDATE/
+      DELETE etc., this flag will enable "autocommit" behavior
+      during execution if no transaction is in progress 
+      [ticket:915]
       
     - The '.c.' attribute on a selectable now gets an entry
       for every column expression in its columns clause.
index e78eedd5c8c465bc790db705bfdc8e582cad117f..3b93862aeb73d48d2a090439b36263808444ed0e 100644 (file)
@@ -164,10 +164,10 @@ class DefaultExecutionContext(base.ExecutionContext):
             self.isupdate = compiled.isupdate
             if isinstance(compiled.statement, expression._TextClause):
                 self.returns_rows = self.returns_rows_text(self.statement)
-                self.should_autocommit = self.should_autocommit_text(self.statement)
+                self.should_autocommit = compiled.statement._autocommit or self.should_autocommit_text(self.statement)
             else:
                 self.returns_rows = self.returns_rows_compiled(compiled)
-                self.should_autocommit = self.should_autocommit_compiled(compiled)
+                self.should_autocommit = getattr(compiled.statement, '_autocommit', False) or self.should_autocommit_compiled(compiled)
             
             if not parameters:
                 self.compiled_parameters = [compiled.construct_params()]
index 6c0c4659ec9a991419340ada0127931bf0ca3a9f..2e5797b34632b0a90c64e73163e6e5c25a3146b2 100644 (file)
@@ -158,6 +158,11 @@ def select(columns=None, whereclause=None, from_obj=[], **kwargs):
     \**kwargs
       Additional parameters include:
 
+      autocommit
+        indicates this SELECT statement modifies the database, and 
+        should be subject to autocommit behavior if no transaction
+        has been started.
+        
       prefixes
         a list of strings or ``ClauseElement`` objects to include
         directly after the SELECT keyword in the generated statement,
@@ -727,6 +732,11 @@ def text(text, bind=None, *args, **kwargs):
     bind
       an optional connection or engine to be used for this text query.
 
+    autocommit=True
+      indicates this SELECT statement modifies the database, and 
+      should be subject to autocommit behavior if no transaction
+      has been started.
+
     bindparams
       a list of ``bindparam()`` instances which can be used to define
       the types and/or initial values for the bind parameters within
@@ -740,6 +750,7 @@ def text(text, bind=None, *args, **kwargs):
       which will be used to perform post-processing on columns within
       the result set (for textual statements that produce result
       sets).
+
     """
 
     return _TextClause(text, bind=bind, *args, **kwargs)
@@ -1821,10 +1832,11 @@ class _TextClause(ClauseElement):
 
     _bind_params_regex = re.compile(r'(?<![:\w\x5c]):(\w+)(?!:)', re.UNICODE)
 
-    def __init__(self, text = "", bind=None, bindparams=None, typemap=None):
+    def __init__(self, text = "", bind=None, bindparams=None, typemap=None, autocommit=False):
         self._bind = bind
         self.bindparams = {}
         self.typemap = typemap
+        self._autocommit = autocommit
         if typemap is not None:
             for key in typemap.keys():
                 typemap[key] = sqltypes.to_instance(typemap[key])
@@ -2711,9 +2723,10 @@ class TableClause(FromClause):
 class _SelectBaseMixin(object):
     """Base class for ``Select`` and ``CompoundSelects``."""
 
-    def __init__(self, use_labels=False, for_update=False, limit=None, offset=None, order_by=None, group_by=None, bind=None):
+    def __init__(self, use_labels=False, for_update=False, limit=None, offset=None, order_by=None, group_by=None, bind=None, autocommit=False):
         self.use_labels = use_labels
         self.for_update = for_update
+        self._autocommit = autocommit
         self._limit = limit
         self._offset = offset
         self._bind = bind
@@ -2734,7 +2747,7 @@ class _SelectBaseMixin(object):
         return _ScalarSelect(self)
 
     def apply_labels(self):
-        """set the 'labels' flag on this selectable.
+        """return a new selectable with the 'use_labels' flag set to True.
 
         This will result in column expressions being generated using labels against their table
         name, such as "SELECT somecolumn AS tablename_somecolumn".  This allows selectables which
@@ -2760,6 +2773,13 @@ class _SelectBaseMixin(object):
 
         return True
 
+    def autocommit(self):
+        """return a new selectable with the 'autocommit' flag set to True."""
+        
+        s = self._generate()
+        s._autocommit = True
+        return s
+        
     def _generate(self):
         s = self._clone()
         s._clone_from_clause()
index c54d67e219cb28e86fd056f5e7e9e1e9e534c536..686c640fdba34afcb595570df038143674ed6209 100644 (file)
@@ -375,6 +375,81 @@ class AutoRollbackTest(PersistTest):
         users.drop(conn2)
         conn2.close()
 
+class ExplicitAutoCommitTest(PersistTest):
+    """test the 'autocommit' flag on select() and text() objects.  
+    
+    Requires Postgres so that we may define a custom function which modifies the database.
+    """
+    
+    __only_on__ = 'postgres'
+
+    def setUpAll(self):
+        global metadata, foo
+        metadata = MetaData(testing.db)
+        foo = Table('foo', metadata, Column('id', Integer, primary_key=True), Column('data', String(100)))
+        metadata.create_all()
+        testing.db.execute("create function insert_foo(varchar) returns integer as 'insert into foo(data) values ($1);select 1;' language sql")
+
+    def tearDown(self):
+        foo.delete().execute()
+        
+    def tearDownAll(self):
+        testing.db.execute("drop function insert_foo(varchar)")
+        metadata.drop_all()
+    
+    def test_control(self):
+        # test that not using autocommit does not commit 
+        conn1 = testing.db.connect()
+        conn2 = testing.db.connect()
+
+        conn1.execute(select([func.insert_foo('data1')]))
+        assert conn2.execute(select([foo.c.data])).fetchall() == []
+
+        conn1.execute(text("select insert_foo('moredata')"))
+        assert conn2.execute(select([foo.c.data])).fetchall() == []
+
+        trans = conn1.begin()
+        trans.commit()
+
+        assert conn2.execute(select([foo.c.data])).fetchall() == [('data1',), ('moredata',)]
+        
+        conn1.close()
+        conn2.close()
+        
+    def test_explicit_compiled(self):
+        conn1 = testing.db.connect()
+        conn2 = testing.db.connect()
+        
+        conn1.execute(select([func.insert_foo('data1')], autocommit=True))
+        assert conn2.execute(select([foo.c.data])).fetchall() == [('data1',)]
+
+        conn1.execute(select([func.insert_foo('data2')]).autocommit())
+        assert conn2.execute(select([foo.c.data])).fetchall() == [('data1',), ('data2',)]
+        
+        conn1.close()
+        conn2.close()
+    
+    def test_explicit_text(self):
+        conn1 = testing.db.connect()
+        conn2 = testing.db.connect()
+        
+        conn1.execute(text("select insert_foo('moredata')", autocommit=True))
+        assert conn2.execute(select([foo.c.data])).fetchall() == [('moredata',)]
+        
+        conn1.close()
+        conn2.close()
+
+    def test_implicit_text(self):
+        conn1 = testing.db.connect()
+        conn2 = testing.db.connect()
+        
+        conn1.execute(text("insert into foo (data) values ('implicitdata')"))
+        assert conn2.execute(select([foo.c.data])).fetchall() == [('implicitdata',)]
+        
+        conn1.close()
+        conn2.close()
+        
+    
 class TLTransactionTest(PersistTest):
     def setUpAll(self):
         global users, metadata, tlengine