summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine/base.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2009-08-06 21:11:27 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2009-08-06 21:11:27 +0000
commit8fc5005dfe3eb66a46470ad8a8c7b95fc4d6bdca (patch)
treeae9e27d12c9fbf8297bb90469509e1cb6a206242 /lib/sqlalchemy/engine/base.py
parent7638aa7f242c6ea3d743aa9100e32be2052546a6 (diff)
downloadsqlalchemy-8fc5005dfe3eb66a46470ad8a8c7b95fc4d6bdca.tar.gz
merge 0.6 series to trunk.
Diffstat (limited to 'lib/sqlalchemy/engine/base.py')
-rw-r--r--lib/sqlalchemy/engine/base.py750
1 files changed, 519 insertions, 231 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 39085c359..0a0b0ff0c 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -10,14 +10,16 @@
Defines the basic components used to interface DB-API modules with
higher-level statement-construction, connection-management, execution
and result contexts.
-
"""
-__all__ = ['BufferedColumnResultProxy', 'BufferedColumnRow', 'BufferedRowResultProxy', 'Compiled', 'Connectable',
- 'Connection', 'DefaultRunner', 'Dialect', 'Engine', 'ExecutionContext', 'NestedTransaction', 'ResultProxy',
- 'RootTransaction', 'RowProxy', 'SchemaIterator', 'StringIO', 'Transaction', 'TwoPhaseTransaction', 'connection_memoize']
+__all__ = [
+ 'BufferedColumnResultProxy', 'BufferedColumnRow', 'BufferedRowResultProxy',
+ 'Compiled', 'Connectable', 'Connection', 'DefaultRunner', 'Dialect', 'Engine',
+ 'ExecutionContext', 'NestedTransaction', 'ResultProxy', 'RootTransaction',
+ 'RowProxy', 'SchemaIterator', 'StringIO', 'Transaction', 'TwoPhaseTransaction',
+ 'connection_memoize']
-import inspect, StringIO
+import inspect, StringIO, sys
from sqlalchemy import exc, schema, util, types, log
from sqlalchemy.sql import expression
@@ -32,10 +34,14 @@ class Dialect(object):
ExecutionContext, Compiled, DefaultGenerator, and TypeEngine.
All Dialects implement the following attributes:
-
+
name
- identifying name for the dialect (i.e. 'sqlite')
-
+ identifying name for the dialect from a DBAPI-neutral point of view
+ (i.e. 'sqlite')
+
+ driver
+ identifying name for the dialect's DBAPI
+
positional
True if the paramstyle for this Dialect is positional.
@@ -51,20 +57,25 @@ class Dialect(object):
type of encoding to use for unicode, usually defaults to
'utf-8'.
- schemagenerator
- a :class:`~sqlalchemy.schema.SchemaVisitor` class which generates
- schemas.
-
- schemadropper
- a :class:`~sqlalchemy.schema.SchemaVisitor` class which drops schemas.
-
defaultrunner
a :class:`~sqlalchemy.schema.SchemaVisitor` class which executes
defaults.
statement_compiler
- a :class:`~sqlalchemy.engine.base.Compiled` class used to compile SQL
- statements
+ a :class:`~Compiled` class used to compile SQL statements
+
+ ddl_compiler
+ a :class:`~Compiled` class used to compile DDL statements
+
+ server_version_info
+ a tuple containing a version number for the DB backend in use.
+ This value is only available for supporting dialects, and only for
+ a dialect that's been associated with a connection pool via
+ create_engine() or otherwise had its ``initialize()`` method called
+ with a conneciton.
+
+ execution_ctx_cls
+ a :class:`ExecutionContext` class used to handle statement execution
preparer
a :class:`~sqlalchemy.sql.compiler.IdentifierPreparer` class used to
@@ -77,27 +88,38 @@ class Dialect(object):
The maximum length of identifier names.
supports_unicode_statements
- Indicate whether the DB-API can receive SQL statements as Python unicode strings
+ Indicate whether the DB-API can receive SQL statements as Python
+ unicode strings
+
+ supports_unicode_binds
+ Indicate whether the DB-API can receive string bind parameters
+ as Python unicode strings
supports_sane_rowcount
- Indicate whether the dialect properly implements rowcount for ``UPDATE`` and ``DELETE`` statements.
+ Indicate whether the dialect properly implements rowcount for
+ ``UPDATE`` and ``DELETE`` statements.
supports_sane_multi_rowcount
- Indicate whether the dialect properly implements rowcount for ``UPDATE`` and ``DELETE`` statements
- when executed via executemany.
-
- preexecute_pk_sequences
- Indicate if the dialect should pre-execute sequences on primary key
- columns during an INSERT, if it's desired that the new row's primary key
- be available after execution.
-
- supports_pk_autoincrement
- Indicates if the dialect should allow the database to passively assign
- a primary key column value.
-
+ Indicate whether the dialect properly implements rowcount for
+ ``UPDATE`` and ``DELETE`` statements when executed via
+ executemany.
+
+ preexecute_autoincrement_sequences
+ True if 'implicit' primary key functions must be executed separately
+ in order to get their value. This is currently oriented towards
+ Postgresql.
+
+ implicit_returning
+ use RETURNING or equivalent during INSERT execution in order to load
+ newly generated primary keys and other column defaults in one execution,
+ which are then available via inserted_primary_key.
+ If an insert statement has returning() specified explicitly,
+ the "implicit" functionality is not used and inserted_primary_key
+ will not be available.
+
dbapi_type_map
A mapping of DB-API type objects present in this Dialect's
- DB-API implmentation mapped to TypeEngine implementations used
+ DB-API implementation mapped to TypeEngine implementations used
by the dialect.
This is used to apply types to result sets based on the DB-API
@@ -105,13 +127,15 @@ class Dialect(object):
result sets against textual statements where no explicit
typemap was present.
- supports_default_values
- Indicates if the construct ``INSERT INTO tablename DEFAULT VALUES`` is supported
+ colspecs
+ A dictionary of TypeEngine classes from sqlalchemy.types mapped
+ to subclasses that are specific to the dialect class. This
+ dictionary is class-level only and is not accessed from the
+ dialect instance itself.
- description_encoding
- type of encoding to use for unicode when working with metadata
- descriptions. If set to ``None`` no encoding will be done.
- This usually defaults to 'utf-8'.
+ supports_default_values
+ Indicates if the construct ``INSERT INTO tablename DEFAULT
+ VALUES`` is supported
"""
def create_connect_args(self, url):
@@ -124,25 +148,28 @@ class Dialect(object):
raise NotImplementedError()
+ @classmethod
+ def type_descriptor(cls, typeobj):
+ """Transform a generic type to a dialect-specific type.
- def type_descriptor(self, typeobj):
- """Transform a generic type to a database-specific type.
-
- Transforms the given :class:`~sqlalchemy.types.TypeEngine` instance
- from generic to database-specific.
-
- Subclasses will usually use the
+ Dialect classes will usually use the
:func:`~sqlalchemy.types.adapt_type` method in the types module to
make this job easy.
+
+ The returned result is cached *per dialect class* so can
+ contain no dialect-instance state.
"""
raise NotImplementedError()
+ def initialize(self, connection):
+ """Called during strategized creation of the dialect with a connection.
- def server_version_info(self, connection):
- """Return a tuple of the database's version number."""
+ Allows dialects to configure options based on server version info or
+ other properties.
+ """
- raise NotImplementedError()
+ pass
def reflecttable(self, connection, table, include_columns=None):
"""Load table description from the database.
@@ -156,6 +183,133 @@ class Dialect(object):
raise NotImplementedError()
+ def get_columns(self, connection, table_name, schema=None, **kw):
+ """Return information about columns in `table_name`.
+
+ Given a :class:`~sqlalchemy.engine.Connection`, a string
+ `table_name`, and an optional string `schema`, return column
+ information as a list of dictionaries with these keys:
+
+ name
+ the column's name
+
+ type
+ [sqlalchemy.types#TypeEngine]
+
+ nullable
+ boolean
+
+ default
+ the column's default value
+
+ autoincrement
+ boolean
+
+ sequence
+ a dictionary of the form
+ {'name' : str, 'start' :int, 'increment': int}
+
+ Additional column attributes may be present.
+ """
+
+ raise NotImplementedError()
+
+ def get_primary_keys(self, connection, table_name, schema=None, **kw):
+ """Return information about primary keys in `table_name`.
+
+ Given a :class:`~sqlalchemy.engine.Connection`, a string
+ `table_name`, and an optional string `schema`, return primary
+ key information as a list of column names.
+ """
+
+ raise NotImplementedError()
+
+ def get_foreign_keys(self, connection, table_name, schema=None, **kw):
+ """Return information about foreign_keys in `table_name`.
+
+ Given a :class:`~sqlalchemy.engine.Connection`, a string
+ `table_name`, and an optional string `schema`, return foreign
+ key information as a list of dicts with these keys:
+
+ name
+ the constraint's name
+
+ constrained_columns
+ a list of column names that make up the foreign key
+
+ referred_schema
+ the name of the referred schema
+
+ referred_table
+ the name of the referred table
+
+ referred_columns
+ a list of column names in the referred table that correspond to
+ constrained_columns
+ """
+
+ raise NotImplementedError()
+
+ def get_table_names(self, connection, schema=None, **kw):
+ """Return a list of table names for `schema`."""
+
+ raise NotImplementedError
+
+ def get_view_names(self, connection, schema=None, **kw):
+ """Return a list of all view names available in the database.
+
+ schema:
+ Optional, retrieve names from a non-default schema.
+ """
+
+ raise NotImplementedError()
+
+ def get_view_definition(self, connection, view_name, schema=None, **kw):
+ """Return view definition.
+
+ Given a :class:`~sqlalchemy.engine.Connection`, a string
+ `view_name`, and an optional string `schema`, return the view
+ definition.
+ """
+
+ raise NotImplementedError()
+
+ def get_indexes(self, connection, table_name, schema=None, **kw):
+ """Return information about indexes in `table_name`.
+
+ Given a :class:`~sqlalchemy.engine.Connection`, a string
+ `table_name` and an optional string `schema`, return index
+ information as a list of dictionaries with these keys:
+
+ name
+ the index's name
+
+ column_names
+ list of column names in order
+
+ unique
+ boolean
+ """
+
+ raise NotImplementedError()
+
+ def normalize_name(self, name):
+ """convert the given name to lowercase if it is detected as case insensitive.
+
+ this method is only used if the dialect defines requires_name_normalize=True.
+
+ """
+ raise NotImplementedError()
+
+ def denormalize_name(self, name):
+ """convert the given name to a case insensitive identifier for the backend
+ if it is an all-lowercase name.
+
+ this method is only used if the dialect defines requires_name_normalize=True.
+
+ """
+ raise NotImplementedError()
+
def has_table(self, connection, table_name, schema=None):
"""Check the existence of a particular table in the database.
@@ -178,7 +332,11 @@ class Dialect(object):
raise NotImplementedError()
def get_default_schema_name(self, connection):
- """Return the string name of the currently selected schema given a :class:`~sqlalchemy.engine.Connection`."""
+ """Return the string name of the currently selected schema given a :class:`~sqlalchemy.engine.Connection`.
+
+ DEPRECATED. moving this towards dialect.default_schema_name (not complete).
+
+ """
raise NotImplementedError()
@@ -262,11 +420,14 @@ class Dialect(object):
raise NotImplementedError()
+ def visit_pool(self, pool):
+ """Executed after a pool is created."""
+
class ExecutionContext(object):
"""A messenger object for a Dialect that corresponds to a single execution.
- ExecutionContext should have these datamembers:
+ ExecutionContext should have these data members:
connection
Connection object which can be freely used by default value
@@ -308,20 +469,19 @@ class ExecutionContext(object):
True if the statement is an UPDATE.
should_autocommit
- True if the statement is a "committable" statement
+ True if the statement is a "committable" statement.
postfetch_cols
- a list of Column objects for which a server-side default
- or inline SQL expression value was fired off. applies to inserts and updates.
-
-
+ a list of Column objects for which a server-side default or
+ inline SQL expression value was fired off. Applies to inserts
+ and updates.
"""
def create_cursor(self):
"""Return a new cursor generated from this ExecutionContext's connection.
Some dialects may wish to change the behavior of
- connection.cursor(), such as postgres which may return a PG
+ connection.cursor(), such as postgresql which may return a PG
"server side" cursor.
"""
@@ -357,22 +517,11 @@ class ExecutionContext(object):
def handle_dbapi_exception(self, e):
"""Receive a DBAPI exception which occured upon execute, result fetch, etc."""
-
- raise NotImplementedError()
-
- def should_autocommit_text(self, statement):
- """Parse the given textual statement and return True if it refers to a "committable" statement"""
raise NotImplementedError()
- def last_inserted_ids(self):
- """Return the list of the primary key values for the last insert statement executed.
-
- This does not apply to straight textual clauses; only to
- ``sql.Insert`` objects compiled against a ``schema.Table``
- object. The order of items in the list is the same as that of
- the Table's 'primary_key' attribute.
- """
+ def should_autocommit_text(self, statement):
+ """Parse the given textual statement and return True if it refers to a "committable" statement"""
raise NotImplementedError()
@@ -401,7 +550,7 @@ class ExecutionContext(object):
class Compiled(object):
- """Represent a compiled SQL expression.
+ """Represent a compiled SQL or DDL expression.
The ``__str__`` method of the ``Compiled`` object should produce
the actual text of the statement. ``Compiled`` objects are
@@ -413,53 +562,49 @@ class Compiled(object):
defaults.
"""
- def __init__(self, dialect, statement, column_keys=None, bind=None):
+ def __init__(self, dialect, statement, bind=None):
"""Construct a new ``Compiled`` object.
- dialect
- ``Dialect`` to compile against.
-
- statement
- ``ClauseElement`` to be compiled.
+ :param dialect: ``Dialect`` to compile against.
- column_keys
- a list of column names to be compiled into an INSERT or UPDATE
- statement.
+ :param statement: ``ClauseElement`` to be compiled.
- bind
- Optional Engine or Connection to compile this statement against.
-
+ :param bind: Optional Engine or Connection to compile this statement against.
"""
+
self.dialect = dialect
self.statement = statement
- self.column_keys = column_keys
self.bind = bind
self.can_execute = statement.supports_execution
def compile(self):
"""Produce the internal string representation of this element."""
- raise NotImplementedError()
+ self.string = self.process(self.statement)
- def __str__(self):
- """Return the string text of the generated SQL statement."""
+ def process(self, obj, **kwargs):
+ return obj._compiler_dispatch(self, **kwargs)
- raise NotImplementedError()
+ def __str__(self):
+ """Return the string text of the generated SQL or DDL."""
- @util.deprecated('Deprecated. Use construct_params(). '
- '(supports Unicode key names.)')
- def get_params(self, **params):
- return self.construct_params(params)
+ return self.string or ''
- def construct_params(self, params):
+ def construct_params(self, params=None):
"""Return the bind params for this compiled object.
- `params` is a dict of string/object pairs whos
- values will override bind values compiled in
- to the statement.
+ :param params: a dict of string/object pairs whos values will
+ override bind values compiled in to the
+ statement.
"""
+
raise NotImplementedError()
+ @property
+ def params(self):
+ """Return the bind params for this compiled object."""
+ return self.construct_params()
+
def execute(self, *multiparams, **params):
"""Execute this compiled object."""
@@ -474,12 +619,24 @@ class Compiled(object):
return self.execute(*multiparams, **params).scalar()
+class TypeCompiler(object):
+ """Produces DDL specification for TypeEngine objects."""
+
+ def __init__(self, dialect):
+ self.dialect = dialect
+
+ def process(self, type_):
+ return type_._compiler_dispatch(self)
+
+
class Connectable(object):
"""Interface for an object which supports execution of SQL constructs.
-
+
The two implementations of ``Connectable`` are :class:`Connection` and
:class:`Engine`.
-
+
+ Connectable must also implement the 'dialect' member which references a
+ :class:`Dialect` instance.
"""
def contextual_connect(self):
@@ -503,6 +660,7 @@ class Connectable(object):
def _execute_clauseelement(self, elem, multiparams=None, params=None):
raise NotImplementedError()
+
class Connection(Connectable):
"""Provides high-level functionality for a wrapped DB-API connection.
@@ -514,7 +672,6 @@ class Connection(Connectable):
.. index::
single: thread safety; Connection
-
"""
def __init__(self, engine, connection=None, close_with_result=False,
@@ -524,7 +681,6 @@ class Connection(Connectable):
Connection objects are typically constructed by an
:class:`~sqlalchemy.engine.Engine`, see the ``connect()`` and
``contextual_connect()`` methods of Engine.
-
"""
self.engine = engine
@@ -534,7 +690,7 @@ class Connection(Connectable):
self.__savepoint_seq = 0
self.__branch = _branch
self.__invalid = False
-
+
def _branch(self):
"""Return a new Connection which references this Connection's
engine and connection; but does not have close_with_result enabled,
@@ -542,8 +698,8 @@ class Connection(Connectable):
This is used to execute "sub" statements within a single execution,
usually an INSERT statement.
-
"""
+
return self.engine.Connection(self.engine, self.__connection, _branch=True)
@property
@@ -554,13 +710,13 @@ class Connection(Connectable):
@property
def closed(self):
- """return True if this connection is closed."""
+ """Return True if this connection is closed."""
return not self.__invalid and '_Connection__connection' not in self.__dict__
@property
def invalidated(self):
- """return True if this connection was invalidated."""
+ """Return True if this connection was invalidated."""
return self.__invalid
@@ -583,13 +739,14 @@ class Connection(Connectable):
def should_close_with_result(self):
"""Indicates if this Connection should be closed when a corresponding
ResultProxy is closed; this is essentially an auto-release mode.
-
"""
+
return self.__close_with_result
@property
def info(self):
"""A collection of per-DB-API connection instance properties."""
+
return self.connection.info
def connect(self):
@@ -598,8 +755,8 @@ class Connection(Connectable):
This ``Connectable`` interface method returns self, allowing
Connections to be used interchangably with Engines in most
situations that require a bind.
-
"""
+
return self
def contextual_connect(self, **kwargs):
@@ -608,8 +765,8 @@ class Connection(Connectable):
This ``Connectable`` interface method returns self, allowing
Connections to be used interchangably with Engines in most
situations that require a bind.
-
"""
+
return self
def invalidate(self, exception=None):
@@ -627,8 +784,8 @@ class Connection(Connectable):
rolled back before a reconnect on this Connection can proceed. This
is to prevent applications from accidentally continuing their transactional
operations in a non-transactional state.
-
"""
+
if self.closed:
raise exc.InvalidRequestError("This Connection is closed")
@@ -651,8 +808,8 @@ class Connection(Connectable):
:class:`~sqlalchemy.interfaces.PoolListener` for a mechanism to modify
connection state when connections leave and return to their
connection pool.
-
"""
+
self.__connection.detach()
def begin(self):
@@ -663,8 +820,8 @@ class Connection(Connectable):
outermost transaction may ``commit``. Calls to ``commit`` on
inner transactions are ignored. Any transaction in the
hierarchy may ``rollback``, however.
-
"""
+
if self.__transaction is None:
self.__transaction = RootTransaction(self)
else:
@@ -690,9 +847,8 @@ class Connection(Connectable):
def begin_twophase(self, xid=None):
"""Begin a two-phase or XA transaction and return a Transaction handle.
- xid
- the two phase transaction id. If not supplied, a random id
- will be generated.
+ :param xid: the two phase transaction id. If not supplied, a random id
+ will be generated.
"""
if self.__transaction is not None:
@@ -813,9 +969,6 @@ class Connection(Connectable):
return self.execute(object, *multiparams, **params).scalar()
- def statement_compiler(self, statement, **kwargs):
- return self.dialect.statement_compiler(self.dialect, statement, bind=self, **kwargs)
-
def execute(self, object, *multiparams, **params):
"""Executes and returns a ResultProxy."""
@@ -826,11 +979,12 @@ class Connection(Connectable):
raise exc.InvalidRequestError("Unexecutable object type: " + str(type(object)))
def __distill_params(self, multiparams, params):
- """given arguments from the calling form *multiparams, **params, return a list
+ """Given arguments from the calling form *multiparams, **params, return a list
of bind parameter structures, usually a list of dictionaries.
- in the case of 'raw' execution which accepts positional parameters,
- it may be a list of tuples or lists."""
+ In the case of 'raw' execution which accepts positional parameters,
+ it may be a list of tuples or lists.
+ """
if not multiparams:
if params:
@@ -858,7 +1012,19 @@ class Connection(Connectable):
return self._execute_clauseelement(func.select(), multiparams, params)
def _execute_default(self, default, multiparams, params):
- return self.engine.dialect.defaultrunner(self.__create_execution_context()).traverse_single(default)
+ ret = self.engine.dialect.\
+ defaultrunner(self.__create_execution_context()).\
+ traverse_single(default)
+ if self.__close_with_result:
+ self.close()
+ return ret
+
+ def _execute_ddl(self, ddl, params, multiparams):
+ context = self.__create_execution_context(
+ compiled_ddl=ddl.compile(dialect=self.dialect),
+ parameters=None
+ )
+ return self.__execute_context(context)
def _execute_clauseelement(self, elem, multiparams, params):
params = self.__distill_params(multiparams, params)
@@ -868,7 +1034,7 @@ class Connection(Connectable):
keys = []
context = self.__create_execution_context(
- compiled=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)
@@ -877,7 +1043,7 @@ class Connection(Connectable):
"""Execute a sql.Compiled object."""
context = self.__create_execution_context(
- compiled=compiled,
+ compiled_sql=compiled,
parameters=self.__distill_params(multiparams, params)
)
return self.__execute_context(context)
@@ -886,38 +1052,42 @@ class Connection(Connectable):
parameters = self.__distill_params(multiparams, params)
context = self.__create_execution_context(statement=statement, parameters=parameters)
return self.__execute_context(context)
-
+
def __execute_context(self, context):
if context.compiled:
context.pre_exec()
+
if context.executemany:
self._cursor_executemany(context.cursor, context.statement, context.parameters, context=context)
else:
self._cursor_execute(context.cursor, context.statement, context.parameters[0], context=context)
+
if context.compiled:
context.post_exec()
+
+ if context.isinsert and not context.executemany:
+ context.post_insert()
+
if context.should_autocommit and not self.in_transaction():
self._commit_impl()
- return context.get_result_proxy()
+
+ return context.get_result_proxy()._autoclose()
- def _execute_ddl(self, ddl, params, multiparams):
- if params:
- schema_item, params = params[0], params[1:]
- else:
- schema_item = None
- return ddl(None, schema_item, self, *params, **multiparams)
-
def _handle_dbapi_exception(self, e, statement, parameters, cursor, context):
if getattr(self, '_reentrant_error', False):
- raise exc.DBAPIError.instance(None, None, e)
+ # Py3K
+ #raise exc.DBAPIError.instance(statement, parameters, e) from e
+ # Py2K
+ raise exc.DBAPIError.instance(statement, parameters, e), None, sys.exc_info()[2]
+ # end Py2K
self._reentrant_error = True
try:
if not isinstance(e, self.dialect.dbapi.Error):
return
-
+
if context:
context.handle_dbapi_exception(e)
-
+
is_disconnect = self.dialect.is_disconnect(e)
if is_disconnect:
self.invalidate(e)
@@ -928,7 +1098,12 @@ class Connection(Connectable):
self._autorollback()
if self.__close_with_result:
self.close()
- raise exc.DBAPIError.instance(statement, parameters, e, connection_invalidated=is_disconnect)
+ # Py3K
+ #raise exc.DBAPIError.instance(statement, parameters, e, connection_invalidated=is_disconnect) from e
+ # Py2K
+ raise exc.DBAPIError.instance(statement, parameters, e, connection_invalidated=is_disconnect), None, sys.exc_info()[2]
+ # end Py2K
+
finally:
del self._reentrant_error
@@ -966,7 +1141,7 @@ class Connection(Connectable):
expression.ClauseElement: _execute_clauseelement,
Compiled: _execute_compiled,
schema.SchemaItem: _execute_default,
- schema.DDL: _execute_ddl,
+ schema.DDLElement: _execute_ddl,
basestring: _execute_text
}
@@ -991,6 +1166,7 @@ class Connection(Connectable):
def run_callable(self, callable_):
return callable_(self)
+
class Transaction(object):
"""Represent a Transaction in progress.
@@ -998,14 +1174,13 @@ class Transaction(object):
.. index::
single: thread safety; Transaction
-
"""
def __init__(self, connection, parent):
self.connection = connection
self._parent = parent or self
self.is_active = True
-
+
def close(self):
"""Close this transaction.
@@ -1016,6 +1191,7 @@ class Transaction(object):
This is used to cancel a Transaction without affecting the scope of
an enclosing transaction.
"""
+
if not self._parent.is_active:
return
if self._parent is self:
@@ -1048,6 +1224,7 @@ class Transaction(object):
else:
self.rollback()
+
class RootTransaction(Transaction):
def __init__(self, connection):
super(RootTransaction, self).__init__(connection, None)
@@ -1059,6 +1236,7 @@ class RootTransaction(Transaction):
def _do_commit(self):
self.connection._commit_impl()
+
class NestedTransaction(Transaction):
def __init__(self, connection, parent):
super(NestedTransaction, self).__init__(connection, parent)
@@ -1070,6 +1248,7 @@ class NestedTransaction(Transaction):
def _do_commit(self):
self.connection._release_savepoint_impl(self._savepoint, self._parent)
+
class TwoPhaseTransaction(Transaction):
def __init__(self, connection, xid):
super(TwoPhaseTransaction, self).__init__(connection, None)
@@ -1089,9 +1268,10 @@ class TwoPhaseTransaction(Transaction):
def commit(self):
self.connection._commit_twophase_impl(self.xid, self._is_prepared)
+
class Engine(Connectable):
"""
- Connects a :class:`~sqlalchemy.pool.Pool` and :class:`~sqlalchemy.engine.base.Dialect`
+ Connects a :class:`~sqlalchemy.pool.Pool` and :class:`~sqlalchemy.engine.base.Dialect`
together to provide a source of database connectivity and behavior.
"""
@@ -1111,9 +1291,15 @@ class Engine(Connectable):
@property
def name(self):
"String name of the :class:`~sqlalchemy.engine.Dialect` in use by this ``Engine``."
-
+
return self.dialect.name
+ @property
+ def driver(self):
+ "Driver name of the :class:`~sqlalchemy.engine.Dialect` in use by this ``Engine``."
+
+ return self.dialect.driver
+
echo = log.echo_property()
def __repr__(self):
@@ -1126,12 +1312,16 @@ class Engine(Connectable):
def create(self, entity, connection=None, **kwargs):
"""Create a table or index within this engine's database connection given a schema.Table object."""
- self._run_visitor(self.dialect.schemagenerator, entity, connection=connection, **kwargs)
+ from sqlalchemy.engine import ddl
+
+ self._run_visitor(ddl.SchemaGenerator, entity, connection=connection, **kwargs)
def drop(self, entity, connection=None, **kwargs):
"""Drop a table or index within this engine's database connection given a schema.Table object."""
- self._run_visitor(self.dialect.schemadropper, entity, connection=connection, **kwargs)
+ from sqlalchemy.engine import ddl
+
+ self._run_visitor(ddl.SchemaDropper, entity, connection=connection, **kwargs)
def _execute_default(self, default):
connection = self.contextual_connect()
@@ -1212,9 +1402,6 @@ class Engine(Connectable):
connection = self.contextual_connect(close_with_result=True)
return connection._execute_compiled(compiled, multiparams, params)
- def statement_compiler(self, statement, **kwargs):
- return self.dialect.statement_compiler(self.dialect, statement, bind=self, **kwargs)
-
def connect(self, **kwargs):
"""Return a newly allocated Connection object."""
@@ -1231,12 +1418,10 @@ class Engine(Connectable):
def table_names(self, schema=None, connection=None):
"""Return a list of all table names available in the database.
- schema:
- Optional, retrieve names from a non-default schema.
+ :param schema: Optional, retrieve names from a non-default schema.
- connection:
- Optional, use a specified connection. Default is the
- ``contextual_connect`` for this ``Engine``.
+ :param connection: Optional, use a specified connection. Default is the
+ ``contextual_connect`` for this ``Engine``.
"""
if connection is None:
@@ -1275,22 +1460,24 @@ class Engine(Connectable):
return self.pool.unique_connection()
+
def _proxy_connection_cls(cls, proxy):
class ProxyConnection(cls):
def execute(self, object, *multiparams, **params):
return proxy.execute(self, super(ProxyConnection, self).execute, object, *multiparams, **params)
-
+
def _execute_clauseelement(self, elem, multiparams=None, params=None):
return proxy.execute(self, super(ProxyConnection, self).execute, elem, *(multiparams or []), **(params or {}))
-
+
def _cursor_execute(self, cursor, statement, parameters, context=None):
return proxy.cursor_execute(super(ProxyConnection, self)._cursor_execute, cursor, statement, parameters, context, False)
-
+
def _cursor_executemany(self, cursor, statement, parameters, context=None):
return proxy.cursor_execute(super(ProxyConnection, self)._cursor_executemany, cursor, statement, parameters, context, True)
return ProxyConnection
+
class RowProxy(object):
"""Proxy a single cursor row for a parent ResultProxy.
@@ -1302,7 +1489,7 @@ class RowProxy(object):
"""
__slots__ = ['__parent', '__row']
-
+
def __init__(self, parent, row):
"""RowProxy objects are constructed by ResultProxy objects."""
@@ -1327,7 +1514,7 @@ class RowProxy(object):
yield self.__parent._get_col(self.__row, i)
__hash__ = None
-
+
def __eq__(self, other):
return ((other is self) or
(other == tuple(self.__parent._get_col(self.__row, key)
@@ -1362,18 +1549,19 @@ class RowProxy(object):
"""Return the list of keys as strings represented by this RowProxy."""
return self.__parent.keys
-
+
def iterkeys(self):
return iter(self.__parent.keys)
-
+
def values(self):
"""Return the values represented by this RowProxy as a list."""
return list(self)
-
+
def itervalues(self):
return iter(self)
+
class BufferedColumnRow(RowProxy):
def __init__(self, parent, row):
row = [ResultProxy._get_col(parent, row, i) for i in xrange(len(row))]
@@ -1403,9 +1591,8 @@ class ResultProxy(object):
"""
_process_row = RowProxy
-
+
def __init__(self, context):
- """ResultProxy objects are constructed via the execute() method on SQLEngine."""
self.context = context
self.dialect = context.dialect
self.closed = False
@@ -1413,40 +1600,81 @@ class ResultProxy(object):
self.connection = context.root_connection
self._echo = context.engine._should_log_info
self._init_metadata()
-
- @property
+
+ @util.memoized_property
def rowcount(self):
- if self._rowcount is None:
- return self.context.get_rowcount()
- else:
- return self._rowcount
+ """Return the 'rowcount' for this result.
+
+ The 'rowcount' reports the number of rows affected
+ by an UPDATE or DELETE statement. It has *no* other
+ uses and is not intended to provide the number of rows
+ present from a SELECT.
+
+ Additionally, this value is only meaningful if the
+ dialect's supports_sane_rowcount flag is True for
+ single-parameter executions, or supports_sane_multi_rowcount
+ is true for multiple parameter executions - otherwise
+ results are undefined.
+
+ rowcount may not work at this time for a statement
+ that uses ``returning()``.
+
+ """
+ return self.context.rowcount
@property
def lastrowid(self):
+ """return the 'lastrowid' accessor on the DBAPI cursor.
+
+ This is a DBAPI specific method and is only functional
+ for those backends which support it, for statements
+ where it is appropriate. It's behavior is not
+ consistent across backends.
+
+ Usage of this method is normally unnecessary; the
+ inserted_primary_key method provides a
+ tuple of primary key values for a newly inserted row,
+ regardless of database backend.
+
+ """
return self.cursor.lastrowid
@property
def out_parameters(self):
return self.context.out_parameters
-
+
+ def _cursor_description(self):
+ return self.cursor.description
+
+ def _autoclose(self):
+ if self.context.isinsert:
+ if self.context._is_implicit_returning:
+ self.context._fetch_implicit_returning(self)
+ self.close()
+ elif not self.context._is_explicit_returning:
+ self.close()
+ elif self._metadata is None:
+ # no results, get rowcount
+ # (which requires open cursor on some DB's such as firebird),
+ self.rowcount
+ self.close() # autoclose
+
+ return self
+
+
def _init_metadata(self):
- metadata = self.cursor.description
+ self._metadata = metadata = self._cursor_description()
if metadata is None:
- # no results, get rowcount (which requires open cursor on some DB's such as firebird),
- # then close
- self._rowcount = self.context.get_rowcount()
- self.close()
return
-
- self._rowcount = None
+
self._props = util.populate_column_dict(None)
self._props.creator = self.__key_fallback()
self.keys = []
typemap = self.dialect.dbapi_type_map
- for i, item in enumerate(metadata):
- colname = item[0]
+ for i, (colname, coltype) in enumerate(m[0:2] for m in metadata):
+
if self.dialect.description_encoding:
colname = colname.decode(self.dialect.description_encoding)
@@ -1461,9 +1689,9 @@ class ResultProxy(object):
try:
(name, obj, type_) = self.context.result_map[colname.lower()]
except KeyError:
- (name, obj, type_) = (colname, None, typemap.get(item[1], types.NULLTYPE))
+ (name, obj, type_) = (colname, None, typemap.get(coltype, types.NULLTYPE))
else:
- (name, obj, type_) = (colname, None, typemap.get(item[1], types.NULLTYPE))
+ (name, obj, type_) = (colname, None, typemap.get(coltype, types.NULLTYPE))
rec = (type_, type_.dialect_impl(self.dialect).result_processor(self.dialect), i)
@@ -1474,7 +1702,10 @@ class ResultProxy(object):
if origname:
if self._props.setdefault(origname.lower(), rec) is not rec:
self._props[origname.lower()] = (type_, self.__ambiguous_processor(origname), 0)
-
+
+ if self.dialect.requires_name_normalize:
+ colname = self.dialect.normalize_name(colname)
+
self.keys.append(colname)
self._props[i] = rec
if obj:
@@ -1484,11 +1715,11 @@ class ResultProxy(object):
if self._echo:
self.context.engine.logger.debug(
"Col " + repr(tuple(x[0] for x in metadata)))
-
+
def __key_fallback(self):
# create a closure without 'self' to avoid circular references
props = self._props
-
+
def fallback(key):
if isinstance(key, basestring):
key = key.lower()
@@ -1515,19 +1746,22 @@ class ResultProxy(object):
def close(self):
"""Close this ResultProxy.
-
+
Closes the underlying DBAPI cursor corresponding to the execution.
+
+ Note that any data cached within this ResultProxy is still available.
+ For some types of results, this may include buffered rows.
If this ResultProxy was generated from an implicit execution,
the underlying Connection will also be closed (returns the
underlying DBAPI connection to the connection pool.)
This method is called automatically when:
-
- * all result rows are exhausted using the fetchXXX() methods.
- * cursor.description is None.
-
+
+ * all result rows are exhausted using the fetchXXX() methods.
+ * cursor.description is None.
"""
+
if not self.closed:
self.closed = True
self.cursor.close()
@@ -1550,53 +1784,66 @@ class ResultProxy(object):
raise StopIteration
else:
yield row
-
- def last_inserted_ids(self):
- """Return ``last_inserted_ids()`` from the underlying ExecutionContext.
-
- See ExecutionContext for details.
+
+ @util.memoized_property
+ def inserted_primary_key(self):
+ """Return the primary key for the row just inserted.
+
+ This only applies to single row insert() constructs which
+ did not explicitly specify returning().
"""
- return self.context.last_inserted_ids()
+ if not self.context.isinsert:
+ raise exc.InvalidRequestError("Statement is not an insert() expression construct.")
+ elif self.context._is_explicit_returning:
+ raise exc.InvalidRequestError("Can't call inserted_primary_key when returning() is used.")
+
+ return self.context._inserted_primary_key
+ @util.deprecated("Use inserted_primary_key")
+ def last_inserted_ids(self):
+ """deprecated. use inserted_primary_key."""
+
+ return self.inserted_primary_key
+
def last_updated_params(self):
"""Return ``last_updated_params()`` from the underlying ExecutionContext.
See ExecutionContext for details.
-
"""
+
return self.context.last_updated_params()
def last_inserted_params(self):
"""Return ``last_inserted_params()`` from the underlying ExecutionContext.
See ExecutionContext for details.
-
"""
+
return self.context.last_inserted_params()
def lastrow_has_defaults(self):
"""Return ``lastrow_has_defaults()`` from the underlying ExecutionContext.
See ExecutionContext for details.
-
"""
+
return self.context.lastrow_has_defaults()
def postfetch_cols(self):
"""Return ``postfetch_cols()`` from the underlying ExecutionContext.
See ExecutionContext for details.
-
"""
+
return self.context.postfetch_cols
-
+
def prefetch_cols(self):
return self.context.prefetch_cols
-
+
def supports_sane_rowcount(self):
"""Return ``supports_sane_rowcount`` from the dialect."""
-
+
return self.dialect.supports_sane_rowcount
def supports_sane_multi_rowcount(self):
@@ -1643,7 +1890,12 @@ class ResultProxy(object):
raise
def fetchmany(self, size=None):
- """Fetch many rows, just like DB-API ``cursor.fetchmany(size=cursor.arraysize)``."""
+ """Fetch many rows, just like DB-API ``cursor.fetchmany(size=cursor.arraysize)``.
+
+ If rows are present, the cursor remains open after this is called.
+ Else the cursor is automatically closed and an empty list is returned.
+
+ """
try:
process_row = self._process_row
@@ -1656,7 +1908,13 @@ class ResultProxy(object):
raise
def fetchone(self):
- """Fetch one row, just like DB-API ``cursor.fetchone()``."""
+ """Fetch one row, just like DB-API ``cursor.fetchone()``.
+
+ If a row is present, the cursor remains open after this is called.
+ Else the cursor is automatically closed and None is returned.
+
+ """
+
try:
row = self._fetchone_impl()
if row is not None:
@@ -1668,21 +1926,38 @@ class ResultProxy(object):
self.connection._handle_dbapi_exception(e, None, None, self.cursor, self.context)
raise
- def scalar(self):
- """Fetch the first column of the first row, and close the result set."""
+ def first(self):
+ """Fetch the first row and then close the result set unconditionally.
+
+ Returns None if no row is present.
+
+ """
try:
row = self._fetchone_impl()
except Exception, e:
self.connection._handle_dbapi_exception(e, None, None, self.cursor, self.context)
raise
-
+
try:
if row is not None:
- return self._process_row(self, row)[0]
+ return self._process_row(self, row)
else:
return None
finally:
self.close()
+
+
+ def scalar(self):
+ """Fetch the first column of the first row, and close the result set.
+
+ Returns None if no row is present.
+
+ """
+ row = self.first()
+ if row is not None:
+ return row[0]
+ else:
+ return None
class BufferedRowResultProxy(ResultProxy):
"""A ResultProxy with row buffering behavior.
@@ -1697,7 +1972,6 @@ class BufferedRowResultProxy(ResultProxy):
The pre-fetching behavior fetches only one row initially, and then
grows its buffer size by a fixed amount with each successive need
for additional rows up to a size of 100.
-
"""
def _init_metadata(self):
@@ -1740,7 +2014,44 @@ class BufferedRowResultProxy(ResultProxy):
return result
def _fetchall_impl(self):
- return self.__rowbuffer + list(self.cursor.fetchall())
+ ret = self.__rowbuffer + list(self.cursor.fetchall())
+ self.__rowbuffer[:] = []
+ return ret
+
+class FullyBufferedResultProxy(ResultProxy):
+ """A result proxy that buffers rows fully upon creation.
+
+ Used for operations where a result is to be delivered
+ after the database conversation can not be continued,
+ such as MSSQL INSERT...OUTPUT after an autocommit.
+
+ """
+ def _init_metadata(self):
+ super(FullyBufferedResultProxy, self)._init_metadata()
+ self.__rowbuffer = self._buffer_rows()
+
+ def _buffer_rows(self):
+ return self.cursor.fetchall()
+
+ def _fetchone_impl(self):
+ if self.__rowbuffer:
+ return self.__rowbuffer.pop(0)
+ else:
+ return None
+
+ def _fetchmany_impl(self, size=None):
+ result = []
+ for x in range(0, size):
+ row = self._fetchone_impl()
+ if row is None:
+ break
+ result.append(row)
+ return result
+
+ def _fetchall_impl(self):
+ ret = self.__rowbuffer
+ self.__rowbuffer = []
+ return ret
class BufferedColumnResultProxy(ResultProxy):
"""A ResultProxy with column buffering behavior.
@@ -1791,28 +2102,6 @@ class BufferedColumnResultProxy(ResultProxy):
return l
-class SchemaIterator(schema.SchemaVisitor):
- """A visitor that can gather text into a buffer and execute the contents of the buffer."""
-
- def __init__(self, connection):
- """Construct a new SchemaIterator."""
-
- self.connection = connection
- self.buffer = StringIO.StringIO()
-
- def append(self, s):
- """Append content to the SchemaIterator's query buffer."""
-
- self.buffer.write(s)
-
- def execute(self):
- """Execute the contents of the SchemaIterator's buffer."""
-
- try:
- return self.connection.execute(self.buffer.getvalue())
- finally:
- self.buffer.truncate(0)
-
class DefaultRunner(schema.SchemaVisitor):
"""A visitor which accepts ColumnDefault objects, produces the
dialect-specific SQL corresponding to their execution, and
@@ -1821,7 +2110,6 @@ class DefaultRunner(schema.SchemaVisitor):
DefaultRunners are used internally by Engines and Dialects.
Specific database modules should provide their own subclasses of
DefaultRunner to allow database-specific behavior.
-
"""
def __init__(self, context):
@@ -1854,7 +2142,7 @@ class DefaultRunner(schema.SchemaVisitor):
def execute_string(self, stmt, params=None):
"""execute a string statement, using the raw cursor, and return a scalar result."""
-
+
conn = self.context._connection
if isinstance(stmt, unicode) and not self.dialect.supports_unicode_statements:
stmt = stmt.encode(self.dialect.encoding)
@@ -1883,8 +2171,8 @@ def connection_memoize(key):
Only applicable to functions which take no arguments other than a
connection. The memo will be stored in ``connection.info[key]``.
-
"""
+
@util.decorator
def decorated(fn, self, connection):
connection = connection.connect()