a Windows host [ticket:1580]
- mssql
- - Re-established initial support for pymssql (not functional
- yet, though)
+ - Re-established support for the pymssql dialect.
- Various fixes for implicit returning, reflection,
etc. - the MS-SQL dialects aren't quite complete
`jTDS JDBC Driver`_ ``mssql+zxjdbc`` no no development yes yes
mxodbc_ ``mssql+mxodbc`` yes development no yes with FreeTDS_ yes
pyodbc_ ``mssql+pyodbc``\* yes development no yes with FreeTDS_ yes
-pymssql_ ``mssql+pymssql`` development development no yes yes
+pymssql_ ``mssql+pymssql`` yes development no yes yes
**MySQL**
`MySQL Connector/J`_ ``mysql+zxjdbc`` no no yes yes yes
`MySQL Connector/Python`_ ``mysql+mysqlconnector`` yes partial no yes yes
------
.. automodule:: sqlalchemy.dialects.mssql.pyodbc
-AdoDBAPI
---------
-.. automodule:: sqlalchemy.dialects.mssql.adodbapi
-
pymssql
-------
.. automodule:: sqlalchemy.dialects.mssql.pymssql
--------------
.. automodule:: sqlalchemy.dialects.mssql.zxjdbc
+
+AdoDBAPI
+--------
+.. automodule:: sqlalchemy.dialects.mssql.adodbapi
+
+"""
+The adodbapi dialect is not implemented for 0.6 at this time.
+
+"""
from sqlalchemy import types as sqltypes, util
from sqlalchemy.dialects.mssql.base import MSDateTime, MSDialect
import sys
"""Support for the Microsoft SQL Server database.
-Driver
-------
-
-The MSSQL dialect will work with three different available drivers:
-
-* *pyodbc* - http://pyodbc.sourceforge.net/. This is the recommeded
- driver.
-
-* *pymssql* - http://pymssql.sourceforge.net/
-
-* *adodbapi* - http://adodbapi.sourceforge.net/
-
-Drivers are loaded in the order listed above based on availability.
-
-If you need to load a specific driver pass ``module_name`` when
-creating the engine::
-
- engine = create_engine('mssql+module_name://dsn')
-
-``module_name`` currently accepts: ``pyodbc``, ``pymssql``, and
-``adodbapi``.
-
-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+pyodbc://mydsn* - connects using the specified DSN named ``mydsn``.
- The connection string that is created will appear like::
-
- dsn=mydsn;Trusted_Connection=Yes
-
-* *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::
-
- 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::
-
- dsn=mydsn;UID=user;PWD=pass;LANGUAGE=us_english
-
-* *mssql+pyodbc://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+pyodbc://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+pyodbc://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+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'
-
-Additional arguments which may be specified either as query string
-arguments on the URL, or as keyword argument to
-:func:`~sqlalchemy.create_engine()` are:
-
-* *query_timeout* - allows you to override the default query timeout.
- Defaults to ``None``. This is only supported on pymssql.
-
-* *use_scope_identity* - allows you to specify that SCOPE_IDENTITY
- should be used in place of the non-scoped version @@IDENTITY.
- Defaults to True.
-
-* *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``.
+See the individual driver sections below for details on connecting.
Auto Increment Behavior
-----------------------
* No support for more than one ``IDENTITY`` column per table
-* pymssql has problems with binary and unicode data that this module
- does **not** work around
-
"""
import datetime, decimal, inspect, operator, sys, re
import itertools
"""
Support for the pymssql dialect.
-Going forward we will be supporting the 1.0 release of pymssql.
+This dialect supports pymssql 1.0 and greater.
+
+pymssql is available at:
+
+ http://pymssql.sourceforge.net/
+
+Connect string::
+
+ mssql+pymssql://<username>:<password>@<freetds_name>
+
+Adding "?charset=utf8" or similar will cause pymssql to return
+strings as Python unicode objects. This can potentially improve
+performance in some scenarios as decoding of strings is
+handled natively.
+
+pymssql inherits a lot of limitations from FreeTDS, including:
+
+* no support for multibyte schema identifiers
+* poor support for large decimals
+* poor support for binary fields
+* poor support for VARCHAR/CHAR fields over 255 characters
+
+Please consult the pymssql documentation for further information.
"""
from sqlalchemy.dialects.mssql.base import MSDialect
-from sqlalchemy import types as sqltypes
+from sqlalchemy import types as sqltypes, util, processors
+import re
+import decimal
+class _MSNumeric_pymssql(sqltypes.Numeric):
+ def result_processor(self, dialect, type_):
+ if not self.asdecimal:
+ return processors.to_float
+ else:
+ return sqltypes.Numeric.result_processor(self, dialect, type_)
class MSDialect_pymssql(MSDialect):
supports_sane_rowcount = False
max_identifier_length = 30
driver = 'pymssql'
-
+
+ colspecs = util.update_copy(
+ MSDialect.colspecs,
+ {
+ sqltypes.Numeric:_MSNumeric_pymssql,
+ sqltypes.Float:sqltypes.Float,
+ }
+ )
@classmethod
def dbapi(cls):
- import pymssql as module
+ module = __import__('pymssql')
# pymmsql doesn't have a Binary method. we use string
# TODO: monkeypatching here is less than ideal
- module.Binary = lambda st: str(st)
+ module.Binary = str
+
+ client_ver = tuple(int(x) for x in module.__version__.split("."))
+ if client_ver < (1, ):
+ util.warn("The pymssql dialect expects at least "
+ "the 1.0 series of the pymssql DBAPI.")
return module
def __init__(self, **params):
super(MSDialect_pymssql, self).__init__(**params)
self.use_scope_identity = True
+ def _get_server_version_info(self, connection):
+ vers = connection.scalar("select @@version")
+ m = re.match(r"Microsoft SQL Server.*? - (\d+).(\d+).(\d+).(\d+)", vers)
+ if m:
+ return tuple(int(x) for x in m.group(1, 2, 3, 4))
+ else:
+ return None
def create_connect_args(self, url):
- keys = url.query
- if keys.get('port'):
- # pymssql expects port as host:port, not a separate arg
- keys['host'] = ''.join([keys.get('host', ''), ':', str(keys['port'])])
- del keys['port']
- return [[], keys]
+ opts = url.translate_connect_args(username='user')
+ opts.update(url.query)
+ opts.pop('port', None)
+ return [[], opts]
def is_disconnect(self, e):
- return isinstance(e, self.dbapi.DatabaseError) and "Error 10054" in str(e)
+ for msg in (
+ "Error 10054",
+ "Not connected to any MS SQL server",
+ "Connection is closed"
+ ):
+ if msg in str(e):
+ return True
+ else:
+ return False
dialect = MSDialect_pymssql
\ No newline at end of file
"""
Support for MS-SQL via pyodbc.
-http://pypi.python.org/pypi/pyodbc/
+pyodbc is available at:
-Connect strings are of the form::
+ http://pypi.python.org/pypi/pyodbc/
- mssql+pyodbc://<username>:<password>@<dsn>/
- mssql+pyodbc://<username>:<password>@<host>/<database>
+Examples of pyodbc connection string URLs:
+
+* ``mssql+pyodbc://mydsn`` - connects using the specified DSN named ``mydsn``.
+ The connection string that is created will appear like::
+
+ dsn=mydsn;Trusted_Connection=Yes
+
+* ``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::
+
+ 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::
+
+ dsn=mydsn;UID=user;PWD=pass;LANGUAGE=us_english
+
+* ``mssql+pyodbc://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+pyodbc://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+pyodbc://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+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'
"""
no_support('maxdb', 'database support flakey'),
no_support('oracle', 'FIXME: no support in database?'),
no_support('sybase', 'FIXME: guessing, needs confirmation'),
+ no_support('mssql+pymssql', 'no FreeTDS support'),
exclude('mysql', '<', (4, 1, 1), 'no unicode connection support'),
)
testing.against('oracle+cx_oracle'):
assert testing.db.dialect.returns_unicode_strings == 'conditional'
return
-
+
+ if testing.against('mssql+pymssql'):
+ assert testing.db.dialect.returns_unicode_strings == ('charset' in testing.db.url.query)
+ return
+
assert testing.db.dialect.returns_unicode_strings == \
((testing.db.name, testing.db.driver) in \
(
[15.7563],
filter_ = lambda n:n is not None and round(n, 5) or None
)
-
+
+ @testing.fails_on('mssql+pymssql', 'FIXME: improve pymssql dec handling')
def test_precision_decimal(self):
numbers = set([
decimal.Decimal("54.234246451650"),
numbers,
)
+ @testing.fails_on('mssql+pymssql', 'FIXME: improve pymssql dec handling')
def test_enotation_decimal(self):
"""test exceedingly small decimals.
@testing.fails_on('postgresql+pg8000', 'TODO')
@testing.fails_on("firebird", "Precision must be from 1 to 18")
@testing.fails_on("sybase+pysybase", "TODO")
+ @testing.fails_on('mssql+pymssql', 'FIXME: improve pymssql dec handling')
def test_many_significant_digits(self):
numbers = set([
decimal.Decimal("31943874831932418390.01"),