From: Mike Bayer Date: Fri, 26 Mar 2010 20:47:53 +0000 (-0600) Subject: mssql+mxodbc should use executedirect for all selects and execute for insert/update... X-Git-Tag: rel_0_6beta3~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d56d420e809588d8dea8f36fd4ae3a8b4204be54;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git 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) --- diff --git a/lib/sqlalchemy/connectors/mxodbc.py b/lib/sqlalchemy/connectors/mxodbc.py index 4476ffd789..f50bff7dac 100644 --- a/lib/sqlalchemy/connectors/mxodbc.py +++ b/lib/sqlalchemy/connectors/mxodbc.py @@ -97,10 +97,10 @@ class MxODBCConnector(Connector): """ 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, @@ -126,10 +126,20 @@ class MxODBCConnector(Connector): 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) diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 720edf66c8..6fb0a14a51 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -381,7 +381,10 @@ class DefaultExecutionContext(base.ExecutionContext): 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', diff --git a/test/dialect/test_mssql.py b/test/dialect/test_mssql.py index 7a4e4dc425..21395bd366 100644 --- a/test/dialect/test_mssql.py +++ b/test/dialect/test_mssql.py @@ -49,8 +49,7 @@ class CompileTest(TestBase, AssertsCompiledSQL): ) ]: 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. diff --git a/test/dialect/test_mxodbc.py b/test/dialect/test_mxodbc.py new file mode 100644 index 0000000000..938d457fbb --- /dev/null +++ b/test/dialect/test_mxodbc.py @@ -0,0 +1,69 @@ +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'] + ) + + \ No newline at end of file