From: Mike Bayer Date: Sat, 8 Jan 2011 21:14:47 +0000 (-0500) Subject: - merge r43460573c27a:4993c7eae8e5d117ff342bdc59f3b0635b898e2c of 0.6 branch X-Git-Tag: rel_0_7b1~85 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4e9e0f041c151cfa08d57773b49b254f1b4f8b6a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - merge r43460573c27a:4993c7eae8e5d117ff342bdc59f3b0635b898e2c of 0.6 branch --- diff --git a/CHANGES b/CHANGES index e055c75986..4826eb6ab0 100644 --- 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] diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index 290cd1019b..facfdd7547 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -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 ------------ diff --git a/lib/sqlalchemy/engine/__init__.py b/lib/sqlalchemy/engine/__init__.py index aebf354365..e33dff53bd 100644 --- a/lib/sqlalchemy/engine/__init__.py +++ b/lib/sqlalchemy/engine/__init__.py @@ -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 diff --git a/lib/sqlalchemy/engine/threadlocal.py b/lib/sqlalchemy/engine/threadlocal.py index 2ce0922bf2..d4d0488a98 100644 --- a/lib/sqlalchemy/engine/threadlocal.py +++ b/lib/sqlalchemy/engine/threadlocal.py @@ -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 \ diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index df7dbace02..f4b2140613 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -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. diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index 613dad82a6..a2ecafaa85 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -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) diff --git a/test/engine/test_transaction.py b/test/engine/test_transaction.py index e3176a45a1..bec8b0037f 100644 --- a/test/engine/test_transaction.py +++ b/test/engine/test_transaction.py @@ -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' diff --git a/test/sql/test_metadata.py b/test/sql/test_metadata.py index 75eac9ee8a..3459990fb0 100644 --- a/test/sql/test_metadata.py +++ b/test/sql/test_metadata.py @@ -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)