2016-02-06 09:38:49 +00:00
|
|
|
# engine/default.py
|
2016-02-22 11:49:42 +00:00
|
|
|
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
2016-02-06 09:38:49 +00:00
|
|
|
# <see AUTHORS file>
|
|
|
|
#
|
|
|
|
# This module is part of SQLAlchemy and is released under
|
|
|
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
|
|
|
|
|
|
"""Default implementations of per-dialect sqlalchemy.engine classes.
|
|
|
|
|
|
|
|
These are semi-private implementation classes which are only of importance
|
|
|
|
to database dialect authors; dialects will usually use the classes here
|
|
|
|
as the base class for their own corresponding classes.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
import re
|
|
|
|
import random
|
|
|
|
from . import reflection, interfaces, result
|
|
|
|
from ..sql import compiler, expression
|
|
|
|
from .. import types as sqltypes
|
|
|
|
from .. import exc, util, pool, processors
|
|
|
|
import codecs
|
|
|
|
import weakref
|
|
|
|
from .. import event
|
|
|
|
|
|
|
|
AUTOCOMMIT_REGEXP = re.compile(
|
|
|
|
r'\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER)',
|
|
|
|
re.I | re.UNICODE)
|
|
|
|
|
|
|
|
|
|
|
|
class DefaultDialect(interfaces.Dialect):
|
|
|
|
"""Default implementation of Dialect"""
|
|
|
|
|
|
|
|
statement_compiler = compiler.SQLCompiler
|
|
|
|
ddl_compiler = compiler.DDLCompiler
|
|
|
|
type_compiler = compiler.GenericTypeCompiler
|
|
|
|
preparer = compiler.IdentifierPreparer
|
|
|
|
supports_alter = True
|
|
|
|
|
|
|
|
# the first value we'd get for an autoincrement
|
|
|
|
# column.
|
|
|
|
default_sequence_base = 1
|
|
|
|
|
|
|
|
# most DBAPIs happy with this for execute().
|
|
|
|
# not cx_oracle.
|
|
|
|
execute_sequence_format = tuple
|
|
|
|
|
|
|
|
supports_views = True
|
|
|
|
supports_sequences = False
|
|
|
|
sequences_optional = False
|
|
|
|
preexecute_autoincrement_sequences = False
|
|
|
|
postfetch_lastrowid = True
|
|
|
|
implicit_returning = False
|
|
|
|
|
|
|
|
supports_right_nested_joins = True
|
|
|
|
|
|
|
|
supports_native_enum = False
|
|
|
|
supports_native_boolean = False
|
|
|
|
|
|
|
|
supports_simple_order_by_label = True
|
|
|
|
|
|
|
|
engine_config_types = util.immutabledict([
|
|
|
|
('convert_unicode', util.bool_or_str('force')),
|
2016-02-22 11:49:42 +00:00
|
|
|
('pool_timeout', util.asint),
|
2016-02-06 09:38:49 +00:00
|
|
|
('echo', util.bool_or_str('debug')),
|
|
|
|
('echo_pool', util.bool_or_str('debug')),
|
2016-02-22 11:49:42 +00:00
|
|
|
('pool_recycle', util.asint),
|
|
|
|
('pool_size', util.asint),
|
|
|
|
('max_overflow', util.asint),
|
|
|
|
('pool_threadlocal', util.asbool),
|
2016-02-06 09:38:49 +00:00
|
|
|
])
|
|
|
|
|
|
|
|
# if the NUMERIC type
|
|
|
|
# returns decimal.Decimal.
|
|
|
|
# *not* the FLOAT type however.
|
|
|
|
supports_native_decimal = False
|
|
|
|
|
|
|
|
if util.py3k:
|
|
|
|
supports_unicode_statements = True
|
|
|
|
supports_unicode_binds = True
|
|
|
|
returns_unicode_strings = True
|
|
|
|
description_encoding = None
|
|
|
|
else:
|
|
|
|
supports_unicode_statements = False
|
|
|
|
supports_unicode_binds = False
|
|
|
|
returns_unicode_strings = False
|
|
|
|
description_encoding = 'use_encoding'
|
|
|
|
|
|
|
|
name = 'default'
|
|
|
|
|
|
|
|
# length at which to truncate
|
|
|
|
# any identifier.
|
|
|
|
max_identifier_length = 9999
|
|
|
|
|
|
|
|
# length at which to truncate
|
|
|
|
# the name of an index.
|
|
|
|
# Usually None to indicate
|
|
|
|
# 'use max_identifier_length'.
|
|
|
|
# thanks to MySQL, sigh
|
|
|
|
max_index_name_length = None
|
|
|
|
|
|
|
|
supports_sane_rowcount = True
|
|
|
|
supports_sane_multi_rowcount = True
|
|
|
|
dbapi_type_map = {}
|
|
|
|
colspecs = {}
|
|
|
|
default_paramstyle = 'named'
|
|
|
|
supports_default_values = False
|
|
|
|
supports_empty_insert = True
|
|
|
|
supports_multivalues_insert = False
|
|
|
|
|
|
|
|
server_version_info = None
|
|
|
|
|
|
|
|
construct_arguments = None
|
|
|
|
"""Optional set of argument specifiers for various SQLAlchemy
|
|
|
|
constructs, typically schema items.
|
|
|
|
|
|
|
|
To implement, establish as a series of tuples, as in::
|
|
|
|
|
|
|
|
construct_arguments = [
|
|
|
|
(schema.Index, {
|
|
|
|
"using": False,
|
|
|
|
"where": None,
|
|
|
|
"ops": None
|
|
|
|
})
|
|
|
|
]
|
|
|
|
|
|
|
|
If the above construct is established on the Postgresql dialect,
|
|
|
|
the :class:`.Index` construct will now accept the keyword arguments
|
|
|
|
``postgresql_using``, ``postgresql_where``, nad ``postgresql_ops``.
|
|
|
|
Any other argument specified to the constructor of :class:`.Index`
|
|
|
|
which is prefixed with ``postgresql_`` will raise :class:`.ArgumentError`.
|
|
|
|
|
|
|
|
A dialect which does not include a ``construct_arguments`` member will
|
|
|
|
not participate in the argument validation system. For such a dialect,
|
|
|
|
any argument name is accepted by all participating constructs, within
|
|
|
|
the namespace of arguments prefixed with that dialect name. The rationale
|
|
|
|
here is so that third-party dialects that haven't yet implemented this
|
|
|
|
feature continue to function in the old way.
|
|
|
|
|
|
|
|
.. versionadded:: 0.9.2
|
|
|
|
|
|
|
|
.. seealso::
|
|
|
|
|
|
|
|
:class:`.DialectKWArgs` - implementing base class which consumes
|
|
|
|
:attr:`.DefaultDialect.construct_arguments`
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
# indicates symbol names are
|
|
|
|
# UPPERCASEd if they are case insensitive
|
|
|
|
# within the database.
|
|
|
|
# if this is True, the methods normalize_name()
|
|
|
|
# and denormalize_name() must be provided.
|
|
|
|
requires_name_normalize = False
|
|
|
|
|
|
|
|
reflection_options = ()
|
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
dbapi_exception_translation_map = util.immutabledict()
|
|
|
|
"""mapping used in the extremely unusual case that a DBAPI's
|
|
|
|
published exceptions don't actually have the __name__ that they
|
|
|
|
are linked towards.
|
|
|
|
|
|
|
|
.. versionadded:: 1.0.5
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2016-02-06 09:38:49 +00:00
|
|
|
def __init__(self, convert_unicode=False,
|
|
|
|
encoding='utf-8', paramstyle=None, dbapi=None,
|
|
|
|
implicit_returning=None,
|
|
|
|
supports_right_nested_joins=None,
|
|
|
|
case_sensitive=True,
|
|
|
|
supports_native_boolean=None,
|
|
|
|
label_length=None, **kwargs):
|
|
|
|
|
|
|
|
if not getattr(self, 'ported_sqla_06', True):
|
|
|
|
util.warn(
|
|
|
|
"The %s dialect is not yet ported to the 0.6 format" %
|
|
|
|
self.name)
|
|
|
|
|
|
|
|
self.convert_unicode = convert_unicode
|
|
|
|
self.encoding = encoding
|
|
|
|
self.positional = False
|
|
|
|
self._ischema = None
|
|
|
|
self.dbapi = dbapi
|
|
|
|
if paramstyle is not None:
|
|
|
|
self.paramstyle = paramstyle
|
|
|
|
elif self.dbapi is not None:
|
|
|
|
self.paramstyle = self.dbapi.paramstyle
|
|
|
|
else:
|
|
|
|
self.paramstyle = self.default_paramstyle
|
|
|
|
if implicit_returning is not None:
|
|
|
|
self.implicit_returning = implicit_returning
|
|
|
|
self.positional = self.paramstyle in ('qmark', 'format', 'numeric')
|
|
|
|
self.identifier_preparer = self.preparer(self)
|
|
|
|
self.type_compiler = self.type_compiler(self)
|
|
|
|
if supports_right_nested_joins is not None:
|
|
|
|
self.supports_right_nested_joins = supports_right_nested_joins
|
|
|
|
if supports_native_boolean is not None:
|
|
|
|
self.supports_native_boolean = supports_native_boolean
|
|
|
|
self.case_sensitive = case_sensitive
|
|
|
|
|
|
|
|
if label_length and label_length > self.max_identifier_length:
|
|
|
|
raise exc.ArgumentError(
|
|
|
|
"Label length of %d is greater than this dialect's"
|
|
|
|
" maximum identifier length of %d" %
|
|
|
|
(label_length, self.max_identifier_length))
|
|
|
|
self.label_length = label_length
|
|
|
|
|
|
|
|
if self.description_encoding == 'use_encoding':
|
|
|
|
self._description_decoder = \
|
|
|
|
processors.to_unicode_processor_factory(
|
|
|
|
encoding
|
|
|
|
)
|
|
|
|
elif self.description_encoding is not None:
|
|
|
|
self._description_decoder = \
|
|
|
|
processors.to_unicode_processor_factory(
|
|
|
|
self.description_encoding
|
|
|
|
)
|
|
|
|
self._encoder = codecs.getencoder(self.encoding)
|
|
|
|
self._decoder = processors.to_unicode_processor_factory(self.encoding)
|
|
|
|
|
|
|
|
@util.memoized_property
|
|
|
|
def _type_memos(self):
|
|
|
|
return weakref.WeakKeyDictionary()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def dialect_description(self):
|
|
|
|
return self.name + "+" + self.driver
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_pool_class(cls, url):
|
|
|
|
return getattr(cls, 'poolclass', pool.QueuePool)
|
|
|
|
|
|
|
|
def initialize(self, connection):
|
|
|
|
try:
|
|
|
|
self.server_version_info = \
|
|
|
|
self._get_server_version_info(connection)
|
|
|
|
except NotImplementedError:
|
|
|
|
self.server_version_info = None
|
|
|
|
try:
|
|
|
|
self.default_schema_name = \
|
|
|
|
self._get_default_schema_name(connection)
|
|
|
|
except NotImplementedError:
|
|
|
|
self.default_schema_name = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.default_isolation_level = \
|
|
|
|
self.get_isolation_level(connection.connection)
|
|
|
|
except NotImplementedError:
|
|
|
|
self.default_isolation_level = None
|
|
|
|
|
|
|
|
self.returns_unicode_strings = self._check_unicode_returns(connection)
|
|
|
|
|
|
|
|
if self.description_encoding is not None and \
|
|
|
|
self._check_unicode_description(connection):
|
|
|
|
self._description_decoder = self.description_encoding = None
|
|
|
|
|
|
|
|
self.do_rollback(connection.connection)
|
|
|
|
|
|
|
|
def on_connect(self):
|
|
|
|
"""return a callable which sets up a newly created DBAPI connection.
|
|
|
|
|
|
|
|
This is used to set dialect-wide per-connection options such as
|
|
|
|
isolation modes, unicode modes, etc.
|
|
|
|
|
|
|
|
If a callable is returned, it will be assembled into a pool listener
|
|
|
|
that receives the direct DBAPI connection, with all wrappers removed.
|
|
|
|
|
|
|
|
If None is returned, no listener will be generated.
|
|
|
|
|
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
|
|
|
def _check_unicode_returns(self, connection, additional_tests=None):
|
|
|
|
if util.py2k and not self.supports_unicode_statements:
|
|
|
|
cast_to = util.binary_type
|
|
|
|
else:
|
|
|
|
cast_to = util.text_type
|
|
|
|
|
|
|
|
if self.positional:
|
|
|
|
parameters = self.execute_sequence_format()
|
|
|
|
else:
|
|
|
|
parameters = {}
|
|
|
|
|
|
|
|
def check_unicode(test):
|
|
|
|
statement = cast_to(
|
|
|
|
expression.select([test]).compile(dialect=self))
|
|
|
|
try:
|
|
|
|
cursor = connection.connection.cursor()
|
|
|
|
connection._cursor_execute(cursor, statement, parameters)
|
|
|
|
row = cursor.fetchone()
|
|
|
|
cursor.close()
|
|
|
|
except exc.DBAPIError as de:
|
|
|
|
# note that _cursor_execute() will have closed the cursor
|
|
|
|
# if an exception is thrown.
|
|
|
|
util.warn("Exception attempting to "
|
|
|
|
"detect unicode returns: %r" % de)
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return isinstance(row[0], util.text_type)
|
|
|
|
|
|
|
|
tests = [
|
|
|
|
# detect plain VARCHAR
|
|
|
|
expression.cast(
|
|
|
|
expression.literal_column("'test plain returns'"),
|
|
|
|
sqltypes.VARCHAR(60)
|
|
|
|
),
|
|
|
|
# detect if there's an NVARCHAR type with different behavior
|
|
|
|
# available
|
|
|
|
expression.cast(
|
|
|
|
expression.literal_column("'test unicode returns'"),
|
|
|
|
sqltypes.Unicode(60)
|
|
|
|
),
|
|
|
|
]
|
|
|
|
|
|
|
|
if additional_tests:
|
|
|
|
tests += additional_tests
|
|
|
|
|
|
|
|
results = set([check_unicode(test) for test in tests])
|
|
|
|
|
|
|
|
if results.issuperset([True, False]):
|
|
|
|
return "conditional"
|
|
|
|
else:
|
|
|
|
return results == set([True])
|
|
|
|
|
|
|
|
def _check_unicode_description(self, connection):
|
|
|
|
# all DBAPIs on Py2K return cursor.description as encoded,
|
|
|
|
# until pypy2.1beta2 with sqlite, so let's just check it -
|
|
|
|
# it's likely others will start doing this too in Py2k.
|
|
|
|
|
|
|
|
if util.py2k and not self.supports_unicode_statements:
|
|
|
|
cast_to = util.binary_type
|
|
|
|
else:
|
|
|
|
cast_to = util.text_type
|
|
|
|
|
|
|
|
cursor = connection.connection.cursor()
|
|
|
|
try:
|
|
|
|
cursor.execute(
|
|
|
|
cast_to(
|
|
|
|
expression.select([
|
|
|
|
expression.literal_column("'x'").label("some_label")
|
|
|
|
]).compile(dialect=self)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return isinstance(cursor.description[0][0], util.text_type)
|
|
|
|
finally:
|
|
|
|
cursor.close()
|
|
|
|
|
|
|
|
def type_descriptor(self, typeobj):
|
|
|
|
"""Provide a database-specific :class:`.TypeEngine` object, given
|
|
|
|
the generic object which comes from the types module.
|
|
|
|
|
|
|
|
This method looks for a dictionary called
|
|
|
|
``colspecs`` as a class or instance-level variable,
|
|
|
|
and passes on to :func:`.types.adapt_type`.
|
|
|
|
|
|
|
|
"""
|
|
|
|
return sqltypes.adapt_type(typeobj, self.colspecs)
|
|
|
|
|
|
|
|
def reflecttable(
|
|
|
|
self, connection, table, include_columns, exclude_columns):
|
|
|
|
insp = reflection.Inspector.from_engine(connection)
|
|
|
|
return insp.reflecttable(table, include_columns, exclude_columns)
|
|
|
|
|
|
|
|
def get_pk_constraint(self, conn, table_name, schema=None, **kw):
|
|
|
|
"""Compatibility method, adapts the result of get_primary_keys()
|
|
|
|
for those dialects which don't implement get_pk_constraint().
|
|
|
|
|
|
|
|
"""
|
|
|
|
return {
|
|
|
|
'constrained_columns':
|
|
|
|
self.get_primary_keys(conn, table_name,
|
|
|
|
schema=schema, **kw)
|
|
|
|
}
|
|
|
|
|
|
|
|
def validate_identifier(self, ident):
|
|
|
|
if len(ident) > self.max_identifier_length:
|
|
|
|
raise exc.IdentifierError(
|
|
|
|
"Identifier '%s' exceeds maximum length of %d characters" %
|
|
|
|
(ident, self.max_identifier_length)
|
|
|
|
)
|
|
|
|
|
|
|
|
def connect(self, *cargs, **cparams):
|
|
|
|
return self.dbapi.connect(*cargs, **cparams)
|
|
|
|
|
|
|
|
def create_connect_args(self, url):
|
|
|
|
opts = url.translate_connect_args()
|
|
|
|
opts.update(url.query)
|
|
|
|
return [[], opts]
|
|
|
|
|
|
|
|
def set_engine_execution_options(self, engine, opts):
|
|
|
|
if 'isolation_level' in opts:
|
|
|
|
isolation_level = opts['isolation_level']
|
|
|
|
|
|
|
|
@event.listens_for(engine, "engine_connect")
|
|
|
|
def set_isolation(connection, branch):
|
|
|
|
if not branch:
|
|
|
|
self._set_connection_isolation(connection, isolation_level)
|
|
|
|
|
|
|
|
def set_connection_execution_options(self, connection, opts):
|
|
|
|
if 'isolation_level' in opts:
|
|
|
|
self._set_connection_isolation(connection, opts['isolation_level'])
|
|
|
|
|
|
|
|
def _set_connection_isolation(self, connection, level):
|
2016-02-22 11:49:42 +00:00
|
|
|
if connection.in_transaction():
|
|
|
|
util.warn(
|
|
|
|
"Connection is already established with a Transaction; "
|
|
|
|
"setting isolation_level may implicitly rollback or commit "
|
|
|
|
"the existing transaction, or have no effect until "
|
|
|
|
"next transaction")
|
2016-02-06 09:38:49 +00:00
|
|
|
self.set_isolation_level(connection.connection, level)
|
|
|
|
connection.connection._connection_record.\
|
|
|
|
finalize_callback.append(self.reset_isolation_level)
|
|
|
|
|
|
|
|
def do_begin(self, dbapi_connection):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def do_rollback(self, dbapi_connection):
|
|
|
|
dbapi_connection.rollback()
|
|
|
|
|
|
|
|
def do_commit(self, dbapi_connection):
|
|
|
|
dbapi_connection.commit()
|
|
|
|
|
|
|
|
def do_close(self, dbapi_connection):
|
|
|
|
dbapi_connection.close()
|
|
|
|
|
|
|
|
def create_xid(self):
|
|
|
|
"""Create a random two-phase transaction ID.
|
|
|
|
|
|
|
|
This id will be passed to do_begin_twophase(), do_rollback_twophase(),
|
|
|
|
do_commit_twophase(). Its format is unspecified.
|
|
|
|
"""
|
|
|
|
|
|
|
|
return "_sa_%032x" % random.randint(0, 2 ** 128)
|
|
|
|
|
|
|
|
def do_savepoint(self, connection, name):
|
|
|
|
connection.execute(expression.SavepointClause(name))
|
|
|
|
|
|
|
|
def do_rollback_to_savepoint(self, connection, name):
|
|
|
|
connection.execute(expression.RollbackToSavepointClause(name))
|
|
|
|
|
|
|
|
def do_release_savepoint(self, connection, name):
|
|
|
|
connection.execute(expression.ReleaseSavepointClause(name))
|
|
|
|
|
|
|
|
def do_executemany(self, cursor, statement, parameters, context=None):
|
|
|
|
cursor.executemany(statement, parameters)
|
|
|
|
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
|
|
cursor.execute(statement, parameters)
|
|
|
|
|
|
|
|
def do_execute_no_params(self, cursor, statement, context=None):
|
|
|
|
cursor.execute(statement)
|
|
|
|
|
|
|
|
def is_disconnect(self, e, connection, cursor):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def reset_isolation_level(self, dbapi_conn):
|
|
|
|
# default_isolation_level is read from the first connection
|
|
|
|
# after the initial set of 'isolation_level', if any, so is
|
|
|
|
# the configured default of this dialect.
|
|
|
|
self.set_isolation_level(dbapi_conn, self.default_isolation_level)
|
|
|
|
|
|
|
|
|
|
|
|
class DefaultExecutionContext(interfaces.ExecutionContext):
|
|
|
|
isinsert = False
|
|
|
|
isupdate = False
|
|
|
|
isdelete = False
|
2016-02-22 11:49:42 +00:00
|
|
|
is_crud = False
|
|
|
|
is_text = False
|
2016-02-06 09:38:49 +00:00
|
|
|
isddl = False
|
|
|
|
executemany = False
|
|
|
|
compiled = None
|
|
|
|
statement = None
|
2016-02-22 11:49:42 +00:00
|
|
|
result_column_struct = None
|
2016-02-06 09:38:49 +00:00
|
|
|
_is_implicit_returning = False
|
|
|
|
_is_explicit_returning = False
|
|
|
|
|
|
|
|
# a hook for SQLite's translation of
|
|
|
|
# result column names
|
|
|
|
_translate_colname = None
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl):
|
|
|
|
"""Initialize execution context for a DDLElement construct."""
|
|
|
|
|
|
|
|
self = cls.__new__(cls)
|
|
|
|
self.root_connection = connection
|
|
|
|
self._dbapi_connection = dbapi_connection
|
2016-02-22 11:49:42 +00:00
|
|
|
self.dialect = connection.dialect
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
self.compiled = compiled = compiled_ddl
|
|
|
|
self.isddl = True
|
|
|
|
|
|
|
|
self.execution_options = compiled.statement._execution_options
|
|
|
|
if connection._execution_options:
|
|
|
|
self.execution_options = dict(self.execution_options)
|
|
|
|
self.execution_options.update(connection._execution_options)
|
|
|
|
|
|
|
|
if not dialect.supports_unicode_statements:
|
|
|
|
self.unicode_statement = util.text_type(compiled)
|
|
|
|
self.statement = dialect._encoder(self.unicode_statement)[0]
|
|
|
|
else:
|
|
|
|
self.statement = self.unicode_statement = util.text_type(compiled)
|
|
|
|
|
|
|
|
self.cursor = self.create_cursor()
|
|
|
|
self.compiled_parameters = []
|
|
|
|
|
|
|
|
if dialect.positional:
|
|
|
|
self.parameters = [dialect.execute_sequence_format()]
|
|
|
|
else:
|
|
|
|
self.parameters = [{}]
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _init_compiled(cls, dialect, connection, dbapi_connection,
|
|
|
|
compiled, parameters):
|
|
|
|
"""Initialize execution context for a Compiled construct."""
|
|
|
|
|
|
|
|
self = cls.__new__(cls)
|
|
|
|
self.root_connection = connection
|
|
|
|
self._dbapi_connection = dbapi_connection
|
2016-02-22 11:49:42 +00:00
|
|
|
self.dialect = connection.dialect
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
self.compiled = compiled
|
|
|
|
|
|
|
|
if not compiled.can_execute:
|
|
|
|
raise exc.ArgumentError("Not an executable clause")
|
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
self.execution_options = compiled.statement._execution_options.union(
|
|
|
|
connection._execution_options)
|
2016-02-06 09:38:49 +00:00
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
self.result_column_struct = (
|
|
|
|
compiled._result_columns, compiled._ordered_columns)
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
self.unicode_statement = util.text_type(compiled)
|
|
|
|
if not dialect.supports_unicode_statements:
|
|
|
|
self.statement = self.unicode_statement.encode(
|
|
|
|
self.dialect.encoding)
|
|
|
|
else:
|
|
|
|
self.statement = self.unicode_statement
|
|
|
|
|
|
|
|
self.isinsert = compiled.isinsert
|
|
|
|
self.isupdate = compiled.isupdate
|
|
|
|
self.isdelete = compiled.isdelete
|
2016-02-22 11:49:42 +00:00
|
|
|
self.is_text = compiled.isplaintext
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
if not parameters:
|
|
|
|
self.compiled_parameters = [compiled.construct_params()]
|
|
|
|
else:
|
|
|
|
self.compiled_parameters = \
|
|
|
|
[compiled.construct_params(m, _group_number=grp) for
|
|
|
|
grp, m in enumerate(parameters)]
|
|
|
|
|
|
|
|
self.executemany = len(parameters) > 1
|
|
|
|
|
|
|
|
self.cursor = self.create_cursor()
|
2016-02-22 11:49:42 +00:00
|
|
|
|
|
|
|
if self.isinsert or self.isupdate or self.isdelete:
|
|
|
|
self.is_crud = True
|
|
|
|
self._is_explicit_returning = bool(compiled.statement._returning)
|
|
|
|
self._is_implicit_returning = bool(
|
|
|
|
compiled.returning and not compiled.statement._returning)
|
|
|
|
|
|
|
|
if not self.isdelete:
|
|
|
|
if self.compiled.prefetch:
|
|
|
|
if self.executemany:
|
|
|
|
self._process_executemany_defaults()
|
|
|
|
else:
|
|
|
|
self._process_executesingle_defaults()
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
processors = compiled._bind_processors
|
|
|
|
|
|
|
|
# Convert the dictionary of bind parameter values
|
|
|
|
# into a dict or list to be sent to the DBAPI's
|
|
|
|
# execute() or executemany() method.
|
|
|
|
parameters = []
|
|
|
|
if dialect.positional:
|
|
|
|
for compiled_params in self.compiled_parameters:
|
|
|
|
param = []
|
|
|
|
for key in self.compiled.positiontup:
|
|
|
|
if key in processors:
|
|
|
|
param.append(processors[key](compiled_params[key]))
|
|
|
|
else:
|
|
|
|
param.append(compiled_params[key])
|
|
|
|
parameters.append(dialect.execute_sequence_format(param))
|
|
|
|
else:
|
|
|
|
encode = not dialect.supports_unicode_statements
|
|
|
|
for compiled_params in self.compiled_parameters:
|
2016-02-22 11:49:42 +00:00
|
|
|
|
2016-02-06 09:38:49 +00:00
|
|
|
if encode:
|
2016-02-22 11:49:42 +00:00
|
|
|
param = dict(
|
|
|
|
(
|
|
|
|
dialect._encoder(key)[0],
|
|
|
|
processors[key](compiled_params[key])
|
|
|
|
if key in processors
|
|
|
|
else compiled_params[key]
|
|
|
|
)
|
|
|
|
for key in compiled_params
|
|
|
|
)
|
2016-02-06 09:38:49 +00:00
|
|
|
else:
|
2016-02-22 11:49:42 +00:00
|
|
|
param = dict(
|
|
|
|
(
|
|
|
|
key,
|
|
|
|
processors[key](compiled_params[key])
|
|
|
|
if key in processors
|
|
|
|
else compiled_params[key]
|
|
|
|
)
|
|
|
|
for key in compiled_params
|
|
|
|
)
|
|
|
|
|
2016-02-06 09:38:49 +00:00
|
|
|
parameters.append(param)
|
|
|
|
self.parameters = dialect.execute_sequence_format(parameters)
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _init_statement(cls, dialect, connection, dbapi_connection,
|
|
|
|
statement, parameters):
|
|
|
|
"""Initialize execution context for a string SQL statement."""
|
|
|
|
|
|
|
|
self = cls.__new__(cls)
|
|
|
|
self.root_connection = connection
|
|
|
|
self._dbapi_connection = dbapi_connection
|
2016-02-22 11:49:42 +00:00
|
|
|
self.dialect = connection.dialect
|
|
|
|
self.is_text = True
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
# plain text statement
|
|
|
|
self.execution_options = connection._execution_options
|
|
|
|
|
|
|
|
if not parameters:
|
|
|
|
if self.dialect.positional:
|
|
|
|
self.parameters = [dialect.execute_sequence_format()]
|
|
|
|
else:
|
|
|
|
self.parameters = [{}]
|
|
|
|
elif isinstance(parameters[0], dialect.execute_sequence_format):
|
|
|
|
self.parameters = parameters
|
|
|
|
elif isinstance(parameters[0], dict):
|
|
|
|
if dialect.supports_unicode_statements:
|
|
|
|
self.parameters = parameters
|
|
|
|
else:
|
|
|
|
self.parameters = [
|
|
|
|
dict((dialect._encoder(k)[0], d[k]) for k in d)
|
|
|
|
for d in parameters
|
|
|
|
] or [{}]
|
|
|
|
else:
|
|
|
|
self.parameters = [dialect.execute_sequence_format(p)
|
|
|
|
for p in parameters]
|
|
|
|
|
|
|
|
self.executemany = len(parameters) > 1
|
|
|
|
|
|
|
|
if not dialect.supports_unicode_statements and \
|
|
|
|
isinstance(statement, util.text_type):
|
|
|
|
self.unicode_statement = statement
|
|
|
|
self.statement = dialect._encoder(statement)[0]
|
|
|
|
else:
|
|
|
|
self.statement = self.unicode_statement = statement
|
|
|
|
|
|
|
|
self.cursor = self.create_cursor()
|
|
|
|
return self
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _init_default(cls, dialect, connection, dbapi_connection):
|
|
|
|
"""Initialize execution context for a ColumnDefault construct."""
|
|
|
|
|
|
|
|
self = cls.__new__(cls)
|
|
|
|
self.root_connection = connection
|
|
|
|
self._dbapi_connection = dbapi_connection
|
2016-02-22 11:49:42 +00:00
|
|
|
self.dialect = connection.dialect
|
2016-02-06 09:38:49 +00:00
|
|
|
self.execution_options = connection._execution_options
|
|
|
|
self.cursor = self.create_cursor()
|
|
|
|
return self
|
|
|
|
|
|
|
|
@util.memoized_property
|
2016-02-22 11:49:42 +00:00
|
|
|
def engine(self):
|
|
|
|
return self.root_connection.engine
|
|
|
|
|
|
|
|
@util.memoized_property
|
|
|
|
def postfetch_cols(self):
|
|
|
|
return self.compiled.postfetch
|
|
|
|
|
|
|
|
@util.memoized_property
|
|
|
|
def prefetch_cols(self):
|
|
|
|
return self.compiled.prefetch
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
@util.memoized_property
|
2016-02-22 11:49:42 +00:00
|
|
|
def returning_cols(self):
|
|
|
|
self.compiled.returning
|
|
|
|
|
|
|
|
@util.memoized_property
|
|
|
|
def no_parameters(self):
|
|
|
|
return self.execution_options.get("no_parameters", False)
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
@util.memoized_property
|
|
|
|
def should_autocommit(self):
|
|
|
|
autocommit = self.execution_options.get('autocommit',
|
|
|
|
not self.compiled and
|
|
|
|
self.statement and
|
|
|
|
expression.PARSE_AUTOCOMMIT
|
|
|
|
or False)
|
|
|
|
|
|
|
|
if autocommit is expression.PARSE_AUTOCOMMIT:
|
|
|
|
return self.should_autocommit_text(self.unicode_statement)
|
|
|
|
else:
|
|
|
|
return autocommit
|
|
|
|
|
|
|
|
def _execute_scalar(self, stmt, type_):
|
|
|
|
"""Execute a string statement on the current cursor, returning a
|
|
|
|
scalar result.
|
|
|
|
|
|
|
|
Used to fire off sequences, default phrases, and "select lastrowid"
|
|
|
|
types of statements individually or in the context of a parent INSERT
|
|
|
|
or UPDATE statement.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
conn = self.root_connection
|
|
|
|
if isinstance(stmt, util.text_type) and \
|
|
|
|
not self.dialect.supports_unicode_statements:
|
|
|
|
stmt = self.dialect._encoder(stmt)[0]
|
|
|
|
|
|
|
|
if self.dialect.positional:
|
|
|
|
default_params = self.dialect.execute_sequence_format()
|
|
|
|
else:
|
|
|
|
default_params = {}
|
|
|
|
|
|
|
|
conn._cursor_execute(self.cursor, stmt, default_params, context=self)
|
|
|
|
r = self.cursor.fetchone()[0]
|
|
|
|
if type_ is not None:
|
|
|
|
# apply type post processors to the result
|
|
|
|
proc = type_._cached_result_processor(
|
|
|
|
self.dialect,
|
|
|
|
self.cursor.description[0][1]
|
|
|
|
)
|
|
|
|
if proc:
|
|
|
|
return proc(r)
|
|
|
|
return r
|
|
|
|
|
|
|
|
@property
|
|
|
|
def connection(self):
|
|
|
|
return self.root_connection._branch()
|
|
|
|
|
|
|
|
def should_autocommit_text(self, statement):
|
|
|
|
return AUTOCOMMIT_REGEXP.match(statement)
|
|
|
|
|
|
|
|
def create_cursor(self):
|
|
|
|
return self._dbapi_connection.cursor()
|
|
|
|
|
|
|
|
def pre_exec(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def post_exec(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def get_result_processor(self, type_, colname, coltype):
|
|
|
|
"""Return a 'result processor' for a given type as present in
|
|
|
|
cursor.description.
|
|
|
|
|
|
|
|
This has a default implementation that dialects can override
|
|
|
|
for context-sensitive result type handling.
|
|
|
|
|
|
|
|
"""
|
|
|
|
return type_._cached_result_processor(self.dialect, coltype)
|
|
|
|
|
|
|
|
def get_lastrowid(self):
|
|
|
|
"""return self.cursor.lastrowid, or equivalent, after an INSERT.
|
|
|
|
|
|
|
|
This may involve calling special cursor functions,
|
|
|
|
issuing a new SELECT on the cursor (or a new one),
|
|
|
|
or returning a stored value that was
|
|
|
|
calculated within post_exec().
|
|
|
|
|
|
|
|
This function will only be called for dialects
|
|
|
|
which support "implicit" primary key generation,
|
|
|
|
keep preexecute_autoincrement_sequences set to False,
|
|
|
|
and when no explicit id value was bound to the
|
|
|
|
statement.
|
|
|
|
|
|
|
|
The function is called once, directly after
|
|
|
|
post_exec() and before the transaction is committed
|
|
|
|
or ResultProxy is generated. If the post_exec()
|
|
|
|
method assigns a value to `self._lastrowid`, the
|
|
|
|
value is used in place of calling get_lastrowid().
|
|
|
|
|
|
|
|
Note that this method is *not* equivalent to the
|
|
|
|
``lastrowid`` method on ``ResultProxy``, which is a
|
|
|
|
direct proxy to the DBAPI ``lastrowid`` accessor
|
|
|
|
in all cases.
|
|
|
|
|
|
|
|
"""
|
|
|
|
return self.cursor.lastrowid
|
|
|
|
|
|
|
|
def handle_dbapi_exception(self, e):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def get_result_proxy(self):
|
|
|
|
return result.ResultProxy(self)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def rowcount(self):
|
|
|
|
return self.cursor.rowcount
|
|
|
|
|
|
|
|
def supports_sane_rowcount(self):
|
|
|
|
return self.dialect.supports_sane_rowcount
|
|
|
|
|
|
|
|
def supports_sane_multi_rowcount(self):
|
|
|
|
return self.dialect.supports_sane_multi_rowcount
|
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
def _setup_crud_result_proxy(self):
|
|
|
|
if self.isinsert and \
|
|
|
|
not self.executemany:
|
|
|
|
if not self._is_implicit_returning and \
|
|
|
|
not self.compiled.inline and \
|
|
|
|
self.dialect.postfetch_lastrowid:
|
|
|
|
|
|
|
|
self._setup_ins_pk_from_lastrowid()
|
|
|
|
|
|
|
|
elif not self._is_implicit_returning:
|
|
|
|
self._setup_ins_pk_from_empty()
|
|
|
|
|
|
|
|
result = self.get_result_proxy()
|
|
|
|
|
|
|
|
if self.isinsert:
|
|
|
|
if self._is_implicit_returning:
|
|
|
|
row = result.fetchone()
|
|
|
|
self.returned_defaults = row
|
|
|
|
self._setup_ins_pk_from_implicit_returning(row)
|
|
|
|
result._soft_close(_autoclose_connection=False)
|
|
|
|
result._metadata = None
|
|
|
|
elif not self._is_explicit_returning:
|
|
|
|
result._soft_close(_autoclose_connection=False)
|
|
|
|
result._metadata = None
|
|
|
|
elif self.isupdate and self._is_implicit_returning:
|
|
|
|
row = result.fetchone()
|
|
|
|
self.returned_defaults = row
|
|
|
|
result._soft_close(_autoclose_connection=False)
|
|
|
|
result._metadata = None
|
|
|
|
|
|
|
|
elif result._metadata is None:
|
|
|
|
# no results, get rowcount
|
|
|
|
# (which requires open cursor on some drivers
|
|
|
|
# such as kintersbasdb, mxodbc)
|
|
|
|
result.rowcount
|
|
|
|
result._soft_close(_autoclose_connection=False)
|
|
|
|
return result
|
|
|
|
|
|
|
|
def _setup_ins_pk_from_lastrowid(self):
|
|
|
|
key_getter = self.compiled._key_getters_for_crud_column[2]
|
|
|
|
table = self.compiled.statement.table
|
|
|
|
compiled_params = self.compiled_parameters[0]
|
2016-02-06 09:38:49 +00:00
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
lastrowid = self.get_lastrowid()
|
|
|
|
if lastrowid is not None:
|
2016-02-06 09:38:49 +00:00
|
|
|
autoinc_col = table._autoincrement_column
|
|
|
|
if autoinc_col is not None:
|
|
|
|
# apply type post processors to the lastrowid
|
|
|
|
proc = autoinc_col.type._cached_result_processor(
|
|
|
|
self.dialect, None)
|
|
|
|
if proc is not None:
|
|
|
|
lastrowid = proc(lastrowid)
|
|
|
|
self.inserted_primary_key = [
|
2016-02-22 11:49:42 +00:00
|
|
|
lastrowid if c is autoinc_col else
|
|
|
|
compiled_params.get(key_getter(c), None)
|
|
|
|
for c in table.primary_key
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
# don't have a usable lastrowid, so
|
|
|
|
# do the same as _setup_ins_pk_from_empty
|
|
|
|
self.inserted_primary_key = [
|
|
|
|
compiled_params.get(key_getter(c), None)
|
|
|
|
for c in table.primary_key
|
2016-02-06 09:38:49 +00:00
|
|
|
]
|
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
def _setup_ins_pk_from_empty(self):
|
|
|
|
key_getter = self.compiled._key_getters_for_crud_column[2]
|
2016-02-06 09:38:49 +00:00
|
|
|
table = self.compiled.statement.table
|
2016-02-22 11:49:42 +00:00
|
|
|
compiled_params = self.compiled_parameters[0]
|
|
|
|
self.inserted_primary_key = [
|
|
|
|
compiled_params.get(key_getter(c), None)
|
|
|
|
for c in table.primary_key
|
|
|
|
]
|
2016-02-06 09:38:49 +00:00
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
def _setup_ins_pk_from_implicit_returning(self, row):
|
|
|
|
key_getter = self.compiled._key_getters_for_crud_column[2]
|
|
|
|
table = self.compiled.statement.table
|
|
|
|
compiled_params = self.compiled_parameters[0]
|
2016-02-06 09:38:49 +00:00
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
self.inserted_primary_key = [
|
|
|
|
row[col] if value is None else value
|
|
|
|
for col, value in [
|
|
|
|
(col, compiled_params.get(key_getter(col), None))
|
|
|
|
for col in table.primary_key
|
|
|
|
]
|
|
|
|
]
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
def lastrow_has_defaults(self):
|
|
|
|
return (self.isinsert or self.isupdate) and \
|
2016-02-22 11:49:42 +00:00
|
|
|
bool(self.compiled.postfetch)
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
def set_input_sizes(self, translate=None, exclude_types=None):
|
|
|
|
"""Given a cursor and ClauseParameters, call the appropriate
|
|
|
|
style of ``setinputsizes()`` on the cursor, using DB-API types
|
|
|
|
from the bind parameter's ``TypeEngine`` objects.
|
|
|
|
|
|
|
|
This method only called by those dialects which require it,
|
|
|
|
currently cx_oracle.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
if not hasattr(self.compiled, 'bind_names'):
|
|
|
|
return
|
|
|
|
|
|
|
|
types = dict(
|
|
|
|
(self.compiled.bind_names[bindparam], bindparam.type)
|
|
|
|
for bindparam in self.compiled.bind_names)
|
|
|
|
|
|
|
|
if self.dialect.positional:
|
|
|
|
inputsizes = []
|
|
|
|
for key in self.compiled.positiontup:
|
|
|
|
typeengine = types[key]
|
|
|
|
dbtype = typeengine.dialect_impl(self.dialect).\
|
|
|
|
get_dbapi_type(self.dialect.dbapi)
|
|
|
|
if dbtype is not None and \
|
|
|
|
(not exclude_types or dbtype not in exclude_types):
|
|
|
|
inputsizes.append(dbtype)
|
|
|
|
try:
|
|
|
|
self.cursor.setinputsizes(*inputsizes)
|
|
|
|
except Exception as e:
|
|
|
|
self.root_connection._handle_dbapi_exception(
|
|
|
|
e, None, None, None, self)
|
|
|
|
else:
|
|
|
|
inputsizes = {}
|
|
|
|
for key in self.compiled.bind_names.values():
|
|
|
|
typeengine = types[key]
|
|
|
|
dbtype = typeengine.dialect_impl(self.dialect).\
|
|
|
|
get_dbapi_type(self.dialect.dbapi)
|
|
|
|
if dbtype is not None and \
|
|
|
|
(not exclude_types or dbtype not in exclude_types):
|
|
|
|
if translate:
|
|
|
|
key = translate.get(key, key)
|
|
|
|
if not self.dialect.supports_unicode_binds:
|
|
|
|
key = self.dialect._encoder(key)[0]
|
|
|
|
inputsizes[key] = dbtype
|
|
|
|
try:
|
|
|
|
self.cursor.setinputsizes(**inputsizes)
|
|
|
|
except Exception as e:
|
|
|
|
self.root_connection._handle_dbapi_exception(
|
|
|
|
e, None, None, None, self)
|
|
|
|
|
|
|
|
def _exec_default(self, default, type_):
|
|
|
|
if default.is_sequence:
|
|
|
|
return self.fire_sequence(default, type_)
|
|
|
|
elif default.is_callable:
|
|
|
|
return default.arg(self)
|
|
|
|
elif default.is_clause_element:
|
|
|
|
# TODO: expensive branching here should be
|
|
|
|
# pulled into _exec_scalar()
|
|
|
|
conn = self.connection
|
|
|
|
c = expression.select([default.arg]).compile(bind=conn)
|
|
|
|
return conn._execute_compiled(c, (), {}).scalar()
|
|
|
|
else:
|
|
|
|
return default.arg
|
|
|
|
|
|
|
|
def get_insert_default(self, column):
|
|
|
|
if column.default is None:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return self._exec_default(column.default, column.type)
|
|
|
|
|
|
|
|
def get_update_default(self, column):
|
|
|
|
if column.onupdate is None:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return self._exec_default(column.onupdate, column.type)
|
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
def _process_executemany_defaults(self):
|
2016-02-06 09:38:49 +00:00
|
|
|
key_getter = self.compiled._key_getters_for_crud_column[2]
|
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
prefetch = self.compiled.prefetch
|
|
|
|
scalar_defaults = {}
|
|
|
|
|
|
|
|
# pre-determine scalar Python-side defaults
|
|
|
|
# to avoid many calls of get_insert_default()/
|
|
|
|
# get_update_default()
|
|
|
|
for c in prefetch:
|
|
|
|
if self.isinsert and c.default and c.default.is_scalar:
|
|
|
|
scalar_defaults[c] = c.default.arg
|
|
|
|
elif self.isupdate and c.onupdate and c.onupdate.is_scalar:
|
|
|
|
scalar_defaults[c] = c.onupdate.arg
|
|
|
|
|
|
|
|
for param in self.compiled_parameters:
|
|
|
|
self.current_parameters = param
|
|
|
|
for c in prefetch:
|
|
|
|
if c in scalar_defaults:
|
|
|
|
val = scalar_defaults[c]
|
|
|
|
elif self.isinsert:
|
2016-02-06 09:38:49 +00:00
|
|
|
val = self.get_insert_default(c)
|
|
|
|
else:
|
|
|
|
val = self.get_update_default(c)
|
|
|
|
if val is not None:
|
2016-02-22 11:49:42 +00:00
|
|
|
param[key_getter(c)] = val
|
|
|
|
del self.current_parameters
|
|
|
|
|
|
|
|
def _process_executesingle_defaults(self):
|
|
|
|
key_getter = self.compiled._key_getters_for_crud_column[2]
|
|
|
|
prefetch = self.compiled.prefetch
|
|
|
|
self.current_parameters = compiled_parameters = \
|
|
|
|
self.compiled_parameters[0]
|
2016-02-06 09:38:49 +00:00
|
|
|
|
2016-02-22 11:49:42 +00:00
|
|
|
for c in prefetch:
|
2016-02-06 09:38:49 +00:00
|
|
|
if self.isinsert:
|
2016-02-22 11:49:42 +00:00
|
|
|
if c.default and \
|
|
|
|
not c.default.is_sequence and c.default.is_scalar:
|
|
|
|
val = c.default.arg
|
|
|
|
else:
|
|
|
|
val = self.get_insert_default(c)
|
|
|
|
else:
|
|
|
|
val = self.get_update_default(c)
|
|
|
|
|
|
|
|
if val is not None:
|
|
|
|
compiled_parameters[key_getter(c)] = val
|
|
|
|
del self.current_parameters
|
2016-02-06 09:38:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
DefaultDialect.execution_ctx_cls = DefaultExecutionContext
|