summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/databases/mssql.py
diff options
context:
space:
mode:
authorMichael Trier <mtrier@gmail.com>2008-12-22 20:20:55 +0000
committerMichael Trier <mtrier@gmail.com>2008-12-22 20:20:55 +0000
commit886ddcd12db97984cdb1cc94b9abaee5df4eb6d5 (patch)
tree344b8a10c38d00164301e982b2074ccf7cefa2d7 /lib/sqlalchemy/databases/mssql.py
parent4bb848907339b0e69b5d5ad3d020305ce681b823 (diff)
downloadsqlalchemy-886ddcd12db97984cdb1cc94b9abaee5df4eb6d5.tar.gz
Major refactoring of the MSSQL dialect. Thanks zzzeek.
Includes simplifying the IDENTITY handling and the exception handling. Also includes a cleanup of the connection string handling for pyodbc to favor the DSN syntax.
Diffstat (limited to 'lib/sqlalchemy/databases/mssql.py')
-rw-r--r--lib/sqlalchemy/databases/mssql.py496
1 files changed, 299 insertions, 197 deletions
diff --git a/lib/sqlalchemy/databases/mssql.py b/lib/sqlalchemy/databases/mssql.py
index bcfd975ab..8fb6bfa4a 100644
--- a/lib/sqlalchemy/databases/mssql.py
+++ b/lib/sqlalchemy/databases/mssql.py
@@ -1,48 +1,192 @@
# mssql.py
-"""MSSQL backend, thru either pymssq, adodbapi or pyodbc interfaces.
+"""Support for the Microsoft SQL Server database.
-* ``IDENTITY`` columns are supported by using SA ``schema.Sequence()``
- objects. In other words::
+Driver
+------
+
+The MSSQL dialect will work with three different available drivers:
+
+* *pymssql* - http://pymssql.sourceforge.net/
+
+* *pyodbc* - http://pyodbc.sourceforge.net/. This is the recommeded
+ driver.
+
+* *adodbapi* - http://adodbapi.sourceforge.net/
+
+Drivers are loaded in the order listed above based on availability.
+Currently the pyodbc driver offers the greatest level of
+compatibility.
+
+Connecting
+----------
+
+Connecting with create_engine() uses the standard URL approach of
+``mssql://user:pass@host/dbname[?key=value&key=value...]``.
+
+If the database name is present, the tokens are converted to a
+connection string with the specified values. If the database is not
+present, then the host token is taken directly as the DSN name.
+
+Examples of pyodbc connection string URLs:
+
+* *mssql://mydsn* - connects using the specified DSN named ``mydsn``.
+ The connection string that is created will appear like::
+
+ dsn=mydsn;TrustedConnection=Yes
+
+* *mssql://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::
+
+ dsn=mydsn;UID=user;PWD=pass
+
+* *mssql://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::
+
+ dsn=mydsn;UID=user;PWD=pass;LANGUAGE=us_english
+
+* *mssql://user:pass@host/db* - connects using a connection string
+ dynamically created that would appear like::
+
+ DRIVER={SQL Server};Server=host;Database=db;UID=user;PWD=pass
+
+* *mssql://user:pass@host:123/db* - connects using a connection
+ string that is dynamically created, which also includes the port
+ information using the comma syntax. If your connection string
+ requires the port information to be passed as a ``port`` keyword
+ see the next example. This will create the following connection
+ string::
+
+ DRIVER={SQL Server};Server=host,123;Database=db;UID=user;PWD=pass
+
+* *mssql://user:pass@host/db?port=123* - connects using a connection
+ string that is dynamically created that includes the port
+ information as a separate ``port`` keyword. This will create the
+ following connection string::
+
+ DRIVER={SQL Server};Server=host;Database=db;UID=user;PWD=pass;port=123
+
+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:///?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'
+
+Additional arguments which may be specified either as query string
+arguments on the URL, or as keyword argument to
+:func:`~sqlalchemy.create_engine()` are:
+
+* *auto_identity_insert* - enables support for IDENTITY inserts by
+ automatically turning IDENTITY INSERT ON and OFF as required.
+ Defaults to ``True`.
+
+* *query_timeout* - allows you to override the default query timeout.
+ Defaults to ``None``. This is only supported on pymssql.
+
+* *text_as_varchar* - if enabled this will treat all TEXT column
+ types as their equivalent VARCHAR(max) type. This is often used if
+ you need to compare a VARCHAR to a TEXT field, which is not
+ supported directly on MSSQL. Defaults to ``False``.
+
+* *use_scope_identity* - allows you to specify that SCOPE_IDENTITY
+ should be used in place of the non-scoped version @@IDENTITY.
+ Defaults to ``False``. On pymssql this defaults to ``True``, and on
+ pyodbc this defaults to ``True`` if the version of pyodbc being
+ used supports it.
+
+* *has_window_funcs* - indicates whether or not window functions
+ (LIMIT and OFFSET) are supported on the version of MSSQL being
+ used. If you're running MSSQL 2005 or later turn this on to get
+ OFFSET support. Defaults to ``False``.
+
+* *max_identifier_length* - allows you to se the maximum length of
+ identfiers supported by the database. Defaults to 128. For pymssql
+ the default is 30.
+
+* *schema_name* - use to set the schema name. Defaults to ``dbo``.
+
+Auto Increment Behavior
+-----------------------
+
+``IDENTITY`` columns are supported by using SQLAlchemy
+``schema.Sequence()`` objects. In other words::
Table('test', mss_engine,
- Column('id', Integer, Sequence('blah',100,10), primary_key=True),
+ Column('id', Integer,
+ Sequence('blah',100,10), primary_key=True),
Column('name', String(20))
).create()
- would yield::
+would yield::
CREATE TABLE test (
id INTEGER NOT NULL IDENTITY(100,10) PRIMARY KEY,
name VARCHAR(20) NULL,
)
- Note that the start & increment values for sequences are optional
- and will default to 1,1.
+Note that the ``start`` and ``increment`` values for sequences are
+optional and will default to 1,1.
* Support for ``SET IDENTITY_INSERT ON`` mode (automagic on / off for
``INSERT`` s)
-* Support for auto-fetching of ``@@IDENTITY/@@SCOPE_IDENTITY()`` on ``INSERT``
+* Support for auto-fetching of ``@@IDENTITY/@@SCOPE_IDENTITY()`` on
+ ``INSERT``
+
+LIMIT/OFFSET Support
+--------------------
+
+MSSQL has no support for the LIMIT or OFFSET keysowrds. LIMIT is
+supported directly through the ``TOP`` Transact SQL keyword::
+
+ select.limit
+
+will yield::
-* ``select._limit`` implemented as ``SELECT TOP n``
+ SELECT TOP n
-* Experimental implemention of LIMIT / OFFSET with row_number()
+If the ``has_window_funcs`` flag is set then LIMIT with OFFSET
+support is available through the ``ROW_NUMBER OVER`` construct. This
+construct requires an ``ORDER BY`` to be specified as well and is
+only available on MSSQL 2005 and later.
-* Support for three levels of column nullability provided. The default
- nullability allows nulls::
+Nullability
+-----------
+MSSQL has support for three levels of column nullability. The default
+nullability allows nulls and is explicit in the CREATE TABLE
+construct::
name VARCHAR(20) NULL
- If ``nullable=None`` is specified then no specification is made. In other
- words the database's configured default is used. This will render::
+If ``nullable=None`` is specified then no specification is made. In
+other words the database's configured default is used. This will
+render::
name VARCHAR(20)
- If ``nullable`` is True or False then the column will be ``NULL` or
- ``NOT NULL`` respectively.
+If ``nullable`` is ``True`` or ``False`` then the column will be
+``NULL` or ``NOT NULL`` respectively.
-Known issues / TODO:
+Known Issues
+------------
* No support for more than one ``IDENTITY`` column per table
@@ -50,7 +194,7 @@ Known issues / TODO:
does **not** work around
"""
-import datetime, operator, re, sys
+import datetime, operator, re, sys, urllib
from sqlalchemy import sql, schema, exc, util
from sqlalchemy.sql import compiler, expression, operators as sqlops, functions as sql_functions
@@ -299,77 +443,92 @@ class MSVariant(sqltypes.TypeEngine):
def get_col_spec(self):
return "SQL_VARIANT"
-class MSSQLExecutionContext(default.DefaultExecutionContext):
- def __init__(self, *args, **kwargs):
- self.IINSERT = self.HASIDENT = False
- super(MSSQLExecutionContext, self).__init__(*args, **kwargs)
- def _has_implicit_sequence(self, column):
- if column.primary_key and column.autoincrement:
- if isinstance(column.type, sqltypes.Integer) and not column.foreign_keys:
- if column.default is None or (isinstance(column.default, schema.Sequence) and \
- column.default.optional):
- return True
- return False
+def _has_implicit_sequence(column):
+ return column.primary_key and \
+ column.autoincrement and \
+ isinstance(column.type, sqltypes.Integer) and \
+ not column.foreign_keys and \
+ (
+ column.default is None or
+ (
+ isinstance(column.default, schema.Sequence) and
+ column.default.optional)
+ )
+
+def _table_sequence_column(tbl):
+ if not hasattr(tbl, '_ms_has_sequence'):
+ tbl._ms_has_sequence = None
+ for column in tbl.c:
+ if getattr(column, 'sequence', False) or _has_implicit_sequence(column):
+ tbl._ms_has_sequence = column
+ break
+ return tbl._ms_has_sequence
+
+class MSSQLExecutionContext(default.DefaultExecutionContext):
+ IINSERT = False
+ HASIDENT = False
def pre_exec(self):
- """MS-SQL has a special mode for inserting non-NULL values
- into IDENTITY columns.
+ """Activate IDENTITY_INSERT if needed."""
- Activate it if the feature is turned on and needed.
- """
if self.compiled.isinsert:
tbl = self.compiled.statement.table
- if not hasattr(tbl, 'has_sequence'):
- tbl.has_sequence = None
- for column in tbl.c:
- if getattr(column, 'sequence', False) or self._has_implicit_sequence(column):
- tbl.has_sequence = column
- break
-
- self.HASIDENT = bool(tbl.has_sequence)
+
+ seq_column = _table_sequence_column(tbl)
+ self.HASIDENT = bool(seq_column)
if self.dialect.auto_identity_insert and self.HASIDENT:
- if isinstance(self.compiled_parameters, list):
- self.IINSERT = tbl.has_sequence.key in self.compiled_parameters[0]
- else:
- self.IINSERT = tbl.has_sequence.key in self.compiled_parameters
+ self.IINSERT = tbl._ms_has_sequence.key in self.compiled_parameters[0]
else:
self.IINSERT = False
if self.IINSERT:
- self.cursor.execute("SET IDENTITY_INSERT %s ON" % self.dialect.identifier_preparer.format_table(self.compiled.statement.table))
+ self.cursor.execute("SET IDENTITY_INSERT %s ON" %
+ self.dialect.identifier_preparer.format_table(self.compiled.statement.table))
- super(MSSQLExecutionContext, self).pre_exec()
+ def handle_dbapi_exception(self, e):
+ if self.IINSERT:
+ try:
+ self.cursor.execute("SET IDENTITY_INSERT %s OFF" % self.dialect.identifier_preparer.format_table(self.compiled.statement.table))
+ except:
+ pass
def post_exec(self):
- """Turn off the INDENTITY_INSERT mode if it's been activated,
- and fetch recently inserted IDENTIFY values (works only for
- one column).
- """
+ """Disable IDENTITY_INSERT if enabled."""
- if self.compiled.isinsert and (not self.executemany) and self.HASIDENT and not self.IINSERT:
- if not len(self._last_inserted_ids) or self._last_inserted_ids[0] is None:
+ if self.compiled.isinsert and not self.executemany and self.HASIDENT and not self.IINSERT:
+ if not self._last_inserted_ids or self._last_inserted_ids[0] is None:
if self.dialect.use_scope_identity:
self.cursor.execute("SELECT scope_identity() AS lastrowid")
else:
self.cursor.execute("SELECT @@identity AS lastrowid")
row = self.cursor.fetchone()
self._last_inserted_ids = [int(row[0])] + self._last_inserted_ids[1:]
- super(MSSQLExecutionContext, self).post_exec()
+
+ if self.IINSERT:
+ self.cursor.execute("SET IDENTITY_INSERT %s OFF" % self.dialect.identifier_preparer.format_table(self.compiled.statement.table))
class MSSQLExecutionContext_pyodbc (MSSQLExecutionContext):
def pre_exec(self):
"""where appropriate, issue "select scope_identity()" in the same statement"""
super(MSSQLExecutionContext_pyodbc, self).pre_exec()
- if self.compiled.isinsert and self.HASIDENT and (not self.IINSERT) \
+ if self.compiled.isinsert and self.HASIDENT and not self.IINSERT \
and len(self.parameters) == 1 and self.dialect.use_scope_identity:
self.statement += "; select scope_identity()"
def post_exec(self):
- if self.compiled.isinsert and self.HASIDENT and (not self.IINSERT) and self.dialect.use_scope_identity:
- # do nothing - id was fetched in dialect.do_execute()
- pass
+ if self.HASIDENT and not self.IINSERT and self.dialect.use_scope_identity and not self.executemany:
+ import pyodbc
+ # Fetch the last inserted id from the manipulated statement
+ # We may have to skip over a number of result sets with no data (due to triggers, etc.)
+ while True:
+ try:
+ row = self.cursor.fetchone()
+ break
+ except pyodbc.Error, e:
+ self.cursor.nextset()
+ self._last_inserted_ids = [int(row[0])]
else:
super(MSSQLExecutionContext_pyodbc, self).post_exec()
@@ -377,7 +536,13 @@ class MSSQLDialect(default.DefaultDialect):
name = 'mssql'
supports_default_values = True
supports_empty_insert = False
+ auto_identity_insert = True
execution_ctx_cls = MSSQLExecutionContext
+ text_as_varchar = False
+ use_scope_identity = False
+ has_window_funcs = False
+ max_identifier_length = 128
+ schema_name = "dbo"
colspecs = {
sqltypes.Unicode : MSNVarchar,
@@ -426,23 +591,33 @@ class MSSQLDialect(default.DefaultDialect):
'sql_variant': MSVariant,
}
- def __new__(cls, dbapi=None, *args, **kwargs):
- if cls != MSSQLDialect:
+ def __new__(cls, *args, **kwargs):
+ if cls is not MSSQLDialect:
+ # this gets called with the dialect specific class
return super(MSSQLDialect, cls).__new__(cls, *args, **kwargs)
+ dbapi = kwargs.get('dbapi', None)
if dbapi:
dialect = dialect_mapping.get(dbapi.__name__)
- return dialect(*args, **kwargs)
+ return dialect(**kwargs)
else:
return object.__new__(cls, *args, **kwargs)
- def __init__(self, auto_identity_insert=True, **params):
- super(MSSQLDialect, self).__init__(**params)
- self.auto_identity_insert = auto_identity_insert
- self.text_as_varchar = False
- self.use_scope_identity = False
- self.has_window_funcs = False
- self.set_default_schema_name("dbo")
+ def __init__(self,
+ auto_identity_insert=True, query_timeout=None, text_as_varchar=False,
+ use_scope_identity=False, has_window_funcs=False, max_identifier_length=None,
+ schema_name="dbo", **opts):
+ self.auto_identity_insert = bool(auto_identity_insert)
+ self.query_timeout = int(query_timeout or 0)
+ self.schema_name = schema_name
+ # to-do: the options below should use server version introspection to set themselves on connection
+ self.text_as_varchar = bool(text_as_varchar)
+ self.use_scope_identity = bool(use_scope_identity)
+ self.has_window_funcs = bool(has_window_funcs)
+ self.max_identifier_length = int(max_identifier_length or 0) or 128
+ super(MSSQLDialect, self).__init__(**opts)
+
+ @classmethod
def dbapi(cls, module_name=None):
if module_name:
try:
@@ -458,8 +633,8 @@ class MSSQLDialect(default.DefaultDialect):
pass
else:
raise ImportError('No DBAPI module detected for MSSQL - please install pyodbc, pymssql, or adodbapi')
- dbapi = classmethod(dbapi)
+ @base.connection_memoize(('mssql', 'server_version_info'))
def server_version_info(self, connection):
"""A tuple of the database server version.
@@ -472,14 +647,11 @@ class MSSQLDialect(default.DefaultDialect):
cached per-Connection.
"""
return connection.dialect._server_version_info(connection.connection)
- server_version_info = base.connection_memoize(
- ('mssql', 'server_version_info'))(server_version_info)
def _server_version_info(self, dbapi_con):
"""Return a tuple of the database's version number."""
-
raise NotImplementedError()
-
+
def create_connect_args(self, url):
opts = url.translate_connect_args(username='user')
opts.update(url.query)
@@ -493,7 +665,7 @@ class MSSQLDialect(default.DefaultDialect):
self.use_scope_identity = bool(int(opts.pop('use_scope_identity')))
if 'has_window_funcs' in opts:
self.has_window_funcs = bool(int(opts.pop('has_window_funcs')))
- return self.make_connect_string(opts)
+ return self.make_connect_string(opts, url.query)
def type_descriptor(self, typeobj):
newobj = sqltypes.adapt_type(typeobj, self.colspecs)
@@ -505,51 +677,10 @@ class MSSQLDialect(default.DefaultDialect):
def get_default_schema_name(self, connection):
return self.schema_name
- def set_default_schema_name(self, schema_name):
- self.schema_name = schema_name
-
- def last_inserted_ids(self):
- return self.context.last_inserted_ids
-
- def do_execute(self, cursor, statement, params, context=None, **kwargs):
- if params == {}:
- params = ()
- try:
- super(MSSQLDialect, self).do_execute(cursor, statement, params, context=context, **kwargs)
- finally:
- if context.IINSERT:
- cursor.execute("SET IDENTITY_INSERT %s OFF" % self.identifier_preparer.format_table(context.compiled.statement.table))
-
- def do_executemany(self, cursor, statement, params, context=None, **kwargs):
- try:
- super(MSSQLDialect, self).do_executemany(cursor, statement, params, context=context, **kwargs)
- finally:
- if context.IINSERT:
- cursor.execute("SET IDENTITY_INSERT %s OFF" % self.identifier_preparer.format_table(context.compiled.statement.table))
-
- def _execute(self, c, statement, parameters):
- try:
- if parameters == {}:
- parameters = ()
- c.execute(statement, parameters)
- self.context.rowcount = c.rowcount
- c.DBPROP_COMMITPRESERVE = "Y"
- except Exception, e:
- raise exc.DBAPIError.instance(statement, parameters, e)
-
def table_names(self, connection, schema):
from sqlalchemy.databases import information_schema as ischema
return ischema.table_names(connection, schema)
- def raw_connection(self, connection):
- """Pull the raw pymmsql connection out--sensative to "pool.ConnectionFairy" and pymssql.pymssqlCnx Classes"""
- try:
- # TODO: probably want to move this to individual dialect subclasses to
- # save on the exception throw + simplify
- return connection.connection.__dict__['_pymssqlCnx__cnx']
- except:
- return connection.connection.adoConn
-
def uppercase_table(self, t):
# convert all names to uppercase -- fixes refs to INFORMATION_SCHEMA for case-senstive DBs, and won't matter for case-insensitive
t.name = t.name.upper()
@@ -559,6 +690,7 @@ class MSSQLDialect(default.DefaultDialect):
c.name = c.name.upper()
return t
+
def has_table(self, connection, tablename, schema=None):
import sqlalchemy.databases.information_schema as ischema
@@ -645,7 +777,7 @@ class MSSQLDialect(default.DefaultDialect):
ic = table.c[col_name]
ic.autoincrement = True
# setup a psuedo-sequence to represent the identity attribute - we interpret this at table.create() time as the identity attribute
- ic.sequence = schema.Sequence(ic.name + '_identity')
+ ic.sequence = schema.Sequence(ic.name + '_identity', 1, 1)
# MSSQL: only one identity per table allowed
cursor.close()
break
@@ -722,16 +854,13 @@ class MSSQLDialect_pymssql(MSSQLDialect):
supports_sane_rowcount = False
max_identifier_length = 30
+ @classmethod
def import_dbapi(cls):
import pymssql as module
# pymmsql doesn't have a Binary method. we use string
# TODO: monkeypatching here is less than ideal
module.Binary = lambda st: str(st)
return module
- import_dbapi = classmethod(import_dbapi)
-
- ischema_names = MSSQLDialect.ischema_names.copy()
-
def __init__(self, **params):
super(MSSQLDialect_pymssql, self).__init__(**params)
@@ -739,23 +868,16 @@ class MSSQLDialect_pymssql(MSSQLDialect):
# pymssql understands only ascii
if self.convert_unicode:
+ util.warn("pymssql does not support unicode")
self.encoding = params.get('encoding', 'ascii')
- def do_rollback(self, connection):
- # pymssql throws an error on repeated rollbacks. Ignore it.
- # TODO: this is normal behavior for most DBs. are we sure we want to ignore it ?
- try:
- connection.rollback()
- except:
- pass
-
def create_connect_args(self, url):
r = super(MSSQLDialect_pymssql, self).create_connect_args(url)
if hasattr(self, 'query_timeout'):
self.dbapi._mssql.set_query_timeout(self.query_timeout)
return r
- def make_connect_string(self, keys):
+ def make_connect_string(self, keys, query):
if keys.get('port'):
# pymssql expects port as host:port, not a separate arg
keys['host'] = ''.join([keys.get('host', ''), ':', str(keys['port'])])
@@ -776,6 +898,7 @@ class MSSQLDialect_pyodbc(MSSQLDialect):
def __init__(self, **params):
super(MSSQLDialect_pyodbc, self).__init__(**params)
+ # FIXME: scope_identity sniff should look at server version, not the ODBC driver
# whether use_scope_identity will work depends on the version of pyodbc
try:
import pyodbc
@@ -783,10 +906,10 @@ class MSSQLDialect_pyodbc(MSSQLDialect):
except:
pass
+ @classmethod
def import_dbapi(cls):
import pyodbc as module
return module
- import_dbapi = classmethod(import_dbapi)
colspecs = MSSQLDialect.colspecs.copy()
if supports_unicode:
@@ -800,45 +923,41 @@ class MSSQLDialect_pyodbc(MSSQLDialect):
ischema_names['smalldatetime'] = MSDate_pyodbc
ischema_names['datetime'] = MSDateTime_pyodbc
- def make_connect_string(self, keys):
+ def make_connect_string(self, keys, query):
if 'max_identifier_length' in keys:
self.max_identifier_length = int(keys.pop('max_identifier_length'))
- if 'dsn' in keys:
- connectors = ['dsn=%s' % keys.pop('dsn')]
+
+ if 'odbc_connect' in keys:
+ connectors = [urllib.unquote_plus(keys.pop('odbc_connect'))]
else:
- port = ''
- if 'port' in keys and (
- keys.get('driver', 'SQL Server') == 'SQL Server'):
- port = ',%d' % int(keys.pop('port'))
+ dsn_connection = 'dsn' in keys or ('host' in keys and 'database' not in keys)
+ if dsn_connection:
+ connectors= ['dsn=%s' % (keys.pop('host', '') or keys.pop('dsn', ''))]
+ else:
+ port = ''
+ if 'port' in keys and not 'port' in query:
+ port = ',%d' % int(keys.pop('port'))
+
+ connectors = ["DRIVER={%s}" % keys.pop('driver', 'SQL Server'),
+ 'Server=%s%s' % (keys.pop('host', ''), port),
+ 'Database=%s' % keys.pop('database', '') ]
+
+ user = keys.pop("user", None)
+ if user:
+ connectors.append("UID=%s" % user)
+ connectors.append("PWD=%s" % keys.pop('password', ''))
+ else:
+ connectors.append("TrustedConnection=Yes")
- connectors = ["DRIVER={%s}" % keys.pop('driver', 'SQL Server'),
- 'Server=%s%s' % (keys.pop('host', ''), port),
- 'Database=%s' % keys.pop('database', '') ]
+ # if set to 'Yes', the ODBC layer will try to automagically convert
+ # textual data from your database encoding to your client encoding
+ # This should obviously be set to 'No' if you query a cp1253 encoded
+ # database from a latin1 client...
+ if 'odbc_autotranslate' in keys:
+ connectors.append("AutoTranslate=%s" % keys.pop("odbc_autotranslate"))
- if 'port' in keys and not port:
- connectors.append('Port=%d' % int(keys.pop('port')))
+ connectors.extend(['%s=%s' % (k,v) for k,v in keys.iteritems()])
- user = keys.pop("user", None)
- if user:
- connectors.append("UID=%s" % user)
- connectors.append("PWD=%s" % keys.pop('password', ''))
- else:
- connectors.append("TrustedConnection=Yes")
-
- # if set to 'Yes', the ODBC layer will try to automagically convert
- # textual data from your database encoding to your client encoding
- # This should obviously be set to 'No' if you query a cp1253 encoded
- # database from a latin1 client...
- if 'odbc_autotranslate' in keys:
- connectors.append("AutoTranslate=%s" % keys.pop("odbc_autotranslate"))
-
- # Allow specification of partial ODBC connect string
- if 'odbc_options' in keys:
- odbc_options=keys.pop('odbc_options')
- if odbc_options[0]=="'" and odbc_options[-1]=="'":
- odbc_options=odbc_options[1:-1]
- connectors.append(odbc_options)
- connectors.extend(['%s=%s' % (k,v) for k,v in keys.iteritems()])
return [[";".join (connectors)], {}]
def is_disconnect(self, e):
@@ -850,23 +969,8 @@ class MSSQLDialect_pyodbc(MSSQLDialect):
return False
- def do_execute(self, cursor, statement, parameters, context=None, **kwargs):
- super(MSSQLDialect_pyodbc, self).do_execute(cursor, statement, parameters, context=context, **kwargs)
- if context and context.HASIDENT and (not context.IINSERT) and context.dialect.use_scope_identity:
- import pyodbc
- # Fetch the last inserted id from the manipulated statement
- # We may have to skip over a number of result sets with no data (due to triggers, etc.)
- while True:
- try:
- row = cursor.fetchone()
- break
- except pyodbc.Error, e:
- cursor.nextset()
- context._last_inserted_ids = [int(row[0])]
-
def _server_version_info(self, dbapi_con):
"""Convert a pyodbc SQL_DBMS_VER string into a tuple."""
-
version = []
r = re.compile('[.\-]')
for n in r.split(dbapi_con.getinfo(self.dbapi.SQL_DBMS_VER)):
@@ -882,10 +986,10 @@ class MSSQLDialect_adodbapi(MSSQLDialect):
supports_unicode = sys.maxunicode == 65535
supports_unicode_statements = True
+ @classmethod
def import_dbapi(cls):
import adodbapi as module
return module
- import_dbapi = classmethod(import_dbapi)
colspecs = MSSQLDialect.colspecs.copy()
colspecs[sqltypes.Unicode] = AdoMSNVarchar
@@ -895,7 +999,7 @@ class MSSQLDialect_adodbapi(MSSQLDialect):
ischema_names['nvarchar'] = AdoMSNVarchar
ischema_names['datetime'] = MSDateTime_adodbapi
- def make_connect_string(self, keys):
+ def make_connect_string(self, keys, query):
connectors = ["Provider=SQLOLEDB"]
if 'port' in keys:
connectors.append ("Data Source=%s, %s" % (keys.get("host"), keys.get("port")))
@@ -963,7 +1067,7 @@ class MSSQLCompiler(compiler.DefaultCompiler):
so tries to wrap it in a subquery with ``row_number()`` criterion.
"""
- if self.dialect.has_window_funcs and (not getattr(select, '_mssql_visit', None)) and select._offset:
+ if self.dialect.has_window_funcs and not getattr(select, '_mssql_visit', None) and select._offset:
# to use ROW_NUMBER(), an ORDER BY is required.
orderby = self.process(select._order_by_clause)
if not orderby:
@@ -1073,21 +1177,25 @@ class MSSQLSchemaGenerator(compiler.SchemaGenerator):
def get_column_specification(self, column, **kwargs):
colspec = self.preparer.format_column(column) + " " + column.type.dialect_impl(self.dialect).get_col_spec()
- # install a IDENTITY Sequence if we have an implicit IDENTITY column
- if (not getattr(column.table, 'has_sequence', False)) and column.primary_key and \
- column.autoincrement and isinstance(column.type, sqltypes.Integer) and not column.foreign_keys:
- if column.default is None or (isinstance(column.default, schema.Sequence) and column.default.optional):
- column.sequence = schema.Sequence(column.name + '_seq')
-
if column.nullable is not None:
if not column.nullable:
colspec += " NOT NULL"
else:
colspec += " NULL"
+
+ if not column.table:
+ raise exc.InvalidRequestError("mssql requires Table-bound columns in order to generate DDL")
+
+ seq_col = _table_sequence_column(column.table)
- if hasattr(column, 'sequence'):
- column.table.has_sequence = column
- colspec += " IDENTITY(%s,%s)" % (column.sequence.start or 1, column.sequence.increment or 1)
+ # install a IDENTITY Sequence if we have an implicit IDENTITY column
+ if seq_col is column:
+ sequence = getattr(column, 'sequence', None)
+ if sequence:
+ start, increment = sequence.start or 1, sequence.increment or 1
+ else:
+ start, increment = 1, 1
+ colspec += " IDENTITY(%s,%s)" % (start, increment)
else:
default = self.get_column_default_string(column)
if default is not None:
@@ -1104,11 +1212,6 @@ class MSSQLSchemaDropper(compiler.SchemaDropper):
self.execute()
-class MSSQLDefaultRunner(base.DefaultRunner):
- # TODO: does ms-sql have standalone sequences ?
- # A: No, only auto-incrementing IDENTITY property of a column
- pass
-
class MSSQLIdentifierPreparer(compiler.IdentifierPreparer):
reserved_words = compiler.IdentifierPreparer.reserved_words.union(MSSQL_RESERVED_WORDS)
@@ -1116,7 +1219,7 @@ class MSSQLIdentifierPreparer(compiler.IdentifierPreparer):
super(MSSQLIdentifierPreparer, self).__init__(dialect, initial_quote='[', final_quote=']')
def _escape_identifier(self, value):
- #TODO: determin MSSQL's escapeing rules
+ #TODO: determine MSSQL's escaping rules
return value
dialect = MSSQLDialect
@@ -1124,4 +1227,3 @@ dialect.statement_compiler = MSSQLCompiler
dialect.schemagenerator = MSSQLSchemaGenerator
dialect.schemadropper = MSSQLSchemaDropper
dialect.preparer = MSSQLIdentifierPreparer
-dialect.defaultrunner = MSSQLDefaultRunner