Hide keyboard shortcuts

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 

7 

8"""Default implementations of per-dialect sqlalchemy.engine classes. 

9 

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. 

13 

14""" 

15 

16import codecs 

17import random 

18import re 

19import weakref 

20 

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 

34 

35 

36AUTOCOMMIT_REGEXP = re.compile( 

37 r"\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER)", re.I | re.UNICODE 

38) 

39 

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) 

42 

43 

44class DefaultDialect(interfaces.Dialect): 

45 """Default implementation of Dialect""" 

46 

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 

54 

55 # the first value we'd get for an autoincrement 

56 # column. 

57 default_sequence_base = 1 

58 

59 # most DBAPIs happy with this for execute(). 

60 # not cx_oracle. 

61 execute_sequence_format = tuple 

62 

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 

69 

70 supports_right_nested_joins = True 

71 cte_follows_insert = False 

72 

73 supports_native_enum = False 

74 supports_native_boolean = False 

75 non_native_boolean_check_constraint = True 

76 

77 supports_simple_order_by_label = True 

78 

79 tuple_in_values = False 

80 

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 ) 

93 

94 # if the NUMERIC type 

95 # returns decimal.Decimal. 

96 # *not* the FLOAT type however. 

97 supports_native_decimal = False 

98 

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" 

109 

110 name = "default" 

111 

112 # length at which to truncate 

113 # any identifier. 

114 max_identifier_length = 9999 

115 _user_defined_max_identifier_length = None 

116 

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 

123 

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 

131 

132 supports_is_distinct_from = True 

133 

134 supports_server_side_cursors = False 

135 

136 # extra record-level locking features (#4860) 

137 supports_for_update_of = False 

138 

139 server_version_info = None 

140 

141 construct_arguments = None 

142 """Optional set of argument specifiers for various SQLAlchemy 

143 constructs, typically schema items. 

144 

145 To implement, establish as a series of tuples, as in:: 

146 

147 construct_arguments = [ 

148 (schema.Index, { 

149 "using": False, 

150 "where": None, 

151 "ops": None 

152 }) 

153 ] 

154 

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`. 

160 

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. 

167 

168 .. versionadded:: 0.9.2 

169 

170 .. seealso:: 

171 

172 :class:`.DialectKWArgs` - implementing base class which consumes 

173 :attr:`.DefaultDialect.construct_arguments` 

174 

175 

176 """ 

177 

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 

184 

185 reflection_options = () 

186 

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. 

191 

192 .. versionadded:: 1.0.5 

193 

194 """ 

195 

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 ): 

220 

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 ) 

226 

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 

248 

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 ) 

260 

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 

267 

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) 

278 

279 @util.memoized_property 

280 def _type_memos(self): 

281 return weakref.WeakKeyDictionary() 

282 

283 @property 

284 def dialect_description(self): 

285 return self.name + "+" + self.driver 

286 

287 @property 

288 def supports_sane_rowcount_returning(self): 

289 """True if this dialect supports sane rowcount even if RETURNING is 

290 in use. 

291 

292 For dialects that don't support RETURNING, this is synomous 

293 with supports_sane_rowcount. 

294 

295 """ 

296 return self.supports_sane_rowcount 

297 

298 @classmethod 

299 def get_pool_class(cls, url): 

300 return getattr(cls, "poolclass", pool.QueuePool) 

301 

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 

309 

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 

323 

324 try: 

325 self.default_isolation_level = self.get_isolation_level( 

326 connection.connection 

327 ) 

328 except NotImplementedError: 

329 self.default_isolation_level = None 

330 

331 self.returns_unicode_strings = self._check_unicode_returns(connection) 

332 

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 

338 

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 

343 

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 ) 

353 

354 def on_connect(self): 

355 # inherits the docstring from interfaces.Dialect.on_connect 

356 return None 

357 

358 def _check_max_identifier_length(self, connection): 

359 """Perform a connection / server version specific check to determine 

360 the max_identifier_length. 

361 

362 If the dialect's class level max_identifier_length should be used, 

363 can return None. 

364 

365 .. versionadded:: 1.3.9 

366 

367 """ 

368 return None 

369 

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 

375 

376 if self.positional: 

377 parameters = self.execute_sequence_format() 

378 else: 

379 parameters = {} 

380 

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) 

400 

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 ] 

414 

415 if additional_tests: 

416 tests += additional_tests 

417 

418 results = {check_unicode(test) for test in tests} 

419 

420 if results.issuperset([True, False]): 

421 return "conditional" 

422 else: 

423 return results == {True} 

424 

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. 

429 

430 if util.py2k and not self.supports_unicode_statements: 

431 cast_to = util.binary_type 

432 else: 

433 cast_to = util.text_type 

434 

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() 

447 

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. 

451 

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`. 

455 

456 """ 

