codekingpro/portable-devtools
115k
1# engine/default.py
2# Copyright (C) 2005-2026 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: https://www.opensource.org/licenses/mit-license.php
7# mypy: allow-untyped-defs, allow-untyped-calls
8
9"""Default implementations of per-dialect sqlalchemy.engine classes.
10
11These are semi-private implementation classes which are only of importance
12to database dialect authors; dialects will usually use the classes here
13as the base class for their own corresponding classes.
14
15"""
16
17from __future__ import annotations
18
19import functools
20import operator
21import random
22import re
23from time import perf_counter
24import typing
25from typing import Any
26from typing import Callable
27from typing import cast
28from typing import Dict
29from typing import List
30from typing import Mapping
31from typing import MutableMapping
32from typing import MutableSequence
33from typing import Optional
34from typing import Sequence
35from typing import Set
36from typing import Tuple
37from typing import Type
38from typing import TYPE_CHECKING
39from typing import Union
40import weakref
41
42from . import characteristics
43from . import cursor as _cursor
44from . import interfaces
45from .base import Connection
46from .interfaces import CacheStats
47from .interfaces import DBAPICursor
48from .interfaces import Dialect
49from .interfaces import ExecuteStyle
50from .interfaces import ExecutionContext
51from .reflection import ObjectKind
52from .reflection import ObjectScope
53from .. import event
54from .. import exc
55from .. import pool
56from .. import util
57from ..sql import compiler
58from ..sql import dml
59from ..sql import expression
60from ..sql import type_api
61from ..sql import util as sql_util
62from ..sql._typing import is_tuple_type
63from ..sql.base import _NoArg
64from ..sql.compiler import DDLCompiler
65from ..sql.compiler import InsertmanyvaluesSentinelOpts
66from ..sql.compiler import SQLCompiler
67from ..sql.elements import quoted_name
68from ..util.typing import Final
69from ..util.typing import Literal
70
71if typing.TYPE_CHECKING:
72 from types import ModuleType
73
74 from .base import Engine
75 from .cursor import ResultFetchStrategy
76 from .interfaces import _CoreMultiExecuteParams
77 from .interfaces import _CoreSingleExecuteParams
78 from .interfaces import _DBAPICursorDescription
79 from .interfaces import _DBAPIMultiExecuteParams
80 from .interfaces import _DBAPISingleExecuteParams
81 from .interfaces import _ExecuteOptions
82 from .interfaces import _MutableCoreSingleExecuteParams
83 from .interfaces import _ParamStyle
84 from .interfaces import ConnectArgsType
85 from .interfaces import DBAPIConnection
86 from .interfaces import DBAPIModule
87 from .interfaces import DBAPIType
88 from .interfaces import IsolationLevel
89 from .row import Row
90 from .url import URL
91 from ..event import _ListenerFnType
92 from ..pool import Pool
93 from ..pool import PoolProxiedConnection
94 from ..sql import Executable
95 from ..sql.compiler import Compiled
96 from ..sql.compiler import Linting
97 from ..sql.compiler import ResultColumnsEntry
98 from ..sql.dml import DMLState
99 from ..sql.dml import UpdateBase
100 from ..sql.elements import BindParameter
101 from ..sql.schema import Column
102 from ..sql.type_api import _BindProcessorType
103 from ..sql.type_api import _ResultProcessorType
104 from ..sql.type_api import TypeEngine
105
106
107# When we're handed literal SQL, ensure it's a SELECT query
108SERVER_SIDE_CURSOR_RE = re.compile(r"\s*SELECT", re.I | re.UNICODE)
109
110
111(
112 CACHE_HIT,
113 CACHE_MISS,
114 CACHING_DISABLED,
115 NO_CACHE_KEY,
116 NO_DIALECT_SUPPORT,
117) = list(CacheStats)
118
119
120class DefaultDialect(Dialect):
121 """Default implementation of Dialect"""
122
123 statement_compiler = compiler.SQLCompiler
124 ddl_compiler = compiler.DDLCompiler
125 type_compiler_cls = compiler.GenericTypeCompiler
126
127 preparer = compiler.IdentifierPreparer
128 supports_alter = True
129 supports_comments = False
130 supports_constraint_comments = False
131 inline_comments = False
132 supports_statement_cache = True
133
134 div_is_floordiv = True
135
136 bind_typing = interfaces.BindTyping.NONE
137
138 include_set_input_sizes: Optional[Set[Any]] = None
139 exclude_set_input_sizes: Optional[Set[Any]] = None
140
141 # the first value we'd get for an autoincrement column.
142 default_sequence_base = 1
143
144 # most DBAPIs happy with this for execute().
145 # not cx_oracle.
146 execute_sequence_format = tuple
147
148 supports_schemas = True
149 supports_views = True
150 supports_sequences = False
151 sequences_optional = False
152 preexecute_autoincrement_sequences = False
153 supports_identity_columns = False
154 postfetch_lastrowid = True
155 favor_returning_over_lastrowid = False
156 insert_null_pk_still_autoincrements = False
157 update_returning = False
158 delete_returning = False
159 update_returning_multifrom = False
160 delete_returning_multifrom = False
161 insert_returning = False
162
163 cte_follows_insert = False
164
165 supports_native_enum = False
166 supports_native_boolean = False
167 supports_native_uuid = False
168 returns_native_bytes = False
169
170 non_native_boolean_check_constraint = True
171
172 supports_simple_order_by_label = True
173
174 tuple_in_values = False
175
176 connection_characteristics = util.immutabledict(
177 {
178 "isolation_level": characteristics.IsolationLevelCharacteristic(),
179 "logging_token": characteristics.LoggingTokenCharacteristic(),
180 }
181 )
182
183 engine_config_types: Mapping[str, Any] = util.immutabledict(
184 {
185 "pool_timeout": util.asint,
186 "echo": util.bool_or_str("debug"),
187 "echo_pool": util.bool_or_str("debug"),
188 "pool_recycle": util.asint,
189 "pool_size": util.asint,
190 "max_overflow": util.asint,
191 "future": util.asbool,
192 }
193 )
194
195 # if the NUMERIC type
196 # returns decimal.Decimal.
197 # *not* the FLOAT type however.
198 supports_native_decimal = False
199
200 name = "default"
201
202 # length at which to truncate
203 # any identifier.
204 max_identifier_length = 9999
205 _user_defined_max_identifier_length: Optional[int] = None
206
207 isolation_level: Optional[str] = None
208
209 # sub-categories of max_identifier_length.
210 # currently these accommodate for MySQL which allows alias names
211 # of 255 but DDL names only of 64.
212 max_index_name_length: Optional[int] = None
213 max_constraint_name_length: Optional[int] = None
214
215 supports_sane_rowcount = True
216 supports_sane_multi_rowcount = True
217 colspecs: MutableMapping[Type[TypeEngine[Any]], Type[TypeEngine[Any]]] = {}
218 default_paramstyle = "named"
219
220 supports_default_values = False
221 """dialect supports INSERT... DEFAULT VALUES syntax"""
222
223 supports_default_metavalue = False
224 """dialect supports INSERT... VALUES (DEFAULT) syntax"""
225
226 default_metavalue_token = "DEFAULT"
227 """for INSERT... VALUES (DEFAULT) syntax, the token to put in the
228 parenthesis."""
229
230 # not sure if this is a real thing but the compiler will deliver it
231 # if this is the only flag enabled.
232 supports_empty_insert = True
233 """dialect supports INSERT () VALUES ()"""
234
235 supports_multivalues_insert = False
236
237 use_insertmanyvalues: bool = False
238
239 use_insertmanyvalues_wo_returning: bool = False
240
241 insertmanyvalues_implicit_sentinel: InsertmanyvaluesSentinelOpts = (
242 InsertmanyvaluesSentinelOpts.NOT_SUPPORTED
243 )
244
245 insertmanyvalues_page_size: int = 1000
246 insertmanyvalues_max_parameters = 32700
247
248 supports_is_distinct_from = True
249
250 supports_server_side_cursors = False
251
252 server_side_cursors = False
253
254 # extra record-level locking features (#4860)
255 supports_for_update_of = False
256
257 server_version_info = None
258
259 default_schema_name: Optional[str] = None
260
261 # indicates symbol names are
262 # UPPERCASED if they are case insensitive
263 # within the database.
264 # if this is True, the methods normalize_name()
265 # and denormalize_name() must be provided.
266 requires_name_normalize = False
267
268 is_async = False
269
270 has_terminate = False
271
272 # TODO: this is not to be part of 2.0. implement rudimentary binary
273 # literals for SQLite, PostgreSQL, MySQL only within
274 # _Binary.literal_processor
275 _legacy_binary_type_literal_encoding = "utf-8"
276
277 @util.deprecated_params(
278 empty_in_strategy=(
279 "1.4",
280 "The :paramref:`_sa.create_engine.empty_in_strategy` keyword is "
281 "deprecated, and no longer has any effect. All IN expressions "
282 "are now rendered using "
283 'the "expanding parameter" strategy which renders a set of bound'
284 'expressions, or an "empty set" SELECT, at statement execution'
285 "time.",
286 ),
287 server_side_cursors=(
288 "1.4",
289 "The :paramref:`_sa.create_engine.server_side_cursors` parameter "
290 "is deprecated and will be removed in a future release. Please "
291 "use the "
292 ":paramref:`_engine.Connection.execution_options.stream_results` "
293 "parameter.",
294 ),
295 )
296 def __init__(
297 self,
298 paramstyle: Optional[_ParamStyle] = None,
299 isolation_level: Optional[IsolationLevel] = None,
300 dbapi: Optional[ModuleType] = None,
301 implicit_returning: Literal[True] = True,
302 supports_native_boolean: Optional[bool] = None,
303 max_identifier_length: Optional[int] = None,
304 label_length: Optional[int] = None,
305 insertmanyvalues_page_size: Union[_NoArg, int] = _NoArg.NO_ARG,
306 use_insertmanyvalues: Optional[bool] = None,
307 # util.deprecated_params decorator cannot render the
308 # Linting.NO_LINTING constant
309 compiler_linting: Linting = int(compiler.NO_LINTING), # type: ignore
310 server_side_cursors: bool = False,
311 skip_autocommit_rollback: bool = False,
312 **kwargs: Any,
313 ):
314 if server_side_cursors:
315 if not self.supports_server_side_cursors:
316 raise exc.ArgumentError(
317 "Dialect %s does not support server side cursors" % self
318 )
319 else:
320 self.server_side_cursors = True
321
322 if getattr(self, "use_setinputsizes", False):
323 util.warn_deprecated(
324 "The dialect-level use_setinputsizes attribute is "
325 "deprecated. Please use "
326 "bind_typing = BindTyping.SETINPUTSIZES",
327 "2.0",
328 )
329 self.bind_typing = interfaces.BindTyping.SETINPUTSIZES
330
331 self.positional = False
332 self._ischema = None
333
334 self.dbapi = dbapi
335
336 self.skip_autocommit_rollback = skip_autocommit_rollback
337
338 if paramstyle is not None:
339 self.paramstyle = paramstyle
340 elif self.dbapi is not None:
341 self.paramstyle = self.dbapi.paramstyle
342 else:
343 self.paramstyle = self.default_paramstyle
344 self.positional = self.paramstyle in (
345 "qmark",
346 "format",
347 "numeric",
348 "numeric_dollar",
349 )
350 self.identifier_preparer = self.preparer(self)
351 self._on_connect_isolation_level = isolation_level
352
353 legacy_tt_callable = getattr(self, "type_compiler", None)
354 if legacy_tt_callable is not None:
355 tt_callable = cast(
356 Type[compiler.GenericTypeCompiler],
357 self.type_compiler,
358 )
359 else:
360 tt_callable = self.type_compiler_cls
361
362 self.type_compiler_instance = self.type_compiler = tt_callable(self)
363
364 if supports_native_boolean is not None:
365 self.supports_native_boolean = supports_native_boolean
366
367 self._user_defined_max_identifier_length = max_identifier_length
368 if self._user_defined_max_identifier_length:
369 self.max_identifier_length = (
370 self._user_defined_max_identifier_length
371 )
372 self.label_length = label_length
373 self.compiler_linting = compiler_linting
374
375 if use_insertmanyvalues is not None:
376 self.use_insertmanyvalues = use_insertmanyvalues
377
378 if insertmanyvalues_page_size is not _NoArg.NO_ARG:
379 self.insertmanyvalues_page_size = insertmanyvalues_page_size
380
381 @property
382 @util.deprecated(
383 "2.0",
384 "full_returning is deprecated, please use insert_returning, "
385 "update_returning, delete_returning",
386 )
387 def full_returning(self):
388 return (
389 self.insert_returning
390 and self.update_returning
391 and self.delete_returning
392 )
393
394 @util.memoized_property
395 def insert_executemany_returning(self):
396 """Default implementation for insert_executemany_returning, if not
397 otherwise overridden by the specific dialect.
398
399 The default dialect determines "insert_executemany_returning" is
400 available if the dialect in use has opted into using the
401 "use_insertmanyvalues" feature. If they haven't opted into that, then
402 this attribute is False, unless the dialect in question overrides this
403 and provides some other implementation (such as the Oracle Database
404 dialects).
405
406 """
407 return self.insert_returning and self.use_insertmanyvalues
408
409 @util.memoized_property
410 def insert_executemany_returning_sort_by_parameter_order(self):
411 """Default implementation for
412 insert_executemany_returning_deterministic_order, if not otherwise
413 overridden by the specific dialect.
414
415 The default dialect determines "insert_executemany_returning" can have
416 deterministic order only if the dialect in use has opted into using the
417 "use_insertmanyvalues" feature, which implements deterministic ordering
418 using client side sentinel columns only by default. The
419 "insertmanyvalues" feature also features alternate forms that can
420 use server-generated PK values as "sentinels", but those are only
421 used if the :attr:`.Dialect.insertmanyvalues_implicit_sentinel`
422 bitflag enables those alternate SQL forms, which are disabled
423 by default.
424
425 If the dialect in use hasn't opted into that, then this attribute is
426 False, unless the dialect in question overrides this and provides some
427 other implementation (such as the Oracle Database dialects).
428
429 """
430 return self.insert_returning and self.use_insertmanyvalues
431
432 update_executemany_returning = False
433 delete_executemany_returning = False
434
435 @util.memoized_property
436 def loaded_dbapi(self) -> DBAPIModule:
437 if self.dbapi is None:
438 raise exc.InvalidRequestError(
439 f"Dialect {self} does not have a Python DBAPI established "
440 "and cannot be used for actual database interaction"
441 )
442 return self.dbapi
443
444 @util.memoized_property
445 def _bind_typing_render_casts(self):
446 return self.bind_typing is interfaces.BindTyping.RENDER_CASTS
447
448 def _ensure_has_table_connection(self, arg: Connection) -> None:
449 if not isinstance(arg, Connection):
450 raise exc.ArgumentError(
451 "The argument passed to Dialect.has_table() should be a "
452 "%s, got %s. "
453 "Additionally, the Dialect.has_table() method is for "
454 "internal dialect "
455 "use only; please use "
456 "``inspect(some_engine).has_table(<tablename>>)`` "
457 "for public API use." % (Connection, type(arg))
458 )
459
460 @util.memoized_property
461 def _supports_statement_cache(self):
462 ssc = self.__class__.__dict__.get("supports_statement_cache", None)
463 if ssc is None:
464 util.warn(
465 "Dialect %s:%s will not make use of SQL compilation caching "
466 "as it does not set the 'supports_statement_cache' attribute "
467 "to ``True``. This can have "
468 "significant performance implications including some "
469 "performance degradations in comparison to prior SQLAlchemy "
470 "versions. Dialect maintainers should seek to set this "
471 "attribute to True after appropriate development and testing "
472 "for SQLAlchemy 1.4 caching support. Alternatively, this "
473 "attribute may be set to False which will disable this "
474 "warning." % (self.name, self.driver),
475 code="cprf",
476 )
477
478 return bool(ssc)
479
480 @util.memoized_property
481 def _type_memos(self):
482 return weakref.WeakKeyDictionary()
483
484 @property
485 def dialect_description(self): # type: ignore[override]
486 return self.name + "+" + self.driver
487
488 @property
489 def supports_sane_rowcount_returning(self):
490 """True if this dialect supports sane rowcount even if RETURNING is
491 in use.
492
493 For dialects that don't support RETURNING, this is synonymous with
494 ``supports_sane_rowcount``.
495
496 """
497 return self.supports_sane_rowcount
498
499 @classmethod
500 def get_pool_class(cls, url: URL) -> Type[Pool]:
501 return getattr(cls, "poolclass", pool.QueuePool)
502
503 def get_dialect_pool_class(self, url: URL) -> Type[Pool]:
504 return self.get_pool_class(url)
505
506 @classmethod
507 def load_provisioning(cls):
508 package = ".".join(cls.__module__.split(".")[0:-1])
509 try:
510 __import__(package + ".provision")
511 except ImportError:
512 pass
513
514 def _builtin_onconnect(self) -> Optional[_ListenerFnType]:
515 if self._on_connect_isolation_level is not None:
516
517 def builtin_connect(dbapi_conn, conn_rec):
518 self._assert_and_set_isolation_level(
519 dbapi_conn, self._on_connect_isolation_level
520 )
521
522 return builtin_connect
523 else:
524 return None
525
526 def initialize(self, connection: Connection) -> None:
527 try:
528 self.server_version_info = self._get_server_version_info(
529 connection
530 )
531 except NotImplementedError:
532 self.server_version_info = None
533 try:
534 self.default_schema_name = self._get_default_schema_name(
535 connection
536 )
537 except NotImplementedError:
538 self.default_schema_name = None
539
540 try:
541 self.default_isolation_level = self.get_default_isolation_level(
542 connection.connection.dbapi_connection
543 )
544 except NotImplementedError:
545 self.default_isolation_level = None
546
547 if not self._user_defined_max_identifier_length:
548 max_ident_length = self._check_max_identifier_length(connection)
549 if max_ident_length:
550 self.max_identifier_length = max_ident_length
551
552 if (
553 self.label_length
554 and self.label_length > self.max_identifier_length
555 ):
556 raise exc.ArgumentError(
557 "Label length of %d is greater than this dialect's"
558 " maximum identifier length of %d"
559 % (self.label_length, self.max_identifier_length)
560 )
561
562 def on_connect(self) -> Optional[Callable[[Any], None]]:
563 # inherits the docstring from interfaces.Dialect.on_connect
564 return None
565
566 def _check_max_identifier_length(self, connection):
567 """Perform a connection / server version specific check to determine
568 the max_identifier_length.
569
570 If the dialect's class level max_identifier_length should be used,
571 can return None.
572
573 .. versionadded:: 1.3.9
574
575 """
576 return None
577
578 def get_default_isolation_level(self, dbapi_conn):
579 """Given a DBAPI connection, return its isolation level, or
580 a default isolation level if one cannot be retrieved.
581
582 May be overridden by subclasses in order to provide a
583 "fallback" isolation level for databases that cannot reliably
584 retrieve the actual isolation level.
585
586 By default, calls the :meth:`_engine.Interfaces.get_isolation_level`
587 method, propagating any exceptions raised.
588
589 .. versionadded:: 1.3.22
590
591 """
592 return self.get_isolation_level(dbapi_conn)
593
594 def type_descriptor(self, typeobj):
595 """Provide a database-specific :class:`.TypeEngine` object, given
596 the generic object which comes from the types module.
597
598 This method looks for a dictionary called
599 ``colspecs`` as a class or instance-level variable,
600 and passes on to :func:`_types.adapt_type`.
601
602 """
603 return type_api.adapt_type(typeobj, self.colspecs)
604
605 def has_index(self, connection, table_name, index_name, schema=None, **kw):
606 if not self.has_table(connection, table_name, schema=schema, **kw):
607 return False
608 for idx in self.get_indexes(
609 connection, table_name, schema=schema, **kw
610 ):
611 if idx["name"] == index_name:
612 return True
613 else:
614 return False
615
616 def has_schema(
617 self, connection: Connection, schema_name: str, **kw: Any
618 ) -> bool:
619 return schema_name in self.get_schema_names(connection, **kw)
620
621 def validate_identifier(self, ident: str) -> None:
622 if len(ident) > self.max_identifier_length:
623 raise exc.IdentifierError(
624 "Identifier '%s' exceeds maximum length of %d characters"
625 % (ident, self.max_identifier_length)
626 )
627
628 def connect(self, *cargs: Any, **cparams: Any) -> DBAPIConnection:
629 # inherits the docstring from interfaces.Dialect.connect
630 return self.loaded_dbapi.connect(*cargs, **cparams) # type: ignore[no-any-return] # NOQA: E501
631
632 def create_connect_args(self, url: URL) -> ConnectArgsType:
633 # inherits the docstring from interfaces.Dialect.create_connect_args
634 opts = url.translate_connect_args()
635 opts.update(url.query)
636 return ([], opts)
637
638 def set_engine_execution_options(
639 self, engine: Engine, opts: Mapping[str, Any]
640 ) -> None:
641 supported_names = set(self.connection_characteristics).intersection(
642 opts
643 )
644 if supported_names:
645 characteristics: Mapping[str, Any] = util.immutabledict(
646 (name, opts[name]) for name in supported_names
647 )
648
649 @event.listens_for(engine, "engine_connect")
650 def set_connection_characteristics(connection):
651 self._set_connection_characteristics(
652 connection, characteristics
653 )
654
655 def set_connection_execution_options(
656 self, connection: Connection, opts: Mapping[str, Any]
657 ) -> None:
658 supported_names = set(self.connection_characteristics).intersection(
659 opts
660 )
661 if supported_names:
662 characteristics: Mapping[str, Any] = util.immutabledict(
663 (name, opts[name]) for name in supported_names
664 )
665 self._set_connection_characteristics(connection, characteristics)
666
667 def _set_connection_characteristics(self, connection, characteristics):
668 characteristic_values = [
669 (name, self.connection_characteristics[name], value)
670 for name, value in characteristics.items()
671 ]
672
673 if connection.in_transaction():
674 trans_objs = [
675 (name, obj)
676 for name, obj, _ in characteristic_values
677 if obj.transactional
678 ]
679 if trans_objs:
680 raise exc.InvalidRequestError(
681 "This connection has already initialized a SQLAlchemy "
682 "Transaction() object via begin() or autobegin; "
683 "%s may not be altered unless rollback() or commit() "
684 "is called first."
685 % (", ".join(name for name, obj in trans_objs))
686 )
687
688 dbapi_connection = connection.connection.dbapi_connection
689 for _, characteristic, value in characteristic_values:
690 characteristic.set_connection_characteristic(
691 self, connection, dbapi_connection, value
692 )
693 connection.connection._connection_record.finalize_callback.append(
694 functools.partial(self._reset_characteristics, characteristics)
695 )
696
697 def _reset_characteristics(self, characteristics, dbapi_connection):
698 for characteristic_name in characteristics:
699 characteristic = self.connection_characteristics[
700 characteristic_name
701 ]
702 characteristic.reset_characteristic(self, dbapi_connection)
703
704 def do_begin(self, dbapi_connection):
705 pass
706
707 def do_rollback(self, dbapi_connection):
708 if self.skip_autocommit_rollback and self.detect_autocommit_setting(
709 dbapi_connection
710 ):
711 return
712 dbapi_connection.rollback()
713
714 def do_commit(self, dbapi_connection):
715 dbapi_connection.commit()
716
717 def do_terminate(self, dbapi_connection):
718 self.do_close(dbapi_connection)
719
720 def do_close(self, dbapi_connection):
721 dbapi_connection.close()
722
723 @util.memoized_property
724 def _dialect_specific_select_one(self):
725 return str(expression.select(1).compile(dialect=self))
726
727 def _do_ping_w_event(self, dbapi_connection: DBAPIConnection) -> bool:
728 try:
729 return self.do_ping(dbapi_connection)
730 except self.loaded_dbapi.Error as err:
731 is_disconnect = self.is_disconnect(err, dbapi_connection, None)
732
733 if self._has_events:
734 try:
735 Connection._handle_dbapi_exception_noconnection(
736 err,
737 self,
738 is_disconnect=is_disconnect,
739 invalidate_pool_on_disconnect=False,
740 is_pre_ping=True,
741 )
742 except exc.StatementError as new_err:
743 is_disconnect = new_err.connection_invalidated
744
745 if is_disconnect:
746 return False
747 else:
748 raise
749
750 def do_ping(self, dbapi_connection: DBAPIConnection) -> bool:
751 cursor = dbapi_connection.cursor()
752 try:
753 cursor.execute(self._dialect_specific_select_one)
754 finally:
755 cursor.close()
756 return True
757
758 def create_xid(self):
759 """Create a random two-phase transaction ID.
760
761 This id will be passed to do_begin_twophase(), do_rollback_twophase(),
762 do_commit_twophase(). Its format is unspecified.
763 """
764
765 return "_sa_%032x" % random.randint(0, 2**128)
766
767 def do_savepoint(self, connection, name):
768 connection.execute(expression.SavepointClause(name))
769
770 def do_rollback_to_savepoint(self, connection, name):
771 connection.execute(expression.RollbackToSavepointClause(name))
772
773 def do_release_savepoint(self, connection, name):
774 connection.execute(expression.ReleaseSavepointClause(name))
775
776 def _deliver_insertmanyvalues_batches(
777 self,
778 connection,
779 cursor,
780 statement,
781 parameters,
782 generic_setinputsizes,
783 context,
784 ):
785 context = cast(DefaultExecutionContext, context)
786 compiled = cast(SQLCompiler, context.compiled)
787
788 _composite_sentinel_proc: Sequence[
789 Optional[_ResultProcessorType[Any]]
790 ] = ()
791 _scalar_sentinel_proc: Optional[_ResultProcessorType[Any]] = None
792 _sentinel_proc_initialized: bool = False
793
794 compiled_parameters = context.compiled_parameters
795
796 imv = compiled._insertmanyvalues
797 assert imv is not None
798
799 is_returning: Final[bool] = bool(compiled.effective_returning)
800 batch_size = context.execution_options.get(
801 "insertmanyvalues_page_size", self.insertmanyvalues_page_size
802 )
803
804 if compiled.schema_translate_map:
805 schema_translate_map = context.execution_options.get(
806 "schema_translate_map", {}
807 )
808 else:
809 schema_translate_map = None
810
811 if is_returning:
812 result: Optional[List[Any]] = []
813 context._insertmanyvalues_rows = result
814
815 sort_by_parameter_order = imv.sort_by_parameter_order
816
817 else:
818 sort_by_parameter_order = False
819 result = None
820
821 for imv_batch in compiled._deliver_insertmanyvalues_batches(
822 statement,
823 parameters,
824 compiled_parameters,
825 generic_setinputsizes,
826 batch_size,
827 sort_by_parameter_order,
828 schema_translate_map,
829 ):
830 yield imv_batch
831
832 if is_returning:
833
834 try:
835 rows = context.fetchall_for_returning(cursor)
836 except BaseException as be:
837 connection._handle_dbapi_exception(
838 be,
839 sql_util._long_statement(imv_batch.replaced_statement),
840 imv_batch.replaced_parameters,
841 None,
842 context,
843 is_sub_exec=True,
844 )
845
846 # I would have thought "is_returning: Final[bool]"
847 # would have assured this but pylance thinks not
848 assert result is not None
849
850 if imv.num_sentinel_columns and not imv_batch.is_downgraded:
851 composite_sentinel = imv.num_sentinel_columns > 1
852 if imv.implicit_sentinel:
853 # for implicit sentinel, which is currently single-col
854 # integer autoincrement, do a simple sort.
855 assert not composite_sentinel
856 result.extend(
857 sorted(rows, key=operator.itemgetter(-1))
858 )
859 continue
860
861 # otherwise, create dictionaries to match up batches
862 # with parameters
863 assert imv.sentinel_param_keys
864 assert imv.sentinel_columns
865
866 _nsc = imv.num_sentinel_columns
867
868 if not _sentinel_proc_initialized:
869 if composite_sentinel:
870 _composite_sentinel_proc = [
871 col.type._cached_result_processor(
872 self, cursor_desc[1]
873 )
874 for col, cursor_desc in zip(
875 imv.sentinel_columns,
876 cursor.description[-_nsc:],
877 )
878 ]
879 else:
880 _scalar_sentinel_proc = (
881 imv.sentinel_columns[0]
882 ).type._cached_result_processor(
883 self, cursor.description[-1][1]
884 )
885 _sentinel_proc_initialized = True
886
887 rows_by_sentinel: Union[
888 Dict[Tuple[Any, ...], Any],
889 Dict[Any, Any],
890 ]
891 if composite_sentinel:
892 rows_by_sentinel = {
893 tuple(
894 (proc(val) if proc else val)
895 for val, proc in zip(
896 row[-_nsc:], _composite_sentinel_proc
897 )
898 ): row
899 for row in rows
900 }
901 elif _scalar_sentinel_proc:
902 rows_by_sentinel = {
903 _scalar_sentinel_proc(row[-1]): row for row in rows
904 }
905 else:
906 rows_by_sentinel = {row[-1]: row for row in rows}
907
908 if len(rows_by_sentinel) != len(imv_batch.batch):
909 # see test_insert_exec.py::
910 # IMVSentinelTest::test_sentinel_incorrect_rowcount
911 # for coverage / demonstration
912 raise exc.InvalidRequestError(
913 f"Sentinel-keyed result set did not produce "
914 f"correct number of rows {len(imv_batch.batch)}; "
915 "produced "
916 f"{len(rows_by_sentinel)}. Please ensure the "
917 "sentinel column is fully unique and populated in "
918 "all cases."
919 )
920
921 try:
922 ordered_rows = [
923 rows_by_sentinel[sentinel_keys]
924 for sentinel_keys in imv_batch.sentinel_values
925 ]
926 except KeyError as ke:
927 # see test_insert_exec.py::
928 # IMVSentinelTest::test_sentinel_cant_match_keys
929 # for coverage / demonstration
930 raise exc.InvalidRequestError(
931 f"Can't match sentinel values in result set to "
932 f"parameter sets; key {ke.args[0]!r} was not "
933 "found. "
934 "There may be a mismatch between the datatype "
935 "passed to the DBAPI driver vs. that which it "
936 "returns in a result row. Ensure the given "
937 "Python value matches the expected result type "
938 "*exactly*, taking care to not rely upon implicit "
939 "conversions which may occur such as when using "
940 "strings in place of UUID or integer values, etc. "
941 ) from ke
942
943 result.extend(ordered_rows)
944
945 else:
946 result.extend(rows)
947
948 def do_executemany(self, cursor, statement, parameters, context=None):
949 cursor.executemany(statement, parameters)
950
951 def do_execute(self, cursor, statement, parameters, context=None):
952 cursor.execute(statement, parameters)
953
954 def do_execute_no_params(self, cursor, statement, context=None):
955 cursor.execute(statement)
956
957 def is_disconnect(
958 self,
959 e: DBAPIModule.Error,
960 connection: Union[
961 pool.PoolProxiedConnection, interfaces.DBAPIConnection, None
962 ],
963 cursor: Optional[interfaces.DBAPICursor],
964 ) -> bool:
965 return False
966
967 @util.memoized_instancemethod
968 def _gen_allowed_isolation_levels(self, dbapi_conn):
969 try:
970 raw_levels = list(self.get_isolation_level_values(dbapi_conn))
971 except NotImplementedError:
972 return None
973 else:
974 normalized_levels = [
975 level.replace("_", " ").upper() for level in raw_levels
976 ]
977 if raw_levels != normalized_levels:
978 raise ValueError(
979 f"Dialect {self.name!r} get_isolation_level_values() "
980 f"method should return names as UPPERCASE using spaces, "
981 f"not underscores; got "
982 f"{sorted(set(raw_levels).difference(normalized_levels))}"
983 )
984 return tuple(normalized_levels)
985
986 def _assert_and_set_isolation_level(self, dbapi_conn, level):
987 level = level.replace("_", " ").upper()
988
989 _allowed_isolation_levels = self._gen_allowed_isolation_levels(
990 dbapi_conn
991 )
992 if (
993 _allowed_isolation_levels
994 and level not in _allowed_isolation_levels
995 ):
996 raise exc.ArgumentError(
997 f"Invalid value {level!r} for isolation_level. "
998 f"Valid isolation levels for {self.name!r} are "
999 f"{', '.join(_allowed_isolation_levels)}"
1000 )
1001
1002 self.set_isolation_level(dbapi_conn, level)
1003
1004 def reset_isolation_level(self, dbapi_conn):
1005 if self._on_connect_isolation_level is not None:
1006 assert (
1007 self._on_connect_isolation_level == "AUTOCOMMIT"
1008 or self._on_connect_isolation_level
1009 == self.default_isolation_level
1010 )
1011 self._assert_and_set_isolation_level(
1012 dbapi_conn, self._on_connect_isolation_level
1013 )
1014 else:
1015 assert self.default_isolation_level is not None
1016 self._assert_and_set_isolation_level(
1017 dbapi_conn,
1018 self.default_isolation_level,
1019 )
1020
1021 def normalize_name(self, name):
1022 if name is None:
1023 return None
1024
1025 name_lower = name.lower()
1026 name_upper = name.upper()
1027
1028 if name_upper == name_lower:
1029 # name has no upper/lower conversion, e.g. non-european characters.
1030 # return unchanged
1031 return name
1032 elif name_upper == name and not (
1033 self.identifier_preparer._requires_quotes
1034 )(name_lower):
1035 # name is all uppercase and doesn't require quoting; normalize
1036 # to all lower case
1037 return name_lower
1038 elif name_lower == name:
1039 # name is all lower case, which if denormalized means we need to
1040 # force quoting on it
1041 return quoted_name(name, quote=True)
1042 else:
1043 # name is mixed case, means it will be quoted in SQL when used
1044 # later, no normalizes
1045 return name
1046
1047 def denormalize_name(self, name):
1048 if name is None:
1049 return None
1050
1051 name_lower = name.lower()
1052 name_upper = name.upper()
1053
1054 if name_upper == name_lower:
1055 # name has no upper/lower conversion, e.g. non-european characters.
1056 # return unchanged
1057 return name
1058 elif name_lower == name and not (
1059 self.identifier_preparer._requires_quotes
1060 )(name_lower):
1061 name = name_upper
1062 return name
1063
1064 def get_driver_connection(self, connection: DBAPIConnection) -> Any:
1065 return connection
1066
1067 def _overrides_default(self, method):
1068 return (
1069 getattr(type(self), method).__code__
1070 is not getattr(DefaultDialect, method).__code__
1071 )
1072
1073 def _default_multi_reflect(
1074 self,
1075 single_tbl_method,
1076 connection,
1077 kind,
1078 schema,
1079 filter_names,
1080 scope,
1081 **kw,
1082 ):
1083 names_fns = []
1084 temp_names_fns = []
1085 if ObjectKind.TABLE in kind:
1086 names_fns.append(self.get_table_names)
1087 temp_names_fns.append(self.get_temp_table_names)
1088 if ObjectKind.VIEW in kind:
1089 names_fns.append(self.get_view_names)
1090 temp_names_fns.append(self.get_temp_view_names)
1091 if ObjectKind.MATERIALIZED_VIEW in kind:
1092 names_fns.append(self.get_materialized_view_names)
1093 # no temp materialized view at the moment
1094 # temp_names_fns.append(self.get_temp_materialized_view_names)
1095
1096 unreflectable = kw.pop("unreflectable", {})
1097
1098 if (
1099 filter_names
1100 and scope is ObjectScope.ANY
1101 and kind is ObjectKind.ANY
1102 ):
1103 # if names are given and no qualification on type of table
1104 # (i.e. the Table(..., autoload) case), take the names as given,
1105 # don't run names queries. If a table does not exit
1106 # NoSuchTableError is raised and it's skipped
1107
1108 # this also suits the case for mssql where we can reflect
1109 # individual temp tables but there's no temp_names_fn
1110 names = filter_names
1111 else:
1112 names = []
1113 name_kw = {"schema": schema, **kw}
1114 fns = []
1115 if ObjectScope.DEFAULT in scope:
1116 fns.extend(names_fns)
1117 if ObjectScope.TEMPORARY in scope:
1118 fns.extend(temp_names_fns)
1119
1120 for fn in fns:
1121 try:
1122 names.extend(fn(connection, **name_kw))
1123 except NotImplementedError:
1124 pass
1125
1126 if filter_names:
1127 filter_names = set(filter_names)
1128
1129 # iterate over all the tables/views and call the single table method
1130 for table in names:
1131 if not filter_names or table in filter_names:
1132 key = (schema, table)
1133 try:
1134 yield (
1135 key,
1136 single_tbl_method(
1137 connection, table, schema=schema, **kw
1138 ),
1139 )
1140 except exc.UnreflectableTableError as err:
1141 if key not in unreflectable:
1142 unreflectable[key] = err
1143 except exc.NoSuchTableError:
1144 pass
1145
1146 def get_multi_table_options(self, connection, **kw):
1147 return self._default_multi_reflect(
1148 self.get_table_options, connection, **kw
1149 )
1150
1151 def get_multi_columns(self, connection, **kw):
1152 return self._default_multi_reflect(self.get_columns, connection, **kw)
1153
1154 def get_multi_pk_constraint(self, connection, **kw):
1155 return self._default_multi_reflect(
1156 self.get_pk_constraint, connection, **kw
1157 )
1158
1159 def get_multi_foreign_keys(self, connection, **kw):
1160 return self._default_multi_reflect(
1161 self.get_foreign_keys, connection, **kw
1162 )
1163
1164 def get_multi_indexes(self, connection, **kw):
1165 return self._default_multi_reflect(self.get_indexes, connection, **kw)
1166
1167 def get_multi_unique_constraints(self, connection, **kw):
1168 return self._default_multi_reflect(
1169 self.get_unique_constraints, connection, **kw
1170 )
1171
1172 def get_multi_check_constraints(self, connection, **kw):
1173 return self._default_multi_reflect(
1174 self.get_check_constraints, connection, **kw
1175 )
1176
1177 def get_multi_table_comment(self, connection, **kw):
1178 return self._default_multi_reflect(
1179 self.get_table_comment, connection, **kw
1180 )
1181
1182
1183class StrCompileDialect(DefaultDialect):
1184 statement_compiler = compiler.StrSQLCompiler
1185 ddl_compiler = compiler.DDLCompiler
1186 type_compiler_cls = compiler.StrSQLTypeCompiler
1187 preparer = compiler.IdentifierPreparer
1188
1189 insert_returning = True
1190 update_returning = True
1191 delete_returning = True
1192
1193 supports_statement_cache = True
1194
1195 supports_identity_columns = True
1196
1197 supports_sequences = True
1198 sequences_optional = True
1199 preexecute_autoincrement_sequences = False
1200
