diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-03-26 14:47:53 -0600 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-03-26 14:47:53 -0600 |
| commit | d56d420e809588d8dea8f36fd4ae3a8b4204be54 (patch) | |
| tree | eeeb939cf4beaae903dcebc24073ff26565a0756 | |
| parent | 1a3f424c864d1bbf782db20d0840895c8ae0f35d (diff) | |
| download | sqlalchemy-d56d420e809588d8dea8f36fd4ae3a8b4204be54.tar.gz | |
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)
| -rw-r--r-- | lib/sqlalchemy/connectors/mxodbc.py | 30 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/default.py | 5 | ||||
| -rw-r--r-- | test/dialect/test_mssql.py | 3 | ||||
| -rw-r--r-- | test/dialect/test_mxodbc.py | 69 |
4 files changed, 94 insertions, 13 deletions
diff --git a/lib/sqlalchemy/connectors/mxodbc.py b/lib/sqlalchemy/connectors/mxodbc.py index 4476ffd78..f50bff7da 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 720edf66c..6fb0a14a5 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 7a4e4dc42..21395bd36 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 000000000..938d457fb --- /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 |