457 return sqltypes.adapt_type(typeobj, self.colspecs) 

458 

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 ) 

472 

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(). 

476 

477 """ 

478 return { 

479 "constrained_columns": self.get_primary_keys( 

480 conn, table_name, schema=schema, **kw 

481 ) 

482 } 

483 

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 ) 

490 

491 def connect(self, *cargs, **cparams): 

492 # inherits the docstring from interfaces.Dialect.connect 

493 return self.dbapi.connect(*cargs, **cparams) 

494 

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] 

500 

501 def set_engine_execution_options(self, engine, opts): 

502 if "isolation_level" in opts: 

503 isolation_level = opts["isolation_level"] 

504 

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) 

509 

510 if "schema_translate_map" in opts: 

511 getter = schema._schema_getter(opts["schema_translate_map"]) 

512 engine.schema_for_object = getter 

513 

514 @event.listens_for(engine, "engine_connect") 

515 def set_schema_translate_map(connection, branch): 

516 connection.schema_for_object = getter 

517 

518 def set_connection_execution_options(self, connection, opts): 

519 if "isolation_level" in opts: 

520 self._set_connection_isolation(connection, opts["isolation_level"]) 

521 

522 if "schema_translate_map" in opts: 

523 getter = schema._schema_getter(opts["schema_translate_map"]) 

524 connection.schema_for_object = getter 

525 

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 ) 

538 

539 def do_begin(self, dbapi_connection): 

540 pass 

541 

542 def do_rollback(self, dbapi_connection): 

543 dbapi_connection.rollback() 

544 

545 def do_commit(self, dbapi_connection): 

546 dbapi_connection.commit() 

547 

548 def do_close(self, dbapi_connection): 

549 dbapi_connection.close() 

550 

551 @util.memoized_property 

552 def _dialect_specific_select_one(self): 

553 return str(expression.select([1]).compile(dialect=self)) 

554 

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 

570 

571 def create_xid(self): 

572 """Create a random two-phase transaction ID. 

573 

574 This id will be passed to do_begin_twophase(), do_rollback_twophase(), 

575 do_commit_twophase(). Its format is unspecified. 

576 """ 

577 

578 return "_sa_%032x" % random.randint(0, 2 ** 128) 

579 

580 def do_savepoint(self, connection, name): 

581 connection.execute(expression.SavepointClause(name)) 

582 

583 def do_rollback_to_savepoint(self, connection, name): 

584 connection.execute(expression.RollbackToSavepointClause(name)) 

585 

586 def do_release_savepoint(self, connection, name): 

587 connection.execute(expression.ReleaseSavepointClause(name)) 

588 

589 def do_executemany(self, cursor, statement, parameters, context=None): 

590 cursor.executemany(statement, parameters) 

591 

592 def do_execute(self, cursor, statement, parameters, context=None): 

593 cursor.execute(statement, parameters) 

594 

595 def do_execute_no_params(self, cursor, statement, context=None): 

596 cursor.execute(statement) 

597 

598 def is_disconnect(self, e, connection, cursor): 

599 return False 

600 

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) 

606 

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) 

613 

614 name_lower = name.lower() 

615 name_upper = name.upper() 

616 

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 

635 

636 def denormalize_name(self, name): 

637 if name is None: 

638 return None 

639 

640 name_lower = name.lower() 

641 name_upper = name.upper() 

642 

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 

657 

658 

659class _RendersLiteral(object): 

660 def literal_processor(self, dialect): 

661 def process(value): 

662 return "'%s'" % value 

663 

664 return process 

665 

666 

667class _StrDateTime(_RendersLiteral, sqltypes.DateTime): 

668 pass 

669 

670 

671class _StrDate(_RendersLiteral, sqltypes.Date): 

672 pass 

673 

674 

675class _StrTime(_RendersLiteral, sqltypes.Time): 

676 pass 

677 

678 

679class StrCompileDialect(DefaultDialect): 

680 

681 statement_compiler = compiler.StrSQLCompiler 

682 ddl_compiler = compiler.DDLCompiler 

683 type_compiler = compiler.StrSQLTypeCompiler 

684 preparer = compiler.IdentifierPreparer 

685 

686 supports_sequences = True 

687 sequences_optional = True 

688 preexecute_autoincrement_sequences = False 

689 implicit_returning = False 

690 

691 supports_native_boolean = True 

692 

693 supports_simple_order_by_label = True 

694 

695 colspecs = { 

696 sqltypes.DateTime: _StrDateTime, 

697 sqltypes.Date: _StrDate, 

698 sqltypes.Time: _StrTime, 

699 } 

700 

701 

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 

716 

717 # a hook for SQLite's translation of 

718 # result column names 

719 _translate_colname = None 

720 

721 _expanded_parameters = util.immutabledict() 

722 

723 @classmethod 

724 def _init_ddl(cls, dialect, connection, dbapi_connection, compiled_ddl): 

725 """Initialize execution context for a DDLElement construct.""" 

726 

727 self = cls.__new__(cls) 

728 self.root_connection = connection 

729 self._dbapi_connection = dbapi_connection 

730 self.dialect = connection.dialect 

731 

732 self.compiled = compiled = compiled_ddl 

733 self.isddl = True 

734 

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) 

739 

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) 

745 

746 self.cursor = self.create_cursor() 

747 self.compiled_parameters = [] 

748 

749 if dialect.positional: 

750 self.parameters = [dialect.execute_sequence_format()] 

751 else: 

752 self.parameters = [{}] 

753 

754 return self 

755 

756 @classmethod 

757 def _init_compiled( 

758 cls, dialect, connection, dbapi_connection, compiled, parameters 

759 ): 

760 """Initialize execution context for a Compiled construct.""" 

761 

762 self = cls.__new__(cls) 

763 self.root_connection = connection 

764 self._dbapi_connection = dbapi_connection 

765 self.dialect = connection.dialect 

766 

767 self.compiled = compiled 

768 

769 # this should be caught in the engine before 

770 # we get here 

771 assert compiled.can_execute 

772 

773 self.execution_options = compiled.execution_options.union( 

774 connection._execution_options 

775 ) 

776 

777 self.result_column_struct = ( 

778 compiled._result_columns, 

779 compiled._ordered_columns, 

780 compiled._textual_ordered_columns, 

781 ) 

782 

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 

790 

791 self.isinsert = compiled.isinsert 

792 self.isupdate = compiled.isupdate 

793 self.isdelete = compiled.isdelete 

794 self.is_text = compiled.isplaintext 

795 

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 ] 

803 

804 self.executemany = len(parameters) > 1 

805 

806 self.cursor = self.create_cursor() 

807 

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 ) 

814 

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() 

820 

821 processors = compiled._bind_processors 

822 

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 

829 

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: 

846 

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 ) 

867 

868 parameters.append(param) 

869 

870 self.parameters = dialect.execute_sequence_format(parameters) 

871 

872 return self 

873 

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. 

877 

878 """ 

