]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- pymssql now works again, expecting at least the 1.0 series.
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 20 Mar 2010 15:50:39 +0000 (11:50 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 20 Mar 2010 15:50:39 +0000 (11:50 -0400)
CHANGES
doc/build/dbengine.rst
doc/build/reference/dialects/mssql.rst
lib/sqlalchemy/dialects/mssql/adodbapi.py
lib/sqlalchemy/dialects/mssql/base.py
lib/sqlalchemy/dialects/mssql/pymssql.py
lib/sqlalchemy/dialects/mssql/pyodbc.py
lib/sqlalchemy/test/requires.py
test/sql/test_types.py

diff --git a/CHANGES b/CHANGES
index 6a0fedc376dda7b685a9fa21411c5ac3c35e0b3b..57a419ae7fd4de09a45804d0678544d04f2dcdb4 100644 (file)
--- 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
index 5fdd61434db1d091da8f23469693aa5a5803d98b..644ede1ba9e7d7c6326e7758ae0a79c2482276c2 100644 (file)
@@ -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
index 029919f36e5fd9b64306fa37f7ef0bb4ce3660b7..4bbb3181b08998d6feec492067640dc15dba1cc9 100644 (file)
@@ -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
+
index 9e12a944d7c7a37a97dd3d1b9460e166d715e34a..502a02acc08fcdfd27daa636e853613742f61808 100644 (file)
@@ -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
index 57b46808376f5e4b0ab5338ab07c6f057e5de8ab..066ab8d04a60e50a46efb7f36da1687827e7f99c 100644 (file)
 
 """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
index b3a57d31860fe7428314946bf5b05f7d6d385d5b..36cb5f370240f896e588ce21cbe707ac644fcbe5 100644 (file)
@@ -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://<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
index 7f46ec7fb588ab12f766987c99f93818b47f6c79..eb4bf5cfff892dd984afa3239db143f029558118 100644 (file)
@@ -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://<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'
 
 
 """
index c4c745c54829b2690cd66cbaed37fd88d896dc9d..73b2120959dd7e91954714ec6d134e141c300737 100644 (file)
@@ -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'),
         )
 
index 7e130ee099d307af3da07714114abc53bd1a18cc..3539bf91abd60d362997859a6d4edf1356b7e7fd 100644 (file)
@@ -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"),