Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/engine/default.py : 47%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# engine/default.py
2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
8"""Default implementations of per-dialect sqlalchemy.engine classes.
10These are semi-private implementation classes which are only of importance
11to database dialect authors; dialects will usually use the classes here
12as the base class for their own corresponding classes.
14"""
16import codecs
17import random
18import re
19import weakref
21from . import interfaces
22from . import reflection
23from . import result
24from .. import event
25from .. import exc
26from .. import pool
27from .. import processors
28from .. import types as sqltypes
29from .. import util
30from ..sql import compiler
31from ..sql import expression
32from ..sql import schema
33from ..sql.elements import quoted_name
36AUTOCOMMIT_REGEXP = re.compile(
37 r"\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER)", re.I | re.UNICODE
38)
40# When we're handed literal SQL, ensure it's a SELECT query
41SERVER_SIDE_CURSOR_RE = re.compile(r"\s*SELECT", re.I | re.UNICODE)
44class DefaultDialect(interfaces.Dialect):
45 """Default implementation of Dialect"""
47 statement_compiler = compiler.SQLCompiler
48 ddl_compiler = compiler.DDLCompiler
49 type_compiler = compiler.GenericTypeCompiler
50 preparer = compiler.IdentifierPreparer
51 supports_alter = True
52 supports_comments = False
53 inline_comments = False
55 # the first value we'd get for an autoincrement
56 # column.
57 default_sequence_base = 1
59 # most DBAPIs happy with this for execute().
60 # not cx_oracle.
61 execute_sequence_format = tuple
63 supports_views = True
64 supports_sequences = False
65 sequences_optional = False
66 preexecute_autoincrement_sequences = False
67 postfetch_lastrowid = True
68 implicit_returning = False
70 supports_right_nested_joins = True
71 cte_follows_insert = False
73 supports_native_enum = False
74 supports_native_boolean = False
75 non_native_boolean_check_constraint = True
77 supports_simple_order_by_label = True
79 tuple_in_values = False
81 engine_config_types = util.immutabledict(
82 [
83 ("convert_unicode", util.bool_or_str("force")),
84 ("pool_timeout", util.asint),
85 ("echo", util.bool_or_str("debug")),
86 ("echo_pool", util.bool_or_str("debug")),
87 ("pool_recycle", util.asint),
88 ("pool_size", util.asint),
89 ("max_overflow", util.asint),
90 ("pool_threadlocal", util.asbool),
91 ]
92 )
94 # if the NUMERIC type
95 # returns decimal.Decimal.
96 # *not* the FLOAT type however.
97 supports_native_decimal = False
99 if util.py3k:
100 supports_unicode_statements = True
101 supports_unicode_binds = True
102 returns_unicode_strings = True
103 description_encoding = None
104 else:
105 supports_unicode_statements = False
106 supports_unicode_binds = False
107 returns_unicode_strings = False
108 description_encoding = "use_encoding"
110 name = "default"
112 # length at which to truncate
113 # any identifier.
114 max_identifier_length = 9999
115 _user_defined_max_identifier_length = None
117 # length at which to truncate
118 # the name of an index.
119 # Usually None to indicate
120 # 'use max_identifier_length'.
121 # thanks to MySQL, sigh
122 max_index_name_length = None
124 supports_sane_rowcount = True
125 supports_sane_multi_rowcount = True
126 colspecs = {}
127 default_paramstyle = "named"
128 supports_default_values = False
129 supports_empty_insert = True
130 supports_multivalues_insert = False
132 supports_is_distinct_from = True
134 supports_server_side_cursors = False
136 # extra record-level locking features (#4860)
137 supports_for_update_of = False
139 server_version_info = None
141 construct_arguments = None
142 """Optional set of argument specifiers for various SQLAlchemy
143 constructs, typically schema items.
145 To implement, establish as a series of tuples, as in::
147 construct_arguments = [
148 (schema.Index, {
149 "using": False,
150 "where": None,
151 "ops": None
152 })
153 ]
155 If the above construct is established on the PostgreSQL dialect,
156 the :class:`.Index` construct will now accept the keyword arguments
157 ``postgresql_using``, ``postgresql_where``, nad ``postgresql_ops``.
158 Any other argument specified to the constructor of :class:`.Index`
159 which is prefixed with ``postgresql_`` will raise :class:`.ArgumentError`.
161 A dialect which does not include a ``construct_arguments`` member will
162 not participate in the argument validation system. For such a dialect,
163 any argument name is accepted by all participating constructs, within
164 the namespace of arguments prefixed with that dialect name. The rationale
165 here is so that third-party dialects that haven't yet implemented this
166 feature continue to function in the old way.
168 .. versionadded:: 0.9.2
170 .. seealso::
172 :class:`.DialectKWArgs` - implementing base class which consumes
173 :attr:`.DefaultDialect.construct_arguments`
176 """
178 # indicates symbol names are
179 # UPPERCASEd if they are case insensitive
180 # within the database.
181 # if this is True, the methods normalize_name()
182 # and denormalize_name() must be provided.
183 requires_name_normalize = False
185 reflection_options = ()
187 dbapi_exception_translation_map = util.immutabledict()
188 """mapping used in the extremely unusual case that a DBAPI's
189 published exceptions don't actually have the __name__ that they
190 are linked towards.
192 .. versionadded:: 1.0.5
194 """
196 @util.deprecated_params(
197 convert_unicode=(
198 "1.3",
199 "The :paramref:`_sa.create_engine.convert_unicode` parameter "
200 "and corresponding dialect-level parameters are deprecated, "
201 "and will be removed in a future release. Modern DBAPIs support "
202 "Python Unicode natively and this parameter is unnecessary.",
203 )
204 )
205 def __init__(
206 self,
207 convert_unicode=False,
208 encoding="utf-8",
209 paramstyle=None,
210 dbapi=None,
211 implicit_returning=None,
212 supports_right_nested_joins=None,
213 case_sensitive=True,
214 supports_native_boolean=None,
215 empty_in_strategy="static",
216 max_identifier_length=None,
217 label_length=None,
218 **kwargs
219 ):
221 if not getattr(self, "ported_sqla_06", True):
222 util.warn(
223 "The %s dialect is not yet ported to the 0.6 format"
224 % self.name
225 )
227 self.convert_unicode = convert_unicode
228 self.encoding = encoding
229 self.positional = False
230 self._ischema = None
231 self.dbapi = dbapi
232 if paramstyle is not None:
233 self.paramstyle = paramstyle
234 elif self.dbapi is not None:
235 self.paramstyle = self.dbapi.paramstyle
236 else:
237 self.paramstyle = self.default_paramstyle
238 if implicit_returning is not None:
239 self.implicit_returning = implicit_returning
240 self.positional = self.paramstyle in ("qmark", "format", "numeric")
241 self.identifier_preparer = self.preparer(self)
242 self.type_compiler = self.type_compiler(self)
243 if supports_right_nested_joins is not None:
244 self.supports_right_nested_joins = supports_right_nested_joins
245 if supports_native_boolean is not None:
246 self.supports_native_boolean = supports_native_boolean
247 self.case_sensitive = case_sensitive
249 self.empty_in_strategy = empty_in_strategy
250 if empty_in_strategy == "static":
251 self._use_static_in = True
252 elif empty_in_strategy in ("dynamic", "dynamic_warn"):
253 self._use_static_in = False
254 self._warn_on_empty_in = empty_in_strategy == "dynamic_warn"
255 else:
256 raise exc.ArgumentError(
257 "empty_in_strategy may be 'static', "
258 "'dynamic', or 'dynamic_warn'"
259 )
261 self._user_defined_max_identifier_length = max_identifier_length
262 if self._user_defined_max_identifier_length:
263 self.max_identifier_length = (
264 self._user_defined_max_identifier_length
265 )
266 self.label_length = label_length
268 if self.description_encoding == "use_encoding":
269 self._description_decoder = (
270 processors.to_unicode_processor_factory
271 )(encoding)
272 elif self.description_encoding is not None:
273 self._description_decoder = (
274 processors.to_unicode_processor_factory
275 )(self.description_encoding)
276 self._encoder = codecs.getencoder(self.encoding)
277 self._decoder = processors.to_unicode_processor_factory(self.encoding)
279 @util.memoized_property
280 def _type_memos(self):
281 return weakref.WeakKeyDictionary()
283 @property
284 def dialect_description(self):
285 return self.name + "+" + self.driver
287 @property
288 def supports_sane_rowcount_returning(self):
289 """True if this dialect supports sane rowcount even if RETURNING is
290 in use.
292 For dialects that don't support RETURNING, this is synomous
293 with supports_sane_rowcount.
295 """
296 return self.supports_sane_rowcount
298 @classmethod
299 def get_pool_class(cls, url):
300 return getattr(cls, "poolclass", pool.QueuePool)
302 @classmethod
303 def load_provisioning(cls):
304 package = ".".join(cls.__module__.split(".")[0:-1])
305 try:
306 __import__(package + ".provision")
307 except ImportError:
308 pass
310 def initialize(self, connection):
311 try:
312 self.server_version_info = self._get_server_version_info(
313 connection
314 )
315 except NotImplementedError:
316 self.server_version_info = None
317 try:
318 self.default_schema_name = self._get_default_schema_name(
319 connection
320 )
321 except NotImplementedError:
322 self.default_schema_name = None
324 try:
325 self.default_isolation_level = self.get_isolation_level(
326 connection.connection
327 )
328 except NotImplementedError:
329 self.default_isolation_level = None
331 self.returns_unicode_strings = self._check_unicode_returns(connection)
333 if (
334 self.description_encoding is not None
335 and self._check_unicode_description(connection)
336 ):
337 self._description_decoder = self.description_encoding = None
339 if not self._user_defined_max_identifier_length:
340 max_ident_length = self._check_max_identifier_length(connection)
341 if max_ident_length:
342 self.max_identifier_length = max_ident_length
344 if (
345 self.label_length
346 and self.label_length > self.max_identifier_length
347 ):
348 raise exc.ArgumentError(
349 "Label length of %d is greater than this dialect's"
350 " maximum identifier length of %d"
351 % (self.label_length, self.max_identifier_length)
352 )
354 def on_connect(self):
355 # inherits the docstring from interfaces.Dialect.on_connect
356 return None
358 def _check_max_identifier_length(self, connection):
359 """Perform a connection / server version specific check to determine
360 the max_identifier_length.
362 If the dialect's class level max_identifier_length should be used,
363 can return None.
365 .. versionadded:: 1.3.9
367 """
368 return None
370 def _check_unicode_returns(self, connection, additional_tests=None):
371 if util.py2k and not self.supports_unicode_statements:
372 cast_to = util.binary_type
373 else:
374 cast_to = util.text_type
376 if self.positional:
377 parameters = self.execute_sequence_format()
378 else:
379 parameters = {}
381 def check_unicode(test):
382 statement = cast_to(
383 expression.select([test]).compile(dialect=self)
384 )
385 try:
386 cursor = connection.connection.cursor()
387 connection._cursor_execute(cursor, statement, parameters)
388 row = cursor.fetchone()
389 cursor.close()
390 except exc.DBAPIError as de:
391 # note that _cursor_execute() will have closed the cursor
392 # if an exception is thrown.
393 util.warn(
394 "Exception attempting to "
395 "detect unicode returns: %r" % de
396 )
397 return False
398 else:
399 return isinstance(row[0], util.text_type)
401 tests = [
402 # detect plain VARCHAR
403 expression.cast(
404 expression.literal_column("'test plain returns'"),
405 sqltypes.VARCHAR(60),
406 ),
407 # detect if there's an NVARCHAR type with different behavior
408 # available
409 expression.cast(
410 expression.literal_column("'test unicode returns'"),
411 sqltypes.Unicode(60),
412 ),
413 ]
415 if additional_tests:
416 tests += additional_tests
418 results = {check_unicode(test) for test in tests}
420 if results.issuperset([True, False]):
421 return "conditional"
422 else:
423 return results == {True}
425 def _check_unicode_description(self, connection):
426 # all DBAPIs on Py2K return cursor.description as encoded,
427 # until pypy2.1beta2 with sqlite, so let's just check it -
428 # it's likely others will start doing this too in Py2k.
430 if util.py2k and not self.supports_unicode_statements:
431 cast_to = util.binary_type
432 else:
433 cast_to = util.text_type
435 cursor = connection.connection.cursor()
436 try:
437 cursor.execute(
438 cast_to(
439 expression.select(
440 [expression.literal_column("'x'").label("some_label")]
441 ).compile(dialect=self)
442 )
443 )
444 return isinstance(cursor.description[0][0], util.text_type)
445 finally:
446 cursor.close()
448 def type_descriptor(self, typeobj):
449 """Provide a database-specific :class:`.TypeEngine` object, given
450 the generic object which comes from the types module.
452 This method looks for a dictionary called
453 ``colspecs`` as a class or instance-level variable,
454 and passes on to :func:`_types.adapt_type`.
456 """
457 return sqltypes.adapt_type(typeobj, self.colspecs)
459 def reflecttable(
460 self,
461 connection,
462 table,
463 include_columns,
464 exclude_columns,
465 resolve_fks,
466 **opts
467 ):
468 insp = reflection.Inspector.from_engine(connection)
469 return insp.reflecttable(
470 table, include_columns, exclude_columns, resolve_fks, **opts
471 )
473 def get_pk_constraint(self, conn, table_name, schema=None, **kw):
474 """Compatibility method, adapts the result of get_primary_keys()
475 for those dialects which don't implement get_pk_constraint().
477 """
478 return {
479 "constrained_columns": self.get_primary_keys(
480 conn, table_name, schema=schema, **kw
481 )
482 }
484 def validate_identifier(self, ident):
485 if len(ident) > self.max_identifier_length:
486 raise exc.IdentifierError(
487 "Identifier '%s' exceeds maximum length of %d characters"
488 % (ident, self.max_identifier_length)
489 )
491 def connect(self, *cargs, **cparams):
492 # inherits the docstring from interfaces.Dialect.connect
493 return self.dbapi.connect(*cargs, **cparams)
495 def create_connect_args(self, url):
496 # inherits the docstring from interfaces.Dialect.create_connect_args
497 opts = url.translate_connect_args()
498 opts.update(url.query)
499 return [[], opts]
501 def set_engine_execution_options(self, engine, opts):
502 if "isolation_level" in opts:
503 isolation_level = opts["isolation_level"]
505 @event.listens_for(engine, "engine_connect")
506 def set_isolation(connection, branch):
507 if not branch:
508 self._set_connection_isolation(connection, isolation_level)
510 if "schema_translate_map" in opts:
511 getter = schema._schema_getter(opts["schema_translate_map"])
512 engine.schema_for_object = getter
514 @event.listens_for(engine, "engine_connect")
515 def set_schema_translate_map(connection, branch):
516 connection.schema_for_object = getter
518 def set_connection_execution_options(self, connection, opts):
519 if "isolation_level" in opts:
520 self._set_connection_isolation(connection, opts["isolation_level"])
522 if "schema_translate_map" in opts:
523 getter = schema._schema_getter(opts["schema_translate_map"])
524 connection.schema_for_object = getter
526 def _set_connection_isolation(self, connection, level):
527 if connection.in_transaction():
528 util.warn(
529 "Connection is already established with a Transaction; "
530 "setting isolation_level may implicitly rollback or commit "
531 "the existing transaction, or have no effect until "
532 "next transaction"
533 )
534 self.set_isolation_level(connection.connection, level)
535 connection.connection._connection_record.finalize_callback.append(
536 self.reset_isolation_level
537 )
539 def do_begin(self, dbapi_connection):
540 pass
542 def do_rollback(self, dbapi_connection):
543 dbapi_connection.rollback()
545 def do_commit(self, dbapi_connection):
546 dbapi_connection.commit()
548 def do_close(self, dbapi_connection):
549 dbapi_connection.close()
551 @util.memoized_property
552 def _dialect_specific_select_one(self):
553 return str(expression.select([1]).compile(dialect=self))
555 def do_ping(self, dbapi_connection):
556 cursor = None
557 try:
558 cursor = dbapi_connection.cursor()
559 try:
560 cursor.execute(self._dialect_specific_select_one)
561 finally:
562 cursor.close()
563 except self.dbapi.Error as err:
564 if self.is_disconnect(err, dbapi_connection, cursor):
565 return False
566 else:
567 raise
568 else:
569 return True
571 def create_xid(self):
572 """Create a random two-phase transaction ID.
574 This id will be passed to do_begin_twophase(), do_rollback_twophase(),
575 do_commit_twophase(). Its format is unspecified.
576 """
578 return "_sa_%032x" % random.randint(0, 2 ** 128)
580 def do_savepoint(self, connection, name):
581 connection.execute(expression.SavepointClause(name))
583 def do_rollback_to_savepoint(self, connection, name):
584 connection.execute(expression.RollbackToSavepointClause(name))
586 def do_release_savepoint(self, connection, name):
587 connection.execute(expression.ReleaseSavepointClause(name))
589 def do_executemany(self, cursor, statement, parameters, context=None):
590 cursor.executemany(statement, parameters)
592 def do_execute(self, cursor, statement, parameters, context=None):
593 cursor.execute(statement, parameters)
595 def do_execute_no_params(self, cursor, statement, context=None):
596 cursor.execute(statement)
598 def is_disconnect(self, e, connection, cursor):
599 return False
601 def reset_isolation_level(self, dbapi_conn):
602 # default_isolation_level is read from the first connection
603 # after the initial set of 'isolation_level', if any, so is
604 # the configured default of this dialect.
605 self.set_isolation_level(dbapi_conn, self.default_isolation_level)
607 def normalize_name(self, name):
608 if name is None:
609 return None
610 if util.py2k:
611 if isinstance(name, str):
612 name = name.decode(self.encoding)
614 name_lower = name.lower()
615 name_upper = name.upper()
617 if name_upper == name_lower:
618 # name has no upper/lower conversion, e.g. non-european characters.
619 # return unchanged
620 return name
621 elif name_upper == name and not (
622 self.identifier_preparer._requires_quotes
623 )(name_lower):
624 # name is all uppercase and doesn't require quoting; normalize
625 # to all lower case
626 return name_lower
627 elif name_lower == name:
628 # name is all lower case, which if denormalized means we need to
629 # force quoting on it
630 return quoted_name(name, quote=True)
631 else:
632 # name is mixed case, means it will be quoted in SQL when used
633 # later, no normalizes
634 return name
636 def denormalize_name(self, name):
637 if name is None:
638 return None
640 name_lower = name.lower()
641 name_upper = name.upper()
643 if name_upper == name_lower:
644 # name has no upper/lower conversion, e.g. non-european characters.
645 # return unchanged
646 return name
647 elif name_lower == name and not (
648 self.identifier_preparer._requires_quotes
649 )(name_lower):
650 name = name_upper
651 if util.py2k:
652 if not self.supports_unicode_binds:
653 name = name.encode(self.encoding)
654 else:
655 name = unicode(name) # noqa
656 return name
659class _RendersLiteral(object):
660 def literal_processor(self, dialect):
661 def process(value):
662 return "'%s'" % value
664 return process
667class _StrDateTime(_RendersLiteral, sqltypes.DateTime):
668 pass
671class _StrDate(_RendersLiteral, sqltypes.Date):
672 pass
675class _StrTime(_RendersLiteral, sqltypes.Time):
676 pass
679class StrCompileDialect(DefaultDialect):
681 statement_compiler = compiler.StrSQLCompiler
682 ddl_compiler = compiler.DDLCompiler
683 type_compiler = compiler.StrSQLTypeCompiler
684 preparer = compiler.IdentifierPreparer
686 supports_sequences = True
687 sequences_optional = True
688 preexecute_autoincrement_sequences = False
689 implicit_returning = False
691 supports_native_boolean = True
693 supports_simple_order_by_label = True
695 colspecs = {
696 sqltypes.DateTime: _StrDateTime,
697 sqltypes.Date: _StrDate,
698 sqltypes.Time: _StrTime,
699 }
702class DefaultExecutionContext(interfaces.ExecutionContext):
703 isinsert = False
704 isupdate = False
705 isdelete = False
706 is_crud = False
707 is_text = False
708 isddl = False
709 executemany = False
710 compiled = None
711 statement = None
712 result_column_struct = None
713 returned_defaults = None
714 _is_implicit_returning = False
715 _is_explicit_returning = False
717 # a hook for SQLite's translation of
718 # result column names
719 _translate_colname = None
721 _expanded_parameters = util.immutabledict()
723 @classmethod
724 def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl):
725 """Initialize execution context for a DDLElement construct."""
727 self = cls.__new__(cls)
728 self.root_connection = connection
729 self._dbapi_connection = dbapi_connection
730 self.dialect = connection.dialect
732 self.compiled = compiled = compiled_ddl
733 self.isddl = True
735 self.execution_options = compiled.execution_options
736 if connection._execution_options:
737 self.execution_options = dict(self.execution_options)
738 self.execution_options.update(connection._execution_options)
740 if not dialect.supports_unicode_statements:
741 self.unicode_statement = util.text_type(compiled)
742 self.statement = dialect._encoder(self.unicode_statement)[0]
743 else:
744 self.statement = self.unicode_statement = util.text_type(compiled)
746 self.cursor = self.create_cursor()
747 self.compiled_parameters = []
749 if dialect.positional:
750 self.parameters = [dialect.execute_sequence_format()]
751 else:
752 self.parameters = [{}]
754 return self
756 @classmethod
757 def _init_compiled(
758 cls, dialect, connection, dbapi_connection, compiled, parameters
759 ):
760 """Initialize execution context for a Compiled construct."""
762 self = cls.__new__(cls)
763 self.root_connection = connection
764 self._dbapi_connection = dbapi_connection
765 self.dialect = connection.dialect
767 self.compiled = compiled
769 # this should be caught in the engine before
770 # we get here
771 assert compiled.can_execute
773 self.execution_options = compiled.execution_options.union(
774 connection._execution_options
775 )
777 self.result_column_struct = (
778 compiled._result_columns,
779 compiled._ordered_columns,
780 compiled._textual_ordered_columns,
781 )
783 self.unicode_statement = util.text_type(compiled)
784 if not dialect.supports_unicode_statements:
785 self.statement = self.unicode_statement.encode(
786 self.dialect.encoding
787 )
788 else:
789 self.statement = self.unicode_statement
791 self.isinsert = compiled.isinsert
792 self.isupdate = compiled.isupdate
793 self.isdelete = compiled.isdelete
794 self.is_text = compiled.isplaintext
796 if not parameters:
797 self.compiled_parameters = [compiled.construct_params()]
798 else:
799 self.compiled_parameters = [
800 compiled.construct_params(m, _group_number=grp)
801 for grp, m in enumerate(parameters)
802 ]
804 self.executemany = len(parameters) > 1
806 self.cursor = self.create_cursor()
808 if self.isinsert or self.isupdate or self.isdelete:
809 self.is_crud = True
810 self._is_explicit_returning = bool(compiled.statement._returning)
811 self._is_implicit_returning = bool(
812 compiled.returning and not compiled.statement._returning
813 )
815 if self.compiled.insert_prefetch or self.compiled.update_prefetch:
816 if self.executemany:
817 self._process_executemany_defaults()
818 else:
819 self._process_executesingle_defaults()
821 processors = compiled._bind_processors
823 if compiled.contains_expanding_parameters:
824 # copy processors for this case as they will be mutated
825 processors = dict(processors)
826 positiontup = self._expand_in_parameters(compiled, processors)
827 elif compiled.positional:
828 positiontup = self.compiled.positiontup
830 # Convert the dictionary of bind parameter values
831 # into a dict or list to be sent to the DBAPI's
832 # execute() or executemany() method.
833 parameters = []
834 if compiled.positional:
835 for compiled_params in self.compiled_parameters:
836 param = []
837 for key in positiontup:
838 if key in processors:
839 param.append(processors[key](compiled_params[key]))
840 else:
841 param.append(compiled_params[key])
842 parameters.append(dialect.execute_sequence_format(param))
843 else:
844 encode = not dialect.supports_unicode_statements
845 for compiled_params in self.compiled_parameters:
847 if encode:
848 param = dict(
849 (
850 dialect._encoder(key)[0],
851 processors[key](compiled_params[key])
852 if key in processors
853 else compiled_params[key],
854 )
855 for key in compiled_params
856 )
857 else:
858 param = dict(
859 (
860 key,
861 processors[key](compiled_params[key])
862 if key in processors
863 else compiled_params[key],
864 )
865 for key in compiled_params
866 )
868 parameters.append(param)
870 self.parameters = dialect.execute_sequence_format(parameters)
872 return self
874 def _expand_in_parameters(self, compiled, processors):
875 """handle special 'expanding' parameters, IN tuples that are rendered
876 on a per-parameter basis for an otherwise fixed SQL statement string.
878 """
879 if self.executemany:
880 raise exc.InvalidRequestError(
881 "'expanding' parameters can't be used with " "executemany()"
882 )
884 if self.compiled.positional and self.compiled._numeric_binds:
885 # I'm not familiar with any DBAPI that uses 'numeric'
886 raise NotImplementedError(
887 "'expanding' bind parameters not supported with "
888 "'numeric' paramstyle at this time."
889 )
891 self._expanded_parameters = {}
893 compiled_params = self.compiled_parameters[0]
894 if compiled.positional:
895 positiontup = []
896 else:
897 positiontup = None
899 replacement_expressions = {}
900 to_update_sets = {}
902 for name in (
903 self.compiled.positiontup
904 if compiled.positional
905 else self.compiled.binds
906 ):
907 parameter = self.compiled.binds[name]
908 if parameter.expanding:
910 if name in replacement_expressions:
911 to_update = to_update_sets[name]
912 else:
913 # we are removing the parameter from compiled_params
914 # because it is a list value, which is not expected by
915 # TypeEngine objects that would otherwise be asked to
916 # process it. the single name is being replaced with
917 # individual numbered parameters for each value in the
918 # param.
919 values = compiled_params.pop(name)
921 if not values:
922 to_update = to_update_sets[name] = []
923 replacement_expressions[
924 name
925 ] = self.compiled.visit_empty_set_expr(
926 parameter._expanding_in_types
927 if parameter._expanding_in_types
928 else [parameter.type]
929 )
931 elif isinstance(values[0], (tuple, list)):
932 to_update = to_update_sets[name] = [
933 ("%s_%s_%s" % (name, i, j), value)
934 for i, tuple_element in enumerate(values, 1)
935 for j, value in enumerate(tuple_element, 1)
936 ]
937 replacement_expressions[name] = (
938 "VALUES " if self.dialect.tuple_in_values else ""
939 ) + ", ".join(
940 "(%s)"
941 % ", ".join(
942 self.compiled.bindtemplate
943 % {
944 "name": to_update[
945 i * len(tuple_element) + j
946 ][0]
947 }
948 for j, value in enumerate(tuple_element)
949 )
950 for i, tuple_element in enumerate(values)
951 )
952 else:
953 to_update = to_update_sets[name] = [
954 ("%s_%s" % (name, i), value)
955 for i, value in enumerate(values, 1)
956 ]
957 replacement_expressions[name] = ", ".join(
958 self.compiled.bindtemplate % {"name": key}
959 for key, value in to_update
960 )
962 compiled_params.update(to_update)
963 processors.update(
964 (key, processors[name])
965 for key, value in to_update
966 if name in processors
967 )
968 if compiled.positional:
969 positiontup.extend(name for name, value in to_update)
970 self._expanded_parameters[name] = [
971 expand_key for expand_key, value in to_update
972 ]
973 elif compiled.positional:
974 positiontup.append(name)
976 def process_expanding(m):
977 return replacement_expressions[m.group(1)]
979 self.statement = re.sub(
980 r"\[EXPANDING_(\S+)\]", process_expanding, self.statement
981 )
982 return positiontup
984 @classmethod
985 def _init_statement(
986 cls, dialect, connection, dbapi_connection, statement, parameters
987 ):
988 """Initialize execution context for a string SQL statement."""
990 self = cls.__new__(cls)
991 self.root_connection = connection
992 self._dbapi_connection = dbapi_connection
993 self.dialect = connection.dialect
994 self.is_text = True
996 # plain text statement
997 self.execution_options = connection._execution_options
999 if not parameters:
1000 if self.dialect.positional:
1001 self.parameters = [dialect.execute_sequence_format()]
1002 else:
1003 self.parameters = [{}]
1004 elif isinstance(parameters[0], dialect.execute_sequence_format):
1005 self.parameters = parameters
1006 elif isinstance(parameters[0], dict):
1007 if dialect.supports_unicode_statements:
1008 self.parameters = parameters
1009 else:
1010 self.parameters = [
1011 {dialect._encoder(k)[0]: d[k] for k in d}
1012 for d in parameters
1013 ] or [{}]
1014 else:
1015 self.parameters = [
1016 dialect.execute_sequence_format(p) for p in parameters
1017 ]
1019 self.executemany = len(parameters) > 1
1021 if not dialect.supports_unicode_statements and isinstance(
1022 statement, util.text_type
1023 ):
1024 self.unicode_statement = statement
1025 self.statement = dialect._encoder(statement)[0]
1026 else:
1027 self.statement = self.unicode_statement = statement
1029 self.cursor = self.create_cursor()
1030 return self
1032 @classmethod
1033 def _init_default(cls, dialect, connection, dbapi_connection):
1034 """Initialize execution context for a ColumnDefault construct."""
1036 self = cls.__new__(cls)
1037 self.root_connection = connection
1038 self._dbapi_connection = dbapi_connection
1039 self.dialect = connection.dialect
1040 self.execution_options = connection._execution_options
1041 self.cursor = self.create_cursor()
1042 return self
1044 @util.memoized_property
1045 def engine(self):
1046 return self.root_connection.engine
1048 @util.memoized_property
1049 def postfetch_cols(self):
1050 return self.compiled.postfetch
1052 @util.memoized_property
1053 def prefetch_cols(self):
1054 if self.isinsert:
1055 return self.compiled.insert_prefetch
1056 elif self.isupdate:
1057 return self.compiled.update_prefetch
1058 else:
1059 return ()
1061 @util.memoized_property
1062 def returning_cols(self):
1063 self.compiled.returning
1065 @util.memoized_property
1066 def no_parameters(self):
1067 return self.execution_options.get("no_parameters", False)
1069 @util.memoized_property
1070 def should_autocommit(self):
1071 autocommit = self.execution_options.get(
1072 "autocommit",
1073 not self.compiled
1074 and self.statement
1075 and expression.PARSE_AUTOCOMMIT
1076 or False,
1077 )
1079 if autocommit is expression.PARSE_AUTOCOMMIT:
1080 return self.should_autocommit_text(self.unicode_statement)
1081 else:
1082 return autocommit
1084 def _execute_scalar(self, stmt, type_):
1085 """Execute a string statement on the current cursor, returning a
1086 scalar result.
1088 Used to fire off sequences, default phrases, and "select lastrowid"
1089 types of statements individually or in the context of a parent INSERT
1090 or UPDATE statement.
1092 """
1094 conn = self.root_connection
1095 if (
1096 isinstance(stmt, util.text_type)
1097 and not self.dialect.supports_unicode_statements
1098 ):
1099 stmt = self.dialect._encoder(stmt)[0]
1101 if self.dialect.positional:
1102 default_params = self.dialect.execute_sequence_format()
1103 else:
1104 default_params = {}
1106 conn._cursor_execute(self.cursor, stmt, default_params, context=self)
1107 r = self.cursor.fetchone()[0]
1108 if type_ is not None:
1109 # apply type post processors to the result
1110 proc = type_._cached_result_processor(
1111 self.dialect, self.cursor.description[0][1]
1112 )
1113 if proc:
1114 return proc(r)
1115 return r
1117 @property
1118 def connection(self):
1119 return self.root_connection._branch()
1121 def should_autocommit_text(self, statement):
1122 return AUTOCOMMIT_REGEXP.match(statement)
1124 def _use_server_side_cursor(self):
1125 if not self.dialect.supports_server_side_cursors:
1126 return False
1128 if self.dialect.server_side_cursors:
1129 use_server_side = self.execution_options.get(
1130 "stream_results", True
1131 ) and (
1132 (
1133 self.compiled
1134 and isinstance(
1135 self.compiled.statement, expression.Selectable
1136 )
1137 or (
1138 (
1139 not self.compiled
1140 or isinstance(
1141 self.compiled.statement, expression.TextClause
1142 )
1143 )
1144 and self.statement
1145 and SERVER_SIDE_CURSOR_RE.match(self.statement)
1146 )
1147 )
1148 )
1149 else:
1150 use_server_side = self.execution_options.get(
1151 "stream_results", False
1152 )
1154 return use_server_side
1156 def create_cursor(self):
1157 if self._use_server_side_cursor():
1158 self._is_server_side = True
1159 return self.create_server_side_cursor()
1160 else:
1161 self._is_server_side = False
1162 return self._dbapi_connection.cursor()
1164 def create_server_side_cursor(self):
1165 raise NotImplementedError()
1167 def pre_exec(self):
1168 pass
1170 def post_exec(self):
1171 pass
1173 def get_result_processor(self, type_, colname, coltype):
1174 """Return a 'result processor' for a given type as present in
1175 cursor.description.
1177 This has a default implementation that dialects can override
1178 for context-sensitive result type handling.
1180 """
1181 return type_._cached_result_processor(self.dialect, coltype)
1183 def get_lastrowid(self):
1184 """return self.cursor.lastrowid, or equivalent, after an INSERT.
1186 This may involve calling special cursor functions,
1187 issuing a new SELECT on the cursor (or a new one),
1188 or returning a stored value that was
1189 calculated within post_exec().
1191 This function will only be called for dialects
1192 which support "implicit" primary key generation,
1193 keep preexecute_autoincrement_sequences set to False,
1194 and when no explicit id value was bound to the
1195 statement.
1197 The function is called once, directly after
1198 post_exec() and before the transaction is committed
1199 or ResultProxy is generated. If the post_exec()
1200 method assigns a value to `self._lastrowid`, the
1201 value is used in place of calling get_lastrowid().
1203 Note that this method is *not* equivalent to the
1204 ``lastrowid`` method on ``ResultProxy``, which is a
1205 direct proxy to the DBAPI ``lastrowid`` accessor
1206 in all cases.
1208 """
1209 return self.cursor.lastrowid
1211 def handle_dbapi_exception(self, e):
1212 pass
1214 def get_result_proxy(self):
1215 if self._is_server_side:
1216 return result.BufferedRowResultProxy(self)
1217 else:
1218 return result.ResultProxy(self)
1220 @property
1221 def rowcount(self):
1222 return self.cursor.rowcount
1224 def supports_sane_rowcount(self):
1225 return self.dialect.supports_sane_rowcount
1227 def supports_sane_multi_rowcount(self):
1228 return self.dialect.supports_sane_multi_rowcount
1230 def _setup_crud_result_proxy(self):
1231 if self.isinsert and not self.executemany:
1232 if (
1233 not self._is_implicit_returning
1234 and not self.compiled.inline
1235 and self.dialect.postfetch_lastrowid
1236 ):
1238 self._setup_ins_pk_from_lastrowid()
1240 elif not self._is_implicit_returning:
1241 self._setup_ins_pk_from_empty()
1243 result = self.get_result_proxy()
1245 if self.isinsert:
1246 if self._is_implicit_returning:
1247 row = result.fetchone()
1248 self.returned_defaults = row
1249 self._setup_ins_pk_from_implicit_returning(row)
1250 result._soft_close()
1251 result._metadata = None
1252 elif not self._is_explicit_returning:
1253 result._soft_close()
1254 result._metadata = None
1255 elif self.isupdate and self._is_implicit_returning:
1256 row = result.fetchone()
1257 self.returned_defaults = row
1258 result._soft_close()
1259 result._metadata = None
1261 elif result._metadata is None:
1262 # no results, get rowcount
1263 # (which requires open cursor on some drivers
1264 # such as kintersbasdb, mxodbc)
1265 result.rowcount
1266 result._soft_close()
1267 return result
1269 def _setup_ins_pk_from_lastrowid(self):
1270 key_getter = self.compiled._key_getters_for_crud_column[2]
1271 table = self.compiled.statement.table
1272 compiled_params = self.compiled_parameters[0]
1274 lastrowid = self.get_lastrowid()
1275 if lastrowid is not None:
1276 autoinc_col = table._autoincrement_column
1277 if autoinc_col is not None:
1278 # apply type post processors to the lastrowid
1279 proc = autoinc_col.type._cached_result_processor(
1280 self.dialect, None
1281 )
1282 if proc is not None:
1283 lastrowid = proc(lastrowid)
1284 self.inserted_primary_key = [
1285 lastrowid
1286 if c is autoinc_col
1287 else compiled_params.get(key_getter(c), None)
1288 for c in table.primary_key
1289 ]
1290 else:
1291 # don't have a usable lastrowid, so
1292 # do the same as _setup_ins_pk_from_empty
1293 self.inserted_primary_key = [
1294 compiled_params.get(key_getter(c), None)
1295 for c in table.primary_key
1296 ]
1298 def _setup_ins_pk_from_empty(self):
1299 key_getter = self.compiled._key_getters_for_crud_column[2]
1300 table = self.compiled.statement.table
1301 compiled_params = self.compiled_parameters[0]
1302 self.inserted_primary_key = [
1303 compiled_params.get(key_getter(c), None) for c in table.primary_key
1304 ]
1306 def _setup_ins_pk_from_implicit_returning(self, row):
1307 if row is None:
1308 self.inserted_primary_key = None
1309 return
1311 key_getter = self.compiled._key_getters_for_crud_column[2]
1312 table = self.compiled.statement.table
1313 compiled_params = self.compiled_parameters[0]
1314 self.inserted_primary_key = [
1315 row[col] if value is None else value
1316 for col, value in [
1317 (col, compiled_params.get(key_getter(col), None))
1318 for col in table.primary_key
1319 ]
1320 ]
1322 def lastrow_has_defaults(self):
1323 return (self.isinsert or self.isupdate) and bool(
1324 self.compiled.postfetch
1325 )
1327 def set_input_sizes(
1328 self, translate=None, include_types=None, exclude_types=None
1329 ):
1330 """Given a cursor and ClauseParameters, call the appropriate
1331 style of ``setinputsizes()`` on the cursor, using DB-API types
1332 from the bind parameter's ``TypeEngine`` objects.
1334 This method only called by those dialects which require it,
1335 currently cx_oracle.
1337 """
1339 if not hasattr(self.compiled, "bind_names"):
1340 return
1342 inputsizes = {}
1343 for bindparam in self.compiled.bind_names:
1345 dialect_impl = bindparam.type._unwrapped_dialect_impl(self.dialect)
1346 dialect_impl_cls = type(dialect_impl)
1347 dbtype = dialect_impl.get_dbapi_type(self.dialect.dbapi)
1349 if (
1350 dbtype is not None
1351 and (
1352 not exclude_types
1353 or dbtype not in exclude_types
1354 and dialect_impl_cls not in exclude_types
1355 )
1356 and (
1357 not include_types
1358 or dbtype in include_types
1359 or dialect_impl_cls in include_types
1360 )
1361 ):
1362 inputsizes[bindparam] = dbtype
1363 else:
1364 inputsizes[bindparam] = None
1366 if self.dialect._has_events:
1367 self.dialect.dispatch.do_setinputsizes(
1368 inputsizes, self.cursor, self.statement, self.parameters, self
1369 )
1371 if self.dialect.positional:
1372 positional_inputsizes = []
1373 for key in self.compiled.positiontup:
1374 bindparam = self.compiled.binds[key]
1375 dbtype = inputsizes.get(bindparam, None)
1376 if dbtype is not None:
1377 if key in self._expanded_parameters:
1378 positional_inputsizes.extend(
1379 [dbtype] * len(self._expanded_parameters[key])
1380 )
1381 else:
1382 positional_inputsizes.append(dbtype)
1383 try:
1384 self.cursor.setinputsizes(*positional_inputsizes)
1385 except BaseException as e:
1386 self.root_connection._handle_dbapi_exception(
1387 e, None, None, None, self
1388 )
1389 else:
1390 keyword_inputsizes = {}
1391 for bindparam, key in self.compiled.bind_names.items():
1392 dbtype = inputsizes.get(bindparam, None)
1393 if dbtype is not None:
1394 if translate:
1395 # TODO: this part won't work w/ the
1396 # expanded_parameters feature, e.g. for cx_oracle
1397 # quoted bound names
1398 key = translate.get(key, key)
1399 if not self.dialect.supports_unicode_binds:
1400 key = self.dialect._encoder(key)[0]
1401 if key in self._expanded_parameters:
1402 keyword_inputsizes.update(
1403 (expand_key, dbtype)
1404 for expand_key in self._expanded_parameters[key]
1405 )
1406 else:
1407 keyword_inputsizes[key] = dbtype
1408 try:
1409 self.cursor.setinputsizes(**keyword_inputsizes)
1410 except BaseException as e:
1411 self.root_connection._handle_dbapi_exception(
1412 e, None, None, None, self
1413 )
1415 def _exec_default(self, column, default, type_):
1416 if default.is_sequence:
1417 return self.fire_sequence(default, type_)
1418 elif default.is_callable:
1419 self.current_column = column
1420 return default.arg(self)
1421 elif default.is_clause_element:
1422 # TODO: expensive branching here should be
1423 # pulled into _exec_scalar()
1424 conn = self.connection
1425 if not default._arg_is_typed:
1426 default_arg = expression.type_coerce(default.arg, type_)
1427 else:
1428 default_arg = default.arg
1429 c = expression.select([default_arg]).compile(bind=conn)
1430 return conn._execute_compiled(c, (), {}).scalar()
1431 else:
1432 return default.arg
1434 current_parameters = None
1435 """A dictionary of parameters applied to the current row.
1437 This attribute is only available in the context of a user-defined default
1438 generation function, e.g. as described at :ref:`context_default_functions`.
1439 It consists of a dictionary which includes entries for each column/value
1440 pair that is to be part of the INSERT or UPDATE statement. The keys of the
1441 dictionary will be the key value of each :class:`_schema.Column`,
1442 which is usually
1443 synonymous with the name.
1445 Note that the :attr:`.DefaultExecutionContext.current_parameters` attribute
1446 does not accommodate for the "multi-values" feature of the
1447 :meth:`_expression.Insert.values` method. The
1448 :meth:`.DefaultExecutionContext.get_current_parameters` method should be
1449 preferred.
1451 .. seealso::
1453 :meth:`.DefaultExecutionContext.get_current_parameters`
1455 :ref:`context_default_functions`
1457 """
1459 def get_current_parameters(self, isolate_multiinsert_groups=True):
1460 """Return a dictionary of parameters applied to the current row.
1462 This method can only be used in the context of a user-defined default
1463 generation function, e.g. as described at
1464 :ref:`context_default_functions`. When invoked, a dictionary is
1465 returned which includes entries for each column/value pair that is part
1466 of the INSERT or UPDATE statement. The keys of the dictionary will be
1467 the key value of each :class:`_schema.Column`,
1468 which is usually synonymous
1469 with the name.
1471 :param isolate_multiinsert_groups=True: indicates that multi-valued
1472 INSERT constructs created using :meth:`_expression.Insert.values`
1473 should be
1474 handled by returning only the subset of parameters that are local
1475 to the current column default invocation. When ``False``, the
1476 raw parameters of the statement are returned including the
1477 naming convention used in the case of multi-valued INSERT.
1479 .. versionadded:: 1.2 added
1480 :meth:`.DefaultExecutionContext.get_current_parameters`
1481 which provides more functionality over the existing
1482 :attr:`.DefaultExecutionContext.current_parameters`
1483 attribute.
1485 .. seealso::
1487 :attr:`.DefaultExecutionContext.current_parameters`
1489 :ref:`context_default_functions`
1491 """
1492 try:
1493 parameters = self.current_parameters
1494 column = self.current_column
1495 except AttributeError:
1496 raise exc.InvalidRequestError(
1497 "get_current_parameters() can only be invoked in the "
1498 "context of a Python side column default function"
1499 )
1500 if (
1501 isolate_multiinsert_groups
1502 and self.isinsert
1503 and self.compiled.statement._has_multi_parameters
1504 ):
1505 if column._is_multiparam_column:
1506 index = column.index + 1
1507 d = {column.original.key: parameters[column.key]}
1508 else:
1509 d = {column.key: parameters[column.key]}
1510 index = 0
1511 keys = self.compiled.statement.parameters[0].keys()
1512 d.update(
1513 (key, parameters["%s_m%d" % (key, index)]) for key in keys
1514 )
1515 return d
1516 else:
1517 return parameters
1519 def get_insert_default(self, column):
1520 if column.default is None:
1521 return None
1522 else:
1523 return self._exec_default(column, column.default, column.type)
1525 def get_update_default(self, column):
1526 if column.onupdate is None:
1527 return None
1528 else:
1529 return self._exec_default(column, column.onupdate, column.type)
1531 def _process_executemany_defaults(self):
1532 key_getter = self.compiled._key_getters_for_crud_column[2]
1534 scalar_defaults = {}
1536 insert_prefetch = self.compiled.insert_prefetch
1537 update_prefetch = self.compiled.update_prefetch
1539 # pre-determine scalar Python-side defaults
1540 # to avoid many calls of get_insert_default()/
1541 # get_update_default()
1542 for c in insert_prefetch:
1543 if c.default and c.default.is_scalar:
1544 scalar_defaults[c] = c.default.arg
1545 for c in update_prefetch:
1546 if c.onupdate and c.onupdate.is_scalar:
1547 scalar_defaults[c] = c.onupdate.arg
1549 for param in self.compiled_parameters:
1550 self.current_parameters = param
1551 for c in insert_prefetch:
1552 if c in scalar_defaults:
1553 val = scalar_defaults[c]
1554 else:
1555 val = self.get_insert_default(c)
1556 if val is not None:
1557 param[key_getter(c)] = val
1558 for c in update_prefetch:
1559 if c in scalar_defaults:
1560 val = scalar_defaults[c]
1561 else:
1562 val = self.get_update_default(c)
1563 if val is not None:
1564 param[key_getter(c)] = val
1566 del self.current_parameters
1568 def _process_executesingle_defaults(self):
1569 key_getter = self.compiled._key_getters_for_crud_column[2]
1570 self.current_parameters = (
1571 compiled_parameters
1572 ) = self.compiled_parameters[0]
1574 for c in self.compiled.insert_prefetch:
1575 if c.default and not c.default.is_sequence and c.default.is_scalar:
1576 val = c.default.arg
1577 else:
1578 val = self.get_insert_default(c)
1580 if val is not None:
1581 compiled_parameters[key_getter(c)] = val
1583 for c in self.compiled.update_prefetch:
1584 val = self.get_update_default(c)
1586 if val is not None:
1587 compiled_parameters[key_getter(c)] = val
1588 del self.current_parameters
1591DefaultDialect.execution_ctx_cls = DefaultExecutionContext