diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-02-28 23:51:54 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-02-28 23:51:54 +0000 |
| commit | a76927f584dab481383592645a2a471fed37ecf9 (patch) | |
| tree | 1303912df863199b07bdbcf4a5ffd1abf63701d6 /lib/sqlalchemy/engine | |
| parent | c366b7ec4fded0c8baabcdc6e6f15ecd81a4bf00 (diff) | |
| download | sqlalchemy-a76927f584dab481383592645a2a471fed37ecf9.tar.gz | |
- the execution sequence pulls all rowcount/last inserted ID
info from the cursor before commit() is called on the
DBAPI connection in an "autocommit" scenario. This helps
mxodbc with rowcount and is probably a good idea overall.
- cx_oracle wants list(), not tuple(), for empty execute.
- cleaned up plain SQL param handling
Diffstat (limited to 'lib/sqlalchemy/engine')
| -rw-r--r-- | lib/sqlalchemy/engine/base.py | 55 | ||||
| -rw-r--r-- | lib/sqlalchemy/engine/default.py | 36 |
2 files changed, 62 insertions, 29 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index b4f2524d6..46907dfcf 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -77,6 +77,10 @@ class Dialect(object): execution_ctx_cls a :class:`ExecutionContext` class used to handle statement execution + execute_sequence_format + either the 'tuple' or 'list' type, depending on what cursor.execute() + accepts for the second argument (they vary). + preparer a :class:`~sqlalchemy.sql.compiler.IdentifierPreparer` class used to quote identifiers. @@ -1055,6 +1059,7 @@ class Connection(Connectable): In the case of 'raw' execution which accepts positional parameters, it may be a list of tuples or lists. + """ if not multiparams: @@ -1104,7 +1109,9 @@ class Connection(Connectable): keys = [] context = self.__create_execution_context( - compiled_sql=elem.compile(dialect=self.dialect, column_keys=keys, inline=len(params) > 1), + compiled_sql=elem.compile( + dialect=self.dialect, column_keys=keys, + inline=len(params) > 1), parameters=params ) return self.__execute_context(context) @@ -1128,9 +1135,15 @@ class Connection(Connectable): context.pre_exec() if context.executemany: - self._cursor_executemany(context.cursor, context.statement, context.parameters, context=context) + self._cursor_executemany( + context.cursor, + context.statement, + context.parameters, context=context) else: - self._cursor_execute(context.cursor, context.statement, context.parameters[0], context=context) + self._cursor_execute( + context.cursor, + context.statement, + context.parameters[0], context=context) if context.compiled: context.post_exec() @@ -1138,10 +1151,17 @@ class Connection(Connectable): if context.isinsert and not context.executemany: context.post_insert() + # create a resultproxy, get rowcount/implicit RETURNING + # rows, close cursor if no further results pending + r = context.get_result_proxy()._autoclose() + if self.__transaction is None and context.should_autocommit: self._commit_impl() - - return context.get_result_proxy()._autoclose() + + if r.closed and self.should_close_with_result: + self.close() + + return r def _handle_dbapi_exception(self, e, statement, parameters, cursor, context): if getattr(self, '_reentrant_error', False): @@ -1893,6 +1913,7 @@ class ResultProxy(object): _process_row = RowProxy out_parameters = None + _can_close_connection = False def __init__(self, context): self.context = context @@ -1904,7 +1925,6 @@ class ResultProxy(object): context.engine._should_log_debug() self._init_metadata() - def _init_metadata(self): metadata = self._cursor_description() if metadata is None: @@ -1962,21 +1982,26 @@ class ResultProxy(object): return self.cursor.description def _autoclose(self): + """called by the Connection to autoclose cursors that have no pending results + beyond those used by an INSERT/UPDATE/DELETE with no explicit RETURNING clause. + + """ if self.context.isinsert: if self.context._is_implicit_returning: self.context._fetch_implicit_returning(self) - self.close() + self.close(_autoclose_connection=False) elif not self.context._is_explicit_returning: - self.close() + self.close(_autoclose_connection=False) elif self._metadata is None: # no results, get rowcount - # (which requires open cursor on some DB's such as firebird), + # (which requires open cursor on some drivers + # such as kintersbasdb, mxodbc), self.rowcount - self.close() # autoclose - + self.close(_autoclose_connection=False) + return self - - def close(self): + + def close(self, _autoclose_connection=True): """Close this ResultProxy. Closes the underlying DBAPI cursor corresponding to the execution. @@ -1992,12 +2017,14 @@ class ResultProxy(object): * all result rows are exhausted using the fetchXXX() methods. * cursor.description is None. + """ if not self.closed: self.closed = True self.cursor.close() - if self.connection.should_close_with_result: + if _autoclose_connection and \ + self.connection.should_close_with_result: self.connection.close() def __iter__(self): diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 4d4fd7c71..cd2c10393 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -30,6 +30,10 @@ class DefaultDialect(base.Dialect): preparer = compiler.IdentifierPreparer supports_alter = True + # most DBAPIs happy with this for execute(). + # not cx_oracle. + execute_sequence_format = tuple + supports_sequences = False sequences_optional = False preexecute_autoincrement_sequences = False @@ -365,7 +369,7 @@ class DefaultExecutionContext(base.ExecutionContext): @util.memoized_property def _default_params(self): if self.dialect.positional: - return () + return self.dialect.execute_sequence_format() else: return {} @@ -392,21 +396,23 @@ class DefaultExecutionContext(base.ExecutionContext): """Apply string encoding to the keys of dictionary-based bind parameters. This is only used executing textual, non-compiled SQL expressions. + """ - - if self.dialect.positional or self.dialect.supports_unicode_statements: - if params: + + if not params: + return [self._default_params] + elif isinstance(params[0], self.dialect.execute_sequence_format): + return params + elif isinstance(params[0], dict): + if self.dialect.supports_unicode_statements: return params else: - return [self._default_params] + def proc(d): + return dict((k.encode(self.dialect.encoding), d[k]) for k in d) + return [proc(d) for d in params] or [{}] else: - def proc(d): - # sigh, sometimes we get positional arguments with a dialect - # that doesnt specify positional (because of execute_text()) - if not isinstance(d, dict): - return d - return dict((k.encode(self.dialect.encoding), d[k]) for k in d) - return [proc(d) for d in params] or [{}] + return [self.dialect.execute_sequence_format(p) for p in params] + def __convert_compiled_params(self, compiled_parameters): """Convert the dictionary of bind parameter values into a dict or list @@ -423,7 +429,7 @@ class DefaultExecutionContext(base.ExecutionContext): param.append(processors[key](compiled_params[key])) else: param.append(compiled_params[key]) - parameters.append(tuple(param)) + parameters.append(self.dialect.execute_sequence_format(param)) else: encode = not self.dialect.supports_unicode_statements for compiled_params in compiled_parameters: @@ -442,7 +448,7 @@ class DefaultExecutionContext(base.ExecutionContext): else: param[key] = compiled_params[key] parameters.append(param) - return tuple(parameters) + return self.dialect.execute_sequence_format(parameters) def should_autocommit_text(self, statement): return AUTOCOMMIT_REGEXP.match(statement) @@ -514,7 +520,7 @@ class DefaultExecutionContext(base.ExecutionContext): def _fetch_implicit_returning(self, resultproxy): table = self.compiled.statement.table - row = resultproxy.first() + row = resultproxy.fetchone() self._inserted_primary_key = [v is not None and v or row[c] for c, v in zip(table.primary_key, self._inserted_primary_key) |
