From: Mike Bayer Date: Sat, 20 Mar 2010 15:50:39 +0000 (-0400) Subject: - pymssql now works again, expecting at least the 1.0 series. X-Git-Tag: rel_0_6beta2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eb728389539a5bac4c3f231f578ceb92cb068065;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - pymssql now works again, expecting at least the 1.0 series. --- diff --git a/CHANGES b/CHANGES index 6a0fedc376..57a419ae7f 100644 --- a/CHANGES +++ b/CHANGES @@ -373,8 +373,7 @@ CHANGES 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 diff --git a/doc/build/dbengine.rst b/doc/build/dbengine.rst index 5fdd61434d..644ede1ba9 100644 --- a/doc/build/dbengine.rst +++ b/doc/build/dbengine.rst @@ -88,7 +88,7 @@ adodbapi_ ``mssql+adodbapi`` development development `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 diff --git a/doc/build/reference/dialects/mssql.rst b/doc/build/reference/dialects/mssql.rst index 029919f36e..4bbb3181b0 100644 --- a/doc/build/reference/dialects/mssql.rst +++ b/doc/build/reference/dialects/mssql.rst @@ -7,10 +7,6 @@ PyODBC ------ .. automodule:: sqlalchemy.dialects.mssql.pyodbc -AdoDBAPI --------- -.. automodule:: sqlalchemy.dialects.mssql.adodbapi - pymssql ------- .. automodule:: sqlalchemy.dialects.mssql.pymssql @@ -19,3 +15,8 @@ zxjdbc Notes -------------- .. automodule:: sqlalchemy.dialects.mssql.zxjdbc + +AdoDBAPI +-------- +.. automodule:: sqlalchemy.dialects.mssql.adodbapi + diff --git a/lib/sqlalchemy/dialects/mssql/adodbapi.py b/lib/sqlalchemy/dialects/mssql/adodbapi.py index 9e12a944d7..502a02acc0 100644 --- a/lib/sqlalchemy/dialects/mssql/adodbapi.py +++ b/lib/sqlalchemy/dialects/mssql/adodbapi.py @@ -1,3 +1,7 @@ +""" +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 diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index 57b4680837..066ab8d04a 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -2,119 +2,10 @@ """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 ----------------------- @@ -220,9 +111,6 @@ Known Issues * 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 diff --git a/lib/sqlalchemy/dialects/mssql/pymssql.py b/lib/sqlalchemy/dialects/mssql/pymssql.py index b3a57d3186..36cb5f3702 100644 --- a/lib/sqlalchemy/dialects/mssql/pymssql.py +++ b/lib/sqlalchemy/dialects/mssql/pymssql.py @@ -1,40 +1,95 @@ """ 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://:@ + +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 diff --git a/lib/sqlalchemy/dialects/mssql/pyodbc.py b/lib/sqlalchemy/dialects/mssql/pyodbc.py index 7f46ec7fb5..eb4bf5cfff 100644 --- a/lib/sqlalchemy/dialects/mssql/pyodbc.py +++ b/lib/sqlalchemy/dialects/mssql/pyodbc.py @@ -1,12 +1,71 @@ """ 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://:@/ - mssql+pyodbc://:@/ +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' """ diff --git a/lib/sqlalchemy/test/requires.py b/lib/sqlalchemy/test/requires.py index c4c745c548..73b2120959 100644 --- a/lib/sqlalchemy/test/requires.py +++ b/lib/sqlalchemy/test/requires.py @@ -224,6 +224,7 @@ def unicode_ddl(fn): 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'), ) diff --git a/test/sql/test_types.py b/test/sql/test_types.py index 7e130ee099..3539bf91ab 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -319,7 +319,11 @@ class UnicodeTest(TestBase, AssertsExecutionResults): 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 \ ( @@ -1142,7 +1146,8 @@ class NumericTest(TestBase): [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"), @@ -1156,6 +1161,7 @@ class NumericTest(TestBase): numbers, ) + @testing.fails_on('mssql+pymssql', 'FIXME: improve pymssql dec handling') def test_enotation_decimal(self): """test exceedingly small decimals. @@ -1209,6 +1215,7 @@ class NumericTest(TestBase): @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"),