summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/dialects/mssql/pyodbc.py
blob: 34050271fb020908e4ab468b314b9ca792831c8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""
Support for MS-SQL via pyodbc.

http://pypi.python.org/pypi/pyodbc/

Connect strings are of the form::

    mssql+pyodbc://<username>:<password>@<dsn>/
    mssql+pyodbc://<username>:<password>@<host>/<database>


"""

from sqlalchemy.dialects.mssql.base import MSExecutionContext, MSDialect
from sqlalchemy.connectors.pyodbc import PyODBCConnector
from sqlalchemy import types as sqltypes
import re
import sys

class MSExecutionContext_pyodbc(MSExecutionContext):
    _embedded_scope_identity = False
    
    def pre_exec(self):
        """where appropriate, issue "select scope_identity()" in the same statement.
        
        Background on why "scope_identity()" is preferable to "@@identity":
        http://msdn.microsoft.com/en-us/library/ms190315.aspx
        
        Background on why we attempt to embed "scope_identity()" into the same
        statement as the INSERT:
        http://code.google.com/p/pyodbc/wiki/FAQs#How_do_I_retrieve_autogenerated/identity_values?
        
        """
        
        super(MSExecutionContext_pyodbc, self).pre_exec()

        # don't embed the scope_identity select into an "INSERT .. DEFAULT VALUES"
        if self._select_lastrowid and \
                self.dialect.use_scope_identity and \
                len(self.parameters[0]):
            self._embedded_scope_identity = True
            
            self.statement += "; select scope_identity()"

    def post_exec(self):
        if self._embedded_scope_identity:
            # 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:
                    # fetchall() ensures the cursor is consumed 
                    # without closing it (FreeTDS particularly)
                    row = self.cursor.fetchall()[0]  
                    break
                except self.dialect.dbapi.Error, e:
                    # no way around this - nextset() consumes the previous set
                    # so we need to just keep flipping
                    self.cursor.nextset()
                    
            self._lastrowid = int(row[0])
        else:
            super(MSExecutionContext_pyodbc, self).post_exec()


class MSDialect_pyodbc(PyODBCConnector, MSDialect):

    execution_ctx_cls = MSExecutionContext_pyodbc

    pyodbc_driver_name = 'SQL Server'

    def __init__(self, description_encoding='latin-1', **params):
        super(MSDialect_pyodbc, self).__init__(**params)
        self.description_encoding = description_encoding
        self.use_scope_identity = self.dbapi and hasattr(self.dbapi.Cursor, 'nextset')
        
dialect = MSDialect_pyodbc