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# sql/base.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"""Foundational utilities common to many sql modules. 

9 

10""" 

11 

12 

13import itertools 

14import re 

15 

16from .visitors import ClauseVisitor 

17from .. import exc 

18from .. import util 

19 

20 

21PARSE_AUTOCOMMIT = util.symbol("PARSE_AUTOCOMMIT") 

22NO_ARG = util.symbol("NO_ARG") 

23 

24 

25class Immutable(object): 

26 """mark a ClauseElement as 'immutable' when expressions are cloned.""" 

27 

28 def unique_params(self, *optionaldict, **kwargs): 

29 raise NotImplementedError("Immutable objects do not support copying") 

30 

31 def params(self, *optionaldict, **kwargs): 

32 raise NotImplementedError("Immutable objects do not support copying") 

33 

34 def _clone(self): 

35 return self 

36 

37 

38def _from_objects(*elements): 

39 return itertools.chain(*[element._from_objects for element in elements]) 

40 

41 

42@util.decorator 

43def _generative(fn, *args, **kw): 

44 """Mark a method as generative.""" 

45 

46 self = args[0]._generate() 

47 fn(self, *args[1:], **kw) 

48 return self 

49 

50 

51class _DialectArgView(util.collections_abc.MutableMapping): 

52 """A dictionary view of dialect-level arguments in the form 

53 <dialectname>_<argument_name>. 

54 

55 """ 

56 

57 def __init__(self, obj): 

58 self.obj = obj 

59 

60 def _key(self, key): 

61 try: 

62 dialect, value_key = key.split("_", 1) 

63 except ValueError as err: 

64 util.raise_(KeyError(key), replace_context=err) 

65 else: 

66 return dialect, value_key 

67 

68 def __getitem__(self, key): 

69 dialect, value_key = self._key(key) 

70 

71 try: 

72 opt = self.obj.dialect_options[dialect] 

73 except exc.NoSuchModuleError as err: 

74 util.raise_(KeyError(key), replace_context=err) 

75 else: 

76 return opt[value_key] 

77 

78 def __setitem__(self, key, value): 

79 try: 

80 dialect, value_key = self._key(key) 

81 except KeyError as err: 

82 util.raise_( 

83 exc.ArgumentError( 

84 "Keys must be of the form <dialectname>_<argname>" 

85 ), 

86 replace_context=err, 

87 ) 

88 else: 

89 self.obj.dialect_options[dialect][value_key] = value 

90 

91 def __delitem__(self, key): 

92 dialect, value_key = self._key(key) 

93 del self.obj.dialect_options[dialect][value_key] 

94 

95 def __len__(self): 

96 return sum( 

97 len(args._non_defaults) 

98 for args in self.obj.dialect_options.values() 

99 ) 

100 

101 def __iter__(self): 

102 return ( 

103 util.safe_kwarg("%s_%s" % (dialect_name, value_name)) 

104 for dialect_name in self.obj.dialect_options 

105 for value_name in self.obj.dialect_options[ 

106 dialect_name 

107 ]._non_defaults 

108 ) 

109 

110 

111class _DialectArgDict(util.collections_abc.MutableMapping): 

112 """A dictionary view of dialect-level arguments for a specific 

113 dialect. 

114 

115 Maintains a separate collection of user-specified arguments 

116 and dialect-specified default arguments. 

117 

118 """ 

119 

120 def __init__(self): 

121 self._non_defaults = {} 

122 self._defaults = {} 

123 

124 def __len__(self): 

125 return len(set(self._non_defaults).union(self._defaults)) 

126 

127 def __iter__(self): 

128 return iter(set(self._non_defaults).union(self._defaults)) 

129 

130 def __getitem__(self, key): 

131 if key in self._non_defaults: 

132 return self._non_defaults[key] 

133 else: 

134 return self._defaults[key] 

135 

136 def __setitem__(self, key, value): 

137 self._non_defaults[key] = value 

138 

139 def __delitem__(self, key): 

140 del self._non_defaults[key] 

141 

142 

143class DialectKWArgs(object): 

144 """Establish the ability for a class to have dialect-specific arguments 

145 with defaults and constructor validation. 

146 

147 The :class:`.DialectKWArgs` interacts with the 

148 :attr:`.DefaultDialect.construct_arguments` present on a dialect. 

149 

150 .. seealso:: 

151 

152 :attr:`.DefaultDialect.construct_arguments` 

153 

