summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-03-26 14:47:53 -0600
committerMike Bayer <mike_mp@zzzcomputing.com>2010-03-26 14:47:53 -0600
commitd56d420e809588d8dea8f36fd4ae3a8b4204be54 (patch)
treeeeeb939cf4beaae903dcebc24073ff26565a0756
parent1a3f424c864d1bbf782db20d0840895c8ae0f35d (diff)
downloadsqlalchemy-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.py30
-rw-r--r--lib/sqlalchemy/engine/default.py5
-rw-r--r--test/dialect/test_mssql.py3
-rw-r--r--test/dialect/test_mxodbc.py69
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