879 if self.executemany: 

880 raise exc.InvalidRequestError( 

881 "'expanding' parameters can't be used with " "executemany()" 

882 ) 

883 

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 ) 

890 

891 self._expanded_parameters = {} 

892 

893 compiled_params = self.compiled_parameters[0] 

894 if compiled.positional: 

895 positiontup = [] 

896 else: 

897 positiontup = None 

898 

899 replacement_expressions = {} 

900 to_update_sets = {} 

901 

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: 

909 

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) 

920 

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 ) 

930 

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 ) 

961 

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) 

975 

976 def process_expanding(m): 

977 return replacement_expressions[m.group(1)] 

978 

979 self.statement = re.sub( 

980 r"\[EXPANDING_(\S+)\]", process_expanding, self.statement 

981 ) 

982 return positiontup 

983 

984 @classmethod 

985 def _init_statement( 

986 cls, dialect, connection, dbapi_connection, statement, parameters 

987 ): 

988 """Initialize execution context for a string SQL statement.""" 

989 

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 

995 

996 # plain text statement 

997 self.execution_options = connection._execution_options 

998 

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 ] 

1018 

1019 self.executemany = len(parameters) > 1 

1020 

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 

1028 

1029 self.cursor = self.create_cursor() 

1030 return self 

1031 

1032 @classmethod 

1033 def _init_default(cls, dialect, connection, dbapi_connection): 

1034 """Initialize execution context for a ColumnDefault construct.""" 

1035 

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 

1043 

1044 @util.memoized_property 

1045 def engine(self): 

1046 return self.root_connection.engine 

1047 

1048 @util.memoized_property 

1049 def postfetch_cols(self): 

1050 return self.compiled.postfetch 

1051 

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 () 

1060 

1061 @util.memoized_property 

1062 def returning_cols(self): 

1063 self.compiled.returning 

1064 

1065 @util.memoized_property 

1066 def no_parameters(self): 

1067 return self.execution_options.get("no_parameters", False) 

1068 

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 ) 

1078 

1079 if autocommit is expression.PARSE_AUTOCOMMIT: 

1080 return self.should_autocommit_text(self.unicode_statement) 

1081 else: 

1082 return autocommit 

1083 

1084 def _execute_scalar(self, stmt, type_): 

1085 """Execute a string statement on the current cursor, returning a 

1086 scalar result. 

1087 

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. 

1091 

1092 """ 

1093 

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] 

1100 

1101 if self.dialect.positional: 

1102 default_params = self.dialect.execute_sequence_format() 

1103 else: 

1104 default_params = {} 

1105 

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 

1116 

1117 @property 

1118 def connection(self): 

1119 return self.root_connection._branch() 