154 """ 

155 

156 @classmethod 

157 def argument_for(cls, dialect_name, argument_name, default): 

158 """Add a new kind of dialect-specific keyword argument for this class. 

159 

160 E.g.:: 

161 

162 Index.argument_for("mydialect", "length", None) 

163 

164 some_index = Index('a', 'b', mydialect_length=5) 

165 

166 The :meth:`.DialectKWArgs.argument_for` method is a per-argument 

167 way adding extra arguments to the 

168 :attr:`.DefaultDialect.construct_arguments` dictionary. This 

169 dictionary provides a list of argument names accepted by various 

170 schema-level constructs on behalf of a dialect. 

171 

172 New dialects should typically specify this dictionary all at once as a 

173 data member of the dialect class. The use case for ad-hoc addition of 

174 argument names is typically for end-user code that is also using 

175 a custom compilation scheme which consumes the additional arguments. 

176 

177 :param dialect_name: name of a dialect. The dialect must be 

178 locatable, else a :class:`.NoSuchModuleError` is raised. The 

179 dialect must also include an existing 

180 :attr:`.DefaultDialect.construct_arguments` collection, indicating 

181 that it participates in the keyword-argument validation and default 

182 system, else :class:`.ArgumentError` is raised. If the dialect does 

183 not include this collection, then any keyword argument can be 

184 specified on behalf of this dialect already. All dialects packaged 

185 within SQLAlchemy include this collection, however for third party 

186 dialects, support may vary. 

187 

188 :param argument_name: name of the parameter. 

189 

190 :param default: default value of the parameter. 

191 

192 .. versionadded:: 0.9.4 

193 

194 """ 

195 

196 construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name] 

197 if construct_arg_dictionary is None: 

198 raise exc.ArgumentError( 

199 "Dialect '%s' does have keyword-argument " 

200 "validation and defaults enabled configured" % dialect_name 

201 ) 

202 if cls not in construct_arg_dictionary: 

203 construct_arg_dictionary[cls] = {} 

204 construct_arg_dictionary[cls][argument_name] = default 

205 

206 @util.memoized_property 

207 def dialect_kwargs(self): 

208 """A collection of keyword arguments specified as dialect-specific 

209 options to this construct. 

210 

211 The arguments are present here in their original ``<dialect>_<kwarg>`` 

212 format. Only arguments that were actually passed are included; 

213 unlike the :attr:`.DialectKWArgs.dialect_options` collection, which 

214 contains all options known by this dialect including defaults. 

215 

216 The collection is also writable; keys are accepted of the 

217 form ``<dialect>_<kwarg>`` where the value will be assembled 

218 into the list of options. 

219 

220 .. versionadded:: 0.9.2 

221 

222 .. versionchanged:: 0.9.4 The :attr:`.DialectKWArgs.dialect_kwargs` 

223 collection is now writable. 

224 

225 .. seealso:: 

226 

227 :attr:`.DialectKWArgs.dialect_options` - nested dictionary form 

228 

229 """ 

230 return _DialectArgView(self) 

231 

232 @property 

233 def kwargs(self): 

234 """A synonym for :attr:`.DialectKWArgs.dialect_kwargs`.""" 

235 return self.dialect_kwargs 

236 

237 @util.dependencies("sqlalchemy.dialects") 

238 def _kw_reg_for_dialect(dialects, dialect_name): 

239 dialect_cls = dialects.registry.load(dialect_name) 

240 if dialect_cls.construct_arguments is None: 

241 return None 

242 return dict(dialect_cls.construct_arguments) 

243 

244 _kw_registry = util.PopulateDict(_kw_reg_for_dialect) 

245 

246 def _kw_reg_for_dialect_cls(self, dialect_name): 

247 construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name] 

248 d = _DialectArgDict() 

249 

250 if construct_arg_dictionary is None: 

251 d._defaults.update({"*": None}) 

252 else: 

253 for cls in reversed(self.__class__.__mro__): 

254 if cls in construct_arg_dictionary: 

255 d._defaults.update(construct_arg_dictionary[cls]) 

256 return d 

257 

258 @util.memoized_property 

259 def dialect_options(self): 

260 """A collection of keyword arguments specified as dialect-specific 

261 options to this construct. 

262 

263 This is a two-level nested registry, keyed to ``<dialect_name>`` 

264 and ``<argument_name>``. For example, the ``postgresql_where`` 

265 argument would be locatable as:: 

266 

