Update SQLAlchemy

This commit is contained in:
Ruud
2013-06-14 11:00:06 +02:00
parent 267ecfacab
commit 4aa6700ceb
124 changed files with 6500 additions and 5207 deletions
+115 -90
View File
@@ -1,5 +1,5 @@
# mssql/base.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
@@ -74,7 +74,7 @@ will yield::
SELECT TOP n
If using SQL Server 2005 or above, LIMIT with OFFSET
support is available through the ``ROW_NUMBER OVER`` construct.
support is available through the ``ROW_NUMBER OVER`` construct.
For versions below 2005, LIMIT with OFFSET usage will fail.
Nullability
@@ -107,10 +107,10 @@ Compatibility Levels
--------------------
MSSQL supports the notion of setting compatibility levels at the
database level. This allows, for instance, to run a database that
is compatibile with SQL2000 while running on a SQL2005 database
is compatible with SQL2000 while running on a SQL2005 database
server. ``server_version_info`` will always return the database
server version information (in this case SQL2005) and not the
compatibiility level information. Because of this, if running under
compatibility 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.
@@ -119,14 +119,14 @@ Triggers
SQLAlchemy by default uses OUTPUT INSERTED to get at newly
generated primary key values via IDENTITY columns or other
server side defaults. MS-SQL does not
server side defaults. 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`` for each :class:`.Table`
which has triggers::
Table('mytable', metadata,
Column('id', Integer, primary_key=True),
Table('mytable', metadata,
Column('id', Integer, primary_key=True),
# ...,
implicit_returning=False
)
@@ -144,11 +144,11 @@ This option can also be specified engine-wide using the
Enabling Snapshot Isolation
---------------------------
Not necessarily specific to SQLAlchemy, SQL Server has a default transaction
Not necessarily specific to SQLAlchemy, SQL Server has a default transaction
isolation mode that locks entire tables, and causes even mildly concurrent
applications to have long held locks and frequent deadlocks.
Enabling snapshot isolation for the database as a whole is recommended
for modern levels of concurrency support. This is accomplished via the
applications to have long held locks and frequent deadlocks.
Enabling snapshot isolation for the database as a whole is recommended
for modern levels of concurrency support. This is accomplished via the
following ALTER DATABASE commands executed at the SQL prompt::
ALTER DATABASE MyDatabase SET ALLOW_SNAPSHOT_ISOLATION ON
@@ -161,14 +161,15 @@ http://msdn.microsoft.com/en-us/library/ms175095.aspx.
Scalar Select Comparisons
-------------------------
The MSSQL dialect contains a legacy behavior whereby comparing
a scalar select to a value using the ``=`` or ``!=`` operator
will resolve to IN or NOT IN, respectively. This behavior is
deprecated and will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
should be used when IN/NOT IN are desired.
.. deprecated:: 0.8
The MSSQL dialect contains a legacy behavior whereby comparing
a scalar select to a value using the ``=`` or ``!=`` operator
will resolve to IN or NOT IN, respectively. This behavior
will be removed in 0.8 - the ``s.in_()``/``~s.in_()`` operators
should be used when IN/NOT IN are desired.
For the time being, the existing behavior prevents a comparison
between scalar select and another value that actually wants to use ``=``.
between scalar select and another value that actually wants to use ``=``.
To remove this behavior in a forwards-compatible way, apply this
compilation rule by placing the following code at the module import
level::
@@ -176,7 +177,7 @@ level::
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import _BinaryExpression
from sqlalchemy.sql.compiler import SQLCompiler
@compiles(_BinaryExpression, 'mssql')
def override_legacy_binary(element, compiler, **kw):
return SQLCompiler.visit_binary(compiler, element, **kw)
@@ -272,7 +273,7 @@ class _MSDate(sqltypes.Date):
return value.date()
elif isinstance(value, basestring):
return datetime.date(*[
int(x or 0)
int(x or 0)
for x in self._reg.match(value).groups()
])
else:
@@ -303,7 +304,7 @@ class TIME(sqltypes.TIME):
return value.time()
elif isinstance(value, basestring):
return datetime.time(*[
int(x or 0)
int(x or 0)
for x in self._reg.match(value).groups()])
else:
return value
@@ -608,7 +609,7 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
return self._extend("TEXT", type_)
def visit_VARCHAR(self, type_):
return self._extend("VARCHAR", type_,
return self._extend("VARCHAR", type_,
length = type_.length or 'max')
def visit_CHAR(self, type_):
@@ -618,7 +619,7 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
return self._extend("NCHAR", type_)
def visit_NVARCHAR(self, type_):
return self._extend("NVARCHAR", type_,
return self._extend("NVARCHAR", type_,
length = type_.length or 'max')
def visit_date(self, type_):
@@ -641,8 +642,8 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
def visit_VARBINARY(self, type_):
return self._extend(
"VARBINARY",
type_,
"VARBINARY",
type_,
length=type_.length or 'max')
def visit_boolean(self, type_):
@@ -689,18 +690,22 @@ class MSExecutionContext(default.DefaultExecutionContext):
not self.executemany
if self._enable_identity_insert:
self.cursor.execute("SET IDENTITY_INSERT %s ON" %
self.dialect.identifier_preparer.format_table(tbl))
self.root_connection._cursor_execute(self.cursor,
"SET IDENTITY_INSERT %s ON" %
self.dialect.identifier_preparer.format_table(tbl),
())
def post_exec(self):
"""Disable IDENTITY_INSERT if enabled."""
conn = self.root_connection
if self._select_lastrowid:
if self.dialect.use_scope_identity:
self.cursor.execute(
"SELECT scope_identity() AS lastrowid", ())
conn._cursor_execute(self.cursor,
"SELECT scope_identity() AS lastrowid", ())
else:
self.cursor.execute("SELECT @@identity AS lastrowid", ())
conn._cursor_execute(self.cursor,
"SELECT @@identity AS lastrowid", ())
# fetchall() ensures the cursor is consumed without closing it
row = self.cursor.fetchall()[0]
self._lastrowid = int(row[0])
@@ -710,10 +715,11 @@ class MSExecutionContext(default.DefaultExecutionContext):
self._result_proxy = base.FullyBufferedResultProxy(self)
if self._enable_identity_insert:
self.cursor.execute(
conn._cursor_execute(self.cursor,
"SET IDENTITY_INSERT %s OFF" %
self.dialect.identifier_preparer.
format_table(self.compiled.statement.table)
format_table(self.compiled.statement.table),
()
)
def get_lastrowid(self):
@@ -723,7 +729,7 @@ class MSExecutionContext(default.DefaultExecutionContext):
if self._enable_identity_insert:
try:
self.cursor.execute(
"SET IDENTITY_INSERT %s OFF" %
"SET IDENTITY_INSERT %s OFF" %
self.dialect.identifier_preparer.\
format_table(self.compiled.statement.table)
)
@@ -766,12 +772,12 @@ class MSSQLCompiler(compiler.SQLCompiler):
def visit_concat_op(self, binary, **kw):
return "%s + %s" % \
(self.process(binary.left, **kw),
(self.process(binary.left, **kw),
self.process(binary.right, **kw))
def visit_match_op(self, binary, **kw):
return "CONTAINS (%s, %s)" % (
self.process(binary.left, **kw),
self.process(binary.left, **kw),
self.process(binary.right, **kw))
def get_select_precolumns(self, select):
@@ -803,27 +809,28 @@ class MSSQLCompiler(compiler.SQLCompiler):
so tries to wrap it in a subquery with ``row_number()`` criterion.
"""
if not getattr(select, '_mssql_visit', None) and select._offset:
if select._offset and not getattr(select, '_mssql_visit', None):
# to use ROW_NUMBER(), an ORDER BY is required.
orderby = self.process(select._order_by_clause)
if not orderby:
if not select._order_by_clause.clauses:
raise exc.CompileError('MSSQL requires an order_by when '
'using an offset.')
_offset = select._offset
_limit = select._limit
_order_by_clauses = select._order_by_clause.clauses
select = select._generate()
select._mssql_visit = True
select = select.column(
sql.literal_column("ROW_NUMBER() OVER (ORDER BY %s)" \
% orderby).label("mssql_rn")
sql.func.ROW_NUMBER().over(order_by=_order_by_clauses)
.label("mssql_rn")
).order_by(None).alias()
mssql_rn = sql.column('mssql_rn')
limitselect = sql.select([c for c in select.c if
c.key!='mssql_rn'])
limitselect.append_whereclause(mssql_rn> _offset)
c.key != 'mssql_rn'])
limitselect.append_whereclause(mssql_rn > _offset)
if _limit is not None:
limitselect.append_whereclause(mssql_rn<=(_limit + _offset))
limitselect.append_whereclause(mssql_rn <= (_limit + _offset))
return self.process(limitselect, iswrapper=True, **kwargs)
else:
return compiler.SQLCompiler.visit_select(self, select, **kwargs)
@@ -861,7 +868,7 @@ class MSSQLCompiler(compiler.SQLCompiler):
return "SAVE TRANSACTION %s" % self.preparer.format_savepoint(savepoint_stmt)
def visit_rollback_to_savepoint(self, savepoint_stmt):
return ("ROLLBACK TRANSACTION %s"
return ("ROLLBACK TRANSACTION %s"
% self.preparer.format_savepoint(savepoint_stmt))
def visit_column(self, column, result_map=None, **kwargs):
@@ -875,15 +882,16 @@ class MSSQLCompiler(compiler.SQLCompiler):
if result_map is not None:
result_map[column.name.lower()] = \
(column.name, (column, ),
(column.name, (column, column.name,
column.key),
column.type)
return super(MSSQLCompiler, self).\
visit_column(converted,
visit_column(converted,
result_map=None, **kwargs)
return super(MSSQLCompiler, self).visit_column(column,
result_map=result_map,
return super(MSSQLCompiler, self).visit_column(column,
result_map=result_map,
**kwargs)
def visit_binary(self, binary, **kwargs):
@@ -892,27 +900,27 @@ class MSSQLCompiler(compiler.SQLCompiler):
"""
if (
isinstance(binary.left, expression._BindParamClause)
isinstance(binary.left, expression._BindParamClause)
and binary.operator == operator.eq
and not isinstance(binary.right, expression._BindParamClause)
):
return self.process(
expression._BinaryExpression(binary.right,
binary.left,
binary.operator),
expression._BinaryExpression(binary.right,
binary.left,
binary.operator),
**kwargs)
else:
if (
(binary.operator is operator.eq or
binary.operator is operator.ne)
(binary.operator is operator.eq or
binary.operator is operator.ne)
and (
(isinstance(binary.left, expression._FromGrouping)
and isinstance(binary.left.element,
expression._ScalarSelect))
or (isinstance(binary.right, expression._FromGrouping)
and isinstance(binary.right.element,
expression._ScalarSelect))
or isinstance(binary.left, expression._ScalarSelect)
(isinstance(binary.left, expression._FromGrouping)
and isinstance(binary.left.element,
expression._ScalarSelect))
or (isinstance(binary.right, expression._FromGrouping)
and isinstance(binary.right.element,
expression._ScalarSelect))
or isinstance(binary.left, expression._ScalarSelect)
or isinstance(binary.right, expression._ScalarSelect)
)
):
@@ -944,10 +952,10 @@ class MSSQLCompiler(compiler.SQLCompiler):
columns = [
self.process(
col_label(c),
within_columns_clause=True,
col_label(c),
within_columns_clause=True,
result_map=self.result_map
)
)
for c in expression._select_iterables(returning_cols)
]
return 'OUTPUT ' + ', '.join(columns)
@@ -967,7 +975,7 @@ class MSSQLCompiler(compiler.SQLCompiler):
label_select_column(select, column, asfrom)
def for_update_clause(self, select):
# "FOR UPDATE" is only allowed on "DECLARE CURSOR" which
# "FOR UPDATE" is only allowed on "DECLARE CURSOR" which
# SQLAlchemy doesn't use
return ''
@@ -980,6 +988,22 @@ class MSSQLCompiler(compiler.SQLCompiler):
else:
return ""
def update_from_clause(self, update_stmt,
from_table, extra_froms,
from_hints,
**kw):
"""Render the UPDATE..FROM clause specific to MSSQL.
In MSSQL, if the UPDATE statement involves an alias of the table to
be updated, then the table itself must be added to the FROM list as
well. Otherwise, it is optional. Here, we add it regardless.
"""
return "FROM " + ', '.join(
t._compiler_dispatch(self, asfrom=True,
fromhints=from_hints, **kw)
for t in [from_table] + extra_froms)
class MSSQLStrictCompiler(MSSQLCompiler):
"""A subclass of MSSQLCompiler which disables the usage of bind
parameters where not allowed natively by MS-SQL.
@@ -993,14 +1017,14 @@ class MSSQLStrictCompiler(MSSQLCompiler):
def visit_in_op(self, binary, **kw):
kw['literal_binds'] = True
return "%s IN %s" % (
self.process(binary.left, **kw),
self.process(binary.left, **kw),
self.process(binary.right, **kw)
)
def visit_notin_op(self, binary, **kw):
kw['literal_binds'] = True
return "%s NOT IN %s" % (
self.process(binary.left, **kw),
self.process(binary.left, **kw),
self.process(binary.right, **kw)
)
@@ -1029,7 +1053,7 @@ class MSSQLStrictCompiler(MSSQLCompiler):
class MSDDLCompiler(compiler.DDLCompiler):
def get_column_specification(self, column, **kwargs):
colspec = (self.preparer.format_column(column) + " "
colspec = (self.preparer.format_column(column) + " "
+ self.dialect.type_compiler.process(column.type))
if column.nullable is not None:
@@ -1040,7 +1064,7 @@ class MSDDLCompiler(compiler.DDLCompiler):
if column.table is None:
raise exc.CompileError(
"mssql requires Table-bound columns "
"mssql requires Table-bound columns "
"in order to generate DDL")
seq_col = column.table._autoincrement_column
@@ -1075,7 +1099,7 @@ class MSIdentifierPreparer(compiler.IdentifierPreparer):
reserved_words = RESERVED_WORDS
def __init__(self, dialect):
super(MSIdentifierPreparer, self).__init__(dialect, initial_quote='[',
super(MSIdentifierPreparer, self).__init__(dialect, initial_quote='[',
final_quote=']')
def _escape_identifier(self, value):
@@ -1140,7 +1164,7 @@ class MSDialect(default.DefaultDialect):
super(MSDialect, self).initialize(connection)
if self.server_version_info[0] not in range(8, 17):
# FreeTDS with version 4.2 seems to report here
# a number like "95.10.255". Don't know what
# a number like "95.10.255". Don't know what
# that is. So emit warning.
util.warn(
"Unrecognized server version info '%s'. Version specific "
@@ -1241,11 +1265,11 @@ class MSDialect(default.DefaultDialect):
"join sys.schemas as sch on sch.schema_id=tab.schema_id "
"where tab.name = :tabname "
"and sch.name=:schname "
"and ind.is_primary_key=0",
"and ind.is_primary_key=0",
bindparams=[
sql.bindparam('tabname', tablename,
sql.bindparam('tabname', tablename,
sqltypes.String(convert_unicode=True)),
sql.bindparam('schname', current_schema,
sql.bindparam('schname', current_schema,
sqltypes.String(convert_unicode=True))
],
typemap = {
@@ -1272,9 +1296,9 @@ class MSDialect(default.DefaultDialect):
"where tab.name=:tabname "
"and sch.name=:schname",
bindparams=[
sql.bindparam('tabname', tablename,
sql.bindparam('tabname', tablename,
sqltypes.String(convert_unicode=True)),
sql.bindparam('schname', current_schema,
sql.bindparam('schname', current_schema,
sqltypes.String(convert_unicode=True))
],
typemap = {
@@ -1302,9 +1326,9 @@ class MSDialect(default.DefaultDialect):
"views.schema_id=sch.schema_id and "
"views.name=:viewname and sch.name=:schname",
bindparams=[
sql.bindparam('viewname', viewname,
sql.bindparam('viewname', viewname,
sqltypes.String(convert_unicode=True)),
sql.bindparam('schname', current_schema,
sql.bindparam('schname', current_schema,
sqltypes.String(convert_unicode=True))
]
)
@@ -1332,7 +1356,7 @@ class MSDialect(default.DefaultDialect):
row = c.fetchone()
if row is None:
break
(name, type, nullable, charlen,
(name, type, nullable, charlen,
numericprec, numericscale, default, collation) = (
row[columns.c.column_name],
row[columns.c.data_type],
@@ -1346,7 +1370,7 @@ class MSDialect(default.DefaultDialect):
coltype = self.ischema_names.get(type, None)
kwargs = {}
if coltype in (MSString, MSChar, MSNVarchar, MSNChar, MSText,
if coltype in (MSString, MSChar, MSNVarchar, MSNChar, MSText,
MSNText, MSBinary, MSVarBinary,
sqltypes.LargeBinary):
kwargs['length'] = charlen
@@ -1358,7 +1382,7 @@ class MSDialect(default.DefaultDialect):
if coltype is None:
util.warn(
"Did not recognize type '%s' of column '%s'" %
"Did not recognize type '%s' of column '%s'" %
(type, name))
coltype = sqltypes.NULLTYPE
else:
@@ -1382,7 +1406,7 @@ class MSDialect(default.DefaultDialect):
colmap[col['name']] = col
# We also run an sp_columns to check for identity columns:
cursor = connection.execute("sp_columns @table_name = '%s', "
"@table_owner = '%s'"
"@table_owner = '%s'"
% (tablename, current_schema))
ic = None
while True:
@@ -1401,7 +1425,7 @@ class MSDialect(default.DefaultDialect):
if ic is not None and self.server_version_info >= MS_2005_VERSION:
table_fullname = "%s.%s" % (current_schema, tablename)
cursor = connection.execute(
"select ident_seed('%s'), ident_incr('%s')"
"select ident_seed('%s'), ident_incr('%s')"
% (table_fullname, table_fullname)
)
@@ -1421,16 +1445,17 @@ class MSDialect(default.DefaultDialect):
RR = ischema.ref_constraints
# information_schema.table_constraints
TC = ischema.constraints
# information_schema.constraint_column_usage:
# information_schema.constraint_column_usage:
# the constrained column
C = ischema.key_constraints.alias('C')
# information_schema.constraint_column_usage:
C = ischema.key_constraints.alias('C')
# information_schema.constraint_column_usage:
# the referenced column
R = ischema.key_constraints.alias('R')
R = ischema.key_constraints.alias('R')
# Primary key constraints
s = sql.select([C.c.column_name, TC.c.constraint_type],
sql.and_(TC.c.constraint_name == C.c.constraint_name,
TC.c.table_schema == C.c.table_schema,
C.c.table_name == tablename,
C.c.table_schema == current_schema)
)
@@ -1448,12 +1473,12 @@ class MSDialect(default.DefaultDialect):
RR = ischema.ref_constraints
# information_schema.table_constraints
TC = ischema.constraints
# information_schema.constraint_column_usage:
# information_schema.constraint_column_usage:
# the constrained column
C = ischema.key_constraints.alias('C')
# information_schema.constraint_column_usage:
C = ischema.key_constraints.alias('C')
# information_schema.constraint_column_usage:
# the referenced column
R = ischema.key_constraints.alias('R')
R = ischema.key_constraints.alias('R')
# Foreign key constraints
s = sql.select([C.c.column_name,