1120 

1121 def should_autocommit_text(self, statement): 

1122 return AUTOCOMMIT_REGEXP.match(statement) 

1123 

1124 def _use_server_side_cursor(self): 

1125 if not self.dialect.supports_server_side_cursors: 

1126 return False 

1127 

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 ) 

1153 

1154 return use_server_side 

1155 

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() 

1163 

1164 def create_server_side_cursor(self): 

1165 raise NotImplementedError() 

1166 

1167 def pre_exec(self): 

1168 pass 

1169 

1170 def post_exec(self): 

1171 pass 

1172 

1173 def get_result_processor(self, type_, colname, coltype): 

1174 """Return a 'result processor' for a given type as present in 

1175 cursor.description. 

1176 

1177 This has a default implementation that dialects can override 

1178 for context-sensitive result type handling. 

1179 

1180 """ 

1181 return type_._cached_result_processor(self.dialect, coltype) 

1182 

1183 def get_lastrowid(self): 

1184 """return self.cursor.lastrowid, or equivalent, after an INSERT. 

1185 

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(). 

1190 

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. 

1196 

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(). 

1202 

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. 

1207 

1208 """ 

1209 return self.cursor.lastrowid 

1210 

1211 def handle_dbapi_exception(self, e): 

1212 pass 

1213 

1214 def get_result_proxy(self): 

1215 if self._is_server_side: 

1216 return result.BufferedRowResultProxy(self) 

1217 else: 

1218 return result.ResultProxy(self) 

1219 

1220 @property 

1221 def rowcount(self): 

1222 return self.cursor.rowcount 

1223 

1224 def supports_sane_rowcount(self): 

1225 return self.dialect.supports_sane_rowcount 

1226 

1227 def supports_sane_multi_rowcount(self): 

1228 return self.dialect.supports_sane_multi_rowcount 

1229 

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 ): 

1237 

1238 self._setup_ins_pk_from_lastrowid() 

1239 

1240 elif not self._is_implicit_returning: 

1241 self._setup_ins_pk_from_empty() 

1242 

1243 result = self.get_result_proxy() 

1244 

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 

1260 

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 

1268 

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] 

1273 

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 ] 

1297 

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 ] 

1305 

1306 def _setup_ins_pk_from_implicit_returning(self, row): 

1307 if row is None: 

1308 self.inserted_primary_key = None 

1309 return 

1310 

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 ] 

1321 

1322 def lastrow_has_defaults(self): 

1323 return (self.isinsert or self.isupdate) and bool( 

1324 self.compiled.postfetch 

1325 ) 

1326 

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. 

1333 

1334 This method only called by those dialects which require it, 

1335 currently cx_oracle. 

1336 

1337 """ 

1338 

1339 if not hasattr(self.compiled, "bind_names"): 

1340 return 

1341 

1342 inputsizes = {} 

1343 for bindparam in self.compiled.bind_names: 

1344 

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) 

1348 

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 

1365 

1366 if self.dialect._has_events: 

1367 self.dialect.dispatch.do_setinputsizes( 

1368 inputsizes, self.cursor, self.statement, self.parameters, self 

1369 ) 

1370 

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 ) 

1414 

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 

1433 

1434 current_parameters = None 

1435 """A dictionary of parameters applied to the current row. 

1436 

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. 

1444 

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. 

1450 

1451 .. seealso:: 

1452 

1453 :meth:`.DefaultExecutionContext.get_current_parameters` 

1454 

1455 :ref:`context_default_functions` 

1456 

1457 """ 

1458 

1459 def get_current_parameters(self, isolate_multiinsert_groups=True): 

1460 """Return a dictionary of parameters applied to the current row. 

1461 

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. 

1470 

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. 

1478 

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. 

1484 

1485 .. seealso:: 

1486 

1487 :attr:`.DefaultExecutionContext.current_parameters` 

1488 

1489 :ref:`context_default_functions` 

1490 

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 

1518 

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) 

1524 

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) 

1530 

1531 def _process_executemany_defaults(self): 

1532 key_getter = self.compiled._key_getters_for_crud_column[2] 

1533 

1534 scalar_defaults = {} 

1535 

1536 insert_prefetch = self.compiled.insert_prefetch 

1537 update_prefetch = self.compiled.update_prefetch 

1538 

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 

1548 

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 

1565 

1566 del self.current_parameters 

1567 

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] 

1573 

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) 

1579 

1580 if val is not None: 

1581 compiled_parameters[key_getter(c)] = val 

1582 

1583 for c in self.compiled.update_prefetch: 

1584 val = self.get_update_default(c) 

1585 

1586 if val is not None: 

1587 compiled_parameters[key_getter(c)] = val 

1588 del self.current_parameters 

1589 

1590 

1591DefaultDialect.execution_ctx_cls = DefaultExecutionContext