Remove Elixir library
Update SQLAlchemy
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
# sql/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.sql.expression import (
|
||||
from .expression import (
|
||||
Alias,
|
||||
ClauseElement,
|
||||
ColumnCollection,
|
||||
@@ -35,6 +35,7 @@ from sqlalchemy.sql.expression import (
|
||||
exists,
|
||||
extract,
|
||||
false,
|
||||
False_,
|
||||
func,
|
||||
insert,
|
||||
intersect,
|
||||
@@ -55,6 +56,7 @@ from sqlalchemy.sql.expression import (
|
||||
table,
|
||||
text,
|
||||
true,
|
||||
True_,
|
||||
tuple_,
|
||||
type_coerce,
|
||||
union,
|
||||
@@ -62,8 +64,26 @@ from sqlalchemy.sql.expression import (
|
||||
update,
|
||||
)
|
||||
|
||||
from sqlalchemy.sql.visitors import ClauseVisitor
|
||||
from .visitors import ClauseVisitor
|
||||
|
||||
__tmp = locals().keys()
|
||||
__all__ = sorted([i for i in __tmp if not i.startswith('__')])
|
||||
|
||||
def __go(lcls):
|
||||
global __all__
|
||||
from .. import util as _sa_util
|
||||
|
||||
import inspect as _inspect
|
||||
|
||||
__all__ = sorted(name for name, obj in lcls.items()
|
||||
if not (name.startswith('_') or _inspect.ismodule(obj)))
|
||||
|
||||
from .annotation import _prepare_annotations, Annotated
|
||||
from .elements import AnnotatedColumnElement, ClauseList
|
||||
from .selectable import AnnotatedFromClause
|
||||
_prepare_annotations(ColumnElement, AnnotatedColumnElement)
|
||||
_prepare_annotations(FromClause, AnnotatedFromClause)
|
||||
_prepare_annotations(ClauseList, Annotated)
|
||||
|
||||
_sa_util.dependencies.resolve_all("sqlalchemy.sql")
|
||||
|
||||
__go(locals())
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# sql/annotation.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""The :class:`.Annotated` class and related routines; creates hash-equivalent
|
||||
copies of SQL constructs which contain context-specific markers and associations.
|
||||
|
||||
"""
|
||||
|
||||
from .. import util
|
||||
from . import operators
|
||||
|
||||
class Annotated(object):
|
||||
"""clones a ClauseElement and applies an 'annotations' dictionary.
|
||||
|
||||
Unlike regular clones, this clone also mimics __hash__() and
|
||||
__cmp__() of the original element so that it takes its place
|
||||
in hashed collections.
|
||||
|
||||
A reference to the original element is maintained, for the important
|
||||
reason of keeping its hash value current. When GC'ed, the
|
||||
hash value may be reused, causing conflicts.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, *args):
|
||||
if not args:
|
||||
# clone constructor
|
||||
return object.__new__(cls)
|
||||
else:
|
||||
element, values = args
|
||||
# pull appropriate subclass from registry of annotated
|
||||
# classes
|
||||
try:
|
||||
cls = annotated_classes[element.__class__]
|
||||
except KeyError:
|
||||
cls = _new_annotation_type(element.__class__, cls)
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, element, values):
|
||||
self.__dict__ = element.__dict__.copy()
|
||||
self.__element = element
|
||||
self._annotations = values
|
||||
|
||||
def _annotate(self, values):
|
||||
_values = self._annotations.copy()
|
||||
_values.update(values)
|
||||
return self._with_annotations(_values)
|
||||
|
||||
def _with_annotations(self, values):
|
||||
clone = self.__class__.__new__(self.__class__)
|
||||
clone.__dict__ = self.__dict__.copy()
|
||||
clone._annotations = values
|
||||
return clone
|
||||
|
||||
def _deannotate(self, values=None, clone=True):
|
||||
if values is None:
|
||||
return self.__element
|
||||
else:
|
||||
_values = self._annotations.copy()
|
||||
for v in values:
|
||||
_values.pop(v, None)
|
||||
return self._with_annotations(_values)
|
||||
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return self.__element.__class__._compiler_dispatch(self, visitor, **kw)
|
||||
|
||||
@property
|
||||
def _constructor(self):
|
||||
return self.__element._constructor
|
||||
|
||||
def _clone(self):
|
||||
clone = self.__element._clone()
|
||||
if clone is self.__element:
|
||||
# detect immutable, don't change anything
|
||||
return self
|
||||
else:
|
||||
# update the clone with any changes that have occurred
|
||||
# to this object's __dict__.
|
||||
clone.__dict__.update(self.__dict__)
|
||||
return self.__class__(clone, self._annotations)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__element)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(self.__element, operators.ColumnOperators):
|
||||
return self.__element.__class__.__eq__(self, other)
|
||||
else:
|
||||
return hash(other) == hash(self)
|
||||
|
||||
|
||||
|
||||
# hard-generate Annotated subclasses. this technique
|
||||
# is used instead of on-the-fly types (i.e. type.__new__())
|
||||
# so that the resulting objects are pickleable.
|
||||
annotated_classes = {}
|
||||
|
||||
|
||||
|
||||
def _deep_annotate(element, annotations, exclude=None):
|
||||
"""Deep copy the given ClauseElement, annotating each element
|
||||
with the given annotations dictionary.
|
||||
|
||||
Elements within the exclude collection will be cloned but not annotated.
|
||||
|
||||
"""
|
||||
def clone(elem):
|
||||
if exclude and \
|
||||
hasattr(elem, 'proxy_set') and \
|
||||
elem.proxy_set.intersection(exclude):
|
||||
newelem = elem._clone()
|
||||
elif annotations != elem._annotations:
|
||||
newelem = elem._annotate(annotations)
|
||||
else:
|
||||
newelem = elem
|
||||
newelem._copy_internals(clone=clone)
|
||||
return newelem
|
||||
|
||||
if element is not None:
|
||||
element = clone(element)
|
||||
return element
|
||||
|
||||
|
||||
def _deep_deannotate(element, values=None):
|
||||
"""Deep copy the given element, removing annotations."""
|
||||
|
||||
cloned = util.column_dict()
|
||||
|
||||
def clone(elem):
|
||||
# if a values dict is given,
|
||||
# the elem must be cloned each time it appears,
|
||||
# as there may be different annotations in source
|
||||
# elements that are remaining. if totally
|
||||
# removing all annotations, can assume the same
|
||||
# slate...
|
||||
if values or elem not in cloned:
|
||||
newelem = elem._deannotate(values=values, clone=True)
|
||||
newelem._copy_internals(clone=clone)
|
||||
if not values:
|
||||
cloned[elem] = newelem
|
||||
return newelem
|
||||
else:
|
||||
return cloned[elem]
|
||||
|
||||
if element is not None:
|
||||
element = clone(element)
|
||||
return element
|
||||
|
||||
|
||||
def _shallow_annotate(element, annotations):
|
||||
"""Annotate the given ClauseElement and copy its internals so that
|
||||
internal objects refer to the new annotated object.
|
||||
|
||||
Basically used to apply a "dont traverse" annotation to a
|
||||
selectable, without digging throughout the whole
|
||||
structure wasting time.
|
||||
"""
|
||||
element = element._annotate(annotations)
|
||||
element._copy_internals()
|
||||
return element
|
||||
|
||||
def _new_annotation_type(cls, base_cls):
|
||||
if issubclass(cls, Annotated):
|
||||
return cls
|
||||
elif cls in annotated_classes:
|
||||
return annotated_classes[cls]
|
||||
annotated_classes[cls] = anno_cls = type(
|
||||
"Annotated%s" % cls.__name__,
|
||||
(base_cls, cls), {})
|
||||
globals()["Annotated%s" % cls.__name__] = anno_cls
|
||||
return anno_cls
|
||||
|
||||
def _prepare_annotations(target_hierarchy, base_cls):
|
||||
stack = [target_hierarchy]
|
||||
while stack:
|
||||
cls = stack.pop()
|
||||
stack.extend(cls.__subclasses__())
|
||||
|
||||
_new_annotation_type(cls, base_cls)
|
||||
@@ -0,0 +1,348 @@
|
||||
# sql/base.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Foundational utilities common to many sql modules.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from .. import util, exc
|
||||
import itertools
|
||||
from .visitors import ClauseVisitor
|
||||
|
||||
|
||||
PARSE_AUTOCOMMIT = util.symbol('PARSE_AUTOCOMMIT')
|
||||
NO_ARG = util.symbol('NO_ARG')
|
||||
|
||||
class Immutable(object):
|
||||
"""mark a ClauseElement as 'immutable' when expressions are cloned."""
|
||||
|
||||
def unique_params(self, *optionaldict, **kwargs):
|
||||
raise NotImplementedError("Immutable objects do not support copying")
|
||||
|
||||
def params(self, *optionaldict, **kwargs):
|
||||
raise NotImplementedError("Immutable objects do not support copying")
|
||||
|
||||
def _clone(self):
|
||||
return self
|
||||
|
||||
|
||||
|
||||
def _from_objects(*elements):
|
||||
return itertools.chain(*[element._from_objects for element in elements])
|
||||
|
||||
@util.decorator
|
||||
def _generative(fn, *args, **kw):
|
||||
"""Mark a method as generative."""
|
||||
|
||||
self = args[0]._generate()
|
||||
fn(self, *args[1:], **kw)
|
||||
return self
|
||||
|
||||
|
||||
class Generative(object):
|
||||
"""Allow a ClauseElement to generate itself via the
|
||||
@_generative decorator.
|
||||
|
||||
"""
|
||||
|
||||
def _generate(self):
|
||||
s = self.__class__.__new__(self.__class__)
|
||||
s.__dict__ = self.__dict__.copy()
|
||||
return s
|
||||
|
||||
|
||||
class Executable(Generative):
|
||||
"""Mark a ClauseElement as supporting execution.
|
||||
|
||||
:class:`.Executable` is a superclass for all "statement" types
|
||||
of objects, including :func:`select`, :func:`delete`, :func:`update`,
|
||||
:func:`insert`, :func:`text`.
|
||||
|
||||
"""
|
||||
|
||||
supports_execution = True
|
||||
_execution_options = util.immutabledict()
|
||||
_bind = None
|
||||
|
||||
@_generative
|
||||
def execution_options(self, **kw):
|
||||
""" Set non-SQL options for the statement which take effect during
|
||||
execution.
|
||||
|
||||
Execution options can be set on a per-statement or
|
||||
per :class:`.Connection` basis. Additionally, the
|
||||
:class:`.Engine` and ORM :class:`~.orm.query.Query` objects provide
|
||||
access to execution options which they in turn configure upon
|
||||
connections.
|
||||
|
||||
The :meth:`execution_options` method is generative. A new
|
||||
instance of this statement is returned that contains the options::
|
||||
|
||||
statement = select([table.c.x, table.c.y])
|
||||
statement = statement.execution_options(autocommit=True)
|
||||
|
||||
Note that only a subset of possible execution options can be applied
|
||||
to a statement - these include "autocommit" and "stream_results",
|
||||
but not "isolation_level" or "compiled_cache".
|
||||
See :meth:`.Connection.execution_options` for a full list of
|
||||
possible options.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.Connection.execution_options()`
|
||||
|
||||
:meth:`.Query.execution_options()`
|
||||
|
||||
"""
|
||||
if 'isolation_level' in kw:
|
||||
raise exc.ArgumentError(
|
||||
"'isolation_level' execution option may only be specified "
|
||||
"on Connection.execution_options(), or "
|
||||
"per-engine using the isolation_level "
|
||||
"argument to create_engine()."
|
||||
)
|
||||
if 'compiled_cache' in kw:
|
||||
raise exc.ArgumentError(
|
||||
"'compiled_cache' execution option may only be specified "
|
||||
"on Connection.execution_options(), not per statement."
|
||||
)
|
||||
self._execution_options = self._execution_options.union(kw)
|
||||
|
||||
def execute(self, *multiparams, **params):
|
||||
"""Compile and execute this :class:`.Executable`."""
|
||||
e = self.bind
|
||||
if e is None:
|
||||
label = getattr(self, 'description', self.__class__.__name__)
|
||||
msg = ('This %s is not directly bound to a Connection or Engine.'
|
||||
'Use the .execute() method of a Connection or Engine '
|
||||
'to execute this construct.' % label)
|
||||
raise exc.UnboundExecutionError(msg)
|
||||
return e._execute_clauseelement(self, multiparams, params)
|
||||
|
||||
def scalar(self, *multiparams, **params):
|
||||
"""Compile and execute this :class:`.Executable`, returning the
|
||||
result's scalar representation.
|
||||
|
||||
"""
|
||||
return self.execute(*multiparams, **params).scalar()
|
||||
|
||||
@property
|
||||
def bind(self):
|
||||
"""Returns the :class:`.Engine` or :class:`.Connection` to
|
||||
which this :class:`.Executable` is bound, or None if none found.
|
||||
|
||||
This is a traversal which checks locally, then
|
||||
checks among the "from" clauses of associated objects
|
||||
until a bound engine or connection is found.
|
||||
|
||||
"""
|
||||
if self._bind is not None:
|
||||
return self._bind
|
||||
|
||||
for f in _from_objects(self):
|
||||
if f is self:
|
||||
continue
|
||||
engine = f.bind
|
||||
if engine is not None:
|
||||
return engine
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class SchemaEventTarget(object):
|
||||
"""Base class for elements that are the targets of :class:`.DDLEvents`
|
||||
events.
|
||||
|
||||
This includes :class:`.SchemaItem` as well as :class:`.SchemaType`.
|
||||
|
||||
"""
|
||||
|
||||
def _set_parent(self, parent):
|
||||
"""Associate with this SchemaEvent's parent object."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def _set_parent_with_dispatch(self, parent):
|
||||
self.dispatch.before_parent_attach(self, parent)
|
||||
self._set_parent(parent)
|
||||
self.dispatch.after_parent_attach(self, parent)
|
||||
|
||||
class SchemaVisitor(ClauseVisitor):
|
||||
"""Define the visiting for ``SchemaItem`` objects."""
|
||||
|
||||
__traverse_options__ = {'schema_visitor': True}
|
||||
|
||||
class ColumnCollection(util.OrderedProperties):
|
||||
"""An ordered dictionary that stores a list of ColumnElement
|
||||
instances.
|
||||
|
||||
Overrides the ``__eq__()`` method to produce SQL clauses between
|
||||
sets of correlated columns.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *cols):
|
||||
super(ColumnCollection, self).__init__()
|
||||
self._data.update((c.key, c) for c in cols)
|
||||
self.__dict__['_all_cols'] = util.column_set(self)
|
||||
|
||||
def __str__(self):
|
||||
return repr([str(c) for c in self])
|
||||
|
||||
def replace(self, column):
|
||||
"""add the given column to this collection, removing unaliased
|
||||
versions of this column as well as existing columns with the
|
||||
same key.
|
||||
|
||||
e.g.::
|
||||
|
||||
t = Table('sometable', metadata, Column('col1', Integer))
|
||||
t.columns.replace(Column('col1', Integer, key='columnone'))
|
||||
|
||||
will remove the original 'col1' from the collection, and add
|
||||
the new column under the name 'columnname'.
|
||||
|
||||
Used by schema.Column to override columns during table reflection.
|
||||
|
||||
"""
|
||||
if column.name in self and column.key != column.name:
|
||||
other = self[column.name]
|
||||
if other.name == other.key:
|
||||
del self._data[other.name]
|
||||
self._all_cols.remove(other)
|
||||
if column.key in self._data:
|
||||
self._all_cols.remove(self._data[column.key])
|
||||
self._all_cols.add(column)
|
||||
self._data[column.key] = column
|
||||
|
||||
def add(self, column):
|
||||
"""Add a column to this collection.
|
||||
|
||||
The key attribute of the column will be used as the hash key
|
||||
for this dictionary.
|
||||
|
||||
"""
|
||||
self[column.key] = column
|
||||
|
||||
def __delitem__(self, key):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __setattr__(self, key, object):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key in self:
|
||||
|
||||
# this warning is primarily to catch select() statements
|
||||
# which have conflicting column names in their exported
|
||||
# columns collection
|
||||
|
||||
existing = self[key]
|
||||
if not existing.shares_lineage(value):
|
||||
util.warn('Column %r on table %r being replaced by '
|
||||
'%r, which has the same key. Consider '
|
||||
'use_labels for select() statements.' % (key,
|
||||
getattr(existing, 'table', None), value))
|
||||
self._all_cols.remove(existing)
|
||||
# pop out memoized proxy_set as this
|
||||
# operation may very well be occurring
|
||||
# in a _make_proxy operation
|
||||
util.memoized_property.reset(value, "proxy_set")
|
||||
self._all_cols.add(value)
|
||||
self._data[key] = value
|
||||
|
||||
def clear(self):
|
||||
self._data.clear()
|
||||
self._all_cols.clear()
|
||||
|
||||
def remove(self, column):
|
||||
del self._data[column.key]
|
||||
self._all_cols.remove(column)
|
||||
|
||||
def update(self, value):
|
||||
self._data.update(value)
|
||||
self._all_cols.clear()
|
||||
self._all_cols.update(self._data.values())
|
||||
|
||||
def extend(self, iter):
|
||||
self.update((c.key, c) for c in iter)
|
||||
|
||||
__hash__ = None
|
||||
|
||||
@util.dependencies("sqlalchemy.sql.elements")
|
||||
def __eq__(self, elements, other):
|
||||
l = []
|
||||
for c in other:
|
||||
for local in self:
|
||||
if c.shares_lineage(local):
|
||||
l.append(c == local)
|
||||
return elements.and_(*l)
|
||||
|
||||
def __contains__(self, other):
|
||||
if not isinstance(other, util.string_types):
|
||||
raise exc.ArgumentError("__contains__ requires a string argument")
|
||||
return util.OrderedProperties.__contains__(self, other)
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__['_data'] = state['_data']
|
||||
self.__dict__['_all_cols'] = util.column_set(self._data.values())
|
||||
|
||||
def contains_column(self, col):
|
||||
# this has to be done via set() membership
|
||||
return col in self._all_cols
|
||||
|
||||
def as_immutable(self):
|
||||
return ImmutableColumnCollection(self._data, self._all_cols)
|
||||
|
||||
|
||||
class ImmutableColumnCollection(util.ImmutableProperties, ColumnCollection):
|
||||
def __init__(self, data, colset):
|
||||
util.ImmutableProperties.__init__(self, data)
|
||||
self.__dict__['_all_cols'] = colset
|
||||
|
||||
extend = remove = util.ImmutableProperties._immutable
|
||||
|
||||
|
||||
class ColumnSet(util.ordered_column_set):
|
||||
def contains_column(self, col):
|
||||
return col in self
|
||||
|
||||
def extend(self, cols):
|
||||
for col in cols:
|
||||
self.add(col)
|
||||
|
||||
def __add__(self, other):
|
||||
return list(self) + list(other)
|
||||
|
||||
@util.dependencies("sqlalchemy.sql.elements")
|
||||
def __eq__(self, elements, other):
|
||||
l = []
|
||||
for c in other:
|
||||
for local in self:
|
||||
if c.shares_lineage(local):
|
||||
l.append(c == local)
|
||||
return elements.and_(*l)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(tuple(x for x in self))
|
||||
|
||||
def _bind_or_error(schemaitem, msg=None):
|
||||
bind = schemaitem.bind
|
||||
if not bind:
|
||||
name = schemaitem.__class__.__name__
|
||||
label = getattr(schemaitem, 'fullname',
|
||||
getattr(schemaitem, 'name', None))
|
||||
if label:
|
||||
item = '%s object %r' % (name, label)
|
||||
else:
|
||||
item = '%s object' % name
|
||||
if msg is None:
|
||||
msg = "%s is not bound to an Engine or Connection. "\
|
||||
"Execution can not proceed without a database to execute "\
|
||||
"against." % item
|
||||
raise exc.UnboundExecutionError(msg)
|
||||
return bind
|
||||
+1201
-450
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,864 @@
|
||||
# sql/ddl.py
|
||||
# Copyright (C) 2009-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""
|
||||
Provides the hierarchy of DDL-defining schema items as well as routines
|
||||
to invoke them for a create/drop call.
|
||||
|
||||
"""
|
||||
|
||||
from .. import util
|
||||
from .elements import ClauseElement
|
||||
from .visitors import traverse
|
||||
from .base import Executable, _generative, SchemaVisitor, _bind_or_error
|
||||
from ..util import topological
|
||||
from .. import event
|
||||
from .. import exc
|
||||
|
||||
class _DDLCompiles(ClauseElement):
|
||||
def _compiler(self, dialect, **kw):
|
||||
"""Return a compiler appropriate for this ClauseElement, given a
|
||||
Dialect."""
|
||||
|
||||
return dialect.ddl_compiler(dialect, self, **kw)
|
||||
|
||||
|
||||
class DDLElement(Executable, _DDLCompiles):
|
||||
"""Base class for DDL expression constructs.
|
||||
|
||||
This class is the base for the general purpose :class:`.DDL` class,
|
||||
as well as the various create/drop clause constructs such as
|
||||
:class:`.CreateTable`, :class:`.DropTable`, :class:`.AddConstraint`,
|
||||
etc.
|
||||
|
||||
:class:`.DDLElement` integrates closely with SQLAlchemy events,
|
||||
introduced in :ref:`event_toplevel`. An instance of one is
|
||||
itself an event receiving callable::
|
||||
|
||||
event.listen(
|
||||
users,
|
||||
'after_create',
|
||||
AddConstraint(constraint).execute_if(dialect='postgresql')
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.DDL`
|
||||
|
||||
:class:`.DDLEvents`
|
||||
|
||||
:ref:`event_toplevel`
|
||||
|
||||
:ref:`schema_ddl_sequences`
|
||||
|
||||
"""
|
||||
|
||||
_execution_options = Executable.\
|
||||
_execution_options.union({'autocommit': True})
|
||||
|
||||
target = None
|
||||
on = None
|
||||
dialect = None
|
||||
callable_ = None
|
||||
|
||||
def _execute_on_connection(self, connection, multiparams, params):
|
||||
return connection._execute_ddl(self, multiparams, params)
|
||||
|
||||
def execute(self, bind=None, target=None):
|
||||
"""Execute this DDL immediately.
|
||||
|
||||
Executes the DDL statement in isolation using the supplied
|
||||
:class:`.Connectable` or
|
||||
:class:`.Connectable` assigned to the ``.bind``
|
||||
property, if not supplied. If the DDL has a conditional ``on``
|
||||
criteria, it will be invoked with None as the event.
|
||||
|
||||
:param bind:
|
||||
Optional, an ``Engine`` or ``Connection``. If not supplied, a valid
|
||||
:class:`.Connectable` must be present in the
|
||||
``.bind`` property.
|
||||
|
||||
:param target:
|
||||
Optional, defaults to None. The target SchemaItem for the
|
||||
execute call. Will be passed to the ``on`` callable if any,
|
||||
and may also provide string expansion data for the
|
||||
statement. See ``execute_at`` for more information.
|
||||
|
||||
"""
|
||||
|
||||
if bind is None:
|
||||
bind = _bind_or_error(self)
|
||||
|
||||
if self._should_execute(target, bind):
|
||||
return bind.execute(self.against(target))
|
||||
else:
|
||||
bind.engine.logger.info(
|
||||
"DDL execution skipped, criteria not met.")
|
||||
|
||||
@util.deprecated("0.7", "See :class:`.DDLEvents`, as well as "
|
||||
":meth:`.DDLElement.execute_if`.")
|
||||
def execute_at(self, event_name, target):
|
||||
"""Link execution of this DDL to the DDL lifecycle of a SchemaItem.
|
||||
|
||||
Links this ``DDLElement`` to a ``Table`` or ``MetaData`` instance,
|
||||
executing it when that schema item is created or dropped. The DDL
|
||||
statement will be executed using the same Connection and transactional
|
||||
context as the Table create/drop itself. The ``.bind`` property of
|
||||
this statement is ignored.
|
||||
|
||||
:param event:
|
||||
One of the events defined in the schema item's ``.ddl_events``;
|
||||
e.g. 'before-create', 'after-create', 'before-drop' or 'after-drop'
|
||||
|
||||
:param target:
|
||||
The Table or MetaData instance for which this DDLElement will
|
||||
be associated with.
|
||||
|
||||
A DDLElement instance can be linked to any number of schema items.
|
||||
|
||||
``execute_at`` builds on the ``append_ddl_listener`` interface of
|
||||
:class:`.MetaData` and :class:`.Table` objects.
|
||||
|
||||
Caveat: Creating or dropping a Table in isolation will also trigger
|
||||
any DDL set to ``execute_at`` that Table's MetaData. This may change
|
||||
in a future release.
|
||||
|
||||
"""
|
||||
|
||||
def call_event(target, connection, **kw):
|
||||
if self._should_execute_deprecated(event_name,
|
||||
target, connection, **kw):
|
||||
return connection.execute(self.against(target))
|
||||
|
||||
event.listen(target, "" + event_name.replace('-', '_'), call_event)
|
||||
|
||||
@_generative
|
||||
def against(self, target):
|
||||
"""Return a copy of this DDL against a specific schema item."""
|
||||
|
||||
self.target = target
|
||||
|
||||
@_generative
|
||||
def execute_if(self, dialect=None, callable_=None, state=None):
|
||||
"""Return a callable that will execute this
|
||||
DDLElement conditionally.
|
||||
|
||||
Used to provide a wrapper for event listening::
|
||||
|
||||
event.listen(
|
||||
metadata,
|
||||
'before_create',
|
||||
DDL("my_ddl").execute_if(dialect='postgresql')
|
||||
)
|
||||
|
||||
:param dialect: May be a string, tuple or a callable
|
||||
predicate. If a string, it will be compared to the name of the
|
||||
executing database dialect::
|
||||
|
||||
DDL('something').execute_if(dialect='postgresql')
|
||||
|
||||
If a tuple, specifies multiple dialect names::
|
||||
|
||||
DDL('something').execute_if(dialect=('postgresql', 'mysql'))
|
||||
|
||||
:param callable_: A callable, which will be invoked with
|
||||
four positional arguments as well as optional keyword
|
||||
arguments:
|
||||
|
||||
:ddl:
|
||||
This DDL element.
|
||||
|
||||
:target:
|
||||
The :class:`.Table` or :class:`.MetaData` object which is the
|
||||
target of this event. May be None if the DDL is executed
|
||||
explicitly.
|
||||
|
||||
:bind:
|
||||
The :class:`.Connection` being used for DDL execution
|
||||
|
||||
:tables:
|
||||
Optional keyword argument - a list of Table objects which are to
|
||||
be created/ dropped within a MetaData.create_all() or drop_all()
|
||||
method call.
|
||||
|
||||
:state:
|
||||
Optional keyword argument - will be the ``state`` argument
|
||||
passed to this function.
|
||||
|
||||
:checkfirst:
|
||||
Keyword argument, will be True if the 'checkfirst' flag was
|
||||
set during the call to ``create()``, ``create_all()``,
|
||||
``drop()``, ``drop_all()``.
|
||||
|
||||
If the callable returns a true value, the DDL statement will be
|
||||
executed.
|
||||
|
||||
:param state: any value which will be passed to the callable\_
|
||||
as the ``state`` keyword argument.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.DDLEvents`
|
||||
|
||||
:ref:`event_toplevel`
|
||||
|
||||
"""
|
||||
self.dialect = dialect
|
||||
self.callable_ = callable_
|
||||
self.state = state
|
||||
|
||||
def _should_execute(self, target, bind, **kw):
|
||||
if self.on is not None and \
|
||||
not self._should_execute_deprecated(None, target, bind, **kw):
|
||||
return False
|
||||
|
||||
if isinstance(self.dialect, util.string_types):
|
||||
if self.dialect != bind.engine.name:
|
||||
return False
|
||||
elif isinstance(self.dialect, (tuple, list, set)):
|
||||
if bind.engine.name not in self.dialect:
|
||||
return False
|
||||
if self.callable_ is not None and \
|
||||
not self.callable_(self, target, bind, state=self.state, **kw):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _should_execute_deprecated(self, event, target, bind, **kw):
|
||||
if self.on is None:
|
||||
return True
|
||||
elif isinstance(self.on, util.string_types):
|
||||
return self.on == bind.engine.name
|
||||
elif isinstance(self.on, (tuple, list, set)):
|
||||
return bind.engine.name in self.on
|
||||
else:
|
||||
return self.on(self, event, target, bind, **kw)
|
||||
|
||||
def __call__(self, target, bind, **kw):
|
||||
"""Execute the DDL as a ddl_listener."""
|
||||
|
||||
if self._should_execute(target, bind, **kw):
|
||||
return bind.execute(self.against(target))
|
||||
|
||||
def _check_ddl_on(self, on):
|
||||
if (on is not None and
|
||||
(not isinstance(on, util.string_types + (tuple, list, set)) and
|
||||
not util.callable(on))):
|
||||
raise exc.ArgumentError(
|
||||
"Expected the name of a database dialect, a tuple "
|
||||
"of names, or a callable for "
|
||||
"'on' criteria, got type '%s'." % type(on).__name__)
|
||||
|
||||
def bind(self):
|
||||
if self._bind:
|
||||
return self._bind
|
||||
|
||||
def _set_bind(self, bind):
|
||||
self._bind = bind
|
||||
bind = property(bind, _set_bind)
|
||||
|
||||
def _generate(self):
|
||||
s = self.__class__.__new__(self.__class__)
|
||||
s.__dict__ = self.__dict__.copy()
|
||||
return s
|
||||
|
||||
|
||||
class DDL(DDLElement):
|
||||
"""A literal DDL statement.
|
||||
|
||||
Specifies literal SQL DDL to be executed by the database. DDL objects
|
||||
function as DDL event listeners, and can be subscribed to those events
|
||||
listed in :class:`.DDLEvents`, using either :class:`.Table` or
|
||||
:class:`.MetaData` objects as targets. Basic templating support allows
|
||||
a single DDL instance to handle repetitive tasks for multiple tables.
|
||||
|
||||
Examples::
|
||||
|
||||
from sqlalchemy import event, DDL
|
||||
|
||||
tbl = Table('users', metadata, Column('uid', Integer))
|
||||
event.listen(tbl, 'before_create', DDL('DROP TRIGGER users_trigger'))
|
||||
|
||||
spow = DDL('ALTER TABLE %(table)s SET secretpowers TRUE')
|
||||
event.listen(tbl, 'after_create', spow.execute_if(dialect='somedb'))
|
||||
|
||||
drop_spow = DDL('ALTER TABLE users SET secretpowers FALSE')
|
||||
connection.execute(drop_spow)
|
||||
|
||||
When operating on Table events, the following ``statement``
|
||||
string substitions are available::
|
||||
|
||||
%(table)s - the Table name, with any required quoting applied
|
||||
%(schema)s - the schema name, with any required quoting applied
|
||||
%(fullname)s - the Table name including schema, quoted if needed
|
||||
|
||||
The DDL's "context", if any, will be combined with the standard
|
||||
substutions noted above. Keys present in the context will override
|
||||
the standard substitutions.
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = "ddl"
|
||||
|
||||
def __init__(self, statement, on=None, context=None, bind=None):
|
||||
"""Create a DDL statement.
|
||||
|
||||
:param statement:
|
||||
A string or unicode string to be executed. Statements will be
|
||||
processed with Python's string formatting operator. See the
|
||||
``context`` argument and the ``execute_at`` method.
|
||||
|
||||
A literal '%' in a statement must be escaped as '%%'.
|
||||
|
||||
SQL bind parameters are not available in DDL statements.
|
||||
|
||||
:param on:
|
||||
.. deprecated:: 0.7
|
||||
See :meth:`.DDLElement.execute_if`.
|
||||
|
||||
Optional filtering criteria. May be a string, tuple or a callable
|
||||
predicate. If a string, it will be compared to the name of the
|
||||
executing database dialect::
|
||||
|
||||
DDL('something', on='postgresql')
|
||||
|
||||
If a tuple, specifies multiple dialect names::
|
||||
|
||||
DDL('something', on=('postgresql', 'mysql'))
|
||||
|
||||
If a callable, it will be invoked with four positional arguments
|
||||
as well as optional keyword arguments:
|
||||
|
||||
:ddl:
|
||||
This DDL element.
|
||||
|
||||
:event:
|
||||
The name of the event that has triggered this DDL, such as
|
||||
'after-create' Will be None if the DDL is executed explicitly.
|
||||
|
||||
:target:
|
||||
The ``Table`` or ``MetaData`` object which is the target of
|
||||
this event. May be None if the DDL is executed explicitly.
|
||||
|
||||
:connection:
|
||||
The ``Connection`` being used for DDL execution
|
||||
|
||||
:tables:
|
||||
Optional keyword argument - a list of Table objects which are to
|
||||
be created/ dropped within a MetaData.create_all() or drop_all()
|
||||
method call.
|
||||
|
||||
|
||||
If the callable returns a true value, the DDL statement will be
|
||||
executed.
|
||||
|
||||
:param context:
|
||||
Optional dictionary, defaults to None. These values will be
|
||||
available for use in string substitutions on the DDL statement.
|
||||
|
||||
:param bind:
|
||||
Optional. A :class:`.Connectable`, used by
|
||||
default when ``execute()`` is invoked without a bind argument.
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.DDLEvents`
|
||||
|
||||
:mod:`sqlalchemy.event`
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(statement, util.string_types):
|
||||
raise exc.ArgumentError(
|
||||
"Expected a string or unicode SQL statement, got '%r'" %
|
||||
statement)
|
||||
|
||||
self.statement = statement
|
||||
self.context = context or {}
|
||||
|
||||
self._check_ddl_on(on)
|
||||
self.on = on
|
||||
self._bind = bind
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s@%s; %s>' % (
|
||||
type(self).__name__, id(self),
|
||||
', '.join([repr(self.statement)] +
|
||||
['%s=%r' % (key, getattr(self, key))
|
||||
for key in ('on', 'context')
|
||||
if getattr(self, key)]))
|
||||
|
||||
|
||||
|
||||
class _CreateDropBase(DDLElement):
|
||||
"""Base class for DDL constucts that represent CREATE and DROP or
|
||||
equivalents.
|
||||
|
||||
The common theme of _CreateDropBase is a single
|
||||
``element`` attribute which refers to the element
|
||||
to be created or dropped.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, element, on=None, bind=None):
|
||||
self.element = element
|
||||
self._check_ddl_on(on)
|
||||
self.on = on
|
||||
self.bind = bind
|
||||
|
||||
def _create_rule_disable(self, compiler):
|
||||
"""Allow disable of _create_rule using a callable.
|
||||
|
||||
Pass to _create_rule using
|
||||
util.portable_instancemethod(self._create_rule_disable)
|
||||
to retain serializability.
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
class CreateSchema(_CreateDropBase):
|
||||
"""Represent a CREATE SCHEMA statement.
|
||||
|
||||
.. versionadded:: 0.7.4
|
||||
|
||||
The argument here is the string name of the schema.
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = "create_schema"
|
||||
|
||||
def __init__(self, name, quote=None, **kw):
|
||||
"""Create a new :class:`.CreateSchema` construct."""
|
||||
|
||||
self.quote = quote
|
||||
super(CreateSchema, self).__init__(name, **kw)
|
||||
|
||||
|
||||
class DropSchema(_CreateDropBase):
|
||||
"""Represent a DROP SCHEMA statement.
|
||||
|
||||
The argument here is the string name of the schema.
|
||||
|
||||
.. versionadded:: 0.7.4
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = "drop_schema"
|
||||
|
||||
def __init__(self, name, quote=None, cascade=False, **kw):
|
||||
"""Create a new :class:`.DropSchema` construct."""
|
||||
|
||||
self.quote = quote
|
||||
self.cascade = cascade
|
||||
super(DropSchema, self).__init__(name, **kw)
|
||||
|
||||
|
||||
class CreateTable(_CreateDropBase):
|
||||
"""Represent a CREATE TABLE statement."""
|
||||
|
||||
__visit_name__ = "create_table"
|
||||
|
||||
def __init__(self, element, on=None, bind=None):
|
||||
"""Create a :class:`.CreateTable` construct.
|
||||
|
||||
:param element: a :class:`.Table` that's the subject
|
||||
of the CREATE
|
||||
:param on: See the description for 'on' in :class:`.DDL`.
|
||||
:param bind: See the description for 'bind' in :class:`.DDL`.
|
||||
|
||||
"""
|
||||
super(CreateTable, self).__init__(element, on=on, bind=bind)
|
||||
self.columns = [CreateColumn(column)
|
||||
for column in element.columns
|
||||
]
|
||||
|
||||
|
||||
class _DropView(_CreateDropBase):
|
||||
"""Semi-public 'DROP VIEW' construct.
|
||||
|
||||
Used by the test suite for dialect-agnostic drops of views.
|
||||
This object will eventually be part of a public "view" API.
|
||||
|
||||
"""
|
||||
__visit_name__ = "drop_view"
|
||||
|
||||
|
||||
class CreateColumn(_DDLCompiles):
|
||||
"""Represent a :class:`.Column` as rendered in a CREATE TABLE statement,
|
||||
via the :class:`.CreateTable` construct.
|
||||
|
||||
This is provided to support custom column DDL within the generation
|
||||
of CREATE TABLE statements, by using the
|
||||
compiler extension documented in :ref:`sqlalchemy.ext.compiler_toplevel`
|
||||
to extend :class:`.CreateColumn`.
|
||||
|
||||
Typical integration is to examine the incoming :class:`.Column`
|
||||
object, and to redirect compilation if a particular flag or condition
|
||||
is found::
|
||||
|
||||
from sqlalchemy import schema
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
|
||||
@compiles(schema.CreateColumn)
|
||||
def compile(element, compiler, **kw):
|
||||
column = element.element
|
||||
|
||||
if "special" not in column.info:
|
||||
return compiler.visit_create_column(element, **kw)
|
||||
|
||||
text = "%s SPECIAL DIRECTIVE %s" % (
|
||||
column.name,
|
||||
compiler.type_compiler.process(column.type)
|
||||
)
|
||||
default = compiler.get_column_default_string(column)
|
||||
if default is not None:
|
||||
text += " DEFAULT " + default
|
||||
|
||||
if not column.nullable:
|
||||
text += " NOT NULL"
|
||||
|
||||
if column.constraints:
|
||||
text += " ".join(
|
||||
compiler.process(const)
|
||||
for const in column.constraints)
|
||||
return text
|
||||
|
||||
The above construct can be applied to a :class:`.Table` as follows::
|
||||
|
||||
from sqlalchemy import Table, Metadata, Column, Integer, String
|
||||
from sqlalchemy import schema
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
table = Table('mytable', MetaData(),
|
||||
Column('x', Integer, info={"special":True}, primary_key=True),
|
||||
Column('y', String(50)),
|
||||
Column('z', String(20), info={"special":True})
|
||||
)
|
||||
|
||||
metadata.create_all(conn)
|
||||
|
||||
Above, the directives we've added to the :attr:`.Column.info` collection
|
||||
will be detected by our custom compilation scheme::
|
||||
|
||||
CREATE TABLE mytable (
|
||||
x SPECIAL DIRECTIVE INTEGER NOT NULL,
|
||||
y VARCHAR(50),
|
||||
z SPECIAL DIRECTIVE VARCHAR(20),
|
||||
PRIMARY KEY (x)
|
||||
)
|
||||
|
||||
The :class:`.CreateColumn` construct can also be used to skip certain
|
||||
columns when producing a ``CREATE TABLE``. This is accomplished by
|
||||
creating a compilation rule that conditionally returns ``None``.
|
||||
This is essentially how to produce the same effect as using the
|
||||
``system=True`` argument on :class:`.Column`, which marks a column
|
||||
as an implicitly-present "system" column.
|
||||
|
||||
For example, suppose we wish to produce a :class:`.Table` which skips
|
||||
rendering of the Postgresql ``xmin`` column against the Postgresql backend,
|
||||
but on other backends does render it, in anticipation of a triggered rule.
|
||||
A conditional compilation rule could skip this name only on Postgresql::
|
||||
|
||||
from sqlalchemy.schema import CreateColumn
|
||||
|
||||
@compiles(CreateColumn, "postgresql")
|
||||
def skip_xmin(element, compiler, **kw):
|
||||
if element.element.name == 'xmin':
|
||||
return None
|
||||
else:
|
||||
return compiler.visit_create_column(element, **kw)
|
||||
|
||||
|
||||
my_table = Table('mytable', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('xmin', Integer)
|
||||
)
|
||||
|
||||
Above, a :class:`.CreateTable` construct will generate a ``CREATE TABLE``
|
||||
which only includes the ``id`` column in the string; the ``xmin`` column
|
||||
will be omitted, but only against the Postgresql backend.
|
||||
|
||||
.. versionadded:: 0.8.3 The :class:`.CreateColumn` construct supports
|
||||
skipping of columns by returning ``None`` from a custom compilation rule.
|
||||
|
||||
.. versionadded:: 0.8 The :class:`.CreateColumn` construct was added
|
||||
to support custom column creation styles.
|
||||
|
||||
"""
|
||||
__visit_name__ = 'create_column'
|
||||
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
|
||||
class DropTable(_CreateDropBase):
|
||||
"""Represent a DROP TABLE statement."""
|
||||
|
||||
__visit_name__ = "drop_table"
|
||||
|
||||
|
||||
class CreateSequence(_CreateDropBase):
|
||||
"""Represent a CREATE SEQUENCE statement."""
|
||||
|
||||
__visit_name__ = "create_sequence"
|
||||
|
||||
|
||||
class DropSequence(_CreateDropBase):
|
||||
"""Represent a DROP SEQUENCE statement."""
|
||||
|
||||
__visit_name__ = "drop_sequence"
|
||||
|
||||
|
||||
class CreateIndex(_CreateDropBase):
|
||||
"""Represent a CREATE INDEX statement."""
|
||||
|
||||
__visit_name__ = "create_index"
|
||||
|
||||
|
||||
class DropIndex(_CreateDropBase):
|
||||
"""Represent a DROP INDEX statement."""
|
||||
|
||||
__visit_name__ = "drop_index"
|
||||
|
||||
|
||||
class AddConstraint(_CreateDropBase):
|
||||
"""Represent an ALTER TABLE ADD CONSTRAINT statement."""
|
||||
|
||||
__visit_name__ = "add_constraint"
|
||||
|
||||
def __init__(self, element, *args, **kw):
|
||||
super(AddConstraint, self).__init__(element, *args, **kw)
|
||||
element._create_rule = util.portable_instancemethod(
|
||||
self._create_rule_disable)
|
||||
|
||||
|
||||
class DropConstraint(_CreateDropBase):
|
||||
"""Represent an ALTER TABLE DROP CONSTRAINT statement."""
|
||||
|
||||
__visit_name__ = "drop_constraint"
|
||||
|
||||
def __init__(self, element, cascade=False, **kw):
|
||||
self.cascade = cascade
|
||||
super(DropConstraint, self).__init__(element, **kw)
|
||||
element._create_rule = util.portable_instancemethod(
|
||||
self._create_rule_disable)
|
||||
|
||||
|
||||
class DDLBase(SchemaVisitor):
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
|
||||
class SchemaGenerator(DDLBase):
|
||||
|
||||
def __init__(self, dialect, connection, checkfirst=False,
|
||||
tables=None, **kwargs):
|
||||
super(SchemaGenerator, self).__init__(connection, **kwargs)
|
||||
self.checkfirst = checkfirst
|
||||
self.tables = tables
|
||||
self.preparer = dialect.identifier_preparer
|
||||
self.dialect = dialect
|
||||
self.memo = {}
|
||||
|
||||
def _can_create_table(self, table):
|
||||
self.dialect.validate_identifier(table.name)
|
||||
if table.schema:
|
||||
self.dialect.validate_identifier(table.schema)
|
||||
return not self.checkfirst or \
|
||||
not self.dialect.has_table(self.connection,
|
||||
table.name, schema=table.schema)
|
||||
|
||||
def _can_create_sequence(self, sequence):
|
||||
return self.dialect.supports_sequences and \
|
||||
(
|
||||
(not self.dialect.sequences_optional or
|
||||
not sequence.optional) and
|
||||
(
|
||||
not self.checkfirst or
|
||||
not self.dialect.has_sequence(
|
||||
self.connection,
|
||||
sequence.name,
|
||||
schema=sequence.schema)
|
||||
)
|
||||
)
|
||||
|
||||
def visit_metadata(self, metadata):
|
||||
if self.tables is not None:
|
||||
tables = self.tables
|
||||
else:
|
||||
tables = list(metadata.tables.values())
|
||||
collection = [t for t in sort_tables(tables)
|
||||
if self._can_create_table(t)]
|
||||
seq_coll = [s for s in metadata._sequences.values()
|
||||
if s.column is None and self._can_create_sequence(s)]
|
||||
|
||||
metadata.dispatch.before_create(metadata, self.connection,
|
||||
tables=collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for seq in seq_coll:
|
||||
self.traverse_single(seq, create_ok=True)
|
||||
|
||||
for table in collection:
|
||||
self.traverse_single(table, create_ok=True)
|
||||
|
||||
metadata.dispatch.after_create(metadata, self.connection,
|
||||
tables=collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_table(self, table, create_ok=False):
|
||||
if not create_ok and not self._can_create_table(table):
|
||||
return
|
||||
|
||||
table.dispatch.before_create(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for column in table.columns:
|
||||
if column.default is not None:
|
||||
self.traverse_single(column.default)
|
||||
|
||||
self.connection.execute(CreateTable(table))
|
||||
|
||||
if hasattr(table, 'indexes'):
|
||||
for index in table.indexes:
|
||||
self.traverse_single(index)
|
||||
|
||||
table.dispatch.after_create(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_sequence(self, sequence, create_ok=False):
|
||||
if not create_ok and not self._can_create_sequence(sequence):
|
||||
return
|
||||
self.connection.execute(CreateSequence(sequence))
|
||||
|
||||
def visit_index(self, index):
|
||||
self.connection.execute(CreateIndex(index))
|
||||
|
||||
|
||||
class SchemaDropper(DDLBase):
|
||||
|
||||
def __init__(self, dialect, connection, checkfirst=False,
|
||||
tables=None, **kwargs):
|
||||
super(SchemaDropper, self).__init__(connection, **kwargs)
|
||||
self.checkfirst = checkfirst
|
||||
self.tables = tables
|
||||
self.preparer = dialect.identifier_preparer
|
||||
self.dialect = dialect
|
||||
self.memo = {}
|
||||
|
||||
def visit_metadata(self, metadata):
|
||||
if self.tables is not None:
|
||||
tables = self.tables
|
||||
else:
|
||||
tables = list(metadata.tables.values())
|
||||
|
||||
collection = [
|
||||
t
|
||||
for t in reversed(sort_tables(tables))
|
||||
if self._can_drop_table(t)
|
||||
]
|
||||
|
||||
seq_coll = [
|
||||
s
|
||||
for s in metadata._sequences.values()
|
||||
if s.column is None and self._can_drop_sequence(s)
|
||||
]
|
||||
|
||||
metadata.dispatch.before_drop(
|
||||
metadata, self.connection, tables=collection,
|
||||
checkfirst=self.checkfirst, _ddl_runner=self)
|
||||
|
||||
for table in collection:
|
||||
self.traverse_single(table, drop_ok=True)
|
||||
|
||||
for seq in seq_coll:
|
||||
self.traverse_single(seq, drop_ok=True)
|
||||
|
||||
metadata.dispatch.after_drop(
|
||||
metadata, self.connection, tables=collection,
|
||||
checkfirst=self.checkfirst, _ddl_runner=self)
|
||||
|
||||
def _can_drop_table(self, table):
|
||||
self.dialect.validate_identifier(table.name)
|
||||
if table.schema:
|
||||
self.dialect.validate_identifier(table.schema)
|
||||
return not self.checkfirst or self.dialect.has_table(self.connection,
|
||||
table.name, schema=table.schema)
|
||||
|
||||
def _can_drop_sequence(self, sequence):
|
||||
return self.dialect.supports_sequences and \
|
||||
((not self.dialect.sequences_optional or
|
||||
not sequence.optional) and
|
||||
(not self.checkfirst or
|
||||
self.dialect.has_sequence(
|
||||
self.connection,
|
||||
sequence.name,
|
||||
schema=sequence.schema))
|
||||
)
|
||||
|
||||
def visit_index(self, index):
|
||||
self.connection.execute(DropIndex(index))
|
||||
|
||||
def visit_table(self, table, drop_ok=False):
|
||||
if not drop_ok and not self._can_drop_table(table):
|
||||
return
|
||||
|
||||
table.dispatch.before_drop(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for column in table.columns:
|
||||
if column.default is not None:
|
||||
self.traverse_single(column.default)
|
||||
|
||||
self.connection.execute(DropTable(table))
|
||||
|
||||
table.dispatch.after_drop(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_sequence(self, sequence, drop_ok=False):
|
||||
if not drop_ok and not self._can_drop_sequence(sequence):
|
||||
return
|
||||
self.connection.execute(DropSequence(sequence))
|
||||
|
||||
def sort_tables(tables, skip_fn=None, extra_dependencies=None):
|
||||
"""sort a collection of Table objects in order of
|
||||
their foreign-key dependency."""
|
||||
|
||||
tables = list(tables)
|
||||
tuples = []
|
||||
if extra_dependencies is not None:
|
||||
tuples.extend(extra_dependencies)
|
||||
|
||||
def visit_foreign_key(fkey):
|
||||
if fkey.use_alter:
|
||||
return
|
||||
elif skip_fn and skip_fn(fkey):
|
||||
return
|
||||
parent_table = fkey.column.table
|
||||
if parent_table in tables:
|
||||
child_table = fkey.parent.table
|
||||
if parent_table is not child_table:
|
||||
tuples.append((parent_table, child_table))
|
||||
|
||||
for table in tables:
|
||||
traverse(table,
|
||||
{'schema_visitor': True},
|
||||
{'foreign_key': visit_foreign_key})
|
||||
|
||||
tuples.extend(
|
||||
[parent, table] for parent in table._extra_dependencies
|
||||
)
|
||||
|
||||
return list(topological.sort(tuples, tables))
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
# sql/default_comparator.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Default implementation of SQL comparison operations.
|
||||
"""
|
||||
|
||||
from .. import exc, util
|
||||
from . import operators
|
||||
from . import type_api
|
||||
from .elements import BindParameter, True_, False_, BinaryExpression, \
|
||||
Null, _const_expr, _clause_element_as_expr, \
|
||||
ClauseList, ColumnElement, TextClause, UnaryExpression, \
|
||||
collate, _is_literal, _literal_as_text
|
||||
from .selectable import SelectBase, Alias, Selectable, ScalarSelect
|
||||
|
||||
class _DefaultColumnComparator(operators.ColumnOperators):
|
||||
"""Defines comparison and math operations.
|
||||
|
||||
See :class:`.ColumnOperators` and :class:`.Operators` for descriptions
|
||||
of all operations.
|
||||
|
||||
"""
|
||||
|
||||
@util.memoized_property
|
||||
def type(self):
|
||||
return self.expr.type
|
||||
|
||||
def operate(self, op, *other, **kwargs):
|
||||
o = self.operators[op.__name__]
|
||||
return o[0](self, self.expr, op, *(other + o[1:]), **kwargs)
|
||||
|
||||
def reverse_operate(self, op, other, **kwargs):
|
||||
o = self.operators[op.__name__]
|
||||
return o[0](self, self.expr, op, other, reverse=True, *o[1:], **kwargs)
|
||||
|
||||
def _adapt_expression(self, op, other_comparator):
|
||||
"""evaluate the return type of <self> <op> <othertype>,
|
||||
and apply any adaptations to the given operator.
|
||||
|
||||
This method determines the type of a resulting binary expression
|
||||
given two source types and an operator. For example, two
|
||||
:class:`.Column` objects, both of the type :class:`.Integer`, will
|
||||
produce a :class:`.BinaryExpression` that also has the type
|
||||
:class:`.Integer` when compared via the addition (``+``) operator.
|
||||
However, using the addition operator with an :class:`.Integer`
|
||||
and a :class:`.Date` object will produce a :class:`.Date`, assuming
|
||||
"days delta" behavior by the database (in reality, most databases
|
||||
other than Postgresql don't accept this particular operation).
|
||||
|
||||
The method returns a tuple of the form <operator>, <type>.
|
||||
The resulting operator and type will be those applied to the
|
||||
resulting :class:`.BinaryExpression` as the final operator and the
|
||||
right-hand side of the expression.
|
||||
|
||||
Note that only a subset of operators make usage of
|
||||
:meth:`._adapt_expression`,
|
||||
including math operators and user-defined operators, but not
|
||||
boolean comparison or special SQL keywords like MATCH or BETWEEN.
|
||||
|
||||
"""
|
||||
return op, other_comparator.type
|
||||
|
||||
def _boolean_compare(self, expr, op, obj, negate=None, reverse=False,
|
||||
_python_is_types=(util.NoneType, bool),
|
||||
**kwargs):
|
||||
|
||||
if isinstance(obj, _python_is_types + (Null, True_, False_)):
|
||||
|
||||
# allow x ==/!= True/False to be treated as a literal.
|
||||
# this comes out to "== / != true/false" or "1/0" if those
|
||||
# constants aren't supported and works on all platforms
|
||||
if op in (operators.eq, operators.ne) and \
|
||||
isinstance(obj, (bool, True_, False_)):
|
||||
return BinaryExpression(expr,
|
||||
_literal_as_text(obj),
|
||||
op,
|
||||
type_=type_api.BOOLEANTYPE,
|
||||
negate=negate, modifiers=kwargs)
|
||||
else:
|
||||
# all other None/True/False uses IS, IS NOT
|
||||
if op in (operators.eq, operators.is_):
|
||||
return BinaryExpression(expr, _const_expr(obj),
|
||||
operators.is_,
|
||||
negate=operators.isnot)
|
||||
elif op in (operators.ne, operators.isnot):
|
||||
return BinaryExpression(expr, _const_expr(obj),
|
||||
operators.isnot,
|
||||
negate=operators.is_)
|
||||
else:
|
||||
raise exc.ArgumentError(
|
||||
"Only '=', '!=', 'is_()', 'isnot()' operators can "
|
||||
"be used with None/True/False")
|
||||
else:
|
||||
obj = self._check_literal(expr, op, obj)
|
||||
|
||||
if reverse:
|
||||
return BinaryExpression(obj,
|
||||
expr,
|
||||
op,
|
||||
type_=type_api.BOOLEANTYPE,
|
||||
negate=negate, modifiers=kwargs)
|
||||
else:
|
||||
return BinaryExpression(expr,
|
||||
obj,
|
||||
op,
|
||||
type_=type_api.BOOLEANTYPE,
|
||||
negate=negate, modifiers=kwargs)
|
||||
|
||||
def _binary_operate(self, expr, op, obj, reverse=False, result_type=None,
|
||||
**kw):
|
||||
obj = self._check_literal(expr, op, obj)
|
||||
|
||||
if reverse:
|
||||
left, right = obj, expr
|
||||
else:
|
||||
left, right = expr, obj
|
||||
|
||||
if result_type is None:
|
||||
op, result_type = left.comparator._adapt_expression(
|
||||
op, right.comparator)
|
||||
|
||||
return BinaryExpression(left, right, op, type_=result_type)
|
||||
|
||||
def _scalar(self, expr, op, fn, **kw):
|
||||
return fn(expr)
|
||||
|
||||
def _in_impl(self, expr, op, seq_or_selectable, negate_op, **kw):
|
||||
seq_or_selectable = _clause_element_as_expr(seq_or_selectable)
|
||||
|
||||
if isinstance(seq_or_selectable, ScalarSelect):
|
||||
return self._boolean_compare(expr, op, seq_or_selectable,
|
||||
negate=negate_op)
|
||||
elif isinstance(seq_or_selectable, SelectBase):
|
||||
|
||||
# TODO: if we ever want to support (x, y, z) IN (select x,
|
||||
# y, z from table), we would need a multi-column version of
|
||||
# as_scalar() to produce a multi- column selectable that
|
||||
# does not export itself as a FROM clause
|
||||
|
||||
return self._boolean_compare(
|
||||
expr, op, seq_or_selectable.as_scalar(),
|
||||
negate=negate_op, **kw)
|
||||
elif isinstance(seq_or_selectable, (Selectable, TextClause)):
|
||||
return self._boolean_compare(expr, op, seq_or_selectable,
|
||||
negate=negate_op, **kw)
|
||||
|
||||
# Handle non selectable arguments as sequences
|
||||
args = []
|
||||
for o in seq_or_selectable:
|
||||
if not _is_literal(o):
|
||||
if not isinstance(o, operators.ColumnOperators):
|
||||
raise exc.InvalidRequestError('in() function accept'
|
||||
's either a list of non-selectable values, '
|
||||
'or a selectable: %r' % o)
|
||||
elif o is None:
|
||||
o = Null()
|
||||
else:
|
||||
o = expr._bind_param(op, o)
|
||||
args.append(o)
|
||||
if len(args) == 0:
|
||||
|
||||
# Special case handling for empty IN's, behave like
|
||||
# comparison against zero row selectable. We use != to
|
||||
# build the contradiction as it handles NULL values
|
||||
# appropriately, i.e. "not (x IN ())" should not return NULL
|
||||
# values for x.
|
||||
|
||||
util.warn('The IN-predicate on "%s" was invoked with an '
|
||||
'empty sequence. This results in a '
|
||||
'contradiction, which nonetheless can be '
|
||||
'expensive to evaluate. Consider alternative '
|
||||
'strategies for improved performance.' % expr)
|
||||
if op is operators.in_op:
|
||||
return expr != expr
|
||||
else:
|
||||
return expr == expr
|
||||
|
||||
return self._boolean_compare(expr, op,
|
||||
ClauseList(*args).self_group(against=op),
|
||||
negate=negate_op)
|
||||
|
||||
def _unsupported_impl(self, expr, op, *arg, **kw):
|
||||
raise NotImplementedError("Operator '%s' is not supported on "
|
||||
"this expression" % op.__name__)
|
||||
|
||||
def _neg_impl(self, expr, op, **kw):
|
||||
"""See :meth:`.ColumnOperators.__neg__`."""
|
||||
return UnaryExpression(expr, operator=operators.neg)
|
||||
|
||||
def _match_impl(self, expr, op, other, **kw):
|
||||
"""See :meth:`.ColumnOperators.match`."""
|
||||
return self._boolean_compare(expr, operators.match_op,
|
||||
self._check_literal(expr, operators.match_op,
|
||||
other))
|
||||
|
||||
def _distinct_impl(self, expr, op, **kw):
|
||||
"""See :meth:`.ColumnOperators.distinct`."""
|
||||
return UnaryExpression(expr, operator=operators.distinct_op,
|
||||
type_=expr.type)
|
||||
|
||||
def _between_impl(self, expr, op, cleft, cright, **kw):
|
||||
"""See :meth:`.ColumnOperators.between`."""
|
||||
return BinaryExpression(
|
||||
expr,
|
||||
ClauseList(
|
||||
self._check_literal(expr, operators.and_, cleft),
|
||||
self._check_literal(expr, operators.and_, cright),
|
||||
operator=operators.and_,
|
||||
group=False, group_contents=False),
|
||||
operators.between_op)
|
||||
|
||||
def _collate_impl(self, expr, op, other, **kw):
|
||||
return collate(expr, other)
|
||||
|
||||
# a mapping of operators with the method they use, along with
|
||||
# their negated operator for comparison operators
|
||||
operators = {
|
||||
"add": (_binary_operate,),
|
||||
"mul": (_binary_operate,),
|
||||
"sub": (_binary_operate,),
|
||||
"div": (_binary_operate,),
|
||||
"mod": (_binary_operate,),
|
||||
"truediv": (_binary_operate,),
|
||||
"custom_op": (_binary_operate,),
|
||||
"concat_op": (_binary_operate,),
|
||||
"lt": (_boolean_compare, operators.ge),
|
||||
"le": (_boolean_compare, operators.gt),
|
||||
"ne": (_boolean_compare, operators.eq),
|
||||
"gt": (_boolean_compare, operators.le),
|
||||
"ge": (_boolean_compare, operators.lt),
|
||||
"eq": (_boolean_compare, operators.ne),
|
||||
"like_op": (_boolean_compare, operators.notlike_op),
|
||||
"ilike_op": (_boolean_compare, operators.notilike_op),
|
||||
"notlike_op": (_boolean_compare, operators.like_op),
|
||||
"notilike_op": (_boolean_compare, operators.ilike_op),
|
||||
"contains_op": (_boolean_compare, operators.notcontains_op),
|
||||
"startswith_op": (_boolean_compare, operators.notstartswith_op),
|
||||
"endswith_op": (_boolean_compare, operators.notendswith_op),
|
||||
"desc_op": (_scalar, UnaryExpression._create_desc),
|
||||
"asc_op": (_scalar, UnaryExpression._create_asc),
|
||||
"nullsfirst_op": (_scalar, UnaryExpression._create_nullsfirst),
|
||||
"nullslast_op": (_scalar, UnaryExpression._create_nullslast),
|
||||
"in_op": (_in_impl, operators.notin_op),
|
||||
"notin_op": (_in_impl, operators.in_op),
|
||||
"is_": (_boolean_compare, operators.is_),
|
||||
"isnot": (_boolean_compare, operators.isnot),
|
||||
"collate": (_collate_impl,),
|
||||
"match_op": (_match_impl,),
|
||||
"distinct_op": (_distinct_impl,),
|
||||
"between_op": (_between_impl, ),
|
||||
"neg": (_neg_impl,),
|
||||
"getitem": (_unsupported_impl,),
|
||||
"lshift": (_unsupported_impl,),
|
||||
"rshift": (_unsupported_impl,),
|
||||
}
|
||||
|
||||
def _check_literal(self, expr, operator, other):
|
||||
if isinstance(other, (ColumnElement, TextClause)):
|
||||
if isinstance(other, BindParameter) and \
|
||||
other.type._isnull:
|
||||
other = other._clone()
|
||||
other.type = expr.type
|
||||
return other
|
||||
elif hasattr(other, '__clause_element__'):
|
||||
other = other.__clause_element__()
|
||||
elif isinstance(other, type_api.TypeEngine.Comparator):
|
||||
other = other.expr
|
||||
|
||||
if isinstance(other, (SelectBase, Alias)):
|
||||
return other.as_scalar()
|
||||
elif not isinstance(other, (ColumnElement, TextClause)):
|
||||
return expr._bind_param(operator, other)
|
||||
else:
|
||||
return other
|
||||
|
||||
@@ -0,0 +1,770 @@
|
||||
# sql/dml.py
|
||||
# Copyright (C) 2009-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""
|
||||
Provide :class:`.Insert`, :class:`.Update` and :class:`.Delete`.
|
||||
|
||||
"""
|
||||
|
||||
from .base import Executable, _generative, _from_objects
|
||||
from .elements import ClauseElement, _literal_as_text, Null, and_, _clone
|
||||
from .selectable import _interpret_as_from, _interpret_as_select, HasPrefixes
|
||||
from .. import util
|
||||
from .. import exc
|
||||
|
||||
class UpdateBase(HasPrefixes, Executable, ClauseElement):
|
||||
"""Form the base for ``INSERT``, ``UPDATE``, and ``DELETE`` statements.
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'update_base'
|
||||
|
||||
_execution_options = \
|
||||
Executable._execution_options.union({'autocommit': True})
|
||||
kwargs = util.immutabledict()
|
||||
_hints = util.immutabledict()
|
||||
_prefixes = ()
|
||||
|
||||
def _process_colparams(self, parameters):
|
||||
def process_single(p):
|
||||
if isinstance(p, (list, tuple)):
|
||||
return dict(
|
||||
(c.key, pval)
|
||||
for c, pval in zip(self.table.c, p)
|
||||
)
|
||||
else:
|
||||
return p
|
||||
|
||||
if isinstance(parameters, (list, tuple)) and \
|
||||
isinstance(parameters[0], (list, tuple, dict)):
|
||||
|
||||
if not self._supports_multi_parameters:
|
||||
raise exc.InvalidRequestError(
|
||||
"This construct does not support "
|
||||
"multiple parameter sets.")
|
||||
|
||||
return [process_single(p) for p in parameters], True
|
||||
else:
|
||||
return process_single(parameters), False
|
||||
|
||||
def params(self, *arg, **kw):
|
||||
"""Set the parameters for the statement.
|
||||
|
||||
This method raises ``NotImplementedError`` on the base class,
|
||||
and is overridden by :class:`.ValuesBase` to provide the
|
||||
SET/VALUES clause of UPDATE and INSERT.
|
||||
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"params() is not supported for INSERT/UPDATE/DELETE statements."
|
||||
" To set the values for an INSERT or UPDATE statement, use"
|
||||
" stmt.values(**parameters).")
|
||||
|
||||
def bind(self):
|
||||
"""Return a 'bind' linked to this :class:`.UpdateBase`
|
||||
or a :class:`.Table` associated with it.
|
||||
|
||||
"""
|
||||
return self._bind or self.table.bind
|
||||
|
||||
def _set_bind(self, bind):
|
||||
self._bind = bind
|
||||
bind = property(bind, _set_bind)
|
||||
|
||||
@_generative
|
||||
def returning(self, *cols):
|
||||
"""Add a :term:`RETURNING` or equivalent clause to this statement.
|
||||
|
||||
e.g.::
|
||||
|
||||
stmt = table.update().\\
|
||||
where(table.c.data == 'value').\\
|
||||
values(status='X').\\
|
||||
returning(table.c.server_flag, table.c.updated_timestamp)
|
||||
|
||||
for server_flag, updated_timestamp in connection.execute(stmt):
|
||||
print(server_flag, updated_timestamp)
|
||||
|
||||
The given collection of column expressions should be derived from
|
||||
the table that is
|
||||
the target of the INSERT, UPDATE, or DELETE. While :class:`.Column`
|
||||
objects are typical, the elements can also be expressions::
|
||||
|
||||
stmt = table.insert().returning(
|
||||
(table.c.first_name + " " + table.c.last_name).label('fullname')
|
||||
)
|
||||
|
||||
Upon compilation, a RETURNING clause, or database equivalent,
|
||||
will be rendered within the statement. For INSERT and UPDATE,
|
||||
the values are the newly inserted/updated values. For DELETE,
|
||||
the values are those of the rows which were deleted.
|
||||
|
||||
Upon execution, the values of the columns to be returned
|
||||
are made available via the result set and can be iterated
|
||||
using :meth:`.ResultProxy.fetchone` and similar. For DBAPIs which do not
|
||||
natively support returning values (i.e. cx_oracle),
|
||||
SQLAlchemy will approximate this behavior at the result level
|
||||
so that a reasonable amount of behavioral neutrality is
|
||||
provided.
|
||||
|
||||
Note that not all databases/DBAPIs
|
||||
support RETURNING. For those backends with no support,
|
||||
an exception is raised upon compilation and/or execution.
|
||||
For those who do support it, the functionality across backends
|
||||
varies greatly, including restrictions on executemany()
|
||||
and other statements which return multiple rows. Please
|
||||
read the documentation notes for the database in use in
|
||||
order to determine the availability of RETURNING.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.ValuesBase.return_defaults` - an alternative method tailored
|
||||
towards efficient fetching of server-side defaults and triggers
|
||||
for single-row INSERTs or UPDATEs.
|
||||
|
||||
|
||||
"""
|
||||
self._returning = cols
|
||||
|
||||
|
||||
@_generative
|
||||
def with_hint(self, text, selectable=None, dialect_name="*"):
|
||||
"""Add a table hint for a single table to this
|
||||
INSERT/UPDATE/DELETE statement.
|
||||
|
||||
.. note::
|
||||
|
||||
:meth:`.UpdateBase.with_hint` currently applies only to
|
||||
Microsoft SQL Server. For MySQL INSERT/UPDATE/DELETE hints, use
|
||||
:meth:`.UpdateBase.prefix_with`.
|
||||
|
||||
The text of the hint is rendered in the appropriate
|
||||
location for the database backend in use, relative
|
||||
to the :class:`.Table` that is the subject of this
|
||||
statement, or optionally to that of the given
|
||||
:class:`.Table` passed as the ``selectable`` argument.
|
||||
|
||||
The ``dialect_name`` option will limit the rendering of a particular
|
||||
hint to a particular backend. Such as, to add a hint
|
||||
that only takes effect for SQL Server::
|
||||
|
||||
mytable.insert().with_hint("WITH (PAGLOCK)", dialect_name="mssql")
|
||||
|
||||
.. versionadded:: 0.7.6
|
||||
|
||||
:param text: Text of the hint.
|
||||
:param selectable: optional :class:`.Table` that specifies
|
||||
an element of the FROM clause within an UPDATE or DELETE
|
||||
to be the subject of the hint - applies only to certain backends.
|
||||
:param dialect_name: defaults to ``*``, if specified as the name
|
||||
of a particular dialect, will apply these hints only when
|
||||
that dialect is in use.
|
||||
"""
|
||||
if selectable is None:
|
||||
selectable = self.table
|
||||
|
||||
self._hints = self._hints.union(
|
||||
{(selectable, dialect_name): text})
|
||||
|
||||
|
||||
class ValuesBase(UpdateBase):
|
||||
"""Supplies support for :meth:`.ValuesBase.values` to
|
||||
INSERT and UPDATE constructs."""
|
||||
|
||||
__visit_name__ = 'values_base'
|
||||
|
||||
_supports_multi_parameters = False
|
||||
_has_multi_parameters = False
|
||||
select = None
|
||||
|
||||
def __init__(self, table, values, prefixes):
|
||||
self.table = _interpret_as_from(table)
|
||||
self.parameters, self._has_multi_parameters = \
|
||||
self._process_colparams(values)
|
||||
if prefixes:
|
||||
self._setup_prefixes(prefixes)
|
||||
|
||||
@_generative
|
||||
def values(self, *args, **kwargs):
|
||||
"""specify a fixed VALUES clause for an INSERT statement, or the SET
|
||||
clause for an UPDATE.
|
||||
|
||||
Note that the :class:`.Insert` and :class:`.Update` constructs support
|
||||
per-execution time formatting of the VALUES and/or SET clauses,
|
||||
based on the arguments passed to :meth:`.Connection.execute`. However,
|
||||
the :meth:`.ValuesBase.values` method can be used to "fix" a particular
|
||||
set of parameters into the statement.
|
||||
|
||||
Multiple calls to :meth:`.ValuesBase.values` will produce a new
|
||||
construct, each one with the parameter list modified to include
|
||||
the new parameters sent. In the typical case of a single
|
||||
dictionary of parameters, the newly passed keys will replace
|
||||
the same keys in the previous construct. In the case of a list-based
|
||||
"multiple values" construct, each new list of values is extended
|
||||
onto the existing list of values.
|
||||
|
||||
:param \**kwargs: key value pairs representing the string key
|
||||
of a :class:`.Column` mapped to the value to be rendered into the
|
||||
VALUES or SET clause::
|
||||
|
||||
users.insert().values(name="some name")
|
||||
|
||||
users.update().where(users.c.id==5).values(name="some name")
|
||||
|
||||
:param \*args: Alternatively, a dictionary, tuple or list
|
||||
of dictionaries or tuples can be passed as a single positional
|
||||
argument in order to form the VALUES or
|
||||
SET clause of the statement. The single dictionary form
|
||||
works the same as the kwargs form::
|
||||
|
||||
users.insert().values({"name": "some name"})
|
||||
|
||||
If a tuple is passed, the tuple should contain the same number
|
||||
of columns as the target :class:`.Table`::
|
||||
|
||||
users.insert().values((5, "some name"))
|
||||
|
||||
The :class:`.Insert` construct also supports multiply-rendered VALUES
|
||||
construct, for those backends which support this SQL syntax
|
||||
(SQLite, Postgresql, MySQL). This mode is indicated by passing a list
|
||||
of one or more dictionaries/tuples::
|
||||
|
||||
users.insert().values([
|
||||
{"name": "some name"},
|
||||
{"name": "some other name"},
|
||||
{"name": "yet another name"},
|
||||
])
|
||||
|
||||
In the case of an :class:`.Update`
|
||||
construct, only the single dictionary/tuple form is accepted,
|
||||
else an exception is raised. It is also an exception case to
|
||||
attempt to mix the single-/multiple- value styles together,
|
||||
either through multiple :meth:`.ValuesBase.values` calls
|
||||
or by sending a list + kwargs at the same time.
|
||||
|
||||
.. note::
|
||||
|
||||
Passing a multiple values list is *not* the same
|
||||
as passing a multiple values list to the :meth:`.Connection.execute`
|
||||
method. Passing a list of parameter sets to :meth:`.ValuesBase.values`
|
||||
produces a construct of this form::
|
||||
|
||||
INSERT INTO table (col1, col2, col3) VALUES
|
||||
(col1_0, col2_0, col3_0),
|
||||
(col1_1, col2_1, col3_1),
|
||||
...
|
||||
|
||||
whereas a multiple list passed to :meth:`.Connection.execute`
|
||||
has the effect of using the DBAPI
|
||||
`executemany() <http://www.python.org/dev/peps/pep-0249/#id18>`_
|
||||
method, which provides a high-performance system of invoking
|
||||
a single-row INSERT statement many times against a series
|
||||
of parameter sets. The "executemany" style is supported by
|
||||
all database backends, as it does not depend on a special SQL
|
||||
syntax.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
Support for multiple-VALUES INSERT statements.
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`inserts_and_updates` - SQL Expression
|
||||
Language Tutorial
|
||||
|
||||
:func:`~.expression.insert` - produce an ``INSERT`` statement
|
||||
|
||||
:func:`~.expression.update` - produce an ``UPDATE`` statement
|
||||
|
||||
"""
|
||||
if self.select is not None:
|
||||
raise exc.InvalidRequestError(
|
||||
"This construct already inserts from a SELECT")
|
||||
if self._has_multi_parameters and kwargs:
|
||||
raise exc.InvalidRequestError(
|
||||
"This construct already has multiple parameter sets.")
|
||||
|
||||
if args:
|
||||
if len(args) > 1:
|
||||
raise exc.ArgumentError(
|
||||
"Only a single dictionary/tuple or list of "
|
||||
"dictionaries/tuples is accepted positionally.")
|
||||
v = args[0]
|
||||
else:
|
||||
v = {}
|
||||
|
||||
if self.parameters is None:
|
||||
self.parameters, self._has_multi_parameters = \
|
||||
self._process_colparams(v)
|
||||
else:
|
||||
if self._has_multi_parameters:
|
||||
self.parameters = list(self.parameters)
|
||||
p, self._has_multi_parameters = self._process_colparams(v)
|
||||
if not self._has_multi_parameters:
|
||||
raise exc.ArgumentError(
|
||||
"Can't mix single-values and multiple values "
|
||||
"formats in one statement")
|
||||
|
||||
self.parameters.extend(p)
|
||||
else:
|
||||
self.parameters = self.parameters.copy()
|
||||
p, self._has_multi_parameters = self._process_colparams(v)
|
||||
if self._has_multi_parameters:
|
||||
raise exc.ArgumentError(
|
||||
"Can't mix single-values and multiple values "
|
||||
"formats in one statement")
|
||||
self.parameters.update(p)
|
||||
|
||||
if kwargs:
|
||||
if self._has_multi_parameters:
|
||||
raise exc.ArgumentError(
|
||||
"Can't pass kwargs and multiple parameter sets "
|
||||
"simultaenously")
|
||||
else:
|
||||
self.parameters.update(kwargs)
|
||||
|
||||
@_generative
|
||||
def return_defaults(self, *cols):
|
||||
"""Make use of a :term:`RETURNING` clause for the purpose
|
||||
of fetching server-side expressions and defaults.
|
||||
|
||||
E.g.::
|
||||
|
||||
stmt = table.insert().values(data='newdata').return_defaults()
|
||||
|
||||
result = connection.execute(stmt)
|
||||
|
||||
server_created_at = result.returned_defaults['created_at']
|
||||
|
||||
When used against a backend that supports RETURNING, all column
|
||||
values generated by SQL expression or server-side-default will be added
|
||||
to any existing RETURNING clause, provided that
|
||||
:meth:`.UpdateBase.returning` is not used simultaneously. The column values
|
||||
will then be available on the result using the
|
||||
:attr:`.ResultProxy.returned_defaults` accessor as a
|
||||
dictionary, referring to values keyed to the :class:`.Column` object
|
||||
as well as its ``.key``.
|
||||
|
||||
This method differs from :meth:`.UpdateBase.returning` in these ways:
|
||||
|
||||
1. :meth:`.ValuesBase.return_defaults` is only intended for use with
|
||||
an INSERT or an UPDATE statement that matches exactly one row.
|
||||
While the RETURNING construct in the general sense supports multiple
|
||||
rows for a multi-row UPDATE or DELETE statement, or for special
|
||||
cases of INSERT that return multiple rows (e.g. INSERT from SELECT,
|
||||
multi-valued VALUES clause), :meth:`.ValuesBase.return_defaults`
|
||||
is intended only
|
||||
for an "ORM-style" single-row INSERT/UPDATE statement. The row
|
||||
returned by the statement is also consumed implcitly when
|
||||
:meth:`.ValuesBase.return_defaults` is used. By contrast,
|
||||
:meth:`.UpdateBase.returning` leaves the RETURNING result-set intact
|
||||
with a collection of any number of rows.
|
||||
|
||||
2. It is compatible with the existing logic to fetch auto-generated
|
||||
primary key values, also known as "implicit returning". Backends that
|
||||
support RETURNING will automatically make use of RETURNING in order
|
||||
to fetch the value of newly generated primary keys; while the
|
||||
:meth:`.UpdateBase.returning` method circumvents this behavior,
|
||||
:meth:`.ValuesBase.return_defaults` leaves it intact.
|
||||
|
||||
3. It can be called against any backend. Backends that don't support
|
||||
RETURNING will skip the usage of the feature, rather than raising
|
||||
an exception. The return value of :attr:`.ResultProxy.returned_defaults`
|
||||
will be ``None``
|
||||
|
||||
:meth:`.ValuesBase.return_defaults` is used by the ORM to provide
|
||||
an efficient implementation for the ``eager_defaults`` feature of
|
||||
:func:`.mapper`.
|
||||
|
||||
:param cols: optional list of column key names or :class:`.Column`
|
||||
objects. If omitted, all column expressions evaulated on the server
|
||||
are added to the returning list.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.UpdateBase.returning`
|
||||
|
||||
:attr:`.ResultProxy.returned_defaults`
|
||||
|
||||
"""
|
||||
self._return_defaults = cols or True
|
||||
|
||||
|
||||
class Insert(ValuesBase):
|
||||
"""Represent an INSERT construct.
|
||||
|
||||
The :class:`.Insert` object is created using the
|
||||
:func:`~.expression.insert()` function.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`coretutorial_insert_expressions`
|
||||
|
||||
"""
|
||||
__visit_name__ = 'insert'
|
||||
|
||||
_supports_multi_parameters = True
|
||||
|
||||
def __init__(self,
|
||||
table,
|
||||
values=None,
|
||||
inline=False,
|
||||
bind=None,
|
||||
prefixes=None,
|
||||
returning=None,
|
||||
return_defaults=False,
|
||||
**kwargs):
|
||||
"""Construct an :class:`.Insert` object.
|
||||
|
||||
Similar functionality is available via the
|
||||
:meth:`~.TableClause.insert` method on
|
||||
:class:`~.schema.Table`.
|
||||
|
||||
:param table: :class:`.TableClause` which is the subject of the insert.
|
||||
|
||||
:param values: collection of values to be inserted; see
|
||||
:meth:`.Insert.values` for a description of allowed formats here.
|
||||
Can be omitted entirely; a :class:`.Insert` construct will also
|
||||
dynamically render the VALUES clause at execution time based on
|
||||
the parameters passed to :meth:`.Connection.execute`.
|
||||
|
||||
:param inline: if True, SQL defaults will be compiled 'inline' into the
|
||||
statement and not pre-executed.
|
||||
|
||||
If both `values` and compile-time bind parameters are present, the
|
||||
compile-time bind parameters override the information specified
|
||||
within `values` on a per-key basis.
|
||||
|
||||
The keys within `values` can be either :class:`~sqlalchemy.schema.Column`
|
||||
objects or their string identifiers. Each key may reference one of:
|
||||
|
||||
* a literal data value (i.e. string, number, etc.);
|
||||
* a Column object;
|
||||
* a SELECT statement.
|
||||
|
||||
If a ``SELECT`` statement is specified which references this
|
||||
``INSERT`` statement's table, the statement will be correlated
|
||||
against the ``INSERT`` statement.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`coretutorial_insert_expressions` - SQL Expression Tutorial
|
||||
|
||||
:ref:`inserts_and_updates` - SQL Expression Tutorial
|
||||
|
||||
"""
|
||||
ValuesBase.__init__(self, table, values, prefixes)
|
||||
self._bind = bind
|
||||
self.select = self.select_names = None
|
||||
self.inline = inline
|
||||
self._returning = returning
|
||||
self.kwargs = kwargs
|
||||
self._return_defaults = return_defaults
|
||||
|
||||
def get_children(self, **kwargs):
|
||||
if self.select is not None:
|
||||
return self.select,
|
||||
else:
|
||||
return ()
|
||||
|
||||
@_generative
|
||||
def from_select(self, names, select):
|
||||
"""Return a new :class:`.Insert` construct which represents
|
||||
an ``INSERT...FROM SELECT`` statement.
|
||||
|
||||
e.g.::
|
||||
|
||||
sel = select([table1.c.a, table1.c.b]).where(table1.c.c > 5)
|
||||
ins = table2.insert().from_select(['a', 'b'], sel)
|
||||
|
||||
:param names: a sequence of string column names or :class:`.Column`
|
||||
objects representing the target columns.
|
||||
:param select: a :func:`.select` construct, :class:`.FromClause`
|
||||
or other construct which resolves into a :class:`.FromClause`,
|
||||
such as an ORM :class:`.Query` object, etc. The order of
|
||||
columns returned from this FROM clause should correspond to the
|
||||
order of columns sent as the ``names`` parameter; while this
|
||||
is not checked before passing along to the database, the database
|
||||
would normally raise an exception if these column lists don't
|
||||
correspond.
|
||||
|
||||
.. note::
|
||||
|
||||
Depending on backend, it may be necessary for the :class:`.Insert`
|
||||
statement to be constructed using the ``inline=True`` flag; this
|
||||
flag will prevent the implicit usage of ``RETURNING`` when the
|
||||
``INSERT`` statement is rendered, which isn't supported on a backend
|
||||
such as Oracle in conjunction with an ``INSERT..SELECT`` combination::
|
||||
|
||||
sel = select([table1.c.a, table1.c.b]).where(table1.c.c > 5)
|
||||
ins = table2.insert(inline=True).from_select(['a', 'b'], sel)
|
||||
|
||||
.. note::
|
||||
|
||||
A SELECT..INSERT construct in SQL has no VALUES clause. Therefore
|
||||
:class:`.Column` objects which utilize Python-side defaults
|
||||
(e.g. as described at :ref:`metadata_defaults_toplevel`)
|
||||
will **not** take effect when using :meth:`.Insert.from_select`.
|
||||
|
||||
.. versionadded:: 0.8.3
|
||||
|
||||
"""
|
||||
if self.parameters:
|
||||
raise exc.InvalidRequestError(
|
||||
"This construct already inserts value expressions")
|
||||
|
||||
self.parameters, self._has_multi_parameters = \
|
||||
self._process_colparams(dict((n, Null()) for n in names))
|
||||
|
||||
self.select_names = names
|
||||
self.select = _interpret_as_select(select)
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
# TODO: coverage
|
||||
self.parameters = self.parameters.copy()
|
||||
if self.select is not None:
|
||||
self.select = _clone(self.select)
|
||||
|
||||
|
||||
class Update(ValuesBase):
|
||||
"""Represent an Update construct.
|
||||
|
||||
The :class:`.Update` object is created using the :func:`update()` function.
|
||||
|
||||
"""
|
||||
__visit_name__ = 'update'
|
||||
|
||||
def __init__(self,
|
||||
table,
|
||||
whereclause=None,
|
||||
values=None,
|
||||
inline=False,
|
||||
bind=None,
|
||||
prefixes=None,
|
||||
returning=None,
|
||||
return_defaults=False,
|
||||
**kwargs):
|
||||
"""Construct an :class:`.Update` object.
|
||||
|
||||
E.g.::
|
||||
|
||||
from sqlalchemy import update
|
||||
|
||||
stmt = update(users).where(users.c.id==5).\\
|
||||
values(name='user #5')
|
||||
|
||||
Similar functionality is available via the
|
||||
:meth:`~.TableClause.update` method on
|
||||
:class:`.Table`::
|
||||
|
||||
stmt = users.update().\\
|
||||
where(users.c.id==5).\\
|
||||
values(name='user #5')
|
||||
|
||||
:param table: A :class:`.Table` object representing the database
|
||||
table to be updated.
|
||||
|
||||
:param whereclause: Optional SQL expression describing the ``WHERE``
|
||||
condition of the ``UPDATE`` statement. Modern applications
|
||||
may prefer to use the generative :meth:`~Update.where()`
|
||||
method to specify the ``WHERE`` clause.
|
||||
|
||||
The WHERE clause can refer to multiple tables.
|
||||
For databases which support this, an ``UPDATE FROM`` clause will
|
||||
be generated, or on MySQL, a multi-table update. The statement
|
||||
will fail on databases that don't have support for multi-table
|
||||
update statements. A SQL-standard method of referring to
|
||||
additional tables in the WHERE clause is to use a correlated
|
||||
subquery::
|
||||
|
||||
users.update().values(name='ed').where(
|
||||
users.c.name==select([addresses.c.email_address]).\\
|
||||
where(addresses.c.user_id==users.c.id).\\
|
||||
as_scalar()
|
||||
)
|
||||
|
||||
.. versionchanged:: 0.7.4
|
||||
The WHERE clause can refer to multiple tables.
|
||||
|
||||
:param values:
|
||||
Optional dictionary which specifies the ``SET`` conditions of the
|
||||
``UPDATE``. If left as ``None``, the ``SET``
|
||||
conditions are determined from those parameters passed to the
|
||||
statement during the execution and/or compilation of the
|
||||
statement. When compiled standalone without any parameters,
|
||||
the ``SET`` clause generates for all columns.
|
||||
|
||||
Modern applications may prefer to use the generative
|
||||
:meth:`.Update.values` method to set the values of the
|
||||
UPDATE statement.
|
||||
|
||||
:param inline:
|
||||
if True, SQL defaults present on :class:`.Column` objects via
|
||||
the ``default`` keyword will be compiled 'inline' into the statement
|
||||
and not pre-executed. This means that their values will not
|
||||
be available in the dictionary returned from
|
||||
:meth:`.ResultProxy.last_updated_params`.
|
||||
|
||||
If both ``values`` and compile-time bind parameters are present, the
|
||||
compile-time bind parameters override the information specified
|
||||
within ``values`` on a per-key basis.
|
||||
|
||||
The keys within ``values`` can be either :class:`.Column`
|
||||
objects or their string identifiers (specifically the "key" of the
|
||||
:class:`.Column`, normally but not necessarily equivalent to
|
||||
its "name"). Normally, the
|
||||
:class:`.Column` objects used here are expected to be
|
||||
part of the target :class:`.Table` that is the table
|
||||
to be updated. However when using MySQL, a multiple-table
|
||||
UPDATE statement can refer to columns from any of
|
||||
the tables referred to in the WHERE clause.
|
||||
|
||||
The values referred to in ``values`` are typically:
|
||||
|
||||
* a literal data value (i.e. string, number, etc.)
|
||||
* a SQL expression, such as a related :class:`.Column`,
|
||||
a scalar-returning :func:`.select` construct,
|
||||
etc.
|
||||
|
||||
When combining :func:`.select` constructs within the values
|
||||
clause of an :func:`.update` construct,
|
||||
the subquery represented by the :func:`.select` should be
|
||||
*correlated* to the parent table, that is, providing criterion
|
||||
which links the table inside the subquery to the outer table
|
||||
being updated::
|
||||
|
||||
users.update().values(
|
||||
name=select([addresses.c.email_address]).\\
|
||||
where(addresses.c.user_id==users.c.id).\\
|
||||
as_scalar()
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`inserts_and_updates` - SQL Expression
|
||||
Language Tutorial
|
||||
|
||||
|
||||
"""
|
||||
ValuesBase.__init__(self, table, values, prefixes)
|
||||
self._bind = bind
|
||||
self._returning = returning
|
||||
if whereclause is not None:
|
||||
self._whereclause = _literal_as_text(whereclause)
|
||||
else:
|
||||
self._whereclause = None
|
||||
self.inline = inline
|
||||
self.kwargs = kwargs
|
||||
self._return_defaults = return_defaults
|
||||
|
||||
|
||||
def get_children(self, **kwargs):
|
||||
if self._whereclause is not None:
|
||||
return self._whereclause,
|
||||
else:
|
||||
return ()
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
# TODO: coverage
|
||||
self._whereclause = clone(self._whereclause, **kw)
|
||||
self.parameters = self.parameters.copy()
|
||||
|
||||
@_generative
|
||||
def where(self, whereclause):
|
||||
"""return a new update() construct with the given expression added to
|
||||
its WHERE clause, joined to the existing clause via AND, if any.
|
||||
|
||||
"""
|
||||
if self._whereclause is not None:
|
||||
self._whereclause = and_(self._whereclause,
|
||||
_literal_as_text(whereclause))
|
||||
else:
|
||||
self._whereclause = _literal_as_text(whereclause)
|
||||
|
||||
@property
|
||||
def _extra_froms(self):
|
||||
# TODO: this could be made memoized
|
||||
# if the memoization is reset on each generative call.
|
||||
froms = []
|
||||
seen = set([self.table])
|
||||
|
||||
if self._whereclause is not None:
|
||||
for item in _from_objects(self._whereclause):
|
||||
if not seen.intersection(item._cloned_set):
|
||||
froms.append(item)
|
||||
seen.update(item._cloned_set)
|
||||
|
||||
return froms
|
||||
|
||||
|
||||
class Delete(UpdateBase):
|
||||
"""Represent a DELETE construct.
|
||||
|
||||
The :class:`.Delete` object is created using the :func:`delete()` function.
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'delete'
|
||||
|
||||
def __init__(self,
|
||||
table,
|
||||
whereclause=None,
|
||||
bind=None,
|
||||
returning=None,
|
||||
prefixes=None,
|
||||
**kwargs):
|
||||
"""Construct :class:`.Delete` object.
|
||||
|
||||
Similar functionality is available via the
|
||||
:meth:`~.TableClause.delete` method on
|
||||
:class:`~.schema.Table`.
|
||||
|
||||
:param table: The table to be updated.
|
||||
|
||||
:param whereclause: A :class:`.ClauseElement` describing the ``WHERE``
|
||||
condition of the ``UPDATE`` statement. Note that the
|
||||
:meth:`~Delete.where()` generative method may be used instead.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`deletes` - SQL Expression Tutorial
|
||||
|
||||
"""
|
||||
self._bind = bind
|
||||
self.table = _interpret_as_from(table)
|
||||
self._returning = returning
|
||||
|
||||
if prefixes:
|
||||
self._setup_prefixes(prefixes)
|
||||
|
||||
if whereclause is not None:
|
||||
self._whereclause = _literal_as_text(whereclause)
|
||||
else:
|
||||
self._whereclause = None
|
||||
|
||||
self.kwargs = kwargs
|
||||
|
||||
def get_children(self, **kwargs):
|
||||
if self._whereclause is not None:
|
||||
return self._whereclause,
|
||||
else:
|
||||
return ()
|
||||
|
||||
@_generative
|
||||
def where(self, whereclause):
|
||||
"""Add the given WHERE clause to a newly returned delete construct."""
|
||||
|
||||
if self._whereclause is not None:
|
||||
self._whereclause = and_(self._whereclause,
|
||||
_literal_as_text(whereclause))
|
||||
else:
|
||||
self._whereclause = _literal_as_text(whereclause)
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
# TODO: coverage
|
||||
self._whereclause = clone(self._whereclause, **kw)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+109
-5712
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,418 @@
|
||||
# sql/functions.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy import types as sqltypes, schema
|
||||
from sqlalchemy.sql.expression import (
|
||||
ClauseList, Function, _literal_as_binds, text, _type_from_args
|
||||
)
|
||||
from sqlalchemy.sql import operators
|
||||
from sqlalchemy.sql.visitors import VisitableType
|
||||
"""SQL function API, factories, and built-in functions.
|
||||
|
||||
"""
|
||||
from . import sqltypes, schema
|
||||
from .base import Executable
|
||||
from .elements import ClauseList, Cast, Extract, _literal_as_binds, \
|
||||
literal_column, _type_from_args, ColumnElement, _clone,\
|
||||
Over, BindParameter
|
||||
from .selectable import FromClause, Select
|
||||
|
||||
from . import operators
|
||||
from .visitors import VisitableType
|
||||
from .. import util
|
||||
|
||||
_registry = util.defaultdict(dict)
|
||||
|
||||
|
||||
def register_function(identifier, fn, package="_default"):
|
||||
"""Associate a callable with a particular func. name.
|
||||
|
||||
This is normally called by _GenericMeta, but is also
|
||||
available by itself so that a non-Function construct
|
||||
can be associated with the :data:`.func` accessor (i.e.
|
||||
CAST, EXTRACT).
|
||||
|
||||
"""
|
||||
reg = _registry[package]
|
||||
reg[identifier] = fn
|
||||
|
||||
|
||||
class FunctionElement(Executable, ColumnElement, FromClause):
|
||||
"""Base for SQL function-oriented constructs.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.Function` - named SQL function.
|
||||
|
||||
:data:`.func` - namespace which produces registered or ad-hoc
|
||||
:class:`.Function` instances.
|
||||
|
||||
:class:`.GenericFunction` - allows creation of registered function
|
||||
types.
|
||||
|
||||
"""
|
||||
|
||||
packagenames = ()
|
||||
|
||||
def __init__(self, *clauses, **kwargs):
|
||||
"""Construct a :class:`.FunctionElement`.
|
||||
"""
|
||||
args = [_literal_as_binds(c, self.name) for c in clauses]
|
||||
self.clause_expr = ClauseList(
|
||||
operator=operators.comma_op,
|
||||
group_contents=True, *args).\
|
||||
self_group()
|
||||
|
||||
def _execute_on_connection(self, connection, multiparams, params):
|
||||
return connection._execute_function(self, multiparams, params)
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
"""Fulfill the 'columns' contract of :class:`.ColumnElement`.
|
||||
|
||||
Returns a single-element list consisting of this object.
|
||||
|
||||
"""
|
||||
return [self]
|
||||
|
||||
@util.memoized_property
|
||||
def clauses(self):
|
||||
"""Return the underlying :class:`.ClauseList` which contains
|
||||
the arguments for this :class:`.FunctionElement`.
|
||||
|
||||
"""
|
||||
return self.clause_expr.element
|
||||
|
||||
def over(self, partition_by=None, order_by=None):
|
||||
"""Produce an OVER clause against this function.
|
||||
|
||||
Used against aggregate or so-called "window" functions,
|
||||
for database backends that support window functions.
|
||||
|
||||
The expression::
|
||||
|
||||
func.row_number().over(order_by='x')
|
||||
|
||||
is shorthand for::
|
||||
|
||||
from sqlalchemy import over
|
||||
over(func.row_number(), order_by='x')
|
||||
|
||||
See :func:`~.expression.over` for a full description.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
"""
|
||||
return Over(self, partition_by=partition_by, order_by=order_by)
|
||||
|
||||
@property
|
||||
def _from_objects(self):
|
||||
return self.clauses._from_objects
|
||||
|
||||
def get_children(self, **kwargs):
|
||||
return self.clause_expr,
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
self.clause_expr = clone(self.clause_expr, **kw)
|
||||
self._reset_exported()
|
||||
FunctionElement.clauses._reset(self)
|
||||
|
||||
def select(self):
|
||||
"""Produce a :func:`~.expression.select` construct
|
||||
against this :class:`.FunctionElement`.
|
||||
|
||||
This is shorthand for::
|
||||
|
||||
s = select([function_element])
|
||||
|
||||
"""
|
||||
s = Select([self])
|
||||
if self._execution_options:
|
||||
s = s.execution_options(**self._execution_options)
|
||||
return s
|
||||
|
||||
def scalar(self):
|
||||
"""Execute this :class:`.FunctionElement` against an embedded
|
||||
'bind' and return a scalar value.
|
||||
|
||||
This first calls :meth:`~.FunctionElement.select` to
|
||||
produce a SELECT construct.
|
||||
|
||||
Note that :class:`.FunctionElement` can be passed to
|
||||
the :meth:`.Connectable.scalar` method of :class:`.Connection`
|
||||
or :class:`.Engine`.
|
||||
|
||||
"""
|
||||
return self.select().execute().scalar()
|
||||
|
||||
def execute(self):
|
||||
"""Execute this :class:`.FunctionElement` against an embedded
|
||||
'bind'.
|
||||
|
||||
This first calls :meth:`~.FunctionElement.select` to
|
||||
produce a SELECT construct.
|
||||
|
||||
Note that :class:`.FunctionElement` can be passed to
|
||||
the :meth:`.Connectable.execute` method of :class:`.Connection`
|
||||
or :class:`.Engine`.
|
||||
|
||||
"""
|
||||
return self.select().execute()
|
||||
|
||||
def _bind_param(self, operator, obj):
|
||||
return BindParameter(None, obj, _compared_to_operator=operator,
|
||||
_compared_to_type=self.type, unique=True)
|
||||
|
||||
|
||||
class _FunctionGenerator(object):
|
||||
"""Generate :class:`.Function` objects based on getattr calls."""
|
||||
|
||||
def __init__(self, **opts):
|
||||
self.__names = []
|
||||
self.opts = opts
|
||||
|
||||
def __getattr__(self, name):
|
||||
# passthru __ attributes; fixes pydoc
|
||||
if name.startswith('__'):
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
elif name.endswith('_'):
|
||||
name = name[0:-1]
|
||||
f = _FunctionGenerator(**self.opts)
|
||||
f.__names = list(self.__names) + [name]
|
||||
return f
|
||||
|
||||
def __call__(self, *c, **kwargs):
|
||||
o = self.opts.copy()
|
||||
o.update(kwargs)
|
||||
|
||||
tokens = len(self.__names)
|
||||
|
||||
if tokens == 2:
|
||||
package, fname = self.__names
|
||||
elif tokens == 1:
|
||||
package, fname = "_default", self.__names[0]
|
||||
else:
|
||||
package = None
|
||||
|
||||
if package is not None:
|
||||
func = _registry[package].get(fname)
|
||||
if func is not None:
|
||||
return func(*c, **o)
|
||||
|
||||
return Function(self.__names[-1],
|
||||
packagenames=self.__names[0:-1], *c, **o)
|
||||
|
||||
|
||||
func = _FunctionGenerator()
|
||||
"""Generate SQL function expressions.
|
||||
|
||||
:data:`.func` is a special object instance which generates SQL
|
||||
functions based on name-based attributes, e.g.::
|
||||
|
||||
>>> print func.count(1)
|
||||
count(:param_1)
|
||||
|
||||
The element is a column-oriented SQL element like any other, and is
|
||||
used in that way::
|
||||
|
||||
>>> print select([func.count(table.c.id)])
|
||||
SELECT count(sometable.id) FROM sometable
|
||||
|
||||
Any name can be given to :data:`.func`. If the function name is unknown to
|
||||
SQLAlchemy, it will be rendered exactly as is. For common SQL functions
|
||||
which SQLAlchemy is aware of, the name may be interpreted as a *generic
|
||||
function* which will be compiled appropriately to the target database::
|
||||
|
||||
>>> print func.current_timestamp()
|
||||
CURRENT_TIMESTAMP
|
||||
|
||||
To call functions which are present in dot-separated packages,
|
||||
specify them in the same manner::
|
||||
|
||||
>>> print func.stats.yield_curve(5, 10)
|
||||
stats.yield_curve(:yield_curve_1, :yield_curve_2)
|
||||
|
||||
SQLAlchemy can be made aware of the return type of functions to enable
|
||||
type-specific lexical and result-based behavior. For example, to ensure
|
||||
that a string-based function returns a Unicode value and is similarly
|
||||
treated as a string in expressions, specify
|
||||
:class:`~sqlalchemy.types.Unicode` as the type:
|
||||
|
||||
>>> print func.my_string(u'hi', type_=Unicode) + ' ' + \
|
||||
... func.my_string(u'there', type_=Unicode)
|
||||
my_string(:my_string_1) || :my_string_2 || my_string(:my_string_3)
|
||||
|
||||
The object returned by a :data:`.func` call is usually an instance of
|
||||
:class:`.Function`.
|
||||
This object meets the "column" interface, including comparison and labeling
|
||||
functions. The object can also be passed the :meth:`~.Connectable.execute`
|
||||
method of a :class:`.Connection` or :class:`.Engine`, where it will be
|
||||
wrapped inside of a SELECT statement first::
|
||||
|
||||
print connection.execute(func.current_timestamp()).scalar()
|
||||
|
||||
In a few exception cases, the :data:`.func` accessor
|
||||
will redirect a name to a built-in expression such as :func:`.cast`
|
||||
or :func:`.extract`, as these names have well-known meaning
|
||||
but are not exactly the same as "functions" from a SQLAlchemy
|
||||
perspective.
|
||||
|
||||
.. versionadded:: 0.8 :data:`.func` can return non-function expression
|
||||
constructs for common quasi-functional names like :func:`.cast`
|
||||
and :func:`.extract`.
|
||||
|
||||
Functions which are interpreted as "generic" functions know how to
|
||||
calculate their return type automatically. For a listing of known generic
|
||||
functions, see :ref:`generic_functions`.
|
||||
|
||||
"""
|
||||
|
||||
modifier = _FunctionGenerator(group=False)
|
||||
|
||||
class Function(FunctionElement):
|
||||
"""Describe a named SQL function.
|
||||
|
||||
See the superclass :class:`.FunctionElement` for a description
|
||||
of public methods.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:data:`.func` - namespace which produces registered or ad-hoc
|
||||
:class:`.Function` instances.
|
||||
|
||||
:class:`.GenericFunction` - allows creation of registered function
|
||||
types.
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'function'
|
||||
|
||||
def __init__(self, name, *clauses, **kw):
|
||||
"""Construct a :class:`.Function`.
|
||||
|
||||
The :data:`.func` construct is normally used to construct
|
||||
new :class:`.Function` instances.
|
||||
|
||||
"""
|
||||
self.packagenames = kw.pop('packagenames', None) or []
|
||||
self.name = name
|
||||
self._bind = kw.get('bind', None)
|
||||
self.type = sqltypes.to_instance(kw.get('type_', None))
|
||||
|
||||
FunctionElement.__init__(self, *clauses, **kw)
|
||||
|
||||
def _bind_param(self, operator, obj):
|
||||
return BindParameter(self.name, obj,
|
||||
_compared_to_operator=operator,
|
||||
_compared_to_type=self.type,
|
||||
unique=True)
|
||||
|
||||
|
||||
class _GenericMeta(VisitableType):
|
||||
def __call__(self, *args, **kwargs):
|
||||
args = [_literal_as_binds(c) for c in args]
|
||||
return type.__call__(self, *args, **kwargs)
|
||||
def __init__(cls, clsname, bases, clsdict):
|
||||
cls.name = name = clsdict.get('name', clsname)
|
||||
cls.identifier = identifier = clsdict.get('identifier', name)
|
||||
package = clsdict.pop('package', '_default')
|
||||
# legacy
|
||||
if '__return_type__' in clsdict:
|
||||
cls.type = clsdict['__return_type__']
|
||||
register_function(identifier, cls, package)
|
||||
super(_GenericMeta, cls).__init__(clsname, bases, clsdict)
|
||||
|
||||
class GenericFunction(Function):
|
||||
__metaclass__ = _GenericMeta
|
||||
|
||||
def __init__(self, type_=None, args=(), **kwargs):
|
||||
class GenericFunction(util.with_metaclass(_GenericMeta, Function)):
|
||||
"""Define a 'generic' function.
|
||||
|
||||
A generic function is a pre-established :class:`.Function`
|
||||
class that is instantiated automatically when called
|
||||
by name from the :data:`.func` attribute. Note that
|
||||
calling any name from :data:`.func` has the effect that
|
||||
a new :class:`.Function` instance is created automatically,
|
||||
given that name. The primary use case for defining
|
||||
a :class:`.GenericFunction` class is so that a function
|
||||
of a particular name may be given a fixed return type.
|
||||
It can also include custom argument parsing schemes as well
|
||||
as additional methods.
|
||||
|
||||
Subclasses of :class:`.GenericFunction` are automatically
|
||||
registered under the name of the class. For
|
||||
example, a user-defined function ``as_utc()`` would
|
||||
be available immediately::
|
||||
|
||||
from sqlalchemy.sql.functions import GenericFunction
|
||||
from sqlalchemy.types import DateTime
|
||||
|
||||
class as_utc(GenericFunction):
|
||||
type = DateTime
|
||||
|
||||
print select([func.as_utc()])
|
||||
|
||||
User-defined generic functions can be organized into
|
||||
packages by specifying the "package" attribute when defining
|
||||
:class:`.GenericFunction`. Third party libraries
|
||||
containing many functions may want to use this in order
|
||||
to avoid name conflicts with other systems. For example,
|
||||
if our ``as_utc()`` function were part of a package
|
||||
"time"::
|
||||
|
||||
class as_utc(GenericFunction):
|
||||
type = DateTime
|
||||
package = "time"
|
||||
|
||||
The above function would be available from :data:`.func`
|
||||
using the package name ``time``::
|
||||
|
||||
print select([func.time.as_utc()])
|
||||
|
||||
A final option is to allow the function to be accessed
|
||||
from one name in :data:`.func` but to render as a different name.
|
||||
The ``identifier`` attribute will override the name used to
|
||||
access the function as loaded from :data:`.func`, but will retain
|
||||
the usage of ``name`` as the rendered name::
|
||||
|
||||
class GeoBuffer(GenericFunction):
|
||||
type = Geometry
|
||||
package = "geo"
|
||||
name = "ST_Buffer"
|
||||
identifier = "buffer"
|
||||
|
||||
The above function will render as follows::
|
||||
|
||||
>>> print func.geo.buffer()
|
||||
ST_Buffer()
|
||||
|
||||
.. versionadded:: 0.8 :class:`.GenericFunction` now supports
|
||||
automatic registration of new functions as well as package
|
||||
and custom naming support.
|
||||
|
||||
.. versionchanged:: 0.8 The attribute name ``type`` is used
|
||||
to specify the function's return type at the class level.
|
||||
Previously, the name ``__return_type__`` was used. This
|
||||
name is still recognized for backwards-compatibility.
|
||||
|
||||
"""
|
||||
|
||||
coerce_arguments = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
parsed_args = kwargs.pop('_parsed_args', None)
|
||||
if parsed_args is None:
|
||||
parsed_args = [_literal_as_binds(c) for c in args]
|
||||
self.packagenames = []
|
||||
self.name = self.__class__.__name__
|
||||
self._bind = kwargs.get('bind', None)
|
||||
self.clause_expr = ClauseList(
|
||||
operator=operators.comma_op,
|
||||
group_contents=True, *args).self_group()
|
||||
group_contents=True, *parsed_args).self_group()
|
||||
self.type = sqltypes.to_instance(
|
||||
type_ or getattr(self, '__return_type__', None))
|
||||
kwargs.pop("type_", None) or getattr(self, 'type', None))
|
||||
|
||||
|
||||
class next_value(Function):
|
||||
register_function("cast", Cast)
|
||||
register_function("extract", Extract)
|
||||
|
||||
|
||||
class next_value(GenericFunction):
|
||||
"""Represent the 'next value', given a :class:`.Sequence`
|
||||
as it's single argument.
|
||||
|
||||
@@ -52,83 +434,101 @@ class next_value(Function):
|
||||
def _from_objects(self):
|
||||
return []
|
||||
|
||||
|
||||
class AnsiFunction(GenericFunction):
|
||||
def __init__(self, **kwargs):
|
||||
GenericFunction.__init__(self, **kwargs)
|
||||
|
||||
|
||||
class ReturnTypeFromArgs(GenericFunction):
|
||||
"""Define a function whose return type is the same as its arguments."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
args = [_literal_as_binds(c) for c in args]
|
||||
kwargs.setdefault('type_', _type_from_args(args))
|
||||
GenericFunction.__init__(self, args=args, **kwargs)
|
||||
kwargs['_parsed_args'] = args
|
||||
GenericFunction.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
class coalesce(ReturnTypeFromArgs):
|
||||
pass
|
||||
|
||||
|
||||
class max(ReturnTypeFromArgs):
|
||||
pass
|
||||
|
||||
|
||||
class min(ReturnTypeFromArgs):
|
||||
pass
|
||||
|
||||
|
||||
class sum(ReturnTypeFromArgs):
|
||||
pass
|
||||
|
||||
|
||||
class now(GenericFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class concat(GenericFunction):
|
||||
__return_type__ = sqltypes.String
|
||||
def __init__(self, *args, **kwargs):
|
||||
GenericFunction.__init__(self, args=args, **kwargs)
|
||||
type = sqltypes.String
|
||||
|
||||
|
||||
class char_length(GenericFunction):
|
||||
__return_type__ = sqltypes.Integer
|
||||
type = sqltypes.Integer
|
||||
|
||||
def __init__(self, arg, **kwargs):
|
||||
GenericFunction.__init__(self, args=[arg], **kwargs)
|
||||
GenericFunction.__init__(self, arg, **kwargs)
|
||||
|
||||
|
||||
class random(GenericFunction):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('type_', None)
|
||||
GenericFunction.__init__(self, args=args, **kwargs)
|
||||
pass
|
||||
|
||||
|
||||
class count(GenericFunction):
|
||||
"""The ANSI COUNT aggregate function. With no arguments, emits COUNT \*."""
|
||||
"""The ANSI COUNT aggregate function. With no arguments,
|
||||
emits COUNT \*.
|
||||
|
||||
__return_type__ = sqltypes.Integer
|
||||
"""
|
||||
type = sqltypes.Integer
|
||||
|
||||
def __init__(self, expression=None, **kwargs):
|
||||
if expression is None:
|
||||
expression = text('*')
|
||||
GenericFunction.__init__(self, args=(expression,), **kwargs)
|
||||
expression = literal_column('*')
|
||||
GenericFunction.__init__(self, expression, **kwargs)
|
||||
|
||||
|
||||
class current_date(AnsiFunction):
|
||||
__return_type__ = sqltypes.Date
|
||||
type = sqltypes.Date
|
||||
|
||||
|
||||
class current_time(AnsiFunction):
|
||||
__return_type__ = sqltypes.Time
|
||||
type = sqltypes.Time
|
||||
|
||||
|
||||
class current_timestamp(AnsiFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class current_user(AnsiFunction):
|
||||
__return_type__ = sqltypes.String
|
||||
type = sqltypes.String
|
||||
|
||||
|
||||
class localtime(AnsiFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class localtimestamp(AnsiFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class session_user(AnsiFunction):
|
||||
__return_type__ = sqltypes.String
|
||||
type = sqltypes.String
|
||||
|
||||
|
||||
class sysdate(AnsiFunction):
|
||||
__return_type__ = sqltypes.DateTime
|
||||
type = sqltypes.DateTime
|
||||
|
||||
|
||||
class user(AnsiFunction):
|
||||
__return_type__ = sqltypes.String
|
||||
|
||||
type = sqltypes.String
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sql/operators.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -9,21 +9,29 @@
|
||||
|
||||
"""Defines operators used in SQL expressions."""
|
||||
|
||||
from .. import util
|
||||
|
||||
|
||||
from operator import (
|
||||
and_, or_, inv, add, mul, sub, mod, truediv, lt, le, ne, gt, ge, eq, neg
|
||||
and_, or_, inv, add, mul, sub, mod, truediv, lt, le, ne, gt, ge, eq, neg,
|
||||
getitem, lshift, rshift
|
||||
)
|
||||
|
||||
# Py2K
|
||||
from operator import (div,)
|
||||
# end Py2K
|
||||
if util.py2k:
|
||||
from operator import div
|
||||
else:
|
||||
div = truediv
|
||||
|
||||
|
||||
from sqlalchemy.util import symbol
|
||||
|
||||
class Operators(object):
|
||||
"""Base of comparison and logical operators.
|
||||
|
||||
Implements base methods :meth:`operate` and :meth:`reverse_operate`,
|
||||
as well as :meth:`__and__`, :meth:`__or__`, :meth:`__invert__`.
|
||||
Implements base methods :meth:`~sqlalchemy.sql.operators.Operators.operate` and
|
||||
:meth:`~sqlalchemy.sql.operators.Operators.reverse_operate`, as well as
|
||||
:meth:`~sqlalchemy.sql.operators.Operators.__and__`,
|
||||
:meth:`~sqlalchemy.sql.operators.Operators.__or__`,
|
||||
:meth:`~sqlalchemy.sql.operators.Operators.__invert__`.
|
||||
|
||||
Usually is used via its most common subclass
|
||||
:class:`.ColumnOperators`.
|
||||
@@ -94,7 +102,7 @@ class Operators(object):
|
||||
"""
|
||||
return self.operate(inv)
|
||||
|
||||
def op(self, opstring):
|
||||
def op(self, opstring, precedence=0):
|
||||
"""produce a generic operator function.
|
||||
|
||||
e.g.::
|
||||
@@ -105,21 +113,37 @@ class Operators(object):
|
||||
|
||||
somecolumn * 5
|
||||
|
||||
:param operator: a string which will be output as the infix operator
|
||||
between this :class:`.ClauseElement` and the expression passed to the
|
||||
generated function.
|
||||
|
||||
This function can also be used to make bitwise operators explicit. For
|
||||
example::
|
||||
|
||||
somecolumn.op('&')(0xff)
|
||||
|
||||
is a bitwise AND of the value in somecolumn.
|
||||
is a bitwise AND of the value in ``somecolumn``.
|
||||
|
||||
:param operator: a string which will be output as the infix operator
|
||||
between this element and the expression passed to the
|
||||
generated function.
|
||||
|
||||
:param precedence: precedence to apply to the operator, when
|
||||
parenthesizing expressions. A lower number will cause the expression
|
||||
to be parenthesized when applied against another operator with
|
||||
higher precedence. The default value of ``0`` is lower than all
|
||||
operators except for the comma (``,``) and ``AS`` operators.
|
||||
A value of 100 will be higher or equal to all operators, and -100
|
||||
will be lower than or equal to all operators.
|
||||
|
||||
.. versionadded:: 0.8 - added the 'precedence' argument.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`types_operators`
|
||||
|
||||
"""
|
||||
def _op(b):
|
||||
return self.operate(op, opstring, b)
|
||||
return _op
|
||||
operator = custom_op(opstring, precedence)
|
||||
|
||||
def against(other):
|
||||
return operator(self, other)
|
||||
return against
|
||||
|
||||
def operate(self, op, *other, **kwargs):
|
||||
"""Operate on an argument.
|
||||
@@ -155,11 +179,48 @@ class Operators(object):
|
||||
"""
|
||||
raise NotImplementedError(str(op))
|
||||
|
||||
class ColumnOperators(Operators):
|
||||
"""Defines comparison and math operations.
|
||||
|
||||
By default all methods call down to
|
||||
:meth:`Operators.operate` or :meth:`Operators.reverse_operate`
|
||||
class custom_op(object):
|
||||
"""Represent a 'custom' operator.
|
||||
|
||||
:class:`.custom_op` is normally instantitated when the
|
||||
:meth:`.ColumnOperators.op` method is used to create a
|
||||
custom operator callable. The class can also be used directly
|
||||
when programmatically constructing expressions. E.g.
|
||||
to represent the "factorial" operation::
|
||||
|
||||
from sqlalchemy.sql import UnaryExpression
|
||||
from sqlalchemy.sql import operators
|
||||
from sqlalchemy import Numeric
|
||||
|
||||
unary = UnaryExpression(table.c.somecolumn,
|
||||
modifier=operators.custom_op("!"),
|
||||
type_=Numeric)
|
||||
|
||||
"""
|
||||
__name__ = 'custom_op'
|
||||
|
||||
def __init__(self, opstring, precedence=0):
|
||||
self.opstring = opstring
|
||||
self.precedence = precedence
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, custom_op) and \
|
||||
other.opstring == self.opstring
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
def __call__(self, left, right, **kw):
|
||||
return left.operate(self, right, **kw)
|
||||
|
||||
|
||||
class ColumnOperators(Operators):
|
||||
"""Defines boolean, comparison, and other operators for
|
||||
:class:`.ColumnElement` expressions.
|
||||
|
||||
By default, all methods call down to
|
||||
:meth:`.operate` or :meth:`.reverse_operate`,
|
||||
passing in the appropriate operator function from the
|
||||
Python builtin ``operator`` module or
|
||||
a SQLAlchemy-specific operator function from
|
||||
@@ -174,15 +235,21 @@ class ColumnOperators(Operators):
|
||||
def eq(a, b):
|
||||
return a == b
|
||||
|
||||
A SQLAlchemy construct like :class:`.ColumnElement` ultimately
|
||||
The core column expression unit :class:`.ColumnElement`
|
||||
overrides :meth:`.Operators.operate` and others
|
||||
to return further :class:`.ClauseElement` constructs,
|
||||
to return further :class:`.ColumnElement` constructs,
|
||||
so that the ``==`` operation above is replaced by a clause
|
||||
construct.
|
||||
|
||||
The docstrings here will describe column-oriented
|
||||
behavior of each operator. For ORM-based operators
|
||||
on related objects and collections, see :class:`.RelationshipProperty.Comparator`.
|
||||
See also:
|
||||
|
||||
:ref:`types_operators`
|
||||
|
||||
:attr:`.TypeEngine.comparator_factory`
|
||||
|
||||
:class:`.ColumnOperators`
|
||||
|
||||
:class:`.PropComparator`
|
||||
|
||||
"""
|
||||
|
||||
@@ -249,6 +316,33 @@ class ColumnOperators(Operators):
|
||||
"""
|
||||
return self.operate(neg)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Implement the [] operator.
|
||||
|
||||
This can be used by some database-specific types
|
||||
such as Postgresql ARRAY and HSTORE.
|
||||
|
||||
"""
|
||||
return self.operate(getitem, index)
|
||||
|
||||
def __lshift__(self, other):
|
||||
"""implement the << operator.
|
||||
|
||||
Not used by SQLAlchemy core, this is provided
|
||||
for custom operator systems which want to use
|
||||
<< as an extension point.
|
||||
"""
|
||||
return self.operate(lshift, other)
|
||||
|
||||
def __rshift__(self, other):
|
||||
"""implement the >> operator.
|
||||
|
||||
Not used by SQLAlchemy core, this is provided
|
||||
for custom operator systems which want to use
|
||||
>> as an extension point.
|
||||
"""
|
||||
return self.operate(rshift, other)
|
||||
|
||||
def concat(self, other):
|
||||
"""Implement the 'concat' operator.
|
||||
|
||||
@@ -263,6 +357,20 @@ class ColumnOperators(Operators):
|
||||
|
||||
In a column context, produces the clause ``a LIKE other``.
|
||||
|
||||
E.g.::
|
||||
|
||||
select([sometable]).where(sometable.c.column.like("%foobar%"))
|
||||
|
||||
:param other: expression to be compared
|
||||
:param escape: optional escape character, renders the ``ESCAPE``
|
||||
keyword, e.g.::
|
||||
|
||||
somecolumn.like("foo/%bar", escape="/")
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.ColumnOperators.ilike`
|
||||
|
||||
"""
|
||||
return self.operate(like_op, other, escape=escape)
|
||||
|
||||
@@ -271,6 +379,20 @@ class ColumnOperators(Operators):
|
||||
|
||||
In a column context, produces the clause ``a ILIKE other``.
|
||||
|
||||
E.g.::
|
||||
|
||||
select([sometable]).where(sometable.c.column.ilike("%foobar%"))
|
||||
|
||||
:param other: expression to be compared
|
||||
:param escape: optional escape character, renders the ``ESCAPE``
|
||||
keyword, e.g.::
|
||||
|
||||
somecolumn.ilike("foo/%bar", escape="/")
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.ColumnOperators.like`
|
||||
|
||||
"""
|
||||
return self.operate(ilike_op, other, escape=escape)
|
||||
|
||||
@@ -284,6 +406,51 @@ class ColumnOperators(Operators):
|
||||
"""
|
||||
return self.operate(in_op, other)
|
||||
|
||||
def notin_(self, other):
|
||||
"""implement the ``NOT IN`` operator.
|
||||
|
||||
This is equivalent to using negation with :meth:`.ColumnOperators.in_`,
|
||||
i.e. ``~x.in_(y)``.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.ColumnOperators.in_`
|
||||
|
||||
"""
|
||||
return self.operate(notin_op, other)
|
||||
|
||||
def notlike(self, other, escape=None):
|
||||
"""implement the ``NOT LIKE`` operator.
|
||||
|
||||
This is equivalent to using negation with
|
||||
:meth:`.ColumnOperators.like`, i.e. ``~x.like(y)``.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.ColumnOperators.like`
|
||||
|
||||
"""
|
||||
return self.operate(notlike_op, other, escape=escape)
|
||||
|
||||
def notilike(self, other, escape=None):
|
||||
"""implement the ``NOT ILIKE`` operator.
|
||||
|
||||
This is equivalent to using negation with
|
||||
:meth:`.ColumnOperators.ilike`, i.e. ``~x.ilike(y)``.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.ColumnOperators.ilike`
|
||||
|
||||
"""
|
||||
return self.operate(notilike_op, other, escape=escape)
|
||||
|
||||
def is_(self, other):
|
||||
"""Implement the ``IS`` operator.
|
||||
|
||||
@@ -376,7 +543,7 @@ class ColumnOperators(Operators):
|
||||
def __radd__(self, other):
|
||||
"""Implement the ``+`` operator in reverse.
|
||||
|
||||
See :meth:`__add__`.
|
||||
See :meth:`.ColumnOperators.__add__`.
|
||||
|
||||
"""
|
||||
return self.reverse_operate(add, other)
|
||||
@@ -384,7 +551,7 @@ class ColumnOperators(Operators):
|
||||
def __rsub__(self, other):
|
||||
"""Implement the ``-`` operator in reverse.
|
||||
|
||||
See :meth:`__sub__`.
|
||||
See :meth:`.ColumnOperators.__sub__`.
|
||||
|
||||
"""
|
||||
return self.reverse_operate(sub, other)
|
||||
@@ -392,7 +559,7 @@ class ColumnOperators(Operators):
|
||||
def __rmul__(self, other):
|
||||
"""Implement the ``*`` operator in reverse.
|
||||
|
||||
See :meth:`__mul__`.
|
||||
See :meth:`.ColumnOperators.__mul__`.
|
||||
|
||||
"""
|
||||
return self.reverse_operate(mul, other)
|
||||
@@ -400,7 +567,7 @@ class ColumnOperators(Operators):
|
||||
def __rdiv__(self, other):
|
||||
"""Implement the ``/`` operator in reverse.
|
||||
|
||||
See :meth:`__div__`.
|
||||
See :meth:`.ColumnOperators.__div__`.
|
||||
|
||||
"""
|
||||
return self.reverse_operate(div, other)
|
||||
@@ -411,7 +578,10 @@ class ColumnOperators(Operators):
|
||||
return self.operate(between_op, cleft, cright)
|
||||
|
||||
def distinct(self):
|
||||
"""Produce a :func:`~.expression.distinct` clause against the parent object."""
|
||||
"""Produce a :func:`~.expression.distinct` clause against the
|
||||
parent object.
|
||||
|
||||
"""
|
||||
return self.operate(distinct_op)
|
||||
|
||||
def __add__(self, other):
|
||||
@@ -421,7 +591,7 @@ class ColumnOperators(Operators):
|
||||
if the parent object has non-string affinity.
|
||||
If the parent object has a string affinity,
|
||||
produces the concatenation operator, ``a || b`` -
|
||||
see :meth:`concat`.
|
||||
see :meth:`.ColumnOperators.concat`.
|
||||
|
||||
"""
|
||||
return self.operate(add, other)
|
||||
@@ -469,144 +639,216 @@ class ColumnOperators(Operators):
|
||||
def __rtruediv__(self, other):
|
||||
"""Implement the ``//`` operator in reverse.
|
||||
|
||||
See :meth:`__truediv__`.
|
||||
See :meth:`.ColumnOperators.__truediv__`.
|
||||
|
||||
"""
|
||||
return self.reverse_operate(truediv, other)
|
||||
|
||||
|
||||
def from_():
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def as_():
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def exists():
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def istrue(a):
|
||||
raise NotImplementedError()
|
||||
|
||||
def isfalse(a):
|
||||
raise NotImplementedError()
|
||||
|
||||
def is_(a, b):
|
||||
return a.is_(b)
|
||||
|
||||
|
||||
def isnot(a, b):
|
||||
return a.isnot(b)
|
||||
|
||||
|
||||
def collate(a, b):
|
||||
return a.collate(b)
|
||||
|
||||
|
||||
def op(a, opstring, b):
|
||||
return a.op(opstring)(b)
|
||||
|
||||
|
||||
def like_op(a, b, escape=None):
|
||||
return a.like(b, escape=escape)
|
||||
|
||||
|
||||
def notlike_op(a, b, escape=None):
|
||||
return ~a.like(b, escape=escape)
|
||||
return a.notlike(b, escape=escape)
|
||||
|
||||
|
||||
def ilike_op(a, b, escape=None):
|
||||
return a.ilike(b, escape=escape)
|
||||
|
||||
|
||||
def notilike_op(a, b, escape=None):
|
||||
return ~a.ilike(b, escape=escape)
|
||||
return a.notilike(b, escape=escape)
|
||||
|
||||
|
||||
def between_op(a, b, c):
|
||||
return a.between(b, c)
|
||||
|
||||
|
||||
def in_op(a, b):
|
||||
return a.in_(b)
|
||||
|
||||
|
||||
def notin_op(a, b):
|
||||
return ~a.in_(b)
|
||||
return a.notin_(b)
|
||||
|
||||
|
||||
def distinct_op(a):
|
||||
return a.distinct()
|
||||
|
||||
|
||||
def startswith_op(a, b, escape=None):
|
||||
return a.startswith(b, escape=escape)
|
||||
|
||||
|
||||
def notstartswith_op(a, b, escape=None):
|
||||
return ~a.startswith(b, escape=escape)
|
||||
|
||||
|
||||
def endswith_op(a, b, escape=None):
|
||||
return a.endswith(b, escape=escape)
|
||||
|
||||
|
||||
def notendswith_op(a, b, escape=None):
|
||||
return ~a.endswith(b, escape=escape)
|
||||
|
||||
|
||||
def contains_op(a, b, escape=None):
|
||||
return a.contains(b, escape=escape)
|
||||
|
||||
|
||||
def notcontains_op(a, b, escape=None):
|
||||
return ~a.contains(b, escape=escape)
|
||||
|
||||
|
||||
def match_op(a, b):
|
||||
return a.match(b)
|
||||
|
||||
|
||||
def comma_op(a, b):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def concat_op(a, b):
|
||||
return a.concat(b)
|
||||
|
||||
|
||||
def desc_op(a):
|
||||
return a.desc()
|
||||
|
||||
|
||||
def asc_op(a):
|
||||
return a.asc()
|
||||
|
||||
|
||||
def nullsfirst_op(a):
|
||||
return a.nullsfirst()
|
||||
|
||||
|
||||
def nullslast_op(a):
|
||||
return a.nullslast()
|
||||
|
||||
|
||||
_commutative = set([eq, ne, add, mul])
|
||||
|
||||
_comparison = set([eq, ne, lt, gt, ge, le, between_op])
|
||||
|
||||
|
||||
def is_comparison(op):
|
||||
return op in _comparison
|
||||
|
||||
|
||||
def is_commutative(op):
|
||||
return op in _commutative
|
||||
|
||||
|
||||
def is_ordering_modifier(op):
|
||||
return op in (asc_op, desc_op,
|
||||
nullsfirst_op, nullslast_op)
|
||||
|
||||
_associative = _commutative.union([concat_op, and_, or_])
|
||||
|
||||
_natural_self_precedent = _associative.union([getitem])
|
||||
"""Operators where if we have (a op b) op c, we don't want to
|
||||
parenthesize (a op b).
|
||||
|
||||
_smallest = symbol('_smallest')
|
||||
_largest = symbol('_largest')
|
||||
"""
|
||||
|
||||
_asbool = util.symbol('_asbool', canonical=-10)
|
||||
_smallest = util.symbol('_smallest', canonical=-100)
|
||||
_largest = util.symbol('_largest', canonical=100)
|
||||
|
||||
_PRECEDENCE = {
|
||||
from_: 15,
|
||||
mul: 7,
|
||||
truediv: 7,
|
||||
# Py2K
|
||||
div: 7,
|
||||
# end Py2K
|
||||
mod: 7,
|
||||
neg: 7,
|
||||
add: 6,
|
||||
sub: 6,
|
||||
getitem: 15,
|
||||
mul: 8,
|
||||
truediv: 8,
|
||||
div: 8,
|
||||
mod: 8,
|
||||
neg: 8,
|
||||
add: 7,
|
||||
sub: 7,
|
||||
|
||||
concat_op: 6,
|
||||
match_op: 6,
|
||||
ilike_op: 5,
|
||||
notilike_op: 5,
|
||||
like_op: 5,
|
||||
notlike_op: 5,
|
||||
in_op: 5,
|
||||
notin_op: 5,
|
||||
is_: 5,
|
||||
isnot: 5,
|
||||
|
||||
ilike_op: 6,
|
||||
notilike_op: 6,
|
||||
like_op: 6,
|
||||
notlike_op: 6,
|
||||
in_op: 6,
|
||||
notin_op: 6,
|
||||
|
||||
is_: 6,
|
||||
isnot: 6,
|
||||
|
||||
eq: 5,
|
||||
ne: 5,
|
||||
gt: 5,
|
||||
lt: 5,
|
||||
ge: 5,
|
||||
le: 5,
|
||||
|
||||
between_op: 5,
|
||||
distinct_op: 5,
|
||||
inv: 5,
|
||||
istrue: 5,
|
||||
isfalse: 5,
|
||||
and_: 3,
|
||||
or_: 2,
|
||||
comma_op: -1,
|
||||
collate: 7,
|
||||
|
||||
desc_op: 3,
|
||||
asc_op: 3,
|
||||
collate: 4,
|
||||
|
||||
as_: -1,
|
||||
exists: 0,
|
||||
_smallest: -1000,
|
||||
_largest: 1000
|
||||
_asbool: -10,
|
||||
_smallest: _smallest,
|
||||
_largest: _largest
|
||||
}
|
||||
|
||||
|
||||
def is_precedent(operator, against):
|
||||
if operator is against and operator in _associative:
|
||||
if operator is against and operator in _natural_self_precedent:
|
||||
return False
|
||||
else:
|
||||
return (_PRECEDENCE.get(operator, _PRECEDENCE[_smallest]) <=
|
||||
_PRECEDENCE.get(against, _PRECEDENCE[_largest]))
|
||||
return (_PRECEDENCE.get(operator,
|
||||
getattr(operator, 'precedence', _smallest)) <=
|
||||
_PRECEDENCE.get(against,
|
||||
getattr(against, 'precedence', _largest)))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+200
-387
@@ -1,41 +1,33 @@
|
||||
# sql/util.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy import exc, schema, util, sql, types as sqltypes
|
||||
from sqlalchemy.util import topological
|
||||
from sqlalchemy.sql import expression, operators, visitors
|
||||
"""High level utilities which build upon other modules here.
|
||||
|
||||
"""
|
||||
|
||||
from .. import exc, util
|
||||
from .base import _from_objects, ColumnSet
|
||||
from . import operators, visitors
|
||||
from itertools import chain
|
||||
from collections import deque
|
||||
|
||||
"""Utility functions that build upon SQL and Schema constructs."""
|
||||
from .elements import BindParameter, ColumnClause, ColumnElement, \
|
||||
Null, UnaryExpression, literal_column, Label
|
||||
from .selectable import ScalarSelect, Join, FromClause, FromGrouping
|
||||
from .schema import Column
|
||||
|
||||
def sort_tables(tables):
|
||||
"""sort a collection of Table objects in order of their foreign-key dependency."""
|
||||
join_condition = util.langhelpers.public_factory(
|
||||
Join._join_condition,
|
||||
".sql.util.join_condition")
|
||||
|
||||
tables = list(tables)
|
||||
tuples = []
|
||||
def visit_foreign_key(fkey):
|
||||
if fkey.use_alter:
|
||||
return
|
||||
parent_table = fkey.column.table
|
||||
if parent_table in tables:
|
||||
child_table = fkey.parent.table
|
||||
if parent_table is not child_table:
|
||||
tuples.append((parent_table, child_table))
|
||||
# names that are still being imported from the outside
|
||||
from .annotation import _shallow_annotate, _deep_annotate, _deep_deannotate
|
||||
from .elements import _find_columns
|
||||
from .ddl import sort_tables
|
||||
|
||||
for table in tables:
|
||||
visitors.traverse(table,
|
||||
{'schema_visitor':True},
|
||||
{'foreign_key':visit_foreign_key})
|
||||
|
||||
tuples.extend(
|
||||
[parent, table] for parent in table._extra_dependencies
|
||||
)
|
||||
|
||||
return list(topological.sort(tuples, tables))
|
||||
|
||||
def find_join_source(clauses, join_to):
|
||||
"""Given a list of FROM clauses and a selectable,
|
||||
@@ -54,7 +46,7 @@ def find_join_source(clauses, join_to):
|
||||
|
||||
"""
|
||||
|
||||
selectables = list(expression._from_objects(join_to))
|
||||
selectables = list(_from_objects(join_to))
|
||||
for i, f in enumerate(clauses):
|
||||
for s in selectables:
|
||||
if f.is_derived_from(s):
|
||||
@@ -62,6 +54,67 @@ def find_join_source(clauses, join_to):
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def visit_binary_product(fn, expr):
|
||||
"""Produce a traversal of the given expression, delivering
|
||||
column comparisons to the given function.
|
||||
|
||||
The function is of the form::
|
||||
|
||||
def my_fn(binary, left, right)
|
||||
|
||||
For each binary expression located which has a
|
||||
comparison operator, the product of "left" and
|
||||
"right" will be delivered to that function,
|
||||
in terms of that binary.
|
||||
|
||||
Hence an expression like::
|
||||
|
||||
and_(
|
||||
(a + b) == q + func.sum(e + f),
|
||||
j == r
|
||||
)
|
||||
|
||||
would have the traversal::
|
||||
|
||||
a <eq> q
|
||||
a <eq> e
|
||||
a <eq> f
|
||||
b <eq> q
|
||||
b <eq> e
|
||||
b <eq> f
|
||||
j <eq> r
|
||||
|
||||
That is, every combination of "left" and
|
||||
"right" that doesn't further contain
|
||||
a binary comparison is passed as pairs.
|
||||
|
||||
"""
|
||||
stack = []
|
||||
|
||||
def visit(element):
|
||||
if isinstance(element, ScalarSelect):
|
||||
# we dont want to dig into correlated subqueries,
|
||||
# those are just column elements by themselves
|
||||
yield element
|
||||
elif element.__visit_name__ == 'binary' and \
|
||||
operators.is_comparison(element.operator):
|
||||
stack.insert(0, element)
|
||||
for l in visit(element.left):
|
||||
for r in visit(element.right):
|
||||
fn(stack[0], l, r)
|
||||
stack.pop(0)
|
||||
for elem in element.get_children():
|
||||
visit(elem)
|
||||
else:
|
||||
if isinstance(element, ColumnClause):
|
||||
yield element
|
||||
for elem in element.get_children():
|
||||
for e in visit(elem):
|
||||
yield e
|
||||
list(visit(expr))
|
||||
|
||||
|
||||
def find_tables(clause, check_columns=False,
|
||||
include_aliases=False, include_joins=False,
|
||||
include_selects=False, include_crud=False):
|
||||
@@ -77,7 +130,7 @@ def find_tables(clause, check_columns=False,
|
||||
_visitors['join'] = tables.append
|
||||
|
||||
if include_aliases:
|
||||
_visitors['alias'] = tables.append
|
||||
_visitors['alias'] = tables.append
|
||||
|
||||
if include_crud:
|
||||
_visitors['insert'] = _visitors['update'] = \
|
||||
@@ -90,15 +143,10 @@ def find_tables(clause, check_columns=False,
|
||||
|
||||
_visitors['table'] = tables.append
|
||||
|
||||
visitors.traverse(clause, {'column_collections':False}, _visitors)
|
||||
visitors.traverse(clause, {'column_collections': False}, _visitors)
|
||||
return tables
|
||||
|
||||
def find_columns(clause):
|
||||
"""locate Column objects within the given expression."""
|
||||
|
||||
cols = util.column_set()
|
||||
visitors.traverse(clause, {}, {'column':cols.add})
|
||||
return cols
|
||||
|
||||
def unwrap_order_by(clause):
|
||||
"""Break up an 'order by' expression into individual column-expressions,
|
||||
@@ -108,9 +156,9 @@ def unwrap_order_by(clause):
|
||||
stack = deque([clause])
|
||||
while stack:
|
||||
t = stack.popleft()
|
||||
if isinstance(t, expression.ColumnElement) and \
|
||||
if isinstance(t, ColumnElement) and \
|
||||
(
|
||||
not isinstance(t, expression._UnaryExpression) or \
|
||||
not isinstance(t, UnaryExpression) or \
|
||||
not operators.is_ordering_modifier(t.modifier)
|
||||
):
|
||||
cols.add(t)
|
||||
@@ -119,6 +167,7 @@ def unwrap_order_by(clause):
|
||||
stack.append(c)
|
||||
return cols
|
||||
|
||||
|
||||
def clause_is_present(clause, search):
|
||||
"""Given a target clause and a second to search within, return True
|
||||
if the target is plainly present in the search without any
|
||||
@@ -128,15 +177,30 @@ def clause_is_present(clause, search):
|
||||
|
||||
"""
|
||||
|
||||
stack = [search]
|
||||
for elem in surface_selectables(search):
|
||||
if clause == elem: # use == here so that Annotated's compare
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def surface_selectables(clause):
|
||||
stack = [clause]
|
||||
while stack:
|
||||
elem = stack.pop()
|
||||
if clause is elem:
|
||||
return True
|
||||
elif isinstance(elem, expression.Join):
|
||||
yield elem
|
||||
if isinstance(elem, Join):
|
||||
stack.extend((elem.left, elem.right))
|
||||
return False
|
||||
elif isinstance(elem, FromGrouping):
|
||||
stack.append(elem.element)
|
||||
|
||||
def selectables_overlap(left, right):
|
||||
"""Return True if left/right have some overlapping selectable"""
|
||||
|
||||
return bool(
|
||||
set(surface_selectables(left)).intersection(
|
||||
surface_selectables(right)
|
||||
)
|
||||
)
|
||||
|
||||
def bind_values(clause):
|
||||
"""Return an ordered list of "bound" values in the given clause.
|
||||
@@ -151,19 +215,22 @@ def bind_values(clause):
|
||||
"""
|
||||
|
||||
v = []
|
||||
|
||||
def visit_bindparam(bind):
|
||||
v.append(bind.effective_value)
|
||||
|
||||
visitors.traverse(clause, {}, {'bindparam':visit_bindparam})
|
||||
visitors.traverse(clause, {}, {'bindparam': visit_bindparam})
|
||||
return v
|
||||
|
||||
|
||||
def _quote_ddl_expr(element):
|
||||
if isinstance(element, basestring):
|
||||
if isinstance(element, util.string_types):
|
||||
element = element.replace("'", "''")
|
||||
return "'%s'" % element
|
||||
else:
|
||||
return repr(element)
|
||||
|
||||
|
||||
class _repr_params(object):
|
||||
"""A string view of bound parameters, truncating
|
||||
display to the given number of 'multi' parameter sets.
|
||||
@@ -177,289 +244,40 @@ class _repr_params(object):
|
||||
if isinstance(self.params, (list, tuple)) and \
|
||||
len(self.params) > self.batches and \
|
||||
isinstance(self.params[0], (list, dict, tuple)):
|
||||
msg = " ... displaying %i of %i total bound parameter sets ... "
|
||||
return ' '.join((
|
||||
repr(self.params[:self.batches - 2])[0:-1],
|
||||
" ... displaying %i of %i total bound parameter sets ... " % (self.batches, len(self.params)),
|
||||
msg % (self.batches, len(self.params)),
|
||||
repr(self.params[-2:])[1:]
|
||||
))
|
||||
else:
|
||||
return repr(self.params)
|
||||
|
||||
|
||||
def expression_as_ddl(clause):
|
||||
"""Given a SQL expression, convert for usage in DDL, such as
|
||||
CREATE INDEX and CHECK CONSTRAINT.
|
||||
|
||||
Converts bind params into quoted literals, column identifiers
|
||||
into detached column constructs so that the parent table
|
||||
identifier is not included.
|
||||
|
||||
"""
|
||||
def repl(element):
|
||||
if isinstance(element, expression._BindParamClause):
|
||||
return expression.literal_column(_quote_ddl_expr(element.value))
|
||||
elif isinstance(element, expression.ColumnClause) and \
|
||||
element.table is not None:
|
||||
return expression.column(element.name)
|
||||
else:
|
||||
return None
|
||||
|
||||
return visitors.replacement_traverse(clause, {}, repl)
|
||||
|
||||
def adapt_criterion_to_null(crit, nulls):
|
||||
"""given criterion containing bind params, convert selected elements to IS NULL."""
|
||||
"""given criterion containing bind params, convert selected elements
|
||||
to IS NULL.
|
||||
|
||||
"""
|
||||
|
||||
def visit_binary(binary):
|
||||
if isinstance(binary.left, expression._BindParamClause) \
|
||||
if isinstance(binary.left, BindParameter) \
|
||||
and binary.left._identifying_key in nulls:
|
||||
# reverse order if the NULL is on the left side
|
||||
binary.left = binary.right
|
||||
binary.right = expression.null()
|
||||
binary.right = Null()
|
||||
binary.operator = operators.is_
|
||||
binary.negate = operators.isnot
|
||||
elif isinstance(binary.right, expression._BindParamClause) \
|
||||
elif isinstance(binary.right, BindParameter) \
|
||||
and binary.right._identifying_key in nulls:
|
||||
binary.right = expression.null()
|
||||
binary.right = Null()
|
||||
binary.operator = operators.is_
|
||||
binary.negate = operators.isnot
|
||||
|
||||
return visitors.cloned_traverse(crit, {}, {'binary':visit_binary})
|
||||
return visitors.cloned_traverse(crit, {}, {'binary': visit_binary})
|
||||
|
||||
def join_condition(a, b, ignore_nonexistent_tables=False, a_subset=None):
|
||||
"""create a join condition between two tables or selectables.
|
||||
|
||||
e.g.::
|
||||
|
||||
join_condition(tablea, tableb)
|
||||
|
||||
would produce an expression along the lines of::
|
||||
|
||||
tablea.c.id==tableb.c.tablea_id
|
||||
|
||||
The join is determined based on the foreign key relationships
|
||||
between the two selectables. If there are multiple ways
|
||||
to join, or no way to join, an error is raised.
|
||||
|
||||
:param ignore_nonexistent_tables: Deprecated - this
|
||||
flag is no longer used. Only resolution errors regarding
|
||||
the two given tables are propagated.
|
||||
|
||||
:param a_subset: An optional expression that is a sub-component
|
||||
of ``a``. An attempt will be made to join to just this sub-component
|
||||
first before looking at the full ``a`` construct, and if found
|
||||
will be successful even if there are other ways to join to ``a``.
|
||||
This allows the "right side" of a join to be passed thereby
|
||||
providing a "natural join".
|
||||
|
||||
"""
|
||||
crit = []
|
||||
constraints = set()
|
||||
|
||||
for left in (a_subset, a):
|
||||
if left is None:
|
||||
continue
|
||||
for fk in sorted(
|
||||
b.foreign_keys,
|
||||
key=lambda fk:fk.parent._creation_order):
|
||||
try:
|
||||
col = fk.get_referent(left)
|
||||
except exc.NoReferenceError, nrte:
|
||||
if nrte.table_name == left.name:
|
||||
raise
|
||||
else:
|
||||
continue
|
||||
|
||||
if col is not None:
|
||||
crit.append(col == fk.parent)
|
||||
constraints.add(fk.constraint)
|
||||
if left is not b:
|
||||
for fk in sorted(
|
||||
left.foreign_keys,
|
||||
key=lambda fk:fk.parent._creation_order):
|
||||
try:
|
||||
col = fk.get_referent(b)
|
||||
except exc.NoReferenceError, nrte:
|
||||
if nrte.table_name == b.name:
|
||||
raise
|
||||
else:
|
||||
# this is totally covered. can't get
|
||||
# coverage to mark it.
|
||||
continue
|
||||
|
||||
if col is not None:
|
||||
crit.append(col == fk.parent)
|
||||
constraints.add(fk.constraint)
|
||||
if crit:
|
||||
break
|
||||
|
||||
if len(crit) == 0:
|
||||
if isinstance(b, expression._FromGrouping):
|
||||
hint = " Perhaps you meant to convert the right side to a "\
|
||||
"subquery using alias()?"
|
||||
else:
|
||||
hint = ""
|
||||
raise exc.ArgumentError(
|
||||
"Can't find any foreign key relationships "
|
||||
"between '%s' and '%s'.%s" % (a.description, b.description, hint))
|
||||
elif len(constraints) > 1:
|
||||
raise exc.ArgumentError(
|
||||
"Can't determine join between '%s' and '%s'; "
|
||||
"tables have more than one foreign key "
|
||||
"constraint relationship between them. "
|
||||
"Please specify the 'onclause' of this "
|
||||
"join explicitly." % (a.description, b.description))
|
||||
elif len(crit) == 1:
|
||||
return (crit[0])
|
||||
else:
|
||||
return sql.and_(*crit)
|
||||
|
||||
|
||||
class Annotated(object):
|
||||
"""clones a ClauseElement and applies an 'annotations' dictionary.
|
||||
|
||||
Unlike regular clones, this clone also mimics __hash__() and
|
||||
__cmp__() of the original element so that it takes its place
|
||||
in hashed collections.
|
||||
|
||||
A reference to the original element is maintained, for the important
|
||||
reason of keeping its hash value current. When GC'ed, the
|
||||
hash value may be reused, causing conflicts.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, *args):
|
||||
if not args:
|
||||
# clone constructor
|
||||
return object.__new__(cls)
|
||||
else:
|
||||
element, values = args
|
||||
# pull appropriate subclass from registry of annotated
|
||||
# classes
|
||||
try:
|
||||
cls = annotated_classes[element.__class__]
|
||||
except KeyError:
|
||||
cls = annotated_classes[element.__class__] = type.__new__(type,
|
||||
"Annotated%s" % element.__class__.__name__,
|
||||
(Annotated, element.__class__), {})
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, element, values):
|
||||
# force FromClause to generate their internal
|
||||
# collections into __dict__
|
||||
if isinstance(element, expression.FromClause):
|
||||
element.c
|
||||
|
||||
self.__dict__ = element.__dict__.copy()
|
||||
self.__element = element
|
||||
self._annotations = values
|
||||
|
||||
def _annotate(self, values):
|
||||
_values = self._annotations.copy()
|
||||
_values.update(values)
|
||||
clone = self.__class__.__new__(self.__class__)
|
||||
clone.__dict__ = self.__dict__.copy()
|
||||
clone._annotations = _values
|
||||
return clone
|
||||
|
||||
def _deannotate(self):
|
||||
return self.__element
|
||||
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return self.__element.__class__._compiler_dispatch(self, visitor, **kw)
|
||||
|
||||
@property
|
||||
def _constructor(self):
|
||||
return self.__element._constructor
|
||||
|
||||
def _clone(self):
|
||||
clone = self.__element._clone()
|
||||
if clone is self.__element:
|
||||
# detect immutable, don't change anything
|
||||
return self
|
||||
else:
|
||||
# update the clone with any changes that have occurred
|
||||
# to this object's __dict__.
|
||||
clone.__dict__.update(self.__dict__)
|
||||
return Annotated(clone, self._annotations)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__element)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(self.__element, expression.ColumnOperators):
|
||||
return self.__element.__class__.__eq__(self, other)
|
||||
else:
|
||||
return hash(other) == hash(self)
|
||||
|
||||
|
||||
# hard-generate Annotated subclasses. this technique
|
||||
# is used instead of on-the-fly types (i.e. type.__new__())
|
||||
# so that the resulting objects are pickleable.
|
||||
annotated_classes = {}
|
||||
|
||||
for cls in expression.__dict__.values() + [schema.Column, schema.Table]:
|
||||
if isinstance(cls, type) and issubclass(cls, expression.ClauseElement):
|
||||
exec "class Annotated%s(Annotated, cls):\n" \
|
||||
" pass" % (cls.__name__, ) in locals()
|
||||
exec "annotated_classes[cls] = Annotated%s" % (cls.__name__)
|
||||
|
||||
def _deep_annotate(element, annotations, exclude=None):
|
||||
"""Deep copy the given ClauseElement, annotating each element
|
||||
with the given annotations dictionary.
|
||||
|
||||
Elements within the exclude collection will be cloned but not annotated.
|
||||
|
||||
"""
|
||||
cloned = util.column_dict()
|
||||
|
||||
def clone(elem):
|
||||
# check if element is present in the exclude list.
|
||||
# take into account proxying relationships.
|
||||
if elem in cloned:
|
||||
return cloned[elem]
|
||||
elif exclude and \
|
||||
hasattr(elem, 'proxy_set') and \
|
||||
elem.proxy_set.intersection(exclude):
|
||||
newelem = elem._clone()
|
||||
elif annotations != elem._annotations:
|
||||
newelem = elem._annotate(annotations)
|
||||
else:
|
||||
newelem = elem
|
||||
newelem._copy_internals(clone=clone)
|
||||
cloned[elem] = newelem
|
||||
return newelem
|
||||
|
||||
if element is not None:
|
||||
element = clone(element)
|
||||
return element
|
||||
|
||||
def _deep_deannotate(element):
|
||||
"""Deep copy the given element, removing all annotations."""
|
||||
|
||||
cloned = util.column_dict()
|
||||
|
||||
def clone(elem):
|
||||
if elem not in cloned:
|
||||
newelem = elem._deannotate()
|
||||
newelem._copy_internals(clone=clone)
|
||||
cloned[elem] = newelem
|
||||
return cloned[elem]
|
||||
|
||||
if element is not None:
|
||||
element = clone(element)
|
||||
return element
|
||||
|
||||
def _shallow_annotate(element, annotations):
|
||||
"""Annotate the given ClauseElement and copy its internals so that
|
||||
internal objects refer to the new annotated object.
|
||||
|
||||
Basically used to apply a "dont traverse" annotation to a
|
||||
selectable, without digging throughout the whole
|
||||
structure wasting time.
|
||||
"""
|
||||
element = element._annotate(annotations)
|
||||
element._copy_internals()
|
||||
return element
|
||||
|
||||
def splice_joins(left, right, stop_on=None):
|
||||
if left is None:
|
||||
@@ -471,7 +289,7 @@ def splice_joins(left, right, stop_on=None):
|
||||
ret = None
|
||||
while stack:
|
||||
(right, prevright) = stack.pop()
|
||||
if isinstance(right, expression.Join) and right is not stop_on:
|
||||
if isinstance(right, Join) and right is not stop_on:
|
||||
right = right._clone()
|
||||
right._reset_exported()
|
||||
right.onclause = adapter.traverse(right.onclause)
|
||||
@@ -485,18 +303,21 @@ def splice_joins(left, right, stop_on=None):
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def reduce_columns(columns, *clauses, **kw):
|
||||
"""given a list of columns, return a 'reduced' set based on natural equivalents.
|
||||
"""given a list of columns, return a 'reduced' set based on natural
|
||||
equivalents.
|
||||
|
||||
the set is reduced to the smallest list of columns which have no natural
|
||||
equivalent present in the list. A "natural equivalent" means that two columns
|
||||
will ultimately represent the same value because they are related by a foreign key.
|
||||
equivalent present in the list. A "natural equivalent" means that two
|
||||
columns will ultimately represent the same value because they are related
|
||||
by a foreign key.
|
||||
|
||||
\*clauses is an optional list of join clauses which will be traversed
|
||||
to further identify columns that are "equivalent".
|
||||
|
||||
\**kw may specify 'ignore_nonexistent_tables' to ignore foreign keys
|
||||
whose tables are not yet configured.
|
||||
whose tables are not yet configured, or columns that aren't yet present.
|
||||
|
||||
This function is primarily used to determine the most minimal "primary key"
|
||||
from a selectable, by reducing the set of primary key columns present
|
||||
@@ -504,6 +325,7 @@ def reduce_columns(columns, *clauses, **kw):
|
||||
|
||||
"""
|
||||
ignore_nonexistent_tables = kw.pop('ignore_nonexistent_tables', False)
|
||||
only_synonyms = kw.pop('only_synonyms', False)
|
||||
|
||||
columns = util.ordered_column_set(columns)
|
||||
|
||||
@@ -515,28 +337,44 @@ def reduce_columns(columns, *clauses, **kw):
|
||||
continue
|
||||
try:
|
||||
fk_col = fk.column
|
||||
except exc.NoReferencedTableError:
|
||||
except exc.NoReferencedColumnError:
|
||||
# TODO: add specific coverage here
|
||||
# to test/sql/test_selectable ReduceTest
|
||||
if ignore_nonexistent_tables:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
if fk_col.shares_lineage(c):
|
||||
except exc.NoReferencedTableError:
|
||||
# TODO: add specific coverage here
|
||||
# to test/sql/test_selectable ReduceTest
|
||||
if ignore_nonexistent_tables:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
if fk_col.shares_lineage(c) and \
|
||||
(not only_synonyms or \
|
||||
c.name == col.name):
|
||||
omit.add(col)
|
||||
break
|
||||
|
||||
if clauses:
|
||||
def visit_binary(binary):
|
||||
if binary.operator == operators.eq:
|
||||
cols = util.column_set(chain(*[c.proxy_set for c in columns.difference(omit)]))
|
||||
cols = util.column_set(chain(*[c.proxy_set
|
||||
for c in columns.difference(omit)]))
|
||||
if binary.left in cols and binary.right in cols:
|
||||
for c in columns:
|
||||
if c.shares_lineage(binary.right):
|
||||
for c in reversed(columns):
|
||||
if c.shares_lineage(binary.right) and \
|
||||
(not only_synonyms or \
|
||||
c.name == binary.left.name):
|
||||
omit.add(c)
|
||||
break
|
||||
for clause in clauses:
|
||||
visitors.traverse(clause, {}, {'binary':visit_binary})
|
||||
if clause is not None:
|
||||
visitors.traverse(clause, {}, {'binary': visit_binary})
|
||||
|
||||
return ColumnSet(columns.difference(omit))
|
||||
|
||||
return expression.ColumnSet(columns.difference(omit))
|
||||
|
||||
def criterion_as_pairs(expression, consider_as_foreign_keys=None,
|
||||
consider_as_referenced_keys=None, any_operator=False):
|
||||
@@ -547,84 +385,47 @@ def criterion_as_pairs(expression, consider_as_foreign_keys=None,
|
||||
"'consider_as_foreign_keys' or "
|
||||
"'consider_as_referenced_keys'")
|
||||
|
||||
def col_is(a, b):
|
||||
#return a is b
|
||||
return a.compare(b)
|
||||
|
||||
def visit_binary(binary):
|
||||
if not any_operator and binary.operator is not operators.eq:
|
||||
return
|
||||
if not isinstance(binary.left, sql.ColumnElement) or \
|
||||
not isinstance(binary.right, sql.ColumnElement):
|
||||
if not isinstance(binary.left, ColumnElement) or \
|
||||
not isinstance(binary.right, ColumnElement):
|
||||
return
|
||||
|
||||
if consider_as_foreign_keys:
|
||||
if binary.left in consider_as_foreign_keys and \
|
||||
(binary.right is binary.left or
|
||||
(col_is(binary.right, binary.left) or
|
||||
binary.right not in consider_as_foreign_keys):
|
||||
pairs.append((binary.right, binary.left))
|
||||
elif binary.right in consider_as_foreign_keys and \
|
||||
(binary.left is binary.right or
|
||||
(col_is(binary.left, binary.right) or
|
||||
binary.left not in consider_as_foreign_keys):
|
||||
pairs.append((binary.left, binary.right))
|
||||
elif consider_as_referenced_keys:
|
||||
if binary.left in consider_as_referenced_keys and \
|
||||
(binary.right is binary.left or
|
||||
(col_is(binary.right, binary.left) or
|
||||
binary.right not in consider_as_referenced_keys):
|
||||
pairs.append((binary.left, binary.right))
|
||||
elif binary.right in consider_as_referenced_keys and \
|
||||
(binary.left is binary.right or
|
||||
(col_is(binary.left, binary.right) or
|
||||
binary.left not in consider_as_referenced_keys):
|
||||
pairs.append((binary.right, binary.left))
|
||||
else:
|
||||
if isinstance(binary.left, schema.Column) and \
|
||||
isinstance(binary.right, schema.Column):
|
||||
if isinstance(binary.left, Column) and \
|
||||
isinstance(binary.right, Column):
|
||||
if binary.left.references(binary.right):
|
||||
pairs.append((binary.right, binary.left))
|
||||
elif binary.right.references(binary.left):
|
||||
pairs.append((binary.left, binary.right))
|
||||
pairs = []
|
||||
visitors.traverse(expression, {}, {'binary':visit_binary})
|
||||
visitors.traverse(expression, {}, {'binary': visit_binary})
|
||||
return pairs
|
||||
|
||||
def folded_equivalents(join, equivs=None):
|
||||
"""Return a list of uniquely named columns.
|
||||
|
||||
The column list of the given Join will be narrowed
|
||||
down to a list of all equivalently-named,
|
||||
equated columns folded into one column, where 'equated' means they are
|
||||
equated to each other in the ON clause of this join.
|
||||
|
||||
This function is used by Join.select(fold_equivalents=True).
|
||||
|
||||
Deprecated. This function is used for a certain kind of
|
||||
"polymorphic_union" which is designed to achieve joined
|
||||
table inheritance where the base table has no "discriminator"
|
||||
column; [ticket:1131] will provide a better way to
|
||||
achieve this.
|
||||
|
||||
"""
|
||||
if equivs is None:
|
||||
equivs = set()
|
||||
def visit_binary(binary):
|
||||
if binary.operator == operators.eq and binary.left.name == binary.right.name:
|
||||
equivs.add(binary.right)
|
||||
equivs.add(binary.left)
|
||||
visitors.traverse(join.onclause, {}, {'binary':visit_binary})
|
||||
collist = []
|
||||
if isinstance(join.left, expression.Join):
|
||||
left = folded_equivalents(join.left, equivs)
|
||||
else:
|
||||
left = list(join.left.columns)
|
||||
if isinstance(join.right, expression.Join):
|
||||
right = folded_equivalents(join.right, equivs)
|
||||
else:
|
||||
right = list(join.right.columns)
|
||||
used = set()
|
||||
for c in left + right:
|
||||
if c in equivs:
|
||||
if c.name not in used:
|
||||
collist.append(c)
|
||||
used.add(c.name)
|
||||
else:
|
||||
collist.append(c)
|
||||
return collist
|
||||
|
||||
class AliasedRow(object):
|
||||
"""Wrap a RowProxy with a translation map.
|
||||
@@ -681,15 +482,27 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
|
||||
s.c.col1 == table2.c.col1
|
||||
|
||||
"""
|
||||
def __init__(self, selectable, equivalents=None, include=None, exclude=None, adapt_on_names=False):
|
||||
self.__traverse_options__ = {'stop_on':[selectable]}
|
||||
def __init__(self, selectable, equivalents=None,
|
||||
include=None, exclude=None,
|
||||
include_fn=None, exclude_fn=None,
|
||||
adapt_on_names=False):
|
||||
self.__traverse_options__ = {'stop_on': [selectable]}
|
||||
self.selectable = selectable
|
||||
self.include = include
|
||||
self.exclude = exclude
|
||||
if include:
|
||||
assert not include_fn
|
||||
self.include_fn = lambda e: e in include
|
||||
else:
|
||||
self.include_fn = include_fn
|
||||
if exclude:
|
||||
assert not exclude_fn
|
||||
self.exclude_fn = lambda e: e in exclude
|
||||
else:
|
||||
self.exclude_fn = exclude_fn
|
||||
self.equivalents = util.column_dict(equivalents or {})
|
||||
self.adapt_on_names = adapt_on_names
|
||||
|
||||
def _corresponding_column(self, col, require_embedded, _seen=util.EMPTY_SET):
|
||||
def _corresponding_column(self, col, require_embedded,
|
||||
_seen=util.EMPTY_SET):
|
||||
newcol = self.selectable.corresponding_column(
|
||||
col,
|
||||
require_embedded=require_embedded)
|
||||
@@ -704,20 +517,20 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
|
||||
newcol = self.selectable.c.get(col.name)
|
||||
return newcol
|
||||
|
||||
magic_flag = False
|
||||
def replace(self, col):
|
||||
if isinstance(col, expression.FromClause):
|
||||
if self.selectable.is_derived_from(col):
|
||||
return self.selectable
|
||||
|
||||
if not isinstance(col, expression.ColumnElement):
|
||||
if not self.magic_flag and isinstance(col, FromClause) and \
|
||||
self.selectable.is_derived_from(col):
|
||||
return self.selectable
|
||||
elif not isinstance(col, ColumnElement):
|
||||
return None
|
||||
|
||||
if self.include and col not in self.include:
|
||||
elif self.include_fn and not self.include_fn(col):
|
||||
return None
|
||||
elif self.exclude and col in self.exclude:
|
||||
elif self.exclude_fn and self.exclude_fn(col):
|
||||
return None
|
||||
else:
|
||||
return self._corresponding_column(col, True)
|
||||
|
||||
return self._corresponding_column(col, True)
|
||||
|
||||
class ColumnAdapter(ClauseAdapter):
|
||||
"""Extends ClauseAdapter with extra utility functions.
|
||||
@@ -761,15 +574,14 @@ class ColumnAdapter(ClauseAdapter):
|
||||
c = self.adapt_clause(col)
|
||||
|
||||
# anonymize labels in case they have a hardcoded name
|
||||
if isinstance(c, expression._Label):
|
||||
if isinstance(c, Label):
|
||||
c = c.label(None)
|
||||
|
||||
# adapt_required indicates that if we got the same column
|
||||
# back which we put in (i.e. it passed through),
|
||||
# it's not correct. this is used by eagerloading which
|
||||
# knows that all columns and expressions need to be adapted
|
||||
# to a result row, and a "passthrough" is definitely targeting
|
||||
# the wrong column.
|
||||
# adapt_required used by eager loading to indicate that
|
||||
# we don't trust a result row column that is not translated.
|
||||
# this is to prevent a column from being interpreted as that
|
||||
# of the child row in a self-referential scenario, see
|
||||
# inheritance/test_basic.py->EagerTargetingTest.test_adapt_stringency
|
||||
if self.adapt_required and c is col:
|
||||
return None
|
||||
|
||||
@@ -786,3 +598,4 @@ class ColumnAdapter(ClauseAdapter):
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
self.columns = util.PopulateDict(self._locate_col)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sql/visitors.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -24,15 +24,17 @@ http://techspot.zzzeek.org/2008/01/23/expression-transformations/
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
import re
|
||||
from sqlalchemy import util
|
||||
from .. import util
|
||||
import operator
|
||||
from .. import exc
|
||||
|
||||
__all__ = ['VisitableType', 'Visitable', 'ClauseVisitor',
|
||||
'CloningVisitor', 'ReplacingCloningVisitor', 'iterate',
|
||||
'iterate_depthfirst', 'traverse_using', 'traverse',
|
||||
'traverse_depthfirst',
|
||||
'cloned_traverse', 'replacement_traverse']
|
||||
|
||||
|
||||
class VisitableType(type):
|
||||
"""Metaclass which assigns a `_compiler_dispatch` method to classes
|
||||
having a `__visit_name__` attribute.
|
||||
@@ -43,16 +45,15 @@ class VisitableType(type):
|
||||
def _compiler_dispatch (self, visitor, **kw):
|
||||
'''Look for an attribute named "visit_" + self.__visit_name__
|
||||
on the visitor, and call it with the same kw params.'''
|
||||
return getattr(visitor, 'visit_%s' % self.__visit_name__)(self, **kw)
|
||||
visit_attr = 'visit_%s' % self.__visit_name__
|
||||
return getattr(visitor, visit_attr)(self, **kw)
|
||||
|
||||
Classes having no __visit_name__ attribute will remain unaffected.
|
||||
"""
|
||||
def __init__(cls, clsname, bases, clsdict):
|
||||
if cls.__name__ == 'Visitable' or not hasattr(cls, '__visit_name__'):
|
||||
super(VisitableType, cls).__init__(clsname, bases, clsdict)
|
||||
return
|
||||
|
||||
_generate_dispatch(cls)
|
||||
if clsname != 'Visitable' and \
|
||||
hasattr(cls, '__visit_name__'):
|
||||
_generate_dispatch(cls)
|
||||
|
||||
super(VisitableType, cls).__init__(clsname, bases, clsdict)
|
||||
|
||||
@@ -68,28 +69,40 @@ def _generate_dispatch(cls):
|
||||
# the string name of the class's __visit_name__ is known at
|
||||
# this early stage (import time) so it can be pre-constructed.
|
||||
getter = operator.attrgetter("visit_%s" % visit_name)
|
||||
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return getter(visitor)(self, **kw)
|
||||
try:
|
||||
meth = getter(visitor)
|
||||
except AttributeError:
|
||||
raise exc.UnsupportedCompilationError(visitor, cls)
|
||||
else:
|
||||
return meth(self, **kw)
|
||||
else:
|
||||
# The optimization opportunity is lost for this case because the
|
||||
# __visit_name__ is not yet a string. As a result, the visit
|
||||
# string has to be recalculated with each compilation.
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return getattr(visitor, 'visit_%s' % self.__visit_name__)(self, **kw)
|
||||
visit_attr = 'visit_%s' % self.__visit_name__
|
||||
try:
|
||||
meth = getattr(visitor, visit_attr)
|
||||
except AttributeError:
|
||||
raise exc.UnsupportedCompilationError(visitor, cls)
|
||||
else:
|
||||
return meth(self, **kw)
|
||||
|
||||
_compiler_dispatch.__doc__ = \
|
||||
_compiler_dispatch.__doc__ = \
|
||||
"""Look for an attribute named "visit_" + self.__visit_name__
|
||||
on the visitor, and call it with the same kw params.
|
||||
"""
|
||||
cls._compiler_dispatch = _compiler_dispatch
|
||||
|
||||
class Visitable(object):
|
||||
|
||||
class Visitable(util.with_metaclass(VisitableType, object)):
|
||||
"""Base class for visitable objects, applies the
|
||||
``VisitableType`` metaclass.
|
||||
|
||||
"""
|
||||
|
||||
__metaclass__ = VisitableType
|
||||
|
||||
class ClauseVisitor(object):
|
||||
"""Base class for visitor objects which can traverse using
|
||||
@@ -106,8 +119,10 @@ class ClauseVisitor(object):
|
||||
return meth(obj, **kw)
|
||||
|
||||
def iterate(self, obj):
|
||||
"""traverse the given expression structure, returning an iterator of all elements."""
|
||||
"""traverse the given expression structure, returning an iterator
|
||||
of all elements.
|
||||
|
||||
"""
|
||||
return iterate(obj, self.__traverse_options__)
|
||||
|
||||
def traverse(self, obj):
|
||||
@@ -143,6 +158,7 @@ class ClauseVisitor(object):
|
||||
tail._next = visitor
|
||||
return self
|
||||
|
||||
|
||||
class CloningVisitor(ClauseVisitor):
|
||||
"""Base class for visitor objects which can traverse using
|
||||
the cloned_traverse() function.
|
||||
@@ -150,14 +166,18 @@ class CloningVisitor(ClauseVisitor):
|
||||
"""
|
||||
|
||||
def copy_and_process(self, list_):
|
||||
"""Apply cloned traversal to the given list of elements, and return the new list."""
|
||||
"""Apply cloned traversal to the given list of elements, and return
|
||||
the new list.
|
||||
|
||||
"""
|
||||
return [self.traverse(x) for x in list_]
|
||||
|
||||
def traverse(self, obj):
|
||||
"""traverse and visit the given expression structure."""
|
||||
|
||||
return cloned_traverse(obj, self.__traverse_options__, self._visitor_dict)
|
||||
return cloned_traverse(
|
||||
obj, self.__traverse_options__, self._visitor_dict)
|
||||
|
||||
|
||||
class ReplacingCloningVisitor(CloningVisitor):
|
||||
"""Base class for visitor objects which can traverse using
|
||||
@@ -184,6 +204,7 @@ class ReplacingCloningVisitor(CloningVisitor):
|
||||
return e
|
||||
return replacement_traverse(obj, self.__traverse_options__, replace)
|
||||
|
||||
|
||||
def iterate(obj, opts):
|
||||
"""traverse the given expression structure, returning an iterator.
|
||||
|
||||
@@ -197,6 +218,7 @@ def iterate(obj, opts):
|
||||
for c in t.get_children(**opts):
|
||||
stack.append(c)
|
||||
|
||||
|
||||
def iterate_depthfirst(obj, opts):
|
||||
"""traverse the given expression structure, returning an iterator.
|
||||
|
||||
@@ -212,43 +234,53 @@ def iterate_depthfirst(obj, opts):
|
||||
stack.append(c)
|
||||
return iter(traversal)
|
||||
|
||||
def traverse_using(iterator, obj, visitors):
|
||||
"""visit the given expression structure using the given iterator of objects."""
|
||||
|
||||
def traverse_using(iterator, obj, visitors):
|
||||
"""visit the given expression structure using the given iterator of
|
||||
objects.
|
||||
|
||||
"""
|
||||
for target in iterator:
|
||||
meth = visitors.get(target.__visit_name__, None)
|
||||
if meth:
|
||||
meth(target)
|
||||
return obj
|
||||
|
||||
def traverse(obj, opts, visitors):
|
||||
"""traverse and visit the given expression structure using the default iterator."""
|
||||
|
||||
def traverse(obj, opts, visitors):
|
||||
"""traverse and visit the given expression structure using the default
|
||||
iterator.
|
||||
|
||||
"""
|
||||
return traverse_using(iterate(obj, opts), obj, visitors)
|
||||
|
||||
def traverse_depthfirst(obj, opts, visitors):
|
||||
"""traverse and visit the given expression structure using the depth-first iterator."""
|
||||
|
||||
def traverse_depthfirst(obj, opts, visitors):
|
||||
"""traverse and visit the given expression structure using the
|
||||
depth-first iterator.
|
||||
|
||||
"""
|
||||
return traverse_using(iterate_depthfirst(obj, opts), obj, visitors)
|
||||
|
||||
|
||||
def cloned_traverse(obj, opts, visitors):
|
||||
"""clone the given expression structure, allowing
|
||||
modifications by visitors."""
|
||||
|
||||
cloned = util.column_dict()
|
||||
stop_on = util.column_set(opts.get('stop_on', []))
|
||||
cloned = {}
|
||||
stop_on = set(opts.get('stop_on', []))
|
||||
|
||||
def clone(elem):
|
||||
if elem in stop_on:
|
||||
return elem
|
||||
else:
|
||||
if elem not in cloned:
|
||||
cloned[elem] = newelem = elem._clone()
|
||||
if id(elem) not in cloned:
|
||||
cloned[id(elem)] = newelem = elem._clone()
|
||||
newelem._copy_internals(clone=clone)
|
||||
meth = visitors.get(newelem.__visit_name__, None)
|
||||
if meth:
|
||||
meth(newelem)
|
||||
return cloned[elem]
|
||||
return cloned[id(elem)]
|
||||
|
||||
if obj is not None:
|
||||
obj = clone(obj)
|
||||
@@ -259,17 +291,17 @@ def replacement_traverse(obj, opts, replace):
|
||||
"""clone the given expression structure, allowing element
|
||||
replacement by a given replacement function."""
|
||||
|
||||
cloned = util.column_dict()
|
||||
stop_on = util.column_set(opts.get('stop_on', []))
|
||||
cloned = {}
|
||||
stop_on = set([id(x) for x in opts.get('stop_on', [])])
|
||||
|
||||
def clone(elem, **kw):
|
||||
if elem in stop_on or \
|
||||
if id(elem) in stop_on or \
|
||||
'no_replacement_traverse' in elem._annotations:
|
||||
return elem
|
||||
else:
|
||||
newelem = replace(elem)
|
||||
if newelem is not None:
|
||||
stop_on.add(newelem)
|
||||
stop_on.add(id(newelem))
|
||||
return newelem
|
||||
else:
|
||||
if elem not in cloned:
|
||||
|
||||
Reference in New Issue
Block a user