update sqlalchemy
This commit is contained in:
parent
6c6c3e68c6
commit
a4267212e4
192 changed files with 17429 additions and 9601 deletions
|
|
@ -1,5 +1,5 @@
|
|||
# mssql/__init__.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# mssql/adodbapi.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# mssql/base.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -166,6 +166,55 @@ how SQLAlchemy handles this:
|
|||
This
|
||||
is an auxilliary use case suitable for testing and bulk insert scenarios.
|
||||
|
||||
.. _legacy_schema_rendering:
|
||||
|
||||
Rendering of SQL statements that include schema qualifiers
|
||||
---------------------------------------------------------
|
||||
|
||||
When using :class:`.Table` metadata that includes a "schema" qualifier,
|
||||
such as::
|
||||
|
||||
account_table = Table(
|
||||
'account', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('info', String(100)),
|
||||
schema="customer_schema"
|
||||
)
|
||||
|
||||
The SQL Server dialect has a long-standing behavior that it will attempt
|
||||
to turn a schema-qualified table name into an alias, such as::
|
||||
|
||||
>>> eng = create_engine("mssql+pymssql://mydsn")
|
||||
>>> print(account_table.select().compile(eng))
|
||||
SELECT account_1.id, account_1.info
|
||||
FROM customer_schema.account AS account_1
|
||||
|
||||
This behavior is legacy, does not function correctly for many forms
|
||||
of SQL statements, and will be disabled by default in the 1.1 series
|
||||
of SQLAlchemy. As of 1.0.5, the above statement will produce the following
|
||||
warning::
|
||||
|
||||
SAWarning: legacy_schema_aliasing flag is defaulted to True;
|
||||
some schema-qualified queries may not function correctly.
|
||||
Consider setting this flag to False for modern SQL Server versions;
|
||||
this flag will default to False in version 1.1
|
||||
|
||||
This warning encourages the :class:`.Engine` to be created as follows::
|
||||
|
||||
>>> eng = create_engine("mssql+pymssql://mydsn", legacy_schema_aliasing=False)
|
||||
|
||||
Where the above SELECT statement will produce::
|
||||
|
||||
>>> print(account_table.select().compile(eng))
|
||||
SELECT customer_schema.account.id, customer_schema.account.info
|
||||
FROM customer_schema.account
|
||||
|
||||
The warning will not emit if the ``legacy_schema_aliasing`` flag is set
|
||||
to either True or False.
|
||||
|
||||
.. versionadded:: 1.0.5 - Added the ``legacy_schema_aliasing`` flag to disable
|
||||
the SQL Server dialect's legacy behavior with schema-qualified table
|
||||
names. This flag will default to False in version 1.1.
|
||||
|
||||
Collation Support
|
||||
-----------------
|
||||
|
|
@ -187,7 +236,7 @@ CREATE TABLE statement for this column will yield::
|
|||
LIMIT/OFFSET Support
|
||||
--------------------
|
||||
|
||||
MSSQL has no support for the LIMIT or OFFSET keysowrds. LIMIT is
|
||||
MSSQL has no support for the LIMIT or OFFSET keywords. LIMIT is
|
||||
supported directly through the ``TOP`` Transact SQL keyword::
|
||||
|
||||
select.limit
|
||||
|
|
@ -226,6 +275,53 @@ The DATE and TIME types are not available for MSSQL 2005 and
|
|||
previous - if a server version below 2008 is detected, DDL
|
||||
for these types will be issued as DATETIME.
|
||||
|
||||
.. _mssql_large_type_deprecation:
|
||||
|
||||
Large Text/Binary Type Deprecation
|
||||
----------------------------------
|
||||
|
||||
Per `SQL Server 2012/2014 Documentation <http://technet.microsoft.com/en-us/library/ms187993.aspx>`_,
|
||||
the ``NTEXT``, ``TEXT`` and ``IMAGE`` datatypes are to be removed from SQL Server
|
||||
in a future release. SQLAlchemy normally relates these types to the
|
||||
:class:`.UnicodeText`, :class:`.Text` and :class:`.LargeBinary` datatypes.
|
||||
|
||||
In order to accommodate this change, a new flag ``deprecate_large_types``
|
||||
is added to the dialect, which will be automatically set based on detection
|
||||
of the server version in use, if not otherwise set by the user. The
|
||||
behavior of this flag is as follows:
|
||||
|
||||
* When this flag is ``True``, the :class:`.UnicodeText`, :class:`.Text` and
|
||||
:class:`.LargeBinary` datatypes, when used to render DDL, will render the
|
||||
types ``NVARCHAR(max)``, ``VARCHAR(max)``, and ``VARBINARY(max)``,
|
||||
respectively. This is a new behavior as of the addition of this flag.
|
||||
|
||||
* When this flag is ``False``, the :class:`.UnicodeText`, :class:`.Text` and
|
||||
:class:`.LargeBinary` datatypes, when used to render DDL, will render the
|
||||
types ``NTEXT``, ``TEXT``, and ``IMAGE``,
|
||||
respectively. This is the long-standing behavior of these types.
|
||||
|
||||
* The flag begins with the value ``None``, before a database connection is
|
||||
established. If the dialect is used to render DDL without the flag being
|
||||
set, it is interpreted the same as ``False``.
|
||||
|
||||
* On first connection, the dialect detects if SQL Server version 2012 or greater
|
||||
is in use; if the flag is still at ``None``, it sets it to ``True`` or
|
||||
``False`` based on whether 2012 or greater is detected.
|
||||
|
||||
* The flag can be set to either ``True`` or ``False`` when the dialect
|
||||
is created, typically via :func:`.create_engine`::
|
||||
|
||||
eng = create_engine("mssql+pymssql://user:pass@host/db",
|
||||
deprecate_large_types=True)
|
||||
|
||||
* Complete control over whether the "old" or "new" types are rendered is
|
||||
available in all SQLAlchemy versions by using the UPPERCASE type objects
|
||||
instead: :class:`.NVARCHAR`, :class:`.VARCHAR`, :class:`.types.VARBINARY`,
|
||||
:class:`.TEXT`, :class:`.mssql.NTEXT`, :class:`.mssql.IMAGE` will always remain
|
||||
fixed and always output exactly that type.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. _mssql_indexes:
|
||||
|
||||
Clustered Index Support
|
||||
|
|
@ -367,19 +463,20 @@ import operator
|
|||
import re
|
||||
|
||||
from ... import sql, schema as sa_schema, exc, util
|
||||
from ...sql import compiler, expression, \
|
||||
util as sql_util, cast
|
||||
from ...sql import compiler, expression, util as sql_util
|
||||
from ... import engine
|
||||
from ...engine import reflection, default
|
||||
from ... import types as sqltypes
|
||||
from ...types import INTEGER, BIGINT, SMALLINT, DECIMAL, NUMERIC, \
|
||||
FLOAT, TIMESTAMP, DATETIME, DATE, BINARY,\
|
||||
VARBINARY, TEXT, VARCHAR, NVARCHAR, CHAR, NCHAR
|
||||
TEXT, VARCHAR, NVARCHAR, CHAR, NCHAR
|
||||
|
||||
|
||||
from ...util import update_wrapper
|
||||
from . import information_schema as ischema
|
||||
|
||||
# http://sqlserverbuilds.blogspot.com/
|
||||
MS_2012_VERSION = (11,)
|
||||
MS_2008_VERSION = (10,)
|
||||
MS_2005_VERSION = (9,)
|
||||
MS_2000_VERSION = (8,)
|
||||
|
|
@ -451,9 +548,13 @@ class _MSDate(sqltypes.Date):
|
|||
if isinstance(value, datetime.datetime):
|
||||
return value.date()
|
||||
elif isinstance(value, util.string_types):
|
||||
m = self._reg.match(value)
|
||||
if not m:
|
||||
raise ValueError(
|
||||
"could not parse %r as a date value" % (value, ))
|
||||
return datetime.date(*[
|
||||
int(x or 0)
|
||||
for x in self._reg.match(value).groups()
|
||||
for x in m.groups()
|
||||
])
|
||||
else:
|
||||
return value
|
||||
|
|
@ -485,9 +586,13 @@ class TIME(sqltypes.TIME):
|
|||
if isinstance(value, datetime.datetime):
|
||||
return value.time()
|
||||
elif isinstance(value, util.string_types):
|
||||
m = self._reg.match(value)
|
||||
if not m:
|
||||
raise ValueError(
|
||||
"could not parse %r as a time value" % (value, ))
|
||||
return datetime.time(*[
|
||||
int(x or 0)
|
||||
for x in self._reg.match(value).groups()])
|
||||
for x in m.groups()])
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
|
@ -545,6 +650,26 @@ class NTEXT(sqltypes.UnicodeText):
|
|||
__visit_name__ = 'NTEXT'
|
||||
|
||||
|
||||
class VARBINARY(sqltypes.VARBINARY, sqltypes.LargeBinary):
|
||||
"""The MSSQL VARBINARY type.
|
||||
|
||||
This type extends both :class:`.types.VARBINARY` and
|
||||
:class:`.types.LargeBinary`. In "deprecate_large_types" mode,
|
||||
the :class:`.types.LargeBinary` type will produce ``VARBINARY(max)``
|
||||
on SQL Server.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`mssql_large_type_deprecation`
|
||||
|
||||
|
||||
|
||||
"""
|
||||
__visit_name__ = 'VARBINARY'
|
||||
|
||||
|
||||
class IMAGE(sqltypes.LargeBinary):
|
||||
__visit_name__ = 'IMAGE'
|
||||
|
||||
|
|
@ -626,7 +751,6 @@ ischema_names = {
|
|||
|
||||
|
||||
class MSTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def _extend(self, spec, type_, length=None):
|
||||
"""Extend a string-type declaration with standard SQL
|
||||
COLLATE annotations.
|
||||
|
|
@ -647,103 +771,115 @@ class MSTypeCompiler(compiler.GenericTypeCompiler):
|
|||
return ' '.join([c for c in (spec, collation)
|
||||
if c is not None])
|
||||
|
||||
def visit_FLOAT(self, type_):
|
||||
def visit_FLOAT(self, type_, **kw):
|
||||
precision = getattr(type_, 'precision', None)
|
||||
if precision is None:
|
||||
return "FLOAT"
|
||||
else:
|
||||
return "FLOAT(%(precision)s)" % {'precision': precision}
|
||||
|
||||
def visit_TINYINT(self, type_):
|
||||
def visit_TINYINT(self, type_, **kw):
|
||||
return "TINYINT"
|
||||
|
||||
def visit_DATETIMEOFFSET(self, type_):
|
||||
if type_.precision:
|
||||
def visit_DATETIMEOFFSET(self, type_, **kw):
|
||||
if type_.precision is not None:
|
||||
return "DATETIMEOFFSET(%s)" % type_.precision
|
||||
else:
|
||||
return "DATETIMEOFFSET"
|
||||
|
||||
def visit_TIME(self, type_):
|
||||
def visit_TIME(self, type_, **kw):
|
||||
precision = getattr(type_, 'precision', None)
|
||||
if precision:
|
||||
if precision is not None:
|
||||
return "TIME(%s)" % precision
|
||||
else:
|
||||
return "TIME"
|
||||
|
||||
def visit_DATETIME2(self, type_):
|
||||
def visit_DATETIME2(self, type_, **kw):
|
||||
precision = getattr(type_, 'precision', None)
|
||||
if precision:
|
||||
if precision is not None:
|
||||
return "DATETIME2(%s)" % precision
|
||||
else:
|
||||
return "DATETIME2"
|
||||
|
||||
def visit_SMALLDATETIME(self, type_):
|
||||
def visit_SMALLDATETIME(self, type_, **kw):
|
||||
return "SMALLDATETIME"
|
||||
|
||||
def visit_unicode(self, type_):
|
||||
return self.visit_NVARCHAR(type_)
|
||||
def visit_unicode(self, type_, **kw):
|
||||
return self.visit_NVARCHAR(type_, **kw)
|
||||
|
||||
def visit_unicode_text(self, type_):
|
||||
return self.visit_NTEXT(type_)
|
||||
def visit_text(self, type_, **kw):
|
||||
if self.dialect.deprecate_large_types:
|
||||
return self.visit_VARCHAR(type_, **kw)
|
||||
else:
|
||||
return self.visit_TEXT(type_, **kw)
|
||||
|
||||
def visit_NTEXT(self, type_):
|
||||
def visit_unicode_text(self, type_, **kw):
|
||||
if self.dialect.deprecate_large_types:
|
||||
return self.visit_NVARCHAR(type_, **kw)
|
||||
else:
|
||||
return self.visit_NTEXT(type_, **kw)
|
||||
|
||||
def visit_NTEXT(self, type_, **kw):
|
||||
return self._extend("NTEXT", type_)
|
||||
|
||||
def visit_TEXT(self, type_):
|
||||
def visit_TEXT(self, type_, **kw):
|
||||
return self._extend("TEXT", type_)
|
||||
|
||||
def visit_VARCHAR(self, type_):
|
||||
def visit_VARCHAR(self, type_, **kw):
|
||||
return self._extend("VARCHAR", type_, length=type_.length or 'max')
|
||||
|
||||
def visit_CHAR(self, type_):
|
||||
def visit_CHAR(self, type_, **kw):
|
||||
return self._extend("CHAR", type_)
|
||||
|
||||
def visit_NCHAR(self, type_):
|
||||
def visit_NCHAR(self, type_, **kw):
|
||||
return self._extend("NCHAR", type_)
|
||||
|
||||
def visit_NVARCHAR(self, type_):
|
||||
def visit_NVARCHAR(self, type_, **kw):
|
||||
return self._extend("NVARCHAR", type_, length=type_.length or 'max')
|
||||
|
||||
def visit_date(self, type_):
|
||||
def visit_date(self, type_, **kw):
|
||||
if self.dialect.server_version_info < MS_2008_VERSION:
|
||||
return self.visit_DATETIME(type_)
|
||||
return self.visit_DATETIME(type_, **kw)
|
||||
else:
|
||||
return self.visit_DATE(type_)
|
||||
return self.visit_DATE(type_, **kw)
|
||||
|
||||
def visit_time(self, type_):
|
||||
def visit_time(self, type_, **kw):
|
||||
if self.dialect.server_version_info < MS_2008_VERSION:
|
||||
return self.visit_DATETIME(type_)
|
||||
return self.visit_DATETIME(type_, **kw)
|
||||
else:
|
||||
return self.visit_TIME(type_)
|
||||
return self.visit_TIME(type_, **kw)
|
||||
|
||||
def visit_large_binary(self, type_):
|
||||
return self.visit_IMAGE(type_)
|
||||
def visit_large_binary(self, type_, **kw):
|
||||
if self.dialect.deprecate_large_types:
|
||||
return self.visit_VARBINARY(type_, **kw)
|
||||
else:
|
||||
return self.visit_IMAGE(type_, **kw)
|
||||
|
||||
def visit_IMAGE(self, type_):
|
||||
def visit_IMAGE(self, type_, **kw):
|
||||
return "IMAGE"
|
||||
|
||||
def visit_VARBINARY(self, type_):
|
||||
def visit_VARBINARY(self, type_, **kw):
|
||||
return self._extend(
|
||||
"VARBINARY",
|
||||
type_,
|
||||
length=type_.length or 'max')
|
||||
|
||||
def visit_boolean(self, type_):
|
||||
def visit_boolean(self, type_, **kw):
|
||||
return self.visit_BIT(type_)
|
||||
|
||||
def visit_BIT(self, type_):
|
||||
def visit_BIT(self, type_, **kw):
|
||||
return "BIT"
|
||||
|
||||
def visit_MONEY(self, type_):
|
||||
def visit_MONEY(self, type_, **kw):
|
||||
return "MONEY"
|
||||
|
||||
def visit_SMALLMONEY(self, type_):
|
||||
def visit_SMALLMONEY(self, type_, **kw):
|
||||
return 'SMALLMONEY'
|
||||
|
||||
def visit_UNIQUEIDENTIFIER(self, type_):
|
||||
def visit_UNIQUEIDENTIFIER(self, type_, **kw):
|
||||
return "UNIQUEIDENTIFIER"
|
||||
|
||||
def visit_SQL_VARIANT(self, type_):
|
||||
def visit_SQL_VARIANT(self, type_, **kw):
|
||||
return 'SQL_VARIANT'
|
||||
|
||||
|
||||
|
|
@ -846,7 +982,7 @@ class MSExecutionContext(default.DefaultExecutionContext):
|
|||
"SET IDENTITY_INSERT %s OFF" %
|
||||
self.dialect.identifier_preparer. format_table(
|
||||
self.compiled.statement.table)))
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_result_proxy(self):
|
||||
|
|
@ -872,6 +1008,15 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||
self.tablealiases = {}
|
||||
super(MSSQLCompiler, self).__init__(*args, **kwargs)
|
||||
|
||||
def _with_legacy_schema_aliasing(fn):
|
||||
def decorate(self, *arg, **kw):
|
||||
if self.dialect.legacy_schema_aliasing:
|
||||
return fn(self, *arg, **kw)
|
||||
else:
|
||||
super_ = getattr(super(MSSQLCompiler, self), fn.__name__)
|
||||
return super_(*arg, **kw)
|
||||
return decorate
|
||||
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
|
||||
|
|
@ -900,19 +1045,24 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||
self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw))
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
""" MS-SQL puts TOP, its version of LIMIT, here """
|
||||
if select._distinct or select._limit is not None:
|
||||
s = select._distinct and "DISTINCT " or ""
|
||||
def get_select_precolumns(self, select, **kw):
|
||||
""" MS-SQL puts TOP, it's version of LIMIT here """
|
||||
|
||||
s = ""
|
||||
if select._distinct:
|
||||
s += "DISTINCT "
|
||||
|
||||
if select._simple_int_limit and not select._offset:
|
||||
# ODBC drivers and possibly others
|
||||
# don't support bind params in the SELECT clause on SQL Server.
|
||||
# so have to use literal here.
|
||||
if select._limit is not None:
|
||||
if not select._offset:
|
||||
s += "TOP %d " % select._limit
|
||||
s += "TOP %d " % select._limit
|
||||
|
||||
if s:
|
||||
return s
|
||||
return compiler.SQLCompiler.get_select_precolumns(self, select)
|
||||
else:
|
||||
return compiler.SQLCompiler.get_select_precolumns(
|
||||
self, select, **kw)
|
||||
|
||||
def get_from_hint_text(self, table, text):
|
||||
return text
|
||||
|
|
@ -920,7 +1070,7 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||
def get_crud_hint_text(self, table, text):
|
||||
return text
|
||||
|
||||
def limit_clause(self, select):
|
||||
def limit_clause(self, select, **kw):
|
||||
# Limit in mssql is after the select keyword
|
||||
return ""
|
||||
|
||||
|
|
@ -929,39 +1079,48 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||
so tries to wrap it in a subquery with ``row_number()`` criterion.
|
||||
|
||||
"""
|
||||
if select._offset and not getattr(select, '_mssql_visit', None):
|
||||
if (
|
||||
(
|
||||
not select._simple_int_limit and
|
||||
select._limit_clause is not None
|
||||
) or (
|
||||
select._offset_clause is not None and
|
||||
not select._simple_int_offset or select._offset
|
||||
)
|
||||
) and not getattr(select, '_mssql_visit', None):
|
||||
|
||||
# to use ROW_NUMBER(), an ORDER BY is required.
|
||||
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
|
||||
'using an OFFSET or a non-simple '
|
||||
'LIMIT clause')
|
||||
|
||||
_order_by_clauses = select._order_by_clause.clauses
|
||||
limit_clause = select._limit_clause
|
||||
offset_clause = select._offset_clause
|
||||
kwargs['select_wraps_for'] = select
|
||||
select = select._generate()
|
||||
select._mssql_visit = True
|
||||
select = select.column(
|
||||
sql.func.ROW_NUMBER().over(order_by=_order_by_clauses)
|
||||
.label("mssql_rn")
|
||||
).order_by(None).alias()
|
||||
.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)
|
||||
if _limit is not None:
|
||||
limitselect.append_whereclause(mssql_rn <= (_limit + _offset))
|
||||
return self.process(limitselect, iswrapper=True, **kwargs)
|
||||
if offset_clause is not None:
|
||||
limitselect.append_whereclause(mssql_rn > offset_clause)
|
||||
if limit_clause is not None:
|
||||
limitselect.append_whereclause(
|
||||
mssql_rn <= (limit_clause + offset_clause))
|
||||
else:
|
||||
limitselect.append_whereclause(
|
||||
mssql_rn <= (limit_clause))
|
||||
return self.process(limitselect, **kwargs)
|
||||
else:
|
||||
return compiler.SQLCompiler.visit_select(self, select, **kwargs)
|
||||
|
||||
def _schema_aliased_table(self, table):
|
||||
if getattr(table, 'schema', None) is not None:
|
||||
if table not in self.tablealiases:
|
||||
self.tablealiases[table] = table.alias()
|
||||
return self.tablealiases[table]
|
||||
else:
|
||||
return None
|
||||
|
||||
@_with_legacy_schema_aliasing
|
||||
def visit_table(self, table, mssql_aliased=False, iscrud=False, **kwargs):
|
||||
if mssql_aliased is table or iscrud:
|
||||
return super(MSSQLCompiler, self).visit_table(table, **kwargs)
|
||||
|
|
@ -973,25 +1132,14 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||
else:
|
||||
return super(MSSQLCompiler, self).visit_table(table, **kwargs)
|
||||
|
||||
def visit_alias(self, alias, **kwargs):
|
||||
@_with_legacy_schema_aliasing
|
||||
def visit_alias(self, alias, **kw):
|
||||
# translate for schema-qualified table aliases
|
||||
kwargs['mssql_aliased'] = alias.original
|
||||
return super(MSSQLCompiler, self).visit_alias(alias, **kwargs)
|
||||
kw['mssql_aliased'] = alias.original
|
||||
return super(MSSQLCompiler, self).visit_alias(alias, **kw)
|
||||
|
||||
def visit_extract(self, extract, **kw):
|
||||
field = self.extract_map.get(extract.field, extract.field)
|
||||
return 'DATEPART("%s", %s)' % \
|
||||
(field, self.process(extract.expr, **kw))
|
||||
|
||||
def visit_savepoint(self, savepoint_stmt):
|
||||
return "SAVE TRANSACTION %s" % \
|
||||
self.preparer.format_savepoint(savepoint_stmt)
|
||||
|
||||
def visit_rollback_to_savepoint(self, savepoint_stmt):
|
||||
return ("ROLLBACK TRANSACTION %s"
|
||||
% self.preparer.format_savepoint(savepoint_stmt))
|
||||
|
||||
def visit_column(self, column, add_to_result_map=None, **kwargs):
|
||||
@_with_legacy_schema_aliasing
|
||||
def visit_column(self, column, add_to_result_map=None, **kw):
|
||||
if column.table is not None and \
|
||||
(not self.isupdate and not self.isdelete) or \
|
||||
self.is_subquery():
|
||||
|
|
@ -1009,10 +1157,40 @@ class MSSQLCompiler(compiler.SQLCompiler):
|
|||
)
|
||||
|
||||
return super(MSSQLCompiler, self).\
|
||||
visit_column(converted, **kwargs)
|
||||
visit_column(converted, **kw)
|
||||
|
||||
return super(MSSQLCompiler, self).visit_column(
|
||||
column, add_to_result_map=add_to_result_map, **kwargs)
|
||||
column, add_to_result_map=add_to_result_map, **kw)
|
||||
|
||||
def _schema_aliased_table(self, table):
|
||||
if getattr(table, 'schema', None) is not None:
|
||||
if self.dialect._warn_schema_aliasing and \
|
||||
table.schema.lower() != 'information_schema':
|
||||
util.warn(
|
||||
"legacy_schema_aliasing flag is defaulted to True; "
|
||||
"some schema-qualified queries may not function "
|
||||
"correctly. Consider setting this flag to False for "
|
||||
"modern SQL Server versions; this flag will default to "
|
||||
"False in version 1.1")
|
||||
|
||||
if table not in self.tablealiases:
|
||||
self.tablealiases[table] = table.alias()
|
||||
return self.tablealiases[table]
|
||||
else:
|
||||
return None
|
||||
|
||||
def visit_extract(self, extract, **kw):
|
||||
field = self.extract_map.get(extract.field, extract.field)
|
||||
return 'DATEPART(%s, %s)' % \
|
||||
(field, self.process(extract.expr, **kw))
|
||||
|
||||
def visit_savepoint(self, savepoint_stmt):
|
||||
return "SAVE TRANSACTION %s" % \
|
||||
self.preparer.format_savepoint(savepoint_stmt)
|
||||
|
||||
def visit_rollback_to_savepoint(self, savepoint_stmt):
|
||||
return ("ROLLBACK TRANSACTION %s"
|
||||
% self.preparer.format_savepoint(savepoint_stmt))
|
||||
|
||||
def visit_binary(self, binary, **kwargs):
|
||||
"""Move bind parameters to the right-hand side of an operator, where
|
||||
|
|
@ -1141,8 +1319,11 @@ class MSSQLStrictCompiler(MSSQLCompiler):
|
|||
class MSDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
colspec = (self.preparer.format_column(column) + " "
|
||||
+ self.dialect.type_compiler.process(column.type))
|
||||
colspec = (
|
||||
self.preparer.format_column(column) + " "
|
||||
+ self.dialect.type_compiler.process(
|
||||
column.type, type_expression=column)
|
||||
)
|
||||
|
||||
if column.nullable is not None:
|
||||
if not column.nullable or column.primary_key or \
|
||||
|
|
@ -1321,6 +1502,10 @@ class MSDialect(default.DefaultDialect):
|
|||
sqltypes.Time: TIME,
|
||||
}
|
||||
|
||||
engine_config_types = default.DefaultDialect.engine_config_types.union([
|
||||
('legacy_schema_aliasing', util.asbool),
|
||||
])
|
||||
|
||||
ischema_names = ischema_names
|
||||
|
||||
supports_native_boolean = False
|
||||
|
|
@ -1351,13 +1536,24 @@ class MSDialect(default.DefaultDialect):
|
|||
query_timeout=None,
|
||||
use_scope_identity=True,
|
||||
max_identifier_length=None,
|
||||
schema_name="dbo", **opts):
|
||||
schema_name="dbo",
|
||||
deprecate_large_types=None,
|
||||
legacy_schema_aliasing=None, **opts):
|
||||
self.query_timeout = int(query_timeout or 0)
|
||||
self.schema_name = schema_name
|
||||
|
||||
self.use_scope_identity = use_scope_identity
|
||||
self.max_identifier_length = int(max_identifier_length or 0) or \
|
||||
self.max_identifier_length
|
||||
self.deprecate_large_types = deprecate_large_types
|
||||
|
||||
if legacy_schema_aliasing is None:
|
||||
self.legacy_schema_aliasing = True
|
||||
self._warn_schema_aliasing = True
|
||||
else:
|
||||
self.legacy_schema_aliasing = legacy_schema_aliasing
|
||||
self._warn_schema_aliasing = False
|
||||
|
||||
super(MSDialect, self).__init__(**opts)
|
||||
|
||||
def do_savepoint(self, connection, name):
|
||||
|
|
@ -1371,21 +1567,31 @@ class MSDialect(default.DefaultDialect):
|
|||
|
||||
def initialize(self, connection):
|
||||
super(MSDialect, self).initialize(connection)
|
||||
self._setup_version_attributes()
|
||||
|
||||
def _setup_version_attributes(self):
|
||||
if self.server_version_info[0] not in list(range(8, 17)):
|
||||
# FreeTDS with version 4.2 seems to report here
|
||||
# a number like "95.10.255". Don't know what
|
||||
# that is. So emit warning.
|
||||
# Use TDS Version 7.0 through 7.3, per the MS information here:
|
||||
# https://msdn.microsoft.com/en-us/library/dd339982.aspx
|
||||
# and FreeTDS information here (7.3 highest supported version):
|
||||
# http://www.freetds.org/userguide/choosingtdsprotocol.htm
|
||||
util.warn(
|
||||
"Unrecognized server version info '%s'. Version specific "
|
||||
"behaviors may not function properly. If using ODBC "
|
||||
"with FreeTDS, ensure server version 7.0 or 8.0, not 4.2, "
|
||||
"is configured in the FreeTDS configuration." %
|
||||
"with FreeTDS, ensure TDS_VERSION 7.0 through 7.3, not "
|
||||
"4.2, is configured in the FreeTDS configuration." %
|
||||
".".join(str(x) for x in self.server_version_info))
|
||||
if self.server_version_info >= MS_2005_VERSION and \
|
||||
'implicit_returning' not in self.__dict__:
|
||||
self.implicit_returning = True
|
||||
if self.server_version_info >= MS_2008_VERSION:
|
||||
self.supports_multivalues_insert = True
|
||||
if self.deprecate_large_types is None:
|
||||
self.deprecate_large_types = \
|
||||
self.server_version_info >= MS_2012_VERSION
|
||||
|
||||
def _get_default_schema_name(self, connection):
|
||||
if self.server_version_info < MS_2005_VERSION:
|
||||
|
|
@ -1573,12 +1779,11 @@ class MSDialect(default.DefaultDialect):
|
|||
if coltype in (MSString, MSChar, MSNVarchar, MSNChar, MSText,
|
||||
MSNText, MSBinary, MSVarBinary,
|
||||
sqltypes.LargeBinary):
|
||||
if charlen == -1:
|
||||
charlen = 'max'
|
||||
kwargs['length'] = charlen
|
||||
if collation:
|
||||
kwargs['collation'] = collation
|
||||
if coltype == MSText or \
|
||||
(coltype in (MSString, MSNVarchar) and charlen == -1):
|
||||
kwargs.pop('length')
|
||||
|
||||
if coltype is None:
|
||||
util.warn(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# mssql/information_schema.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# mssql/mxodbc.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# mssql/pymssql.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -46,11 +46,12 @@ class MSDialect_pymssql(MSDialect):
|
|||
@classmethod
|
||||
def dbapi(cls):
|
||||
module = __import__('pymssql')
|
||||
# pymmsql doesn't have a Binary method. we use string
|
||||
# TODO: monkeypatching here is less than ideal
|
||||
module.Binary = lambda x: x if hasattr(x, 'decode') else str(x)
|
||||
|
||||
# pymmsql < 2.1.1 doesn't have a Binary method. we use string
|
||||
client_ver = tuple(int(x) for x in module.__version__.split("."))
|
||||
if client_ver < (2, 1, 1):
|
||||
# TODO: monkeypatching here is less than ideal
|
||||
module.Binary = lambda x: x if hasattr(x, 'decode') else str(x)
|
||||
|
||||
if client_ver < (1, ):
|
||||
util.warn("The pymssql dialect expects at least "
|
||||
"the 1.0 series of the pymssql DBAPI.")
|
||||
|
|
@ -63,7 +64,7 @@ class MSDialect_pymssql(MSDialect):
|
|||
def _get_server_version_info(self, connection):
|
||||
vers = connection.scalar("select @@version")
|
||||
m = re.match(
|
||||
r"Microsoft SQL Server.*? - (\d+).(\d+).(\d+).(\d+)", vers)
|
||||
r"Microsoft .*? - (\d+).(\d+).(\d+).(\d+)", vers)
|
||||
if m:
|
||||
return tuple(int(x) for x in m.group(1, 2, 3, 4))
|
||||
else:
|
||||
|
|
@ -84,7 +85,8 @@ class MSDialect_pymssql(MSDialect):
|
|||
"message 20003", # connection timeout
|
||||
"Error 10054",
|
||||
"Not connected to any MS SQL server",
|
||||
"Connection is closed"
|
||||
"Connection is closed",
|
||||
"message 20006", # Write to the server failed
|
||||
):
|
||||
if msg in str(e):
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# mssql/pyodbc.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -12,74 +12,57 @@
|
|||
:connectstring: mssql+pyodbc://<username>:<password>@<dsnname>
|
||||
:url: http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
Additional Connection Examples
|
||||
-------------------------------
|
||||
Connecting to PyODBC
|
||||
--------------------
|
||||
|
||||
Examples of pyodbc connection string URLs:
|
||||
The URL here is to be translated to PyODBC connection strings, as
|
||||
detailed in `ConnectionStrings <https://code.google.com/p/pyodbc/wiki/ConnectionStrings>`_.
|
||||
|
||||
* ``mssql+pyodbc://mydsn`` - connects using the specified DSN named ``mydsn``.
|
||||
The connection string that is created will appear like::
|
||||
DSN Connections
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
dsn=mydsn;Trusted_Connection=Yes
|
||||
A DSN-based connection is **preferred** overall when using ODBC. A
|
||||
basic DSN-based connection looks like::
|
||||
|
||||
* ``mssql+pyodbc://user:pass@mydsn`` - connects using the DSN named
|
||||
``mydsn`` passing in the ``UID`` and ``PWD`` information. The
|
||||
connection string that is created will appear like::
|
||||
engine = create_engine("mssql+pyodbc://scott:tiger@some_dsn")
|
||||
|
||||
Which above, will pass the following connection string to PyODBC::
|
||||
|
||||
dsn=mydsn;UID=user;PWD=pass
|
||||
|
||||
* ``mssql+pyodbc://user:pass@mydsn/?LANGUAGE=us_english`` - connects
|
||||
using the DSN named ``mydsn`` passing in the ``UID`` and ``PWD``
|
||||
information, plus the additional connection configuration option
|
||||
``LANGUAGE``. The connection string that is created will appear
|
||||
like::
|
||||
If the username and password are omitted, the DSN form will also add
|
||||
the ``Trusted_Connection=yes`` directive to the ODBC string.
|
||||
|
||||
dsn=mydsn;UID=user;PWD=pass;LANGUAGE=us_english
|
||||
Hostname Connections
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host/db`` - connects using a connection
|
||||
that would appear like::
|
||||
Hostname-based connections are **not preferred**, however are supported.
|
||||
The ODBC driver name must be explicitly specified::
|
||||
|
||||
DRIVER={SQL Server};Server=host;Database=db;UID=user;PWD=pass
|
||||
engine = create_engine("mssql+pyodbc://scott:tiger@myhost:port/databasename?driver=SQL+Server+Native+Client+10.0")
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host:123/db`` - connects using a connection
|
||||
string which includes the port
|
||||
information using the comma syntax. This will create the following
|
||||
connection string::
|
||||
.. versionchanged:: 1.0.0 Hostname-based PyODBC connections now require the
|
||||
SQL Server driver name specified explicitly. SQLAlchemy cannot
|
||||
choose an optimal default here as it varies based on platform
|
||||
and installed drivers.
|
||||
|
||||
DRIVER={SQL Server};Server=host,123;Database=db;UID=user;PWD=pass
|
||||
Other keywords interpreted by the Pyodbc dialect to be passed to
|
||||
``pyodbc.connect()`` in both the DSN and hostname cases include:
|
||||
``odbc_autotranslate``, ``ansi``, ``unicode_results``, ``autocommit``.
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host/db?port=123`` - connects using a connection
|
||||
string that includes the port
|
||||
information as a separate ``port`` keyword. This will create the
|
||||
following connection string::
|
||||
Pass through exact Pyodbc string
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
DRIVER={SQL Server};Server=host;Database=db;UID=user;PWD=pass;port=123
|
||||
A PyODBC connection string can also be sent exactly as specified in
|
||||
`ConnectionStrings <https://code.google.com/p/pyodbc/wiki/ConnectionStrings>`_
|
||||
into the driver using the parameter ``odbc_connect``. The delimeters must be URL escaped, however,
|
||||
as illustrated below using ``urllib.quote_plus``::
|
||||
|
||||
* ``mssql+pyodbc://user:pass@host/db?driver=MyDriver`` - connects using a
|
||||
connection string that includes a custom ODBC driver name. This will create
|
||||
the following connection string::
|
||||
import urllib
|
||||
params = urllib.quote_plus("DRIVER={SQL Server Native Client 10.0};SERVER=dagger;DATABASE=test;UID=user;PWD=password")
|
||||
|
||||
DRIVER={MyDriver};Server=host;Database=db;UID=user;PWD=pass
|
||||
engine = create_engine("mssql+pyodbc:///?odbc_connect=%s" % params)
|
||||
|
||||
If you require a connection string that is outside the options
|
||||
presented above, use the ``odbc_connect`` keyword to pass in a
|
||||
urlencoded connection string. What gets passed in will be urldecoded
|
||||
and passed directly.
|
||||
|
||||
For example::
|
||||
|
||||
mssql+pyodbc:///?odbc_connect=dsn%3Dmydsn%3BDatabase%3Ddb
|
||||
|
||||
would create the following connection string::
|
||||
|
||||
dsn=mydsn;Database=db
|
||||
|
||||
Encoding your connection string can be easily accomplished through
|
||||
the python shell. For example::
|
||||
|
||||
>>> import urllib
|
||||
>>> urllib.quote_plus('dsn=mydsn;Database=db')
|
||||
'dsn%3Dmydsn%3BDatabase%3Ddb'
|
||||
|
||||
Unicode Binds
|
||||
-------------
|
||||
|
|
@ -112,7 +95,7 @@ for unix + PyODBC.
|
|||
|
||||
"""
|
||||
|
||||
from .base import MSExecutionContext, MSDialect
|
||||
from .base import MSExecutionContext, MSDialect, VARBINARY
|
||||
from ...connectors.pyodbc import PyODBCConnector
|
||||
from ... import types as sqltypes, util
|
||||
import decimal
|
||||
|
|
@ -191,6 +174,22 @@ class _MSFloat_pyodbc(_ms_numeric_pyodbc, sqltypes.Float):
|
|||
pass
|
||||
|
||||
|
||||
class _VARBINARY_pyodbc(VARBINARY):
|
||||
def bind_processor(self, dialect):
|
||||
if dialect.dbapi is None:
|
||||
return None
|
||||
|
||||
DBAPIBinary = dialect.dbapi.Binary
|
||||
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return DBAPIBinary(value)
|
||||
else:
|
||||
# pyodbc-specific
|
||||
return dialect.dbapi.BinaryNull
|
||||
return process
|
||||
|
||||
|
||||
class MSExecutionContext_pyodbc(MSExecutionContext):
|
||||
_embedded_scope_identity = False
|
||||
|
||||
|
|
@ -243,13 +242,13 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
|
|||
|
||||
execution_ctx_cls = MSExecutionContext_pyodbc
|
||||
|
||||
pyodbc_driver_name = 'SQL Server'
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MSDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric: _MSNumeric_pyodbc,
|
||||
sqltypes.Float: _MSFloat_pyodbc
|
||||
sqltypes.Float: _MSFloat_pyodbc,
|
||||
VARBINARY: _VARBINARY_pyodbc,
|
||||
sqltypes.LargeBinary: _VARBINARY_pyodbc,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# mssql/zxjdbc.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -13,6 +13,8 @@
|
|||
[?key=value&key=value...]
|
||||
:driverurl: http://jtds.sourceforge.net/
|
||||
|
||||
.. note:: Jython is not supported by current versions of SQLAlchemy. The
|
||||
zxjdbc dialect should be considered as experimental.
|
||||
|
||||
"""
|
||||
from ...connectors.zxJDBC import ZxJDBCConnector
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue