1# sql/base.py
2# Copyright (C) 2005-2024 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
8"""Foundational utilities common to many sql modules.
9
10"""
11
12
13import itertools
14import operator
15import re
16
17from . import roles
18from . import visitors
19from .traversals import HasCacheKey # noqa
20from .traversals import HasCopyInternals # noqa
21from .traversals import MemoizedHasCacheKey # noqa
22from .visitors import ClauseVisitor
23from .visitors import ExtendedInternalTraversal
24from .visitors import InternalTraversal
25from .. import exc
26from .. import util
27from ..util import HasMemoized
28from ..util import hybridmethod
29
30
31coercions = None
32elements = None
33type_api = None
34
35PARSE_AUTOCOMMIT = util.symbol("PARSE_AUTOCOMMIT")
36NO_ARG = util.symbol("NO_ARG")
37
38
39class Immutable(object):
40 """mark a ClauseElement as 'immutable' when expressions are cloned."""
41
42 _is_immutable = True
43
44 def unique_params(self, *optionaldict, **kwargs):
45 raise NotImplementedError("Immutable objects do not support copying")
46
47 def params(self, *optionaldict, **kwargs):
48 raise NotImplementedError("Immutable objects do not support copying")
49
50 def _clone(self, **kw):
51 return self
52
53 def _copy_internals(self, **kw):
54 pass
55
56
57class SingletonConstant(Immutable):
58 """Represent SQL constants like NULL, TRUE, FALSE"""
59
60 _is_singleton_constant = True
61
62 def __new__(cls, *arg, **kw):
63 return cls._singleton
64
65 @classmethod
66 def _create_singleton(cls):
67 obj = object.__new__(cls)
68 obj.__init__()
69
70 # for a long time this was an empty frozenset, meaning
71 # a SingletonConstant would never be a "corresponding column" in
72 # a statement. This referred to #6259. However, in #7154 we see
73 # that we do in fact need "correspondence" to work when matching cols
74 # in result sets, so the non-correspondence was moved to a more
75 # specific level when we are actually adapting expressions for SQL
76 # render only.
77 obj.proxy_set = frozenset([obj])
78 cls._singleton = obj
79
80
81def _from_objects(*elements):
82 return itertools.chain.from_iterable(
83 [element._from_objects for element in elements]
84 )
85
86
87def _select_iterables(elements):
88 """expand tables into individual columns in the
89 given list of column expressions.
90
91 """
92 return itertools.chain.from_iterable(
93 [c._select_iterable for c in elements]
94 )
95
96
97def _generative(fn):
98 """non-caching _generative() decorator.
99
100 This is basically the legacy decorator that copies the object and
101 runs a method on the new copy.
102
103 """
104
105 @util.decorator
106 def _generative(fn, self, *args, **kw):
107 """Mark a method as generative."""
108
109 self = self._generate()
110 x = fn(self, *args, **kw)
111 assert x is None, "generative methods must have no return value"
112 return self
113
114 decorated = _generative(fn)
115 decorated.non_generative = fn
116 return decorated
117
118
119def _exclusive_against(*names, **kw):
120 msgs = kw.pop("msgs", {})
121
122 defaults = kw.pop("defaults", {})
123
124 getters = [
125 (name, operator.attrgetter(name), defaults.get(name, None))
126 for name in names
127 ]
128
129 @util.decorator
130 def check(fn, *args, **kw):
131 # make pylance happy by not including "self" in the argument
132 # list
133 self = args[0]
134 args = args[1:]
135 for name, getter, default_ in getters:
136 if getter(self) is not default_:
137 msg = msgs.get(
138 name,
139 "Method %s() has already been invoked on this %s construct"
140 % (fn.__name__, self.__class__),
141 )
142 raise exc.InvalidRequestError(msg)
143 return fn(self, *args, **kw)
144
145 return check
146
147
148def _clone(element, **kw):
149 return element._clone(**kw)
150
151
152def _expand_cloned(elements):
153 """expand the given set of ClauseElements to be the set of all 'cloned'
154 predecessors.
155
156 """
157 return itertools.chain(*[x._cloned_set for x in elements])
158
159
160def _cloned_intersection(a, b):
161 """return the intersection of sets a and b, counting
162 any overlap between 'cloned' predecessors.
163
164 The returned set is in terms of the entities present within 'a'.
165
166 """
167 all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b))
168 return set(
169 elem for elem in a if all_overlap.intersection(elem._cloned_set)
170 )
171
172
173def _cloned_difference(a, b):
174 all_overlap = set(_expand_cloned(a)).intersection(_expand_cloned(b))
175 return set(
176 elem for elem in a if not all_overlap.intersection(elem._cloned_set)
177 )
178
179
180class _DialectArgView(util.collections_abc.MutableMapping):
181 """A dictionary view of dialect-level arguments in the form
182 <dialectname>_<argument_name>.
183
184 """
185
186 def __init__(self, obj):
187 self.obj = obj
188
189 def _key(self, key):
190 try:
191 dialect, value_key = key.split("_", 1)
192 except ValueError as err:
193 util.raise_(KeyError(key), replace_context=err)
194 else:
195 return dialect, value_key
196
197 def __getitem__(self, key):
198 dialect, value_key = self._key(key)
199
200 try:
201 opt = self.obj.dialect_options[dialect]
202 except exc.NoSuchModuleError as err:
203 util.raise_(KeyError(key), replace_context=err)
204 else:
205 return opt[value_key]
206
207 def __setitem__(self, key, value):
208 try:
209 dialect, value_key = self._key(key)
210 except KeyError as err:
211 util.raise_(
212 exc.ArgumentError(
213 "Keys must be of the form <dialectname>_<argname>"
214 ),
215 replace_context=err,
216 )
217 else:
218 self.obj.dialect_options[dialect][value_key] = value
219
220 def __delitem__(self, key):
221 dialect, value_key = self._key(key)
222 del self.obj.dialect_options[dialect][value_key]
223
224 def __len__(self):
225 return sum(
226 len(args._non_defaults)
227 for args in self.obj.dialect_options.values()
228 )
229
230 def __iter__(self):
231 return (
232 "%s_%s" % (dialect_name, value_name)
233 for dialect_name in self.obj.dialect_options
234 for value_name in self.obj.dialect_options[
235 dialect_name
236 ]._non_defaults
237 )
238
239
240class _DialectArgDict(util.collections_abc.MutableMapping):
241 """A dictionary view of dialect-level arguments for a specific
242 dialect.
243
244 Maintains a separate collection of user-specified arguments
245 and dialect-specified default arguments.
246
247 """
248
249 def __init__(self):
250 self._non_defaults = {}
251 self._defaults = {}
252
253 def __len__(self):
254 return len(set(self._non_defaults).union(self._defaults))
255
256 def __iter__(self):
257 return iter(set(self._non_defaults).union(self._defaults))
258
259 def __getitem__(self, key):
260 if key in self._non_defaults:
261 return self._non_defaults[key]
262 else:
263 return self._defaults[key]
264
265 def __setitem__(self, key, value):
266 self._non_defaults[key] = value
267
268 def __delitem__(self, key):
269 del self._non_defaults[key]
270
271
272@util.preload_module("sqlalchemy.dialects")
273def _kw_reg_for_dialect(dialect_name):
274 dialect_cls = util.preloaded.dialects.registry.load(dialect_name)
275 if dialect_cls.construct_arguments is None:
276 return None
277 return dict(dialect_cls.construct_arguments)
278
279
280class DialectKWArgs(object):
281 """Establish the ability for a class to have dialect-specific arguments
282 with defaults and constructor validation.
283
284 The :class:`.DialectKWArgs` interacts with the
285 :attr:`.DefaultDialect.construct_arguments` present on a dialect.
286
287 .. seealso::
288
289 :attr:`.DefaultDialect.construct_arguments`
290
291 """
292
293 _dialect_kwargs_traverse_internals = [
294 ("dialect_options", InternalTraversal.dp_dialect_options)
295 ]
296
297 @classmethod
298 def argument_for(cls, dialect_name, argument_name, default):
299 """Add a new kind of dialect-specific keyword argument for this class.
300
301 E.g.::
302
303 Index.argument_for("mydialect", "length", None)
304
305 some_index = Index('a', 'b', mydialect_length=5)
306
307 The :meth:`.DialectKWArgs.argument_for` method is a per-argument
308 way adding extra arguments to the
309 :attr:`.DefaultDialect.construct_arguments` dictionary. This
310 dictionary provides a list of argument names accepted by various
311 schema-level constructs on behalf of a dialect.
312
313 New dialects should typically specify this dictionary all at once as a
314 data member of the dialect class. The use case for ad-hoc addition of
315 argument names is typically for end-user code that is also using
316 a custom compilation scheme which consumes the additional arguments.
317
318 :param dialect_name: name of a dialect. The dialect must be
319 locatable, else a :class:`.NoSuchModuleError` is raised. The
320 dialect must also include an existing
321 :attr:`.DefaultDialect.construct_arguments` collection, indicating
322 that it participates in the keyword-argument validation and default
323 system, else :class:`.ArgumentError` is raised. If the dialect does
324 not include this collection, then any keyword argument can be
325 specified on behalf of this dialect already. All dialects packaged
326 within SQLAlchemy include this collection, however for third party
327 dialects, support may vary.
328
329 :param argument_name: name of the parameter.
330
331 :param default: default value of the parameter.
332
333 .. versionadded:: 0.9.4
334
335 """
336
337 construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name]
338 if construct_arg_dictionary is None:
339 raise exc.ArgumentError(
340 "Dialect '%s' does have keyword-argument "
341 "validation and defaults enabled configured" % dialect_name
342 )
343 if cls not in construct_arg_dictionary:
344 construct_arg_dictionary[cls] = {}
345 construct_arg_dictionary[cls][argument_name] = default
346
347 @util.memoized_property
348 def dialect_kwargs(self):
349 """A collection of keyword arguments specified as dialect-specific
350 options to this construct.
351
352 The arguments are present here in their original ``<dialect>_<kwarg>``
353 format. Only arguments that were actually passed are included;
354 unlike the :attr:`.DialectKWArgs.dialect_options` collection, which
355 contains all options known by this dialect including defaults.
356
357 The collection is also writable; keys are accepted of the
358 form ``<dialect>_<kwarg>`` where the value will be assembled
359 into the list of options.
360
361 .. versionadded:: 0.9.2
362
363 .. versionchanged:: 0.9.4 The :attr:`.DialectKWArgs.dialect_kwargs`
364 collection is now writable.
365
366 .. seealso::
367
368 :attr:`.DialectKWArgs.dialect_options` - nested dictionary form
369
370 """
371 return _DialectArgView(self)
372
373 @property
374 def kwargs(self):
375 """A synonym for :attr:`.DialectKWArgs.dialect_kwargs`."""
376 return self.dialect_kwargs
377
378 _kw_registry = util.PopulateDict(_kw_reg_for_dialect)
379
380 def _kw_reg_for_dialect_cls(self, dialect_name):
381 construct_arg_dictionary = DialectKWArgs._kw_registry[dialect_name]
382 d = _DialectArgDict()
383
384 if construct_arg_dictionary is None:
385 d._defaults.update({"*": None})
386 else:
387 for cls in reversed(self.__class__.__mro__):
388 if cls in construct_arg_dictionary:
389 d._defaults.update(construct_arg_dictionary[cls])
390 return d
391
392 @util.memoized_property
393 def dialect_options(self):
394 """A collection of keyword arguments specified as dialect-specific
395 options to this construct.
396
397 This is a two-level nested registry, keyed to ``<dialect_name>``
398 and ``<argument_name>``. For example, the ``postgresql_where``
399 argument would be locatable as::
400
401 arg = my_object.dialect_options['postgresql']['where']
402
403 .. versionadded:: 0.9.2
404
405 .. seealso::
406
407 :attr:`.DialectKWArgs.dialect_kwargs` - flat dictionary form
408
409 """
410
411 return util.PopulateDict(
412 util.portable_instancemethod(self._kw_reg_for_dialect_cls)
413 )
414
415 def _validate_dialect_kwargs(self, kwargs):
416 # validate remaining kwargs that they all specify DB prefixes
417
418 if not kwargs:
419 return
420
421 for k in kwargs:
422 m = re.match("^(.+?)_(.+)$", k)
423 if not m:
424 raise TypeError(
425 "Additional arguments should be "
426 "named <dialectname>_<argument>, got '%s'" % k
427 )
428 dialect_name, arg_name = m.group(1, 2)
429
430 try:
431 construct_arg_dictionary = self.dialect_options[dialect_name]
432 except exc.NoSuchModuleError:
433 util.warn(
434 "Can't validate argument %r; can't "
435 "locate any SQLAlchemy dialect named %r"
436 % (k, dialect_name)
437 )
438 self.dialect_options[dialect_name] = d = _DialectArgDict()
439 d._defaults.update({"*": None})
440 d._non_defaults[arg_name] = kwargs[k]
441 else:
442 if (
443 "*" not in construct_arg_dictionary
444 and arg_name not in construct_arg_dictionary
445 ):
446 raise exc.ArgumentError(
447 "Argument %r is not accepted by "
448 "dialect %r on behalf of %r"
449 % (k, dialect_name, self.__class__)
450 )
451 else:
452 construct_arg_dictionary[arg_name] = kwargs[k]
453
454
455class CompileState(object):
456 """Produces additional object state necessary for a statement to be
457 compiled.
458
459 the :class:`.CompileState` class is at the base of classes that assemble
460 state for a particular statement object that is then used by the
461 compiler. This process is essentially an extension of the process that
462 the SQLCompiler.visit_XYZ() method takes, however there is an emphasis
463 on converting raw user intent into more organized structures rather than
464 producing string output. The top-level :class:`.CompileState` for the
465 statement being executed is also accessible when the execution context
466 works with invoking the statement and collecting results.
467
468 The production of :class:`.CompileState` is specific to the compiler, such
469 as within the :meth:`.SQLCompiler.visit_insert`,
470 :meth:`.SQLCompiler.visit_select` etc. methods. These methods are also
471 responsible for associating the :class:`.CompileState` with the
472 :class:`.SQLCompiler` itself, if the statement is the "toplevel" statement,
473 i.e. the outermost SQL statement that's actually being executed.
474 There can be other :class:`.CompileState` objects that are not the
475 toplevel, such as when a SELECT subquery or CTE-nested
476 INSERT/UPDATE/DELETE is generated.
477
478 .. versionadded:: 1.4
479
480 """
481
482 __slots__ = ("statement",)
483
484 plugins = {}
485
486 @classmethod
487 def create_for_statement(cls, statement, compiler, **kw):
488 # factory construction.
489
490 if statement._propagate_attrs:
491 plugin_name = statement._propagate_attrs.get(
492 "compile_state_plugin", "default"
493 )
494 klass = cls.plugins.get(
495 (plugin_name, statement._effective_plugin_target), None
496 )
497 if klass is None:
498 klass = cls.plugins[
499 ("default", statement._effective_plugin_target)
500 ]
501
502 else:
503 klass = cls.plugins[
504 ("default", statement._effective_plugin_target)
505 ]
506
507 if klass is cls:
508 return cls(statement, compiler, **kw)
509 else:
510 return klass.create_for_statement(statement, compiler, **kw)
511
512 def __init__(self, statement, compiler, **kw):
513 self.statement = statement
514
515 @classmethod
516 def get_plugin_class(cls, statement):
517 plugin_name = statement._propagate_attrs.get(
518 "compile_state_plugin", None
519 )
520
521 if plugin_name:
522 key = (plugin_name, statement._effective_plugin_target)
523 if key in cls.plugins:
524 return cls.plugins[key]
525
526 # there's no case where we call upon get_plugin_class() and want
527 # to get None back, there should always be a default. return that
528 # if there was no plugin-specific class (e.g. Insert with "orm"
529 # plugin)
530 try:
531 return cls.plugins[("default", statement._effective_plugin_target)]
532 except KeyError:
533 return None
534
535 @classmethod
536 def _get_plugin_class_for_plugin(cls, statement, plugin_name):
537 try:
538 return cls.plugins[
539 (plugin_name, statement._effective_plugin_target)
540 ]
541 except KeyError:
542 return None
543
544 @classmethod
545 def plugin_for(cls, plugin_name, visit_name):
546 def decorate(cls_to_decorate):
547 cls.plugins[(plugin_name, visit_name)] = cls_to_decorate
548 return cls_to_decorate
549
550 return decorate
551
552
553class Generative(HasMemoized):
554 """Provide a method-chaining pattern in conjunction with the
555 @_generative decorator."""
556
557 def _generate(self):
558 skip = self._memoized_keys
559 cls = self.__class__
560 s = cls.__new__(cls)
561 if skip:
562 # ensure this iteration remains atomic
563 s.__dict__ = {
564 k: v for k, v in self.__dict__.copy().items() if k not in skip
565 }
566 else:
567 s.__dict__ = self.__dict__.copy()
568 return s
569
570
571class InPlaceGenerative(HasMemoized):
572 """Provide a method-chaining pattern in conjunction with the
573 @_generative decorator that mutates in place."""
574
575 def _generate(self):
576 skip = self._memoized_keys
577 for k in skip:
578 self.__dict__.pop(k, None)
579 return self
580
581
582class HasCompileState(Generative):
583 """A class that has a :class:`.CompileState` associated with it."""
584
585 _compile_state_plugin = None
586
587 _attributes = util.immutabledict()
588
589 _compile_state_factory = CompileState.create_for_statement
590
591
592class _MetaOptions(type):
593 """metaclass for the Options class."""
594
595 def __init__(cls, classname, bases, dict_):
596 cls._cache_attrs = tuple(
597 sorted(
598 d
599 for d in dict_
600 if not d.startswith("__")
601 and d not in ("_cache_key_traversal",)
602 )
603 )
604 type.__init__(cls, classname, bases, dict_)
605
606 def __add__(self, other):
607 o1 = self()
608
609 if set(other).difference(self._cache_attrs):
610 raise TypeError(
611 "dictionary contains attributes not covered by "
612 "Options class %s: %r"
613 % (self, set(other).difference(self._cache_attrs))
614 )
615
616 o1.__dict__.update(other)
617 return o1
618
619
620class Options(util.with_metaclass(_MetaOptions)):
621 """A cacheable option dictionary with defaults."""
622
623 def __init__(self, **kw):
624 self.__dict__.update(kw)
625
626 def __add__(self, other):
627 o1 = self.__class__.__new__(self.__class__)
628 o1.__dict__.update(self.__dict__)
629
630 if set(other).difference(self._cache_attrs):
631 raise TypeError(
632 "dictionary contains attributes not covered by "
633 "Options class %s: %r"
634 % (self, set(other).difference(self._cache_attrs))
635 )
636
637 o1.__dict__.update(other)
638 return o1
639
640 def __eq__(self, other):
641 # TODO: very inefficient. This is used only in test suites
642 # right now.
643 for a, b in util.zip_longest(self._cache_attrs, other._cache_attrs):
644 if getattr(self, a) != getattr(other, b):
645 return False
646 return True
647
648 def __repr__(self):
649 # TODO: fairly inefficient, used only in debugging right now.
650
651 return "%s(%s)" % (
652 self.__class__.__name__,
653 ", ".join(
654 "%s=%r" % (k, self.__dict__[k])
655 for k in self._cache_attrs
656 if k in self.__dict__
657 ),
658 )
659
660 @classmethod
661 def isinstance(cls, klass):
662 return issubclass(cls, klass)
663
664 @hybridmethod
665 def add_to_element(self, name, value):
666 return self + {name: getattr(self, name) + value}
667
668 @hybridmethod
669 def _state_dict(self):
670 return self.__dict__
671
672 _state_dict_const = util.immutabledict()
673
674 @_state_dict.classlevel
675 def _state_dict(cls):
676 return cls._state_dict_const
677
678 @classmethod
679 def safe_merge(cls, other):
680 d = other._state_dict()
681
682 # only support a merge with another object of our class
683 # and which does not have attrs that we don't. otherwise
684 # we risk having state that might not be part of our cache
685 # key strategy
686
687 if (
688 cls is not other.__class__
689 and other._cache_attrs
690 and set(other._cache_attrs).difference(cls._cache_attrs)
691 ):
692 raise TypeError(
693 "other element %r is not empty, is not of type %s, "
694 "and contains attributes not covered here %r"
695 % (
696 other,
697 cls,
698 set(other._cache_attrs).difference(cls._cache_attrs),
699 )
700 )
701 return cls + d
702
703 @classmethod
704 def from_execution_options(
705 cls, key, attrs, exec_options, statement_exec_options
706 ):
707 """process Options argument in terms of execution options.
708
709
710 e.g.::
711
712 (
713 load_options,
714 execution_options,
715 ) = QueryContext.default_load_options.from_execution_options(
716 "_sa_orm_load_options",
717 {
718 "populate_existing",
719 "autoflush",
720 "yield_per"
721 },
722 execution_options,
723 statement._execution_options,
724 )
725
726 get back the Options and refresh "_sa_orm_load_options" in the
727 exec options dict w/ the Options as well
728
729 """
730
731 # common case is that no options we are looking for are
732 # in either dictionary, so cancel for that first
733 check_argnames = attrs.intersection(
734 set(exec_options).union(statement_exec_options)
735 )
736
737 existing_options = exec_options.get(key, cls)
738
739 if check_argnames:
740 result = {}
741 for argname in check_argnames:
742 local = "_" + argname
743 if argname in exec_options:
744 result[local] = exec_options[argname]
745 elif argname in statement_exec_options:
746 result[local] = statement_exec_options[argname]
747
748 new_options = existing_options + result
749 exec_options = util.immutabledict().merge_with(
750 exec_options, {key: new_options}
751 )
752 return new_options, exec_options
753
754 else:
755 return existing_options, exec_options
756
757
758class CacheableOptions(Options, HasCacheKey):
759 @hybridmethod
760 def _gen_cache_key(self, anon_map, bindparams):
761 return HasCacheKey._gen_cache_key(self, anon_map, bindparams)
762
763 @_gen_cache_key.classlevel
764 def _gen_cache_key(cls, anon_map, bindparams):
765 return (cls, ())
766
767 @hybridmethod
768 def _generate_cache_key(self):
769 return HasCacheKey._generate_cache_key_for_object(self)
770
771
772class ExecutableOption(HasCopyInternals):
773 _annotations = util.EMPTY_DICT
774
775 __visit_name__ = "executable_option"
776
777 _is_has_cache_key = False
778
779 def _clone(self, **kw):
780 """Create a shallow copy of this ExecutableOption."""
781 c = self.__class__.__new__(self.__class__)
782 c.__dict__ = dict(self.__dict__)
783 return c
784
785
786class Executable(roles.StatementRole, Generative):
787 """Mark a :class:`_expression.ClauseElement` as supporting execution.
788
789 :class:`.Executable` is a superclass for all "statement" types
790 of objects, including :func:`select`, :func:`delete`, :func:`update`,
791 :func:`insert`, :func:`text`.
792
793 """
794
795 supports_execution = True
796 _execution_options = util.immutabledict()
797 _bind = None
798 _with_options = ()
799 _with_context_options = ()
800
801 _executable_traverse_internals = [
802 ("_with_options", InternalTraversal.dp_executable_options),
803 (
804 "_with_context_options",
805 ExtendedInternalTraversal.dp_with_context_options,
806 ),
807 ("_propagate_attrs", ExtendedInternalTraversal.dp_propagate_attrs),
808 ]
809
810 is_select = False
811 is_update = False
812 is_insert = False
813 is_text = False
814 is_delete = False
815 is_dml = False
816
817 @property
818 def _effective_plugin_target(self):
819 return self.__visit_name__
820
821 @_generative
822 def options(self, *options):
823 """Apply options to this statement.
824
825 In the general sense, options are any kind of Python object
826 that can be interpreted by the SQL compiler for the statement.
827 These options can be consumed by specific dialects or specific kinds
828 of compilers.
829
830 The most commonly known kind of option are the ORM level options
831 that apply "eager load" and other loading behaviors to an ORM
832 query. However, options can theoretically be used for many other
833 purposes.
834
835 For background on specific kinds of options for specific kinds of
836 statements, refer to the documentation for those option objects.
837
838 .. versionchanged:: 1.4 - added :meth:`.Generative.options` to
839 Core statement objects towards the goal of allowing unified
840 Core / ORM querying capabilities.
841
842 .. seealso::
843
844 :ref:`deferred_options` - refers to options specific to the usage
845 of ORM queries
846
847 :ref:`relationship_loader_options` - refers to options specific
848 to the usage of ORM queries
849
850 """
851 self._with_options += tuple(
852 coercions.expect(roles.ExecutableOptionRole, opt)
853 for opt in options
854 )
855
856 @_generative
857 def _set_compile_options(self, compile_options):
858 """Assign the compile options to a new value.
859
860 :param compile_options: appropriate CacheableOptions structure
861
862 """
863
864 self._compile_options = compile_options
865
866 @_generative
867 def _update_compile_options(self, options):
868 """update the _compile_options with new keys."""
869
870 self._compile_options += options
871
872 @_generative
873 def _add_context_option(self, callable_, cache_args):
874 """Add a context option to this statement.
875
876 These are callable functions that will
877 be given the CompileState object upon compilation.
878
879 A second argument cache_args is required, which will be combined with
880 the ``__code__`` identity of the function itself in order to produce a
881 cache key.
882
883 """
884 self._with_context_options += ((callable_, cache_args),)
885
886 @_generative
887 def execution_options(self, **kw):
888 """Set non-SQL options for the statement which take effect during
889 execution.
890
891 Execution options can be set on a per-statement or
892 per :class:`_engine.Connection` basis. Additionally, the
893 :class:`_engine.Engine` and ORM :class:`~.orm.query.Query`
894 objects provide
895 access to execution options which they in turn configure upon
896 connections.
897
898 The :meth:`execution_options` method is generative. A new
899 instance of this statement is returned that contains the options::
900
901 statement = select(table.c.x, table.c.y)
902 statement = statement.execution_options(autocommit=True)
903
904 Note that only a subset of possible execution options can be applied
905 to a statement - these include "autocommit" and "stream_results",
906 but not "isolation_level" or "compiled_cache".
907 See :meth:`_engine.Connection.execution_options` for a full list of
908 possible options.
909
910 .. seealso::
911
912 :meth:`_engine.Connection.execution_options`
913
914 :meth:`_query.Query.execution_options`
915
916 :meth:`.Executable.get_execution_options`
917
918 """
919 if "isolation_level" in kw:
920 raise exc.ArgumentError(
921 "'isolation_level' execution option may only be specified "
922 "on Connection.execution_options(), or "
923 "per-engine using the isolation_level "
924 "argument to create_engine()."
925 )
926 if "compiled_cache" in kw:
927 raise exc.ArgumentError(
928 "'compiled_cache' execution option may only be specified "
929 "on Connection.execution_options(), not per statement."
930 )
931 self._execution_options = self._execution_options.union(kw)
932
933 def get_execution_options(self):
934 """Get the non-SQL options which will take effect during execution.
935
936 .. versionadded:: 1.3
937
938 .. seealso::
939
940 :meth:`.Executable.execution_options`
941 """
942 return self._execution_options
943
944 @util.deprecated_20(
945 ":meth:`.Executable.execute`",
946 alternative="All statement execution in SQLAlchemy 2.0 is performed "
947 "by the :meth:`_engine.Connection.execute` method of "
948 ":class:`_engine.Connection`, "
949 "or in the ORM by the :meth:`.Session.execute` method of "
950 ":class:`.Session`.",
951 )
952 def execute(self, *multiparams, **params):
953 """Compile and execute this :class:`.Executable`."""
954 e = self.bind
955 if e is None:
956 label = (
957 getattr(self, "description", None) or self.__class__.__name__
958 )
959 msg = (
960 "This %s is not directly bound to a Connection or Engine. "
961 "Use the .execute() method of a Connection or Engine "
962 "to execute this construct." % label
963 )
964 raise exc.UnboundExecutionError(msg)
965 return e._execute_clauseelement(
966 self, multiparams, params, util.immutabledict()
967 )
968
969 @util.deprecated_20(
970 ":meth:`.Executable.scalar`",
971 alternative="Scalar execution in SQLAlchemy 2.0 is performed "
972 "by the :meth:`_engine.Connection.scalar` method of "
973 ":class:`_engine.Connection`, "
974 "or in the ORM by the :meth:`.Session.scalar` method of "
975 ":class:`.Session`.",
976 )
977 def scalar(self, *multiparams, **params):
978 """Compile and execute this :class:`.Executable`, returning the
979 result's scalar representation.
980
981 """
982 return self.execute(*multiparams, **params).scalar()
983
984 @property
985 @util.deprecated_20(
986 ":attr:`.Executable.bind`",
987 alternative="Bound metadata is being removed as of SQLAlchemy 2.0.",
988 enable_warnings=False,
989 )
990 def bind(self):
991 """Returns the :class:`_engine.Engine` or :class:`_engine.Connection`
992 to
993 which this :class:`.Executable` is bound, or None if none found.
994
995 This is a traversal which checks locally, then
996 checks among the "from" clauses of associated objects
997 until a bound engine or connection is found.
998
999 """
1000 if self._bind is not None:
1001 return self._bind
1002
1003 for f in _from_objects(self):
1004 if f is self:
1005 continue
1006 engine = f.bind
1007 if engine is not None:
1008 return engine
1009 else:
1010 return None
1011
1012
1013class prefix_anon_map(dict):
1014 """A map that creates new keys for missing key access.
1015
1016 Considers keys of the form "<ident> <name>" to produce
1017 new symbols "<name>_<index>", where "index" is an incrementing integer
1018 corresponding to <name>.
1019
1020 Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
1021 is otherwise usually used for this type of operation.
1022
1023 """
1024
1025 def __missing__(self, key):
1026 (ident, derived) = key.split(" ", 1)
1027 anonymous_counter = self.get(derived, 1)
1028 self[derived] = anonymous_counter + 1
1029 value = derived + "_" + str(anonymous_counter)
1030 self[key] = value
1031 return value
1032
1033
1034class SchemaEventTarget(object):
1035 """Base class for elements that are the targets of :class:`.DDLEvents`
1036 events.
1037
1038 This includes :class:`.SchemaItem` as well as :class:`.SchemaType`.
1039
1040 """
1041
1042 def _set_parent(self, parent, **kw):
1043 """Associate with this SchemaEvent's parent object."""
1044
1045 def _set_parent_with_dispatch(self, parent, **kw):
1046 self.dispatch.before_parent_attach(self, parent)
1047 self._set_parent(parent, **kw)
1048 self.dispatch.after_parent_attach(self, parent)
1049
1050
1051class SchemaVisitor(ClauseVisitor):
1052 """Define the visiting for ``SchemaItem`` objects."""
1053
1054 __traverse_options__ = {"schema_visitor": True}
1055
1056
1057class ColumnCollection(object):
1058 """Collection of :class:`_expression.ColumnElement` instances,
1059 typically for
1060 :class:`_sql.FromClause` objects.
1061
1062 The :class:`_sql.ColumnCollection` object is most commonly available
1063 as the :attr:`_schema.Table.c` or :attr:`_schema.Table.columns` collection
1064 on the :class:`_schema.Table` object, introduced at
1065 :ref:`metadata_tables_and_columns`.
1066
1067 The :class:`_expression.ColumnCollection` has both mapping- and sequence-
1068 like behaviors. A :class:`_expression.ColumnCollection` usually stores
1069 :class:`_schema.Column` objects, which are then accessible both via mapping
1070 style access as well as attribute access style.
1071
1072 To access :class:`_schema.Column` objects using ordinary attribute-style
1073 access, specify the name like any other object attribute, such as below
1074 a column named ``employee_name`` is accessed::
1075
1076 >>> employee_table.c.employee_name
1077
1078 To access columns that have names with special characters or spaces,
1079 index-style access is used, such as below which illustrates a column named
1080 ``employee ' payment`` is accessed::
1081
1082 >>> employee_table.c["employee ' payment"]
1083
1084 As the :class:`_sql.ColumnCollection` object provides a Python dictionary
1085 interface, common dictionary method names like
1086 :meth:`_sql.ColumnCollection.keys`, :meth:`_sql.ColumnCollection.values`,
1087 and :meth:`_sql.ColumnCollection.items` are available, which means that
1088 database columns that are keyed under these names also need to use indexed
1089 access::
1090
1091 >>> employee_table.c["values"]
1092
1093
1094 The name for which a :class:`_schema.Column` would be present is normally
1095 that of the :paramref:`_schema.Column.key` parameter. In some contexts,
1096 such as a :class:`_sql.Select` object that uses a label style set
1097 using the :meth:`_sql.Select.set_label_style` method, a column of a certain
1098 key may instead be represented under a particular label name such
1099 as ``tablename_columnname``::
1100
1101 >>> from sqlalchemy import select, column, table
1102 >>> from sqlalchemy import LABEL_STYLE_TABLENAME_PLUS_COL
1103 >>> t = table("t", column("c"))
1104 >>> stmt = select(t).set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
1105 >>> subq = stmt.subquery()
1106 >>> subq.c.t_c
1107 <sqlalchemy.sql.elements.ColumnClause at 0x7f59dcf04fa0; t_c>
1108
1109 :class:`.ColumnCollection` also indexes the columns in order and allows
1110 them to be accessible by their integer position::
1111
1112 >>> cc[0]
1113 Column('x', Integer(), table=None)
1114 >>> cc[1]
1115 Column('y', Integer(), table=None)
1116
1117 .. versionadded:: 1.4 :class:`_expression.ColumnCollection`
1118 allows integer-based
1119 index access to the collection.
1120
1121 Iterating the collection yields the column expressions in order::
1122
1123 >>> list(cc)
1124 [Column('x', Integer(), table=None),
1125 Column('y', Integer(), table=None)]
1126
1127 The base :class:`_expression.ColumnCollection` object can store
1128 duplicates, which can
1129 mean either two columns with the same key, in which case the column
1130 returned by key access is **arbitrary**::
1131
1132 >>> x1, x2 = Column('x', Integer), Column('x', Integer)
1133 >>> cc = ColumnCollection(columns=[(x1.name, x1), (x2.name, x2)])
1134 >>> list(cc)
1135 [Column('x', Integer(), table=None),
1136 Column('x', Integer(), table=None)]
1137 >>> cc['x'] is x1
1138 False
1139 >>> cc['x'] is x2
1140 True
1141
1142 Or it can also mean the same column multiple times. These cases are
1143 supported as :class:`_expression.ColumnCollection`
1144 is used to represent the columns in
1145 a SELECT statement which may include duplicates.
1146
1147 A special subclass :class:`.DedupeColumnCollection` exists which instead
1148 maintains SQLAlchemy's older behavior of not allowing duplicates; this
1149 collection is used for schema level objects like :class:`_schema.Table`
1150 and
1151 :class:`.PrimaryKeyConstraint` where this deduping is helpful. The
1152 :class:`.DedupeColumnCollection` class also has additional mutation methods
1153 as the schema constructs have more use cases that require removal and
1154 replacement of columns.
1155
1156 .. versionchanged:: 1.4 :class:`_expression.ColumnCollection`
1157 now stores duplicate
1158 column keys as well as the same column in multiple positions. The
1159 :class:`.DedupeColumnCollection` class is added to maintain the
1160 former behavior in those cases where deduplication as well as
1161 additional replace/remove operations are needed.
1162
1163
1164 """
1165
1166 __slots__ = "_collection", "_index", "_colset"
1167
1168 def __init__(self, columns=None):
1169 object.__setattr__(self, "_colset", set())
1170 object.__setattr__(self, "_index", {})
1171 object.__setattr__(self, "_collection", [])
1172 if columns:
1173 self._initial_populate(columns)
1174
1175 def _initial_populate(self, iter_):
1176 self._populate_separate_keys(iter_)
1177
1178 @property
1179 def _all_columns(self):
1180 return [col for (k, col) in self._collection]
1181
1182 def keys(self):
1183 """Return a sequence of string key names for all columns in this
1184 collection."""
1185 return [k for (k, col) in self._collection]
1186
1187 def values(self):
1188 """Return a sequence of :class:`_sql.ColumnClause` or
1189 :class:`_schema.Column` objects for all columns in this
1190 collection."""
1191 return [col for (k, col) in self._collection]
1192
1193 def items(self):
1194 """Return a sequence of (key, column) tuples for all columns in this
1195 collection each consisting of a string key name and a
1196 :class:`_sql.ColumnClause` or
1197 :class:`_schema.Column` object.
1198 """
1199
1200 return list(self._collection)
1201
1202 def __bool__(self):
1203 return bool(self._collection)
1204
1205 def __len__(self):
1206 return len(self._collection)
1207
1208 def __iter__(self):
1209 # turn to a list first to maintain over a course of changes
1210 return iter([col for k, col in self._collection])
1211
1212 def __getitem__(self, key):
1213 try:
1214 return self._index[key]
1215 except KeyError as err:
1216 if isinstance(key, util.int_types):
1217 util.raise_(IndexError(key), replace_context=err)
1218 else:
1219 raise
1220
1221 def __getattr__(self, key):
1222 try:
1223 return self._index[key]
1224 except KeyError as err:
1225 util.raise_(AttributeError(key), replace_context=err)
1226
1227 def __contains__(self, key):
1228 if key not in self._index:
1229 if not isinstance(key, util.string_types):
1230 raise exc.ArgumentError(
1231 "__contains__ requires a string argument"
1232 )
1233 return False
1234 else:
1235 return True
1236
1237 def compare(self, other):
1238 """Compare this :class:`_expression.ColumnCollection` to another
1239 based on the names of the keys"""
1240
1241 for l, r in util.zip_longest(self, other):
1242 if l is not r:
1243 return False
1244 else:
1245 return True
1246
1247 def __eq__(self, other):
1248 return self.compare(other)
1249
1250 def get(self, key, default=None):
1251 """Get a :class:`_sql.ColumnClause` or :class:`_schema.Column` object
1252 based on a string key name from this
1253 :class:`_expression.ColumnCollection`."""
1254
1255 if key in self._index:
1256 return self._index[key]
1257 else:
1258 return default
1259
1260 def __str__(self):
1261 return "%s(%s)" % (
1262 self.__class__.__name__,
1263 ", ".join(str(c) for c in self),
1264 )
1265
1266 def __setitem__(self, key, value):
1267 raise NotImplementedError()
1268
1269 def __delitem__(self, key):
1270 raise NotImplementedError()
1271
1272 def __setattr__(self, key, obj):
1273 raise NotImplementedError()
1274
1275 def clear(self):
1276 """Dictionary clear() is not implemented for
1277 :class:`_sql.ColumnCollection`."""
1278 raise NotImplementedError()
1279
1280 def remove(self, column):
1281 """Dictionary remove() is not implemented for
1282 :class:`_sql.ColumnCollection`."""
1283 raise NotImplementedError()
1284
1285 def update(self, iter_):
1286 """Dictionary update() is not implemented for
1287 :class:`_sql.ColumnCollection`."""
1288 raise NotImplementedError()
1289
1290 __hash__ = None
1291
1292 def _populate_separate_keys(self, iter_):
1293 """populate from an iterator of (key, column)"""
1294 cols = list(iter_)
1295 self._collection[:] = cols
1296 self._colset.update(c for k, c in self._collection)
1297 self._index.update(
1298 (idx, c) for idx, (k, c) in enumerate(self._collection)
1299 )
1300 self._index.update({k: col for k, col in reversed(self._collection)})
1301
1302 def add(self, column, key=None):
1303 """Add a column to this :class:`_sql.ColumnCollection`.
1304
1305 .. note::
1306
1307 This method is **not normally used by user-facing code**, as the
1308 :class:`_sql.ColumnCollection` is usually part of an existing
1309 object such as a :class:`_schema.Table`. To add a
1310 :class:`_schema.Column` to an existing :class:`_schema.Table`
1311 object, use the :meth:`_schema.Table.append_column` method.
1312
1313 """
1314 if key is None:
1315 key = column.key
1316
1317 l = len(self._collection)
1318 self._collection.append((key, column))
1319 self._colset.add(column)
1320 self._index[l] = column
1321 if key not in self._index:
1322 self._index[key] = column
1323
1324 def __getstate__(self):
1325 return {"_collection": self._collection, "_index": self._index}
1326
1327 def __setstate__(self, state):
1328 object.__setattr__(self, "_index", state["_index"])
1329 object.__setattr__(self, "_collection", state["_collection"])
1330 object.__setattr__(
1331 self, "_colset", {col for k, col in self._collection}
1332 )
1333
1334 def contains_column(self, col):
1335 """Checks if a column object exists in this collection"""
1336 if col not in self._colset:
1337 if isinstance(col, util.string_types):
1338 raise exc.ArgumentError(
1339 "contains_column cannot be used with string arguments. "
1340 "Use ``col_name in table.c`` instead."
1341 )
1342 return False
1343 else:
1344 return True
1345
1346 def as_immutable(self):
1347 """Return an "immutable" form of this
1348 :class:`_sql.ColumnCollection`."""
1349
1350 return ImmutableColumnCollection(self)
1351
1352 def corresponding_column(self, column, require_embedded=False):
1353 """Given a :class:`_expression.ColumnElement`, return the exported
1354 :class:`_expression.ColumnElement` object from this
1355 :class:`_expression.ColumnCollection`
1356 which corresponds to that original :class:`_expression.ColumnElement`
1357 via a common
1358 ancestor column.
1359
1360 :param column: the target :class:`_expression.ColumnElement`
1361 to be matched.
1362
1363 :param require_embedded: only return corresponding columns for
1364 the given :class:`_expression.ColumnElement`, if the given
1365 :class:`_expression.ColumnElement`
1366 is actually present within a sub-element
1367 of this :class:`_expression.Selectable`.
1368 Normally the column will match if
1369 it merely shares a common ancestor with one of the exported
1370 columns of this :class:`_expression.Selectable`.
1371
1372 .. seealso::
1373
1374 :meth:`_expression.Selectable.corresponding_column`
1375 - invokes this method
1376 against the collection returned by
1377 :attr:`_expression.Selectable.exported_columns`.
1378
1379 .. versionchanged:: 1.4 the implementation for ``corresponding_column``
1380 was moved onto the :class:`_expression.ColumnCollection` itself.
1381
1382 """
1383
1384 def embedded(expanded_proxy_set, target_set):
1385 for t in target_set.difference(expanded_proxy_set):
1386 if not set(_expand_cloned([t])).intersection(
1387 expanded_proxy_set
1388 ):
1389 return False
1390 return True
1391
1392 # don't dig around if the column is locally present
1393 if column in self._colset:
1394 return column
1395 col, intersect = None, None
1396 target_set = column.proxy_set
1397 cols = [c for (k, c) in self._collection]
1398 for c in cols:
1399 expanded_proxy_set = set(_expand_cloned(c.proxy_set))
1400 i = target_set.intersection(expanded_proxy_set)
1401 if i and (
1402 not require_embedded
1403 or embedded(expanded_proxy_set, target_set)
1404 ):
1405 if col is None:
1406
1407 # no corresponding column yet, pick this one.
1408
1409 col, intersect = c, i
1410 elif len(i) > len(intersect):
1411
1412 # 'c' has a larger field of correspondence than
1413 # 'col'. i.e. selectable.c.a1_x->a1.c.x->table.c.x
1414 # matches a1.c.x->table.c.x better than
1415 # selectable.c.x->table.c.x does.
1416
1417 col, intersect = c, i
1418 elif i == intersect:
1419 # they have the same field of correspondence. see
1420 # which proxy_set has fewer columns in it, which
1421 # indicates a closer relationship with the root
1422 # column. Also take into account the "weight"
1423 # attribute which CompoundSelect() uses to give
1424 # higher precedence to columns based on vertical
1425 # position in the compound statement, and discard
1426 # columns that have no reference to the target
1427 # column (also occurs with CompoundSelect)
1428
1429 col_distance = util.reduce(
1430 operator.add,
1431 [
1432 sc._annotations.get("weight", 1)
1433 for sc in col._uncached_proxy_list()
1434 if sc.shares_lineage(column)
1435 ],
1436 )
1437 c_distance = util.reduce(
1438 operator.add,
1439 [
1440 sc._annotations.get("weight", 1)
1441 for sc in c._uncached_proxy_list()
1442 if sc.shares_lineage(column)
1443 ],
1444 )
1445 if c_distance < col_distance:
1446 col, intersect = c, i
1447 return col
1448
1449
1450class DedupeColumnCollection(ColumnCollection):
1451 """A :class:`_expression.ColumnCollection`
1452 that maintains deduplicating behavior.
1453
1454 This is useful by schema level objects such as :class:`_schema.Table` and
1455 :class:`.PrimaryKeyConstraint`. The collection includes more
1456 sophisticated mutator methods as well to suit schema objects which
1457 require mutable column collections.
1458
1459 .. versionadded:: 1.4
1460
1461 """
1462
1463 def add(self, column, key=None):
1464
1465 if key is not None and column.key != key:
1466 raise exc.ArgumentError(
1467 "DedupeColumnCollection requires columns be under "
1468 "the same key as their .key"
1469 )
1470 key = column.key
1471
1472 if key is None:
1473 raise exc.ArgumentError(
1474 "Can't add unnamed column to column collection"
1475 )
1476
1477 if key in self._index:
1478
1479 existing = self._index[key]
1480
1481 if existing is column:
1482 return
1483
1484 self.replace(column)
1485
1486 # pop out memoized proxy_set as this
1487 # operation may very well be occurring
1488 # in a _make_proxy operation
1489 util.memoized_property.reset(column, "proxy_set")
1490 else:
1491 l = len(self._collection)
1492 self._collection.append((key, column))
1493 self._colset.add(column)
1494 self._index[l] = column
1495 self._index[key] = column
1496
1497 def _populate_separate_keys(self, iter_):
1498 """populate from an iterator of (key, column)"""
1499 cols = list(iter_)
1500
1501 replace_col = []
1502 for k, col in cols:
1503 if col.key != k:
1504 raise exc.ArgumentError(
1505 "DedupeColumnCollection requires columns be under "
1506 "the same key as their .key"
1507 )
1508 if col.name in self._index and col.key != col.name:
1509 replace_col.append(col)
1510 elif col.key in self._index:
1511 replace_col.append(col)
1512 else:
1513 self._index[k] = col
1514 self._collection.append((k, col))
1515 self._colset.update(c for (k, c) in self._collection)
1516 self._index.update(
1517 (idx, c) for idx, (k, c) in enumerate(self._collection)
1518 )
1519 for col in replace_col:
1520 self.replace(col)
1521
1522 def extend(self, iter_):
1523 self._populate_separate_keys((col.key, col) for col in iter_)
1524
1525 def remove(self, column):
1526 if column not in self._colset:
1527 raise ValueError(
1528 "Can't remove column %r; column is not in this collection"
1529 % column
1530 )
1531 del self._index[column.key]
1532 self._colset.remove(column)
1533 self._collection[:] = [
1534 (k, c) for (k, c) in self._collection if c is not column
1535 ]
1536 self._index.update(
1537 {idx: col for idx, (k, col) in enumerate(self._collection)}
1538 )
1539 # delete higher index
1540 del self._index[len(self._collection)]
1541
1542 def replace(self, column):
1543 """add the given column to this collection, removing unaliased
1544 versions of this column as well as existing columns with the
1545 same key.
1546
1547 e.g.::
1548
1549 t = Table('sometable', metadata, Column('col1', Integer))
1550 t.columns.replace(Column('col1', Integer, key='columnone'))
1551
1552 will remove the original 'col1' from the collection, and add
1553 the new column under the name 'columnname'.
1554
1555 Used by schema.Column to override columns during table reflection.
1556
1557 """
1558
1559 remove_col = set()
1560 # remove up to two columns based on matches of name as well as key
1561 if column.name in self._index and column.key != column.name:
1562 other = self._index[column.name]
1563 if other.name == other.key:
1564 remove_col.add(other)
1565
1566 if column.key in self._index:
1567 remove_col.add(self._index[column.key])
1568
1569 new_cols = []
1570 replaced = False
1571 for k, col in self._collection:
1572 if col in remove_col:
1573 if not replaced:
1574 replaced = True
1575 new_cols.append((column.key, column))
1576 else:
1577 new_cols.append((k, col))
1578
1579 if remove_col:
1580 self._colset.difference_update(remove_col)
1581
1582 if not replaced:
1583 new_cols.append((column.key, column))
1584
1585 self._colset.add(column)
1586 self._collection[:] = new_cols
1587
1588 self._index.clear()
1589 self._index.update(
1590 {idx: col for idx, (k, col) in enumerate(self._collection)}
1591 )
1592 self._index.update(self._collection)
1593
1594
1595class ImmutableColumnCollection(util.ImmutableContainer, ColumnCollection):
1596 __slots__ = ("_parent",)
1597
1598 def __init__(self, collection):
1599 object.__setattr__(self, "_parent", collection)
1600 object.__setattr__(self, "_colset", collection._colset)
1601 object.__setattr__(self, "_index", collection._index)
1602 object.__setattr__(self, "_collection", collection._collection)
1603
1604 def __getstate__(self):
1605 return {"_parent": self._parent}
1606
1607 def __setstate__(self, state):
1608 parent = state["_parent"]
1609 self.__init__(parent)
1610
1611 add = extend = remove = util.ImmutableContainer._immutable
1612
1613
1614class ColumnSet(util.ordered_column_set):
1615 def contains_column(self, col):
1616 return col in self
1617
1618 def extend(self, cols):
1619 for col in cols:
1620 self.add(col)
1621
1622 def __add__(self, other):
1623 return list(self) + list(other)
1624
1625 def __eq__(self, other):
1626 l = []
1627 for c in other:
1628 for local in self:
1629 if c.shares_lineage(local):
1630 l.append(c == local)
1631 return elements.and_(*l)
1632
1633 def __hash__(self):
1634 return hash(tuple(x for x in self))
1635
1636
1637def _bind_or_error(schemaitem, msg=None):
1638
1639 util.warn_deprecated_20(
1640 "The ``bind`` argument for schema methods that invoke SQL "
1641 "against an engine or connection will be required in SQLAlchemy 2.0."
1642 )
1643 bind = schemaitem.bind
1644 if not bind:
1645 name = schemaitem.__class__.__name__
1646 label = getattr(
1647 schemaitem, "fullname", getattr(schemaitem, "name", None)
1648 )
1649 if label:
1650 item = "%s object %r" % (name, label)
1651 else:
1652 item = "%s object" % name
1653 if msg is None:
1654 msg = (
1655 "%s is not bound to an Engine or Connection. "
1656 "Execution can not proceed without a database to execute "
1657 "against." % item
1658 )
1659 raise exc.UnboundExecutionError(msg)
1660 return bind
1661
1662
1663def _entity_namespace(entity):
1664 """Return the nearest .entity_namespace for the given entity.
1665
1666 If not immediately available, does an iterate to find a sub-element
1667 that has one, if any.
1668
1669 """
1670 try:
1671 return entity.entity_namespace
1672 except AttributeError:
1673 for elem in visitors.iterate(entity):
1674 if hasattr(elem, "entity_namespace"):
1675 return elem.entity_namespace
1676 else:
1677 raise
1678
1679
1680def _entity_namespace_key(entity, key, default=NO_ARG):
1681 """Return an entry from an entity_namespace.
1682
1683
1684 Raises :class:`_exc.InvalidRequestError` rather than attribute error
1685 on not found.
1686
1687 """
1688
1689 try:
1690 ns = _entity_namespace(entity)
1691 if default is not NO_ARG:
1692 return getattr(ns, key, default)
1693 else:
1694 return getattr(ns, key)
1695 except AttributeError as err:
1696 util.raise_(
1697 exc.InvalidRequestError(
1698 'Entity namespace for "%s" has no property "%s"'
1699 % (entity, key)
1700 ),
1701 replace_context=err,
1702 )