Commits

Mike Bayer committed 1a606aa

mssql+mxodbc should use executedirect for all selects and execute for insert/update/delete. To support this, an is_crud property has been added to the DefaultExecutionContext. The behavior is forcable either way per execution using execution_options(native_odbc_parameters=True|False). Some tests have been added to demonstrate usage. (patch by zzzeek committed by bradallen)

Comments (0)

Files changed (4)

lib/sqlalchemy/connectors/mxodbc.py

         """
         opts = url.translate_connect_args(username='user')
         opts.update(url.query)
-        args = opts['host'],
-        kwargs = {'user':opts['user'],
-                  'password': opts['password']}
-        return args, kwargs
+        args = opts.pop('host')
+        opts.pop('port', None)
+        opts.pop('database', None)
+        return (args,), opts
 
     def is_disconnect(self, e):
         # eGenix recommends checking connection.closed here,
         return tuple(version)
 
     def do_execute(self, cursor, statement, parameters, context=None):
-        # temporary workaround until a more comprehensive solution can
-        # be found for controlling when to use executedirect
-        try:
-            cursor.execute(statement, parameters)
-        except (InterfaceError, ProgrammingError), e:
-            warnings.warn("cursor.execute failed; falling back to executedirect")
+        if context:
+            native_odbc_execute = context.execution_options.\
+                                        get('native_odbc_execute', 'auto')
+            if native_odbc_execute is True:
+                # user specified native_odbc_execute=True
+                cursor.execute(statement, parameters)
+            elif native_odbc_execute is False:
+                # user specified native_odbc_execute=False
+                cursor.executedirect(statement, parameters)
+            elif context.is_crud:
+                # statement is UPDATE, DELETE, INSERT
+                cursor.execute(statement, parameters)
+            else:
+                # all other statements
+                cursor.executedirect(statement, parameters)
+        else:
             cursor.executedirect(statement, parameters)

lib/sqlalchemy/engine/default.py

                 self.execution_options = self.execution_options.union(connection._execution_options)
             self.cursor = self.create_cursor()
         
-            
+    @util.memoized_property
+    def is_crud(self):
+        return self.isinsert or self.isupdate or self.isdelete
+        
     @util.memoized_property
     def should_autocommit(self):
         autocommit = self.execution_options.get('autocommit', 

test/dialect/test_mssql.py

             )
         ]:
             self.assert_compile(expr, compile, dialect=mxodbc_dialect)
-        
-        
+    
     def test_in_with_subqueries(self):
         """Test that when using subqueries in a binary expression
         the == and != are changed to IN and NOT IN respectively.

test/dialect/test_mxodbc.py

+from sqlalchemy import *
+from sqlalchemy.test.testing import eq_, TestBase
+from sqlalchemy.test import engines
+
+# TODO: we should probably build mock bases for
+# these to share with test_reconnect, test_parseconnect
+class MockDBAPI(object):
+    paramstyle = 'qmark'
+    def __init__(self):
+        self.log = []
+    def connect(self, *args, **kwargs):
+        return MockConnection(self)
+    
+class MockConnection(object):
+    def __init__(self, parent):
+        self.parent = parent
+    def cursor(self):
+        return MockCursor(self)
+    def close(self):
+        pass
+    def rollback(self):
+        pass
+    def commit(self):
+        pass
+
+class MockCursor(object):
+    description = None
+    rowcount = None
+    def __init__(self, parent):
+        self.parent = parent
+    def execute(self, *args, **kwargs):
+        self.parent.parent.log.append('execute')
+    def executedirect(self, *args, **kwargs):
+        self.parent.parent.log.append('executedirect')
+    def close(self):
+        pass
+
+
+class MxODBCTest(TestBase):
+    def test_native_odbc_execute(self):
+        t1 = Table('t1', MetaData(), Column('c1', Integer))
+
+        dbapi = MockDBAPI()
+        engine = engines.testing_engine(
+                                'mssql+mxodbc://localhost', 
+                                options={'module':dbapi, 
+                                        '_initialize':False}
+                            )
+        conn = engine.connect()
+        
+        # crud: uses execute
+        conn.execute(t1.insert().values(c1='foo'))
+        conn.execute(t1.delete().where(t1.c.c1=='foo'))
+        conn.execute(t1.update().where(t1.c.c1=='foo').values(c1='bar'))
+        
+        # select: uses executedirect
+        conn.execute(t1.select())
+        
+        # manual flagging
+        conn.execution_options(native_odbc_execute=True).execute(t1.select())
+        conn.execution_options(native_odbc_execute=False).execute(t1.insert().values(c1='foo'))
+        
+        eq_(
+            dbapi.log,
+            ['execute', 'execute', 'execute', 
+                'executedirect', 'execute', 'executedirect']
+        )
+        
+