267 arg = my_object.dialect_options['postgresql']['where'] 

268 

269 .. versionadded:: 0.9.2 

270 

271 .. seealso:: 

272 

273 :attr:`.DialectKWArgs.dialect_kwargs` - flat dictionary form 

274 

275 """ 

276 

277 return util.PopulateDict( 

278 util.portable_instancemethod(self._kw_reg_for_dialect_cls) 

279 ) 

280 

281 def _validate_dialect_kwargs(self, kwargs): 

282 # validate remaining kwargs that they all specify DB prefixes 

283 

284 if not kwargs: 

285 return 

286 

287 for k in kwargs: 

288 m = re.match("^(.+?)_(.+)$", k) 

289 if not m: 

290 raise TypeError( 

291 "Additional arguments should be " 

292 "named <dialectname>_<argument>, got '%s'" % k 

293 ) 

294 dialect_name, arg_name = m.group(1, 2) 

295 

296 try: 

297 construct_arg_dictionary = self.dialect_options[dialect_name] 

298 except exc.NoSuchModuleError: 

299 util.warn( 

300 "Can't validate argument %r; can't " 

301 "locate any SQLAlchemy dialect named %r" 

302 % (k, dialect_name) 

303 ) 

304 self.dialect_options[dialect_name] = d = _DialectArgDict() 

305 d._defaults.update({"*": None}) 

306 d._non_defaults[arg_name] = kwargs[k] 

307 else: 

308 if ( 

309 "*" not in construct_arg_dictionary 

310 and arg_name not in construct_arg_dictionary 

311 ): 

312 raise exc.ArgumentError( 

313 "Argument %r is not accepted by " 

314 "dialect %r on behalf of %r" 

315 % (k, dialect_name, self.__class__) 

316 ) 

317 else: 

318 construct_arg_dictionary[arg_name] = kwargs[k] 

319 

320 

321class Generative(object): 

322 """Allow a ClauseElement to generate itself via the 

323 @_generative decorator. 

324 

325 """ 

326 

327 def _generate(self): 

328 s = self.__class__.__new__(self.__class__) 

329 s.__dict__ = self.__dict__.copy() 

330 return s 

331 

332 

333class Executable(Generative): 

334 """Mark a ClauseElement as supporting execution. 

335 

336 :class:`.Executable` is a superclass for all "statement" types 

337 of objects, including :func:`select`, :func:`delete`, :func:`update`, 

338 :func:`insert`, :func:`text`. 

339 

340 """ 

341 

342 supports_execution = True 

343 _execution_options = util.immutabledict() 

344 _bind = None 

345 

346 @_generative 

347 def execution_options(self, **kw): 

348 """ Set non-SQL options for the statement which take effect during 

349 execution. 

350 

351 Execution options can be set on a per-statement or 

352 per :class:`_engine.Connection` basis. Additionally, the 

353 :class:`_engine.Engine` and ORM :class:`~.orm.query.Query` 

354 objects provide 

355 access to execution options which they in turn configure upon 

356 connections. 

357 

358 The :meth:`execution_options` method is generative. A new 

359 instance of this statement is returned that contains the options:: 

360 

361 statement = select([table.c.x, table.c.y]) 

362 statement = statement.execution_options(autocommit=True) 

363 

364 Note that only a subset of possible execution options can be applied 

365 to a statement - these include "autocommit" and "stream_results", 

366 but not "isolation_level" or "compiled_cache". 

367 See :meth:`_engine.Connection.execution_options` for a full list of 

368 possible options. 

369 

370 .. seealso:: 

371 

372 :meth:`_engine.Connection.execution_options` 

373 

374 :meth:`_query.Query.execution_options` 

375 

376 :meth:`.Executable.get_execution_options` 

377 

378 """ 

379 if "isolation_level" in kw: 

380 raise exc.ArgumentError( 

381 "'isolation_level' execution option may only be specified " 

382 "on Connection.execution_options(), or " 

383 "per-engine using the isolation_level " 

384 "argument to create_engine()." 

385 ) 

386 if "compiled_cache" in kw: 

387 raise exc.ArgumentError( 

388 "'compiled_cache' execution option may only be specified " 

389 "on Connection.execution_options(), not per statement." 

390 ) 

391 self._execution_options = self._execution_options.union(kw) 

392 

393 def get_execution_options(self): 

394 """ Get the non-SQL options which will take effect during execution. 

395 

396 .. versionadded:: 1.3 

