]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- merge r43460573c27a:4993c7eae8e5d117ff342bdc59f3b0635b898e2c of 0.6 branch
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 8 Jan 2011 21:14:47 +0000 (16:14 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 8 Jan 2011 21:14:47 +0000 (16:14 -0500)
CHANGES
lib/sqlalchemy/dialects/mssql/base.py
lib/sqlalchemy/engine/__init__.py
lib/sqlalchemy/engine/threadlocal.py
lib/sqlalchemy/schema.py
lib/sqlalchemy/types.py
test/engine/test_transaction.py
test/sql/test_metadata.py

diff --git a/CHANGES b/CHANGES
index e055c75986911f386ba714a65f16521650b533dc..4826eb6ab0e44363de9275984fc146b4e4a8942e 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -118,6 +118,10 @@ CHANGES
     to int, for DBAPIs such as pymssql that naively call 
     str() on values.
 
+  - CheckConstraint will copy its 'initially', 'deferrable',
+    and '_create_rule' attributes within a copy()/tometadata()
+    [ticket:2000]
+
 - engine
   - The "unicode warning" against non-unicode bind data
     is now raised only when the
@@ -137,6 +141,10 @@ CHANGES
     prepare() won't raise if no transaction is in progress;
     this was a regression introduced in 0.6.  [ticket:1998]
 
+  - Threadlocal engine returns itself upon begin(),
+    begin_nested(); engine then implements contextmanager
+    methods to allow the "with" statement. [ticket:2004]
+
 - postgresql
   - Single element tuple expressions inside an IN clause
     parenthesize correctly, also from [ticket:1984]
index 290cd1019bd7604286fa63cf4d6f38b0426021d9..facfdd75474b87053d457165a5ae746194e48f82 100644 (file)
@@ -114,6 +114,32 @@ compatibiility level information. Because of this, if running under
 a backwards compatibility mode SQAlchemy may attempt to use T-SQL
 statements that are unable to be parsed by the database server.
 
+Triggers
+--------
+
+SQLAlchemy by default uses OUTPUT INSERTED to get at newly
+generated primary key values via SEQUENCE columns.   MS-SQL does not 
+allow the usage of OUTPUT INSERTED on tables that have triggers.
+To disable the usage of OUTPUT INSERTED on a per-table basis,
+specify ``implicit_returning=False`` to each :class:`.Table`
+which has sequences::
+
+    Table('mytable', metadata, 
+        Column('id', Integer, primary_key=True), 
+        # ...,
+        implicit_returning=False
+    )
+    
+Declarative form::
+
+    class MyClass(Base):
+        # ...
+        __table_args__ = {'implicit_returning':False}
+        
+        
+This option can also be specified enginewide using the
+``implicit_returning=False`` argument on :func:`.create_engine`.
+
 Known Issues
 ------------
 
index aebf3543655a671bdd6ba667c8e72bba0734d009..e33dff53bde65204de11327245a6f9c73c8d7d5f 100644 (file)
@@ -175,6 +175,16 @@ def create_engine(*args, **kwargs):
         be applied to all connections.  See
         :meth:`~sqlalchemy.engine.base.Connection.execution_options`
 
+    :param implicit_returning=True: When ``False``, the RETURNING
+        feature of the database, if available, will not be used 
+        to fetch newly generated primary key values.   This applies
+        to those backends which support RETURNING or a compatible
+        construct, including Postgresql, Firebird, Oracle, Microsoft
+        SQL Server.  The default behavior is to use a compatible RETURNING
+        construct when a single-row INSERT statement is emitted with no 
+        existing returning() clause in order to fetch newly generated 
+        primary key values.
+
     :param label_length=None: optional integer value which limits
         the size of dynamically generated column labels to that many
         characters. If less than 6, labels are generated as
index 2ce0922bf2416299c1a2f51435405b70815094a8..d4d0488a986557a30b1d6c9a4a94069065bcad83 100644 (file)
@@ -71,16 +71,28 @@ class TLEngine(base.Engine):
         if not hasattr(self._connections, 'trans'):
             self._connections.trans = []
         self._connections.trans.append(self.contextual_connect().begin_twophase(xid=xid))
+        return self
 
     def begin_nested(self):
         if not hasattr(self._connections, 'trans'):
             self._connections.trans = []
         self._connections.trans.append(self.contextual_connect().begin_nested())
+        return self
 
     def begin(self):
         if not hasattr(self._connections, 'trans'):
             self._connections.trans = []
         self._connections.trans.append(self.contextual_connect().begin())
+        return self
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        if type is None:
+            self.commit()
+        else:
+            self.rollback()
 
     def prepare(self):
         if not hasattr(self._connections, 'trans') or \
index df7dbace02b3627966b9e70c8b110daa075f722c..f4b2140613c70193e229b33c16d8cd05e909d307 100644 (file)
@@ -1648,7 +1648,11 @@ class CheckConstraint(Constraint):
     __visit_name__ = property(__visit_name__)
 
     def copy(self, **kw):
-        return CheckConstraint(self.sqltext, name=self.name)
+        return CheckConstraint(self.sqltext, 
+                                name=self.name,
+                                initially=self.initially,
+                                deferrable=self.deferrable,
+                                _create_rule=self._create_rule)
 
 class ForeignKeyConstraint(Constraint):
     """A table-level FOREIGN KEY constraint.
index 613dad82a6e7e4acf06c90f909f490ab29a06204..a2ecafaa85b38b61bbcb27bfd9b6bb3bb95aec3b 100644 (file)
@@ -189,6 +189,24 @@ class TypeEngine(AbstractType):
         return util.constructor_copy(self, cls, **kw)
 
     def _coerce_compared_value(self, op, value):
+        """Suggest a type for a 'coerced' Python value in an expression.
+        
+        Given an operator and value, gives the type a chance
+        to return a type which the value should be coerced into.
+        
+        The default behavior here is conservative; if the right-hand
+        side is already coerced into a SQL type based on its 
+        Python type, it is usually left alone.
+        
+        End-user functionality extension here should generally be via
+        :class:`.TypeDecorator`, which provides more liberal behavior in that
+        it defaults to coercing the other side of the expression into this
+        type, thus applying special Python conversions above and beyond those
+        needed by the DBAPI to both ides. It also provides the public method
+        :meth:`.TypeDecorator.coerce_compared_value` which is intended for
+        end-user customization of this behavior.
+        
+        """
         _coerced_type = _type_map.get(type(value), NULLTYPE)
         if _coerced_type is NULLTYPE or _coerced_type._type_affinity \
             is self._type_affinity:
@@ -491,6 +509,8 @@ class TypeDecorator(TypeEngine):
         return self
 
     def _coerce_compared_value(self, op, value):
+        """See :meth:`.TypeEngine._coerce_compared_value` for a description."""
+
         return self.coerce_compared_value(op, value)
 
     def copy(self):
@@ -1326,6 +1346,8 @@ class _Binary(TypeEngine):
     # end Py2K
 
     def _coerce_compared_value(self, op, value):
+        """See :meth:`.TypeEngine._coerce_compared_value` for a description."""
+
         if isinstance(value, basestring):
             return self
         else:
@@ -1826,6 +1848,8 @@ class Interval(_DateAffinity, TypeDecorator):
         return Interval
 
     def _coerce_compared_value(self, op, value):
+        """See :meth:`.TypeEngine._coerce_compared_value` for a description."""
+
         return self.impl._coerce_compared_value(op, value)
 
 
index e3176a45a1f84bbb500222b98aaa401352d5dde1..bec8b0037fb22a19897188550b492a14120b08ba 100644 (file)
@@ -641,6 +641,27 @@ class TLTransactionTest(TestBase):
         finally:
             external_connection.close()
 
+    def test_with_interface(self):
+        trans = tlengine.begin()
+        tlengine.execute(users.insert(), user_id=1, user_name='user1')
+        tlengine.execute(users.insert(), user_id=2, user_name='user2')
+        trans.commit()
+
+        trans = tlengine.begin()
+        tlengine.execute(users.insert(), user_id=3, user_name='user3')
+        trans.__exit__(Exception, "fake", None)
+        trans = tlengine.begin()
+        tlengine.execute(users.insert(), user_id=4, user_name='user4')
+        trans.__exit__(None, None, None)
+        eq_(
+            tlengine.execute(users.select().order_by(users.c.user_id)).fetchall(),
+            [
+                (1, 'user1'),
+                (2, 'user2'),
+                (4, 'user4'),
+            ]
+        )
+
     def test_commits(self):
         connection = tlengine.connect()
         assert connection.execute('select count(*) from query_users'
index 75eac9ee8aed17a7a12b5d4b3db0ca9d145cb000..3459990fb09d1888b4e52756f4e37e6df5966a93 100644 (file)
@@ -165,6 +165,20 @@ class MetaDataTest(TestBase, ComparesTables):
             eq_(getattr(fk1c, k), kw[k])
             eq_(getattr(fk2c, k), kw[k])
 
+    def test_check_constraint_copy(self):
+        r = lambda x: x
+        c = CheckConstraint("foo bar", 
+                            name='name', 
+                            initially=True, 
+                            deferrable=True, 
+                            _create_rule = r)
+        c2 = c.copy()
+        eq_(c2.name, 'name')
+        eq_(str(c2.sqltext), "foo bar")
+        eq_(c2.initially, True)
+        eq_(c2.deferrable, True)
+        assert c2._create_rule is r
+
     def test_fk_construct(self):
         c1 = Column('foo', Integer)
         c2 = Column('bar', Integer)