summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/engine')
-rw-r--r--lib/sqlalchemy/engine/base.py157
-rw-r--r--lib/sqlalchemy/engine/reflection.py160
-rw-r--r--lib/sqlalchemy/engine/strategies.py1
3 files changed, 236 insertions, 82 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 220679c12..dd82be1d1 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -45,7 +45,7 @@ class Connection(Connectable):
"""
def __init__(self, engine, connection=None, close_with_result=False,
- _branch=False, _execution_options=None,
+ _branch_from=None, _execution_options=None,
_dispatch=None,
_has_events=None):
"""Construct a new Connection.
@@ -57,48 +57,80 @@ class Connection(Connectable):
"""
self.engine = engine
self.dialect = engine.dialect
- self.__connection = connection or engine.raw_connection()
- self.__transaction = None
- self.should_close_with_result = close_with_result
- self.__savepoint_seq = 0
- self.__branch = _branch
- self.__invalid = False
- self.__can_reconnect = True
- if _dispatch:
+ self.__branch_from = _branch_from
+ self.__branch = _branch_from is not None
+
+ if _branch_from:
+ self.__connection = connection
+ self._execution_options = _execution_options
+ self._echo = _branch_from._echo
+ self.should_close_with_result = False
self.dispatch = _dispatch
- elif _has_events is None:
- # if _has_events is sent explicitly as False,
- # then don't join the dispatch of the engine; we don't
- # want to handle any of the engine's events in that case.
- self.dispatch = self.dispatch._join(engine.dispatch)
- self._has_events = _has_events or (
- _has_events is None and engine._has_events)
-
- self._echo = self.engine._should_log_info()
- if _execution_options:
- self._execution_options =\
- engine._execution_options.union(_execution_options)
+ self._has_events = _branch_from._has_events
else:
+ self.__connection = connection \
+ if connection is not None else engine.raw_connection()
+ self.__transaction = None
+ self.__savepoint_seq = 0
+ self.should_close_with_result = close_with_result
+ self.__invalid = False
+ self.__can_reconnect = True
+ self._echo = self.engine._should_log_info()
+
+ if _has_events is None:
+ # if _has_events is sent explicitly as False,
+ # then don't join the dispatch of the engine; we don't
+ # want to handle any of the engine's events in that case.
+ self.dispatch = self.dispatch._join(engine.dispatch)
+ self._has_events = _has_events or (
+ _has_events is None and engine._has_events)
+
+ assert not _execution_options
self._execution_options = engine._execution_options
if self._has_events or self.engine._has_events:
- self.dispatch.engine_connect(self, _branch)
+ self.dispatch.engine_connect(self, self.__branch)
def _branch(self):
"""Return a new Connection which references this Connection's
engine and connection; but does not have close_with_result enabled,
and also whose close() method does nothing.
- This is used to execute "sub" statements within a single execution,
- usually an INSERT statement.
+ The Core uses this very sparingly, only in the case of
+ custom SQL default functions that are to be INSERTed as the
+ primary key of a row where we need to get the value back, so we have
+ to invoke it distinctly - this is a very uncommon case.
+
+ Userland code accesses _branch() when the connect() or
+ contextual_connect() methods are called. The branched connection
+ acts as much as possible like the parent, except that it stays
+ connected when a close() event occurs.
+
"""
+ if self.__branch_from:
+ return self.__branch_from._branch()
+ else:
+ return self.engine._connection_cls(
+ self.engine,
+ self.__connection,
+ _branch_from=self,
+ _execution_options=self._execution_options,
+ _has_events=self._has_events,
+ _dispatch=self.dispatch)
+
+ @property
+ def _root(self):
+ """return the 'root' connection.
- return self.engine._connection_cls(
- self.engine,
- self.__connection,
- _branch=True,
- _has_events=self._has_events,
- _dispatch=self.dispatch)
+ Returns 'self' if this connection is not a branch, else
+ returns the root connection from which we ultimately branched.
+
+ """
+
+ if self.__branch_from:
+ return self.__branch_from
+ else:
+ return self
def _clone(self):
"""Create a shallow copy of this Connection.
@@ -224,7 +256,7 @@ class Connection(Connectable):
def invalidated(self):
"""Return True if this connection was invalidated."""
- return self.__invalid
+ return self._root.__invalid
@property
def connection(self):
@@ -236,6 +268,9 @@ class Connection(Connectable):
return self._revalidate_connection()
def _revalidate_connection(self):
+ if self.__branch_from:
+ return self.__branch_from._revalidate_connection()
+
if self.__can_reconnect and self.__invalid:
if self.__transaction is not None:
raise exc.InvalidRequestError(
@@ -343,16 +378,17 @@ class Connection(Connectable):
:ref:`pool_connection_invalidation`
"""
+
if self.invalidated:
return
if self.closed:
raise exc.ResourceClosedError("This Connection is closed")
- if self._connection_is_valid:
- self.__connection.invalidate(exception)
- del self.__connection
- self.__invalid = True
+ if self._root._connection_is_valid:
+ self._root.__connection.invalidate(exception)
+ del self._root.__connection
+ self._root.__invalid = True
def detach(self):
"""Detach the underlying DB-API connection from its connection pool.
@@ -415,6 +451,8 @@ class Connection(Connectable):
:class:`.Engine`.
"""
+ if self.__branch_from:
+ return self.__branch_from.begin()
if self.__transaction is None:
self.__transaction = RootTransaction(self)
@@ -436,6 +474,9 @@ class Connection(Connectable):
See also :meth:`.Connection.begin`,
:meth:`.Connection.begin_twophase`.
"""
+ if self.__branch_from:
+ return self.__branch_from.begin_nested()
+
if self.__transaction is None:
self.__transaction = RootTransaction(self)
else:
@@ -459,6 +500,9 @@ class Connection(Connectable):
"""
+ if self.__branch_from:
+ return self.__branch_from.begin_twophase(xid=xid)
+
if self.__transaction is not None:
raise exc.InvalidRequestError(
"Cannot start a two phase transaction when a transaction "
@@ -479,10 +523,11 @@ class Connection(Connectable):
def in_transaction(self):
"""Return True if a transaction is in progress."""
-
- return self.__transaction is not None
+ return self._root.__transaction is not None
def _begin_impl(self, transaction):
+ assert not self.__branch_from
+
if self._echo:
self.engine.logger.info("BEGIN (implicit)")
@@ -497,6 +542,8 @@ class Connection(Connectable):
self._handle_dbapi_exception(e, None, None, None, None)
def _rollback_impl(self):
+ assert not self.__branch_from
+
if self._has_events or self.engine._has_events:
self.dispatch.rollback(self)
@@ -516,6 +563,8 @@ class Connection(Connectable):
self.__transaction = None
def _commit_impl(self, autocommit=False):
+ assert not self.__branch_from
+
if self._has_events or self.engine._has_events:
self.dispatch.commit(self)
@@ -532,6 +581,8 @@ class Connection(Connectable):
self.__transaction = None
def _savepoint_impl(self, name=None):
+ assert not self.__branch_from
+
if self._has_events or self.engine._has_events:
self.dispatch.savepoint(self, name)
@@ -543,6 +594,8 @@ class Connection(Connectable):
return name
def _rollback_to_savepoint_impl(self, name, context):
+ assert not self.__branch_from
+
if self._has_events or self.engine._has_events:
self.dispatch.rollback_savepoint(self, name, context)
@@ -551,6 +604,8 @@ class Connection(Connectable):
self.__transaction = context
def _release_savepoint_impl(self, name, context):
+ assert not self.__branch_from
+
if self._has_events or self.engine._has_events:
self.dispatch.release_savepoint(self, name, context)
@@ -559,6 +614,8 @@ class Connection(Connectable):
self.__transaction = context
def _begin_twophase_impl(self, transaction):
+ assert not self.__branch_from
+
if self._echo:
self.engine.logger.info("BEGIN TWOPHASE (implicit)")
if self._has_events or self.engine._has_events:
@@ -571,6 +628,8 @@ class Connection(Connectable):
self.connection._reset_agent = transaction
def _prepare_twophase_impl(self, xid):
+ assert not self.__branch_from
+
if self._has_events or self.engine._has_events:
self.dispatch.prepare_twophase(self, xid)
@@ -579,6 +638,8 @@ class Connection(Connectable):
self.engine.dialect.do_prepare_twophase(self, xid)
def _rollback_twophase_impl(self, xid, is_prepared):
+ assert not self.__branch_from
+
if self._has_events or self.engine._has_events:
self.dispatch.rollback_twophase(self, xid, is_prepared)
@@ -595,6 +656,8 @@ class Connection(Connectable):
self.__transaction = None
def _commit_twophase_impl(self, xid, is_prepared):
+ assert not self.__branch_from
+
if self._has_events or self.engine._has_events:
self.dispatch.commit_twophase(self, xid, is_prepared)
@@ -610,8 +673,8 @@ class Connection(Connectable):
self.__transaction = None
def _autorollback(self):
- if not self.in_transaction():
- self._rollback_impl()
+ if not self._root.in_transaction():
+ self._root._rollback_impl()
def close(self):
"""Close this :class:`.Connection`.
@@ -632,13 +695,21 @@ class Connection(Connectable):
and will allow no further operations.
"""
+ if self.__branch_from:
+ try:
+ del self.__connection
+ except AttributeError:
+ pass
+ finally:
+ self.__can_reconnect = False
+ return
try:
conn = self.__connection
except AttributeError:
pass
else:
- if not self.__branch:
- conn.close()
+
+ conn.close()
if conn._reset_agent is self.__transaction:
conn._reset_agent = None
@@ -993,8 +1064,8 @@ class Connection(Connectable):
result.rowcount
result.close(_autoclose_connection=False)
- if self.__transaction is None and context.should_autocommit:
- self._commit_impl(autocommit=True)
+ if context.should_autocommit and self._root.__transaction is None:
+ self._root._commit_impl(autocommit=True)
if result.closed and self.should_close_with_result:
self.close()
diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py
index c0a3240a5..2a1def86a 100644
--- a/lib/sqlalchemy/engine/reflection.py
+++ b/lib/sqlalchemy/engine/reflection.py
@@ -489,55 +489,87 @@ class Inspector(object):
for col_d in self.get_columns(
table_name, schema, **table.dialect_kwargs):
found_table = True
- orig_name = col_d['name']
- table.dispatch.column_reflect(self, table, col_d)
+ self._reflect_column(
+ table, col_d, include_columns,
+ exclude_columns, cols_by_orig_name)
- name = col_d['name']
- if include_columns and name not in include_columns:
- continue
- if exclude_columns and name in exclude_columns:
- continue
+ if not found_table:
+ raise exc.NoSuchTableError(table.name)
- coltype = col_d['type']
+ self._reflect_pk(
+ table_name, schema, table, cols_by_orig_name, exclude_columns)
- col_kw = dict(
- (k, col_d[k])
- for k in ['nullable', 'autoincrement', 'quote', 'info', 'key']
- if k in col_d
- )
+ self._reflect_fk(
+ table_name, schema, table, cols_by_orig_name,
+ exclude_columns, reflection_options)
- colargs = []
- if col_d.get('default') is not None:
- # the "default" value is assumed to be a literal SQL
- # expression, so is wrapped in text() so that no quoting
- # occurs on re-issuance.
- colargs.append(
- sa_schema.DefaultClause(
- sql.text(col_d['default']), _reflected=True
- )
- )
+ self._reflect_indexes(
+ table_name, schema, table, cols_by_orig_name,
+ include_columns, exclude_columns, reflection_options)
- if 'sequence' in col_d:
- # TODO: mssql and sybase are using this.
- seq = col_d['sequence']
- sequence = sa_schema.Sequence(seq['name'], 1, 1)
- if 'start' in seq:
- sequence.start = seq['start']
- if 'increment' in seq:
- sequence.increment = seq['increment']
- colargs.append(sequence)
+ self._reflect_unique_constraints(
+ table_name, schema, table, cols_by_orig_name,
+ include_columns, exclude_columns, reflection_options)
- cols_by_orig_name[orig_name] = col = \
- sa_schema.Column(name, coltype, *colargs, **col_kw)
+ def _reflect_column(
+ self, table, col_d, include_columns,
+ exclude_columns, cols_by_orig_name):
- if col.key in table.primary_key:
- col.primary_key = True
- table.append_column(col)
+ orig_name = col_d['name']
- if not found_table:
- raise exc.NoSuchTableError(table.name)
+ table.dispatch.column_reflect(self, table, col_d)
+ # fetch name again as column_reflect is allowed to
+ # change it
+ name = col_d['name']
+ if (include_columns and name not in include_columns) \
+ or (exclude_columns and name in exclude_columns):
+ return
+
+ coltype = col_d['type']
+
+ col_kw = dict(
+ (k, col_d[k])
+ for k in ['nullable', 'autoincrement', 'quote', 'info', 'key']
+ if k in col_d
+ )
+
+ colargs = []
+ if col_d.get('default') is not None:
+ # the "default" value is assumed to be a literal SQL
+ # expression, so is wrapped in text() so that no quoting
+ # occurs on re-issuance.
+ colargs.append(
+ sa_schema.DefaultClause(
+ sql.text(col_d['default']), _reflected=True
+ )
+ )
+
+ if 'sequence' in col_d:
+ self._reflect_col_sequence(col_d, colargs)
+
+ cols_by_orig_name[orig_name] = col = \
+ sa_schema.Column(name, coltype, *colargs, **col_kw)
+
+ if col.key in table.primary_key:
+ col.primary_key = True
+ table.append_column(col)
+
+ def _reflect_col_sequence(self, col_d, colargs):
+ if 'sequence' in col_d:
+ # TODO: mssql and sybase are using this.
+ seq = col_d['sequence']
+ sequence = sa_schema.Sequence(seq['name'], 1, 1)
+ if 'start' in seq:
+ sequence.start = seq['start']
+ if 'increment' in seq:
+ sequence.increment = seq['increment']
+ colargs.append(sequence)
+
+ def _reflect_pk(
+ self, table_name, schema, table,
+ cols_by_orig_name, exclude_columns):
pk_cons = self.get_pk_constraint(
table_name, schema, **table.dialect_kwargs)
if pk_cons:
@@ -554,6 +586,9 @@ class Inspector(object):
# its column collection
table.primary_key._reload(pk_cols)
+ def _reflect_fk(
+ self, table_name, schema, table, cols_by_orig_name,
+ exclude_columns, reflection_options):
fkeys = self.get_foreign_keys(
table_name, schema, **table.dialect_kwargs)
for fkey_d in fkeys:
@@ -596,6 +631,10 @@ class Inspector(object):
sa_schema.ForeignKeyConstraint(constrained_columns, refspec,
conname, link_to_name=True,
**options))
+
+ def _reflect_indexes(
+ self, table_name, schema, table, cols_by_orig_name,
+ include_columns, exclude_columns, reflection_options):
# Indexes
indexes = self.get_indexes(table_name, schema)
for index_d in indexes:
@@ -603,12 +642,15 @@ class Inspector(object):
columns = index_d['column_names']
unique = index_d['unique']
flavor = index_d.get('type', 'index')
+ duplicates = index_d.get('duplicates_constraint')
if include_columns and \
not set(columns).issubset(include_columns):
util.warn(
"Omitting %s key for (%s), key covers omitted columns." %
(flavor, ', '.join(columns)))
continue
+ if duplicates:
+ continue
# look for columns by orig name in cols_by_orig_name,
# but support columns that are in-Python only as fallback
idx_cols = []
@@ -626,3 +668,43 @@ class Inspector(object):
idx_cols.append(idx_col)
sa_schema.Index(name, *idx_cols, **dict(unique=unique))
+
+ def _reflect_unique_constraints(
+ self, table_name, schema, table, cols_by_orig_name,
+ include_columns, exclude_columns, reflection_options):
+
+ # Unique Constraints
+ try:
+ constraints = self.get_unique_constraints(table_name, schema)
+ except NotImplementedError:
+ # optional dialect feature
+ return
+
+ for const_d in constraints:
+ conname = const_d['name']
+ columns = const_d['column_names']
+ duplicates = const_d.get('duplicates_index')
+ if include_columns and \
+ not set(columns).issubset(include_columns):
+ util.warn(
+ "Omitting unique constraint key for (%s), "
+ "key covers omitted columns." %
+ ', '.join(columns))
+ continue
+ if duplicates:
+ continue
+ # look for columns by orig name in cols_by_orig_name,
+ # but support columns that are in-Python only as fallback
+ constrained_cols = []
+ for c in columns:
+ try:
+ constrained_col = cols_by_orig_name[c] \
+ if c in cols_by_orig_name else table.c[c]
+ except KeyError:
+ util.warn(
+ "unique constraint key '%s' was not located in "
+ "columns for table '%s'" % (c, table_name))
+ else:
+ constrained_cols.append(constrained_col)
+ table.append_constraint(
+ sa_schema.UniqueConstraint(*constrained_cols, name=conname))
diff --git a/lib/sqlalchemy/engine/strategies.py b/lib/sqlalchemy/engine/strategies.py
index 38206be89..398ef8df6 100644
--- a/lib/sqlalchemy/engine/strategies.py
+++ b/lib/sqlalchemy/engine/strategies.py
@@ -162,6 +162,7 @@ class DefaultEngineStrategy(EngineStrategy):
def first_connect(dbapi_connection, connection_record):
c = base.Connection(engine, connection=dbapi_connection,
_has_events=False)
+ c._execution_options = util.immutabledict()
dialect.initialize(c)
event.listen(pool, 'first_connect', first_connect, once=True)