397 

398 .. seealso:: 

399 

400 :meth:`.Executable.execution_options` 

401 """ 

402 return self._execution_options 

403 

404 def execute(self, *multiparams, **params): 

405 """Compile and execute this :class:`.Executable`. 

406 

407 """ 

408 e = self.bind 

409 if e is None: 

410 label = getattr(self, "description", self.__class__.__name__) 

411 msg = ( 

412 "This %s is not directly bound to a Connection or Engine. " 

413 "Use the .execute() method of a Connection or Engine " 

414 "to execute this construct." % label 

415 ) 

416 raise exc.UnboundExecutionError(msg) 

417 return e._execute_clauseelement(self, multiparams, params) 

418 

419 def scalar(self, *multiparams, **params): 

420 """Compile and execute this :class:`.Executable`, returning the 

421 result's scalar representation. 

422 

423 """ 

424 return self.execute(*multiparams, **params).scalar() 

425 

426 @property 

427 def bind(self): 

428 """Returns the :class:`_engine.Engine` or :class:`_engine.Connection` 

429 to 

430 which this :class:`.Executable` is bound, or None if none found. 

431 

432 This is a traversal which checks locally, then 

433 checks among the "from" clauses of associated objects 

434 until a bound engine or connection is found. 

435 

436 """ 

437 if self._bind is not None: 

438 return self._bind 

439 

440 for f in _from_objects(self): 

441 if f is self: 

442 continue 

443 engine = f.bind 

444 if engine is not None: 

445 return engine 

446 else: 

447 return None 

448 

449 

450class SchemaEventTarget(object): 

451 """Base class for elements that are the targets of :class:`.DDLEvents` 

452 events. 

453 

454 This includes :class:`.SchemaItem` as well as :class:`.SchemaType`. 

455 

456 """ 

457 

458 def _set_parent(self, parent): 

459 """Associate with this SchemaEvent's parent object.""" 

460 

461 def _set_parent_with_dispatch(self, parent): 

462 self.dispatch.before_parent_attach(self, parent) 

463 self._set_parent(parent) 

464 self.dispatch.after_parent_attach(self, parent) 

465 

466 

467class SchemaVisitor(ClauseVisitor): 

468 """Define the visiting for ``SchemaItem`` objects.""" 

469 

470 __traverse_options__ = {"schema_visitor": True} 

471 

472 

473class ColumnCollection(util.OrderedProperties): 

474 """An ordered dictionary that stores a list of ColumnElement 

475 instances. 

476 

477 Overrides the ``__eq__()`` method to produce SQL clauses between 

478 sets of correlated columns. 

479 

480 """ 

481 

482 __slots__ = "_all_columns" 

483 

484 def __init__(self, *columns): 

485 super(ColumnCollection, self).__init__() 

486 object.__setattr__(self, "_all_columns", []) 

487 for c in columns: 

488 self.add(c) 

489 

490 def __str__(self): 

491 return repr([str(c) for c in self]) 

492 

493 def replace(self, column): 

494 """add the given column to this collection, removing unaliased 

495 versions of this column as well as existing columns with the 

496 same key. 

497 

498 e.g.:: 

499 

500 t = Table('sometable', metadata, Column('col1', Integer)) 

501 t.columns.replace(Column('col1', Integer, key='columnone')) 

502 

503 will remove the original 'col1' from the collection, and add 

504 the new column under the name 'columnname'. 

505 

506 Used by schema.Column to override columns during table reflection. 

507 

508 """ 

509 remove_col = None 

510 if column.name in self and column.key != column.name: 

511 other = self[column.name] 

512 if other.name == other.key: 

513 remove_col = other 

514 del self._data[other.key] 

515 

516 if column.key in self._data: 

517 remove_col = self._data[column.key] 

518 

519 self._data[column.key] = column 

520 if remove_col is not None: 

521 self._all_columns[:] = [ 

522 column if c is remove_col else c for c in self._all_columns 

523 ] 

524 else: 

525 self._all_columns.append(column) 

526 

527 def add(self, column): 

528 """Add a column to this collection. 

529 

530 The key attribute of the column will be used as the hash key 

531 for this dictionary. 

532 

533 """ 

534 if not column.key: 

535 raise exc.ArgumentError( 

536 "Can't add unnamed column to column collection" 

537 ) 

538 self[column.key] = column 

539 

540 def __delitem__(self, key): 

541 raise NotImplementedError() 

542 

543 def __setattr__(self, key, obj): 

544 raise NotImplementedError() 

545 

546 def __setitem__(self, key, value): 

547 if key in self: 

548 

549 # this warning is primarily to catch select() statements 

550 # which have conflicting column names in their exported 

551 # columns collection 

552 

553 existing = self[key] 

554 

555 if existing is value: 

556 return 

557 

558 if not existing.shares_lineage(value): 

559 util.warn( 

560 "Column %r on table %r being replaced by " 

561 "%r, which has the same key. Consider " 

562 "use_labels for select() statements." 

563 % (key, getattr(existing, "table", None), value) 

564 ) 

565 

566 # pop out memoized proxy_set as this 

567 # operation may very well be occurring 

568 # in a _make_proxy operation 

569 util.memoized_property.reset(value, "proxy_set") 

570 

571 self._all_columns.append(value) 

572 self._data[key] = value 

573 

574 def clear(self): 

575 raise NotImplementedError() 

576 

577 def remove(self, column): 

578 del self._data[column.key] 

579 self._all_columns[:] = [ 

580 c for c in self._all_columns if c is not column 

581 ] 

582 

583 def update(self, iter_): 

584 cols = list(iter_) 

585 all_col_set = set(self._all_columns) 

586 self._all_columns.extend( 

587 c for label, c in cols if c not in all_col_set 

588 ) 

589 self._data.update((label, c) for label, c in cols) 

590 

591 def extend(self, iter_): 

592 cols = list(iter_) 

593 all_col_set = set(self._all_columns) 

594 self._all_columns.extend(c for c in cols if c not in all_col_set) 

595 self._data.update((c.key, c) for c in cols) 

596 

597 __hash__ = None 

598 

599 @util.dependencies("sqlalchemy.sql.elements") 

600 def __eq__(self, elements, other): 

601 l = [] 

602 for c in getattr(other, "_all_columns", other): 

603 for local in self._all_columns: 

604 if c.shares_lineage(local): 

605 l.append(c == local) 

606 return elements.and_(*l) 

607 

608 def __contains__(self, other): 

609 if not isinstance(other, util.string_types): 

610 raise exc.ArgumentError("__contains__ requires a string argument") 

611 return util.OrderedProperties.__contains__(self, other) 

612 

613 def __getstate__(self): 

614 return {"_data": self._data, "_all_columns": self._all_columns} 

615 

616 def __setstate__(self, state): 

617 object.__setattr__(self, "_data", state["_data"]) 

618 object.__setattr__(self, "_all_columns", state["_all_columns"]) 

619 

620 def contains_column(self, col): 

621 return col in set(self._all_columns) 

622 

623 def as_immutable(self): 

624 return ImmutableColumnCollection(self._data, self._all_columns) 

625 

626 

627class ImmutableColumnCollection(util.ImmutableProperties, ColumnCollection): 

628 def __init__(self, data, all_columns): 

629 util.ImmutableProperties.__init__(self, data) 

630 object.__setattr__(self, "_all_columns", all_columns) 

631 

632 extend = remove = util.ImmutableProperties._immutable 

633 

634 

635class ColumnSet(util.ordered_column_set): 

636 def contains_column(self, col): 

637 return col in self 

638 

639 def extend(self, cols): 

640 for col in cols: 

641 self.add(col) 

642 

643 def __add__(self, other): 

644 return list(self) + list(other) 

645 

646 @util.dependencies("sqlalchemy.sql.elements") 

647 def __eq__(self, elements, other): 

648 l = [] 

649 for c in other: 

650 for local in self: 

651 if c.shares_lineage(local): 

652 l.append(c == local) 

653 return elements.and_(*l) 

654 

655 def __hash__(self): 

656 return hash(tuple(x for x in self)) 

657 

658 

659def _bind_or_error(schemaitem, msg=None): 

660 bind = schemaitem.bind 

661 if not bind: 

662 name = schemaitem.__class__.__name__ 

663 label = getattr( 

664 schemaitem, "fullname", getattr(schemaitem, "name", None) 

665 ) 

666 if label: 

667 item = "%s object %r" % (name, label) 

668 else: 

669 item = "%s object" % name 

670 if msg is None: 

671 msg = ( 

672 "%s is not bound to an Engine or Connection. " 

673 "Execution can not proceed without a database to execute " 

674 "against." % item 

675 ) 

676 raise exc.UnboundExecutionError(msg) 

677 return bind