update sqlalchemy
This commit is contained in:
parent
6c6c3e68c6
commit
a4267212e4
192 changed files with 17429 additions and 9601 deletions
|
|
@ -1,5 +1,5 @@
|
|||
# sql/__init__.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -38,6 +38,7 @@ from .expression import (
|
|||
false,
|
||||
False_,
|
||||
func,
|
||||
funcfilter,
|
||||
insert,
|
||||
intersect,
|
||||
intersect_all,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/annotation.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -46,6 +46,7 @@ class Annotated(object):
|
|||
self.__dict__ = element.__dict__.copy()
|
||||
self.__element = element
|
||||
self._annotations = values
|
||||
self._hash = hash(element)
|
||||
|
||||
def _annotate(self, values):
|
||||
_values = self._annotations.copy()
|
||||
|
|
@ -87,7 +88,7 @@ class Annotated(object):
|
|||
return self.__class__(clone, self._annotations)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__element)
|
||||
return self._hash
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(self.__element, operators.ColumnOperators):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/base.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -449,10 +449,13 @@ class ColumnCollection(util.OrderedProperties):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
__slots__ = '_all_columns'
|
||||
|
||||
def __init__(self, *columns):
|
||||
super(ColumnCollection, self).__init__()
|
||||
self.__dict__['_all_col_set'] = util.column_set()
|
||||
self.__dict__['_all_columns'] = []
|
||||
object.__setattr__(self, '_all_columns', [])
|
||||
for c in columns:
|
||||
self.add(c)
|
||||
|
||||
def __str__(self):
|
||||
return repr([str(c) for c in self])
|
||||
|
|
@ -478,14 +481,11 @@ class ColumnCollection(util.OrderedProperties):
|
|||
other = self[column.name]
|
||||
if other.name == other.key:
|
||||
remove_col = other
|
||||
self._all_col_set.remove(other)
|
||||
del self._data[other.key]
|
||||
|
||||
if column.key in self._data:
|
||||
remove_col = self._data[column.key]
|
||||
self._all_col_set.remove(remove_col)
|
||||
|
||||
self._all_col_set.add(column)
|
||||
self._data[column.key] = column
|
||||
if remove_col is not None:
|
||||
self._all_columns[:] = [column if c is remove_col
|
||||
|
|
@ -530,7 +530,6 @@ class ColumnCollection(util.OrderedProperties):
|
|||
# in a _make_proxy operation
|
||||
util.memoized_property.reset(value, "proxy_set")
|
||||
|
||||
self._all_col_set.add(value)
|
||||
self._all_columns.append(value)
|
||||
self._data[key] = value
|
||||
|
||||
|
|
@ -539,22 +538,20 @@ class ColumnCollection(util.OrderedProperties):
|
|||
|
||||
def remove(self, column):
|
||||
del self._data[column.key]
|
||||
self._all_col_set.remove(column)
|
||||
self._all_columns[:] = [
|
||||
c for c in self._all_columns if c is not column]
|
||||
|
||||
def update(self, iter):
|
||||
cols = list(iter)
|
||||
all_col_set = set(self._all_columns)
|
||||
self._all_columns.extend(
|
||||
c for label, c in cols if c not in self._all_col_set)
|
||||
self._all_col_set.update(c for label, c in cols)
|
||||
c for label, c in cols if c not in all_col_set)
|
||||
self._data.update((label, c) for label, c in cols)
|
||||
|
||||
def extend(self, iter):
|
||||
cols = list(iter)
|
||||
self._all_columns.extend(c for c in cols if c not in
|
||||
self._all_col_set)
|
||||
self._all_col_set.update(cols)
|
||||
all_col_set = set(self._all_columns)
|
||||
self._all_columns.extend(c for c in cols if c not in all_col_set)
|
||||
self._data.update((c.key, c) for c in cols)
|
||||
|
||||
__hash__ = None
|
||||
|
|
@ -574,28 +571,24 @@ class ColumnCollection(util.OrderedProperties):
|
|||
return util.OrderedProperties.__contains__(self, other)
|
||||
|
||||
def __getstate__(self):
|
||||
return {'_data': self.__dict__['_data'],
|
||||
'_all_columns': self.__dict__['_all_columns']}
|
||||
return {'_data': self._data,
|
||||
'_all_columns': self._all_columns}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__['_data'] = state['_data']
|
||||
self.__dict__['_all_columns'] = state['_all_columns']
|
||||
self.__dict__['_all_col_set'] = util.column_set(state['_all_columns'])
|
||||
object.__setattr__(self, '_data', state['_data'])
|
||||
object.__setattr__(self, '_all_columns', state['_all_columns'])
|
||||
|
||||
def contains_column(self, col):
|
||||
# this has to be done via set() membership
|
||||
return col in self._all_col_set
|
||||
return col in set(self._all_columns)
|
||||
|
||||
def as_immutable(self):
|
||||
return ImmutableColumnCollection(
|
||||
self._data, self._all_col_set, self._all_columns)
|
||||
return ImmutableColumnCollection(self._data, self._all_columns)
|
||||
|
||||
|
||||
class ImmutableColumnCollection(util.ImmutableProperties, ColumnCollection):
|
||||
def __init__(self, data, colset, all_columns):
|
||||
def __init__(self, data, all_columns):
|
||||
util.ImmutableProperties.__init__(self, data)
|
||||
self.__dict__['_all_col_set'] = colset
|
||||
self.__dict__['_all_columns'] = all_columns
|
||||
object.__setattr__(self, '_all_columns', all_columns)
|
||||
|
||||
extend = remove = util.ImmutableProperties._immutable
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
571
lib/python3.5/site-packages/sqlalchemy/sql/crud.py
Normal file
571
lib/python3.5/site-packages/sqlalchemy/sql/crud.py
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
# sql/crud.py
|
||||
# Copyright (C) 2005-2016 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
|
||||
|
||||
"""Functions used by compiler.py to determine the parameters rendered
|
||||
within INSERT and UPDATE statements.
|
||||
|
||||
"""
|
||||
from .. import util
|
||||
from .. import exc
|
||||
from . import elements
|
||||
import operator
|
||||
|
||||
REQUIRED = util.symbol('REQUIRED', """
|
||||
Placeholder for the value within a :class:`.BindParameter`
|
||||
which is required to be present when the statement is passed
|
||||
to :meth:`.Connection.execute`.
|
||||
|
||||
This symbol is typically used when a :func:`.expression.insert`
|
||||
or :func:`.expression.update` statement is compiled without parameter
|
||||
values present.
|
||||
|
||||
""")
|
||||
|
||||
|
||||
def _get_crud_params(compiler, stmt, **kw):
|
||||
"""create a set of tuples representing column/string pairs for use
|
||||
in an INSERT or UPDATE statement.
|
||||
|
||||
Also generates the Compiled object's postfetch, prefetch, and
|
||||
returning column collections, used for default handling and ultimately
|
||||
populating the ResultProxy's prefetch_cols() and postfetch_cols()
|
||||
collections.
|
||||
|
||||
"""
|
||||
|
||||
compiler.postfetch = []
|
||||
compiler.prefetch = []
|
||||
compiler.returning = []
|
||||
|
||||
# no parameters in the statement, no parameters in the
|
||||
# compiled params - return binds for all columns
|
||||
if compiler.column_keys is None and stmt.parameters is None:
|
||||
return [
|
||||
(c, _create_bind_param(
|
||||
compiler, c, None, required=True))
|
||||
for c in stmt.table.columns
|
||||
]
|
||||
|
||||
if stmt._has_multi_parameters:
|
||||
stmt_parameters = stmt.parameters[0]
|
||||
else:
|
||||
stmt_parameters = stmt.parameters
|
||||
|
||||
# getters - these are normally just column.key,
|
||||
# but in the case of mysql multi-table update, the rules for
|
||||
# .key must conditionally take tablename into account
|
||||
_column_as_key, _getattr_col_key, _col_bind_name = \
|
||||
_key_getters_for_crud_column(compiler)
|
||||
|
||||
# if we have statement parameters - set defaults in the
|
||||
# compiled params
|
||||
if compiler.column_keys is None:
|
||||
parameters = {}
|
||||
else:
|
||||
parameters = dict((_column_as_key(key), REQUIRED)
|
||||
for key in compiler.column_keys
|
||||
if not stmt_parameters or
|
||||
key not in stmt_parameters)
|
||||
|
||||
# create a list of column assignment clauses as tuples
|
||||
values = []
|
||||
|
||||
if stmt_parameters is not None:
|
||||
_get_stmt_parameters_params(
|
||||
compiler,
|
||||
parameters, stmt_parameters, _column_as_key, values, kw)
|
||||
|
||||
check_columns = {}
|
||||
|
||||
# special logic that only occurs for multi-table UPDATE
|
||||
# statements
|
||||
if compiler.isupdate and stmt._extra_froms and stmt_parameters:
|
||||
_get_multitable_params(
|
||||
compiler, stmt, stmt_parameters, check_columns,
|
||||
_col_bind_name, _getattr_col_key, values, kw)
|
||||
|
||||
if compiler.isinsert and stmt.select_names:
|
||||
_scan_insert_from_select_cols(
|
||||
compiler, stmt, parameters,
|
||||
_getattr_col_key, _column_as_key,
|
||||
_col_bind_name, check_columns, values, kw)
|
||||
else:
|
||||
_scan_cols(
|
||||
compiler, stmt, parameters,
|
||||
_getattr_col_key, _column_as_key,
|
||||
_col_bind_name, check_columns, values, kw)
|
||||
|
||||
if parameters and stmt_parameters:
|
||||
check = set(parameters).intersection(
|
||||
_column_as_key(k) for k in stmt.parameters
|
||||
).difference(check_columns)
|
||||
if check:
|
||||
raise exc.CompileError(
|
||||
"Unconsumed column names: %s" %
|
||||
(", ".join("%s" % c for c in check))
|
||||
)
|
||||
|
||||
if stmt._has_multi_parameters:
|
||||
values = _extend_values_for_multiparams(compiler, stmt, values, kw)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def _create_bind_param(
|
||||
compiler, col, value, process=True,
|
||||
required=False, name=None, **kw):
|
||||
if name is None:
|
||||
name = col.key
|
||||
bindparam = elements.BindParameter(
|
||||
name, value, type_=col.type, required=required)
|
||||
bindparam._is_crud = True
|
||||
if process:
|
||||
bindparam = bindparam._compiler_dispatch(compiler, **kw)
|
||||
return bindparam
|
||||
|
||||
|
||||
def _key_getters_for_crud_column(compiler):
|
||||
if compiler.isupdate and compiler.statement._extra_froms:
|
||||
# when extra tables are present, refer to the columns
|
||||
# in those extra tables as table-qualified, including in
|
||||
# dictionaries and when rendering bind param names.
|
||||
# the "main" table of the statement remains unqualified,
|
||||
# allowing the most compatibility with a non-multi-table
|
||||
# statement.
|
||||
_et = set(compiler.statement._extra_froms)
|
||||
|
||||
def _column_as_key(key):
|
||||
str_key = elements._column_as_key(key)
|
||||
if hasattr(key, 'table') and key.table in _et:
|
||||
return (key.table.name, str_key)
|
||||
else:
|
||||
return str_key
|
||||
|
||||
def _getattr_col_key(col):
|
||||
if col.table in _et:
|
||||
return (col.table.name, col.key)
|
||||
else:
|
||||
return col.key
|
||||
|
||||
def _col_bind_name(col):
|
||||
if col.table in _et:
|
||||
return "%s_%s" % (col.table.name, col.key)
|
||||
else:
|
||||
return col.key
|
||||
|
||||
else:
|
||||
_column_as_key = elements._column_as_key
|
||||
_getattr_col_key = _col_bind_name = operator.attrgetter("key")
|
||||
|
||||
return _column_as_key, _getattr_col_key, _col_bind_name
|
||||
|
||||
|
||||
def _scan_insert_from_select_cols(
|
||||
compiler, stmt, parameters, _getattr_col_key,
|
||||
_column_as_key, _col_bind_name, check_columns, values, kw):
|
||||
|
||||
need_pks, implicit_returning, \
|
||||
implicit_return_defaults, postfetch_lastrowid = \
|
||||
_get_returning_modifiers(compiler, stmt)
|
||||
|
||||
cols = [stmt.table.c[_column_as_key(name)]
|
||||
for name in stmt.select_names]
|
||||
|
||||
compiler._insert_from_select = stmt.select
|
||||
|
||||
add_select_cols = []
|
||||
if stmt.include_insert_from_select_defaults:
|
||||
col_set = set(cols)
|
||||
for col in stmt.table.columns:
|
||||
if col not in col_set and col.default:
|
||||
cols.append(col)
|
||||
|
||||
for c in cols:
|
||||
col_key = _getattr_col_key(c)
|
||||
if col_key in parameters and col_key not in check_columns:
|
||||
parameters.pop(col_key)
|
||||
values.append((c, None))
|
||||
else:
|
||||
_append_param_insert_select_hasdefault(
|
||||
compiler, stmt, c, add_select_cols, kw)
|
||||
|
||||
if add_select_cols:
|
||||
values.extend(add_select_cols)
|
||||
compiler._insert_from_select = compiler._insert_from_select._generate()
|
||||
compiler._insert_from_select._raw_columns = \
|
||||
tuple(compiler._insert_from_select._raw_columns) + tuple(
|
||||
expr for col, expr in add_select_cols)
|
||||
|
||||
|
||||
def _scan_cols(
|
||||
compiler, stmt, parameters, _getattr_col_key,
|
||||
_column_as_key, _col_bind_name, check_columns, values, kw):
|
||||
|
||||
need_pks, implicit_returning, \
|
||||
implicit_return_defaults, postfetch_lastrowid = \
|
||||
_get_returning_modifiers(compiler, stmt)
|
||||
|
||||
if stmt._parameter_ordering:
|
||||
parameter_ordering = [
|
||||
_column_as_key(key) for key in stmt._parameter_ordering
|
||||
]
|
||||
ordered_keys = set(parameter_ordering)
|
||||
cols = [
|
||||
stmt.table.c[key] for key in parameter_ordering
|
||||
] + [
|
||||
c for c in stmt.table.c if c.key not in ordered_keys
|
||||
]
|
||||
else:
|
||||
cols = stmt.table.columns
|
||||
|
||||
for c in cols:
|
||||
col_key = _getattr_col_key(c)
|
||||
if col_key in parameters and col_key not in check_columns:
|
||||
|
||||
_append_param_parameter(
|
||||
compiler, stmt, c, col_key, parameters, _col_bind_name,
|
||||
implicit_returning, implicit_return_defaults, values, kw)
|
||||
|
||||
elif compiler.isinsert:
|
||||
if c.primary_key and \
|
||||
need_pks and \
|
||||
(
|
||||
implicit_returning or
|
||||
not postfetch_lastrowid or
|
||||
c is not stmt.table._autoincrement_column
|
||||
):
|
||||
|
||||
if implicit_returning:
|
||||
_append_param_insert_pk_returning(
|
||||
compiler, stmt, c, values, kw)
|
||||
else:
|
||||
_append_param_insert_pk(compiler, stmt, c, values, kw)
|
||||
|
||||
elif c.default is not None:
|
||||
|
||||
_append_param_insert_hasdefault(
|
||||
compiler, stmt, c, implicit_return_defaults,
|
||||
values, kw)
|
||||
|
||||
elif c.server_default is not None:
|
||||
if implicit_return_defaults and \
|
||||
c in implicit_return_defaults:
|
||||
compiler.returning.append(c)
|
||||
elif not c.primary_key:
|
||||
compiler.postfetch.append(c)
|
||||
elif implicit_return_defaults and \
|
||||
c in implicit_return_defaults:
|
||||
compiler.returning.append(c)
|
||||
|
||||
elif compiler.isupdate:
|
||||
_append_param_update(
|
||||
compiler, stmt, c, implicit_return_defaults, values, kw)
|
||||
|
||||
|
||||
def _append_param_parameter(
|
||||
compiler, stmt, c, col_key, parameters, _col_bind_name,
|
||||
implicit_returning, implicit_return_defaults, values, kw):
|
||||
value = parameters.pop(col_key)
|
||||
if elements._is_literal(value):
|
||||
value = _create_bind_param(
|
||||
compiler, c, value, required=value is REQUIRED,
|
||||
name=_col_bind_name(c)
|
||||
if not stmt._has_multi_parameters
|
||||
else "%s_0" % _col_bind_name(c),
|
||||
**kw
|
||||
)
|
||||
else:
|
||||
if isinstance(value, elements.BindParameter) and \
|
||||
value.type._isnull:
|
||||
value = value._clone()
|
||||
value.type = c.type
|
||||
|
||||
if c.primary_key and implicit_returning:
|
||||
compiler.returning.append(c)
|
||||
value = compiler.process(value.self_group(), **kw)
|
||||
elif implicit_return_defaults and \
|
||||
c in implicit_return_defaults:
|
||||
compiler.returning.append(c)
|
||||
value = compiler.process(value.self_group(), **kw)
|
||||
else:
|
||||
compiler.postfetch.append(c)
|
||||
value = compiler.process(value.self_group(), **kw)
|
||||
values.append((c, value))
|
||||
|
||||
|
||||
def _append_param_insert_pk_returning(compiler, stmt, c, values, kw):
|
||||
if c.default is not None:
|
||||
if c.default.is_sequence:
|
||||
if compiler.dialect.supports_sequences and \
|
||||
(not c.default.optional or
|
||||
not compiler.dialect.sequences_optional):
|
||||
proc = compiler.process(c.default, **kw)
|
||||
values.append((c, proc))
|
||||
compiler.returning.append(c)
|
||||
elif c.default.is_clause_element:
|
||||
values.append(
|
||||
(c, compiler.process(
|
||||
c.default.arg.self_group(), **kw))
|
||||
)
|
||||
compiler.returning.append(c)
|
||||
else:
|
||||
values.append(
|
||||
(c, _create_prefetch_bind_param(compiler, c))
|
||||
)
|
||||
|
||||
else:
|
||||
compiler.returning.append(c)
|
||||
|
||||
|
||||
def _create_prefetch_bind_param(compiler, c, process=True, name=None):
|
||||
param = _create_bind_param(compiler, c, None, process=process, name=name)
|
||||
compiler.prefetch.append(c)
|
||||
return param
|
||||
|
||||
|
||||
class _multiparam_column(elements.ColumnElement):
|
||||
def __init__(self, original, index):
|
||||
self.key = "%s_%d" % (original.key, index + 1)
|
||||
self.original = original
|
||||
self.default = original.default
|
||||
self.type = original.type
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, _multiparam_column) and \
|
||||
other.key == self.key and \
|
||||
other.original == self.original
|
||||
|
||||
|
||||
def _process_multiparam_default_bind(compiler, c, index, kw):
|
||||
|
||||
if not c.default:
|
||||
raise exc.CompileError(
|
||||
"INSERT value for column %s is explicitly rendered as a bound"
|
||||
"parameter in the VALUES clause; "
|
||||
"a Python-side value or SQL expression is required" % c)
|
||||
elif c.default.is_clause_element:
|
||||
return compiler.process(c.default.arg.self_group(), **kw)
|
||||
else:
|
||||
col = _multiparam_column(c, index)
|
||||
return _create_prefetch_bind_param(compiler, col)
|
||||
|
||||
|
||||
def _append_param_insert_pk(compiler, stmt, c, values, kw):
|
||||
if (
|
||||
(c.default is not None and
|
||||
(not c.default.is_sequence or
|
||||
compiler.dialect.supports_sequences)) or
|
||||
c is stmt.table._autoincrement_column and
|
||||
(compiler.dialect.supports_sequences or
|
||||
compiler.dialect.
|
||||
preexecute_autoincrement_sequences)
|
||||
):
|
||||
values.append(
|
||||
(c, _create_prefetch_bind_param(compiler, c))
|
||||
)
|
||||
|
||||
|
||||
def _append_param_insert_hasdefault(
|
||||
compiler, stmt, c, implicit_return_defaults, values, kw):
|
||||
|
||||
if c.default.is_sequence:
|
||||
if compiler.dialect.supports_sequences and \
|
||||
(not c.default.optional or
|
||||
not compiler.dialect.sequences_optional):
|
||||
proc = compiler.process(c.default, **kw)
|
||||
values.append((c, proc))
|
||||
if implicit_return_defaults and \
|
||||
c in implicit_return_defaults:
|
||||
compiler.returning.append(c)
|
||||
elif not c.primary_key:
|
||||
compiler.postfetch.append(c)
|
||||
elif c.default.is_clause_element:
|
||||
proc = compiler.process(c.default.arg.self_group(), **kw)
|
||||
values.append((c, proc))
|
||||
|
||||
if implicit_return_defaults and \
|
||||
c in implicit_return_defaults:
|
||||
compiler.returning.append(c)
|
||||
elif not c.primary_key:
|
||||
# don't add primary key column to postfetch
|
||||
compiler.postfetch.append(c)
|
||||
else:
|
||||
values.append(
|
||||
(c, _create_prefetch_bind_param(compiler, c))
|
||||
)
|
||||
|
||||
|
||||
def _append_param_insert_select_hasdefault(
|
||||
compiler, stmt, c, values, kw):
|
||||
|
||||
if c.default.is_sequence:
|
||||
if compiler.dialect.supports_sequences and \
|
||||
(not c.default.optional or
|
||||
not compiler.dialect.sequences_optional):
|
||||
proc = c.default
|
||||
values.append((c, proc))
|
||||
elif c.default.is_clause_element:
|
||||
proc = c.default.arg.self_group()
|
||||
values.append((c, proc))
|
||||
else:
|
||||
values.append(
|
||||
(c, _create_prefetch_bind_param(compiler, c, process=False))
|
||||
)
|
||||
|
||||
|
||||
def _append_param_update(
|
||||
compiler, stmt, c, implicit_return_defaults, values, kw):
|
||||
|
||||
if c.onupdate is not None and not c.onupdate.is_sequence:
|
||||
if c.onupdate.is_clause_element:
|
||||
values.append(
|
||||
(c, compiler.process(
|
||||
c.onupdate.arg.self_group(), **kw))
|
||||
)
|
||||
if implicit_return_defaults and \
|
||||
c in implicit_return_defaults:
|
||||
compiler.returning.append(c)
|
||||
else:
|
||||
compiler.postfetch.append(c)
|
||||
else:
|
||||
values.append(
|
||||
(c, _create_prefetch_bind_param(compiler, c))
|
||||
)
|
||||
elif c.server_onupdate is not None:
|
||||
if implicit_return_defaults and \
|
||||
c in implicit_return_defaults:
|
||||
compiler.returning.append(c)
|
||||
else:
|
||||
compiler.postfetch.append(c)
|
||||
elif implicit_return_defaults and \
|
||||
stmt._return_defaults is not True and \
|
||||
c in implicit_return_defaults:
|
||||
compiler.returning.append(c)
|
||||
|
||||
|
||||
def _get_multitable_params(
|
||||
compiler, stmt, stmt_parameters, check_columns,
|
||||
_col_bind_name, _getattr_col_key, values, kw):
|
||||
|
||||
normalized_params = dict(
|
||||
(elements._clause_element_as_expr(c), param)
|
||||
for c, param in stmt_parameters.items()
|
||||
)
|
||||
affected_tables = set()
|
||||
for t in stmt._extra_froms:
|
||||
for c in t.c:
|
||||
if c in normalized_params:
|
||||
affected_tables.add(t)
|
||||
check_columns[_getattr_col_key(c)] = c
|
||||
value = normalized_params[c]
|
||||
if elements._is_literal(value):
|
||||
value = _create_bind_param(
|
||||
compiler, c, value, required=value is REQUIRED,
|
||||
name=_col_bind_name(c))
|
||||
else:
|
||||
compiler.postfetch.append(c)
|
||||
value = compiler.process(value.self_group(), **kw)
|
||||
values.append((c, value))
|
||||
# determine tables which are actually to be updated - process onupdate
|
||||
# and server_onupdate for these
|
||||
for t in affected_tables:
|
||||
for c in t.c:
|
||||
if c in normalized_params:
|
||||
continue
|
||||
elif (c.onupdate is not None and not
|
||||
c.onupdate.is_sequence):
|
||||
if c.onupdate.is_clause_element:
|
||||
values.append(
|
||||
(c, compiler.process(
|
||||
c.onupdate.arg.self_group(),
|
||||
**kw)
|
||||
)
|
||||
)
|
||||
compiler.postfetch.append(c)
|
||||
else:
|
||||
values.append(
|
||||
(c, _create_prefetch_bind_param(
|
||||
compiler, c, name=_col_bind_name(c)))
|
||||
)
|
||||
elif c.server_onupdate is not None:
|
||||
compiler.postfetch.append(c)
|
||||
|
||||
|
||||
def _extend_values_for_multiparams(compiler, stmt, values, kw):
|
||||
values_0 = values
|
||||
values = [values]
|
||||
|
||||
values.extend(
|
||||
[
|
||||
(
|
||||
c,
|
||||
(_create_bind_param(
|
||||
compiler, c, row[c.key],
|
||||
name="%s_%d" % (c.key, i + 1)
|
||||
) if elements._is_literal(row[c.key])
|
||||
else compiler.process(
|
||||
row[c.key].self_group(), **kw))
|
||||
if c.key in row else
|
||||
_process_multiparam_default_bind(compiler, c, i, kw)
|
||||
)
|
||||
for (c, param) in values_0
|
||||
]
|
||||
for i, row in enumerate(stmt.parameters[1:])
|
||||
)
|
||||
return values
|
||||
|
||||
|
||||
def _get_stmt_parameters_params(
|
||||
compiler, parameters, stmt_parameters, _column_as_key, values, kw):
|
||||
for k, v in stmt_parameters.items():
|
||||
colkey = _column_as_key(k)
|
||||
if colkey is not None:
|
||||
parameters.setdefault(colkey, v)
|
||||
else:
|
||||
# a non-Column expression on the left side;
|
||||
# add it to values() in an "as-is" state,
|
||||
# coercing right side to bound param
|
||||
if elements._is_literal(v):
|
||||
v = compiler.process(
|
||||
elements.BindParameter(None, v, type_=k.type),
|
||||
**kw)
|
||||
else:
|
||||
v = compiler.process(v.self_group(), **kw)
|
||||
|
||||
values.append((k, v))
|
||||
|
||||
|
||||
def _get_returning_modifiers(compiler, stmt):
|
||||
need_pks = compiler.isinsert and \
|
||||
not compiler.inline and \
|
||||
not stmt._returning and \
|
||||
not stmt._has_multi_parameters
|
||||
|
||||
implicit_returning = need_pks and \
|
||||
compiler.dialect.implicit_returning and \
|
||||
stmt.table.implicit_returning
|
||||
|
||||
if compiler.isinsert:
|
||||
implicit_return_defaults = (implicit_returning and
|
||||
stmt._return_defaults)
|
||||
elif compiler.isupdate:
|
||||
implicit_return_defaults = (compiler.dialect.implicit_returning and
|
||||
stmt.table.implicit_returning and
|
||||
stmt._return_defaults)
|
||||
else:
|
||||
implicit_return_defaults = False
|
||||
|
||||
if implicit_return_defaults:
|
||||
if stmt._return_defaults is True:
|
||||
implicit_return_defaults = set(stmt.table.c)
|
||||
else:
|
||||
implicit_return_defaults = set(stmt._return_defaults)
|
||||
|
||||
postfetch_lastrowid = need_pks and compiler.dialect.postfetch_lastrowid
|
||||
|
||||
return need_pks, implicit_returning, \
|
||||
implicit_return_defaults, postfetch_lastrowid
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/ddl.py
|
||||
# Copyright (C) 2009-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2009-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -12,7 +12,6 @@ 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
|
||||
|
|
@ -370,7 +369,7 @@ class DDL(DDLElement):
|
|||
|
||||
:class:`.DDLEvents`
|
||||
|
||||
:mod:`sqlalchemy.event`
|
||||
:ref:`event_toplevel`
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -464,19 +463,28 @@ class CreateTable(_CreateDropBase):
|
|||
|
||||
__visit_name__ = "create_table"
|
||||
|
||||
def __init__(self, element, on=None, bind=None):
|
||||
def __init__(
|
||||
self, element, on=None, bind=None,
|
||||
include_foreign_key_constraints=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`.
|
||||
:param include_foreign_key_constraints: optional sequence of
|
||||
:class:`.ForeignKeyConstraint` objects that will be included
|
||||
inline within the CREATE construct; if omitted, all foreign key
|
||||
constraints that do not specify use_alter=True are included.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
"""
|
||||
super(CreateTable, self).__init__(element, on=on, bind=bind)
|
||||
self.columns = [CreateColumn(column)
|
||||
for column in element.columns
|
||||
]
|
||||
self.include_foreign_key_constraints = include_foreign_key_constraints
|
||||
|
||||
|
||||
class _DropView(_CreateDropBase):
|
||||
|
|
@ -696,48 +704,80 @@ class SchemaGenerator(DDLBase):
|
|||
tables = self.tables
|
||||
else:
|
||||
tables = list(metadata.tables.values())
|
||||
collection = [t for t in sort_tables(tables)
|
||||
if self._can_create_table(t)]
|
||||
|
||||
collection = sort_tables_and_constraints(
|
||||
[t for t in 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)]
|
||||
|
||||
event_collection = [
|
||||
t for (t, fks) in collection if t is not None
|
||||
]
|
||||
metadata.dispatch.before_create(metadata, self.connection,
|
||||
tables=collection,
|
||||
tables=event_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)
|
||||
for table, fkcs in collection:
|
||||
if table is not None:
|
||||
self.traverse_single(
|
||||
table, create_ok=True,
|
||||
include_foreign_key_constraints=fkcs,
|
||||
_is_metadata_operation=True)
|
||||
else:
|
||||
for fkc in fkcs:
|
||||
self.traverse_single(fkc)
|
||||
|
||||
metadata.dispatch.after_create(metadata, self.connection,
|
||||
tables=collection,
|
||||
tables=event_collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_table(self, table, create_ok=False):
|
||||
def visit_table(
|
||||
self, table, create_ok=False,
|
||||
include_foreign_key_constraints=None,
|
||||
_is_metadata_operation=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)
|
||||
table.dispatch.before_create(
|
||||
table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self,
|
||||
_is_metadata_operation=_is_metadata_operation)
|
||||
|
||||
for column in table.columns:
|
||||
if column.default is not None:
|
||||
self.traverse_single(column.default)
|
||||
|
||||
self.connection.execute(CreateTable(table))
|
||||
if not self.dialect.supports_alter:
|
||||
# e.g., don't omit any foreign key constraints
|
||||
include_foreign_key_constraints = None
|
||||
|
||||
self.connection.execute(
|
||||
CreateTable(
|
||||
table,
|
||||
include_foreign_key_constraints=include_foreign_key_constraints
|
||||
))
|
||||
|
||||
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)
|
||||
table.dispatch.after_create(
|
||||
table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self,
|
||||
_is_metadata_operation=_is_metadata_operation)
|
||||
|
||||
def visit_foreign_key_constraint(self, constraint):
|
||||
if not self.dialect.supports_alter:
|
||||
return
|
||||
self.connection.execute(AddConstraint(constraint))
|
||||
|
||||
def visit_sequence(self, sequence, create_ok=False):
|
||||
if not create_ok and not self._can_create_sequence(sequence):
|
||||
|
|
@ -765,11 +805,51 @@ class SchemaDropper(DDLBase):
|
|||
else:
|
||||
tables = list(metadata.tables.values())
|
||||
|
||||
collection = [
|
||||
t
|
||||
for t in reversed(sort_tables(tables))
|
||||
if self._can_drop_table(t)
|
||||
]
|
||||
try:
|
||||
unsorted_tables = [t for t in tables if self._can_drop_table(t)]
|
||||
collection = list(reversed(
|
||||
sort_tables_and_constraints(
|
||||
unsorted_tables,
|
||||
filter_fn=lambda constraint: False
|
||||
if not self.dialect.supports_alter
|
||||
or constraint.name is None
|
||||
else None
|
||||
)
|
||||
))
|
||||
except exc.CircularDependencyError as err2:
|
||||
if not self.dialect.supports_alter:
|
||||
util.warn(
|
||||
"Can't sort tables for DROP; an "
|
||||
"unresolvable foreign key "
|
||||
"dependency exists between tables: %s, and backend does "
|
||||
"not support ALTER. To restore at least a partial sort, "
|
||||
"apply use_alter=True to ForeignKey and "
|
||||
"ForeignKeyConstraint "
|
||||
"objects involved in the cycle to mark these as known "
|
||||
"cycles that will be ignored."
|
||||
% (
|
||||
", ".join(sorted([t.fullname for t in err2.cycles]))
|
||||
)
|
||||
)
|
||||
collection = [(t, ()) for t in unsorted_tables]
|
||||
else:
|
||||
util.raise_from_cause(
|
||||
exc.CircularDependencyError(
|
||||
err2.args[0],
|
||||
err2.cycles, err2.edges,
|
||||
msg="Can't sort tables for DROP; an "
|
||||
"unresolvable foreign key "
|
||||
"dependency exists between tables: %s. Please ensure "
|
||||
"that the ForeignKey and ForeignKeyConstraint objects "
|
||||
"involved in the cycle have "
|
||||
"names so that they can be dropped using "
|
||||
"DROP CONSTRAINT."
|
||||
% (
|
||||
", ".join(sorted([t.fullname for t in err2.cycles]))
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
seq_coll = [
|
||||
s
|
||||
|
|
@ -777,18 +857,27 @@ class SchemaDropper(DDLBase):
|
|||
if s.column is None and self._can_drop_sequence(s)
|
||||
]
|
||||
|
||||
event_collection = [
|
||||
t for (t, fks) in collection if t is not None
|
||||
]
|
||||
|
||||
metadata.dispatch.before_drop(
|
||||
metadata, self.connection, tables=collection,
|
||||
metadata, self.connection, tables=event_collection,
|
||||
checkfirst=self.checkfirst, _ddl_runner=self)
|
||||
|
||||
for table in collection:
|
||||
self.traverse_single(table, drop_ok=True)
|
||||
for table, fkcs in collection:
|
||||
if table is not None:
|
||||
self.traverse_single(
|
||||
table, drop_ok=True, _is_metadata_operation=True)
|
||||
else:
|
||||
for fkc in fkcs:
|
||||
self.traverse_single(fkc)
|
||||
|
||||
for seq in seq_coll:
|
||||
self.traverse_single(seq, drop_ok=True)
|
||||
|
||||
metadata.dispatch.after_drop(
|
||||
metadata, self.connection, tables=collection,
|
||||
metadata, self.connection, tables=event_collection,
|
||||
checkfirst=self.checkfirst, _ddl_runner=self)
|
||||
|
||||
def _can_drop_table(self, table):
|
||||
|
|
@ -812,13 +901,15 @@ class SchemaDropper(DDLBase):
|
|||
def visit_index(self, index):
|
||||
self.connection.execute(DropIndex(index))
|
||||
|
||||
def visit_table(self, table, drop_ok=False):
|
||||
def visit_table(self, table, drop_ok=False, _is_metadata_operation=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)
|
||||
table.dispatch.before_drop(
|
||||
table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self,
|
||||
_is_metadata_operation=_is_metadata_operation)
|
||||
|
||||
for column in table.columns:
|
||||
if column.default is not None:
|
||||
|
|
@ -826,9 +917,16 @@ class SchemaDropper(DDLBase):
|
|||
|
||||
self.connection.execute(DropTable(table))
|
||||
|
||||
table.dispatch.after_drop(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
table.dispatch.after_drop(
|
||||
table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self,
|
||||
_is_metadata_operation=_is_metadata_operation)
|
||||
|
||||
def visit_foreign_key_constraint(self, constraint):
|
||||
if not self.dialect.supports_alter:
|
||||
return
|
||||
self.connection.execute(DropConstraint(constraint))
|
||||
|
||||
def visit_sequence(self, sequence, drop_ok=False):
|
||||
if not drop_ok and not self._can_drop_sequence(sequence):
|
||||
|
|
@ -837,32 +935,161 @@ class SchemaDropper(DDLBase):
|
|||
|
||||
|
||||
def sort_tables(tables, skip_fn=None, extra_dependencies=None):
|
||||
"""sort a collection of Table objects in order of
|
||||
their foreign-key dependency."""
|
||||
"""sort a collection of :class:`.Table` objects based on dependency.
|
||||
|
||||
This is a dependency-ordered sort which will emit :class:`.Table`
|
||||
objects such that they will follow their dependent :class:`.Table` objects.
|
||||
Tables are dependent on another based on the presence of
|
||||
:class:`.ForeignKeyConstraint` objects as well as explicit dependencies
|
||||
added by :meth:`.Table.add_is_dependent_on`.
|
||||
|
||||
.. warning::
|
||||
|
||||
The :func:`.sort_tables` function cannot by itself accommodate
|
||||
automatic resolution of dependency cycles between tables, which
|
||||
are usually caused by mutually dependent foreign key constraints.
|
||||
To resolve these cycles, either the
|
||||
:paramref:`.ForeignKeyConstraint.use_alter` parameter may be appled
|
||||
to those constraints, or use the
|
||||
:func:`.sql.sort_tables_and_constraints` function which will break
|
||||
out foreign key constraints involved in cycles separately.
|
||||
|
||||
:param tables: a sequence of :class:`.Table` objects.
|
||||
|
||||
:param skip_fn: optional callable which will be passed a
|
||||
:class:`.ForeignKey` object; if it returns True, this
|
||||
constraint will not be considered as a dependency. Note this is
|
||||
**different** from the same parameter in
|
||||
:func:`.sort_tables_and_constraints`, which is
|
||||
instead passed the owning :class:`.ForeignKeyConstraint` object.
|
||||
|
||||
:param extra_dependencies: a sequence of 2-tuples of tables which will
|
||||
also be considered as dependent on each other.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:func:`.sort_tables_and_constraints`
|
||||
|
||||
:meth:`.MetaData.sorted_tables` - uses this function to sort
|
||||
|
||||
|
||||
"""
|
||||
|
||||
if skip_fn is not None:
|
||||
def _skip_fn(fkc):
|
||||
for fk in fkc.elements:
|
||||
if skip_fn(fk):
|
||||
return True
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
_skip_fn = None
|
||||
|
||||
return [
|
||||
t for (t, fkcs) in
|
||||
sort_tables_and_constraints(
|
||||
tables, filter_fn=_skip_fn, extra_dependencies=extra_dependencies)
|
||||
if t is not None
|
||||
]
|
||||
|
||||
|
||||
def sort_tables_and_constraints(
|
||||
tables, filter_fn=None, extra_dependencies=None):
|
||||
"""sort a collection of :class:`.Table` / :class:`.ForeignKeyConstraint`
|
||||
objects.
|
||||
|
||||
This is a dependency-ordered sort which will emit tuples of
|
||||
``(Table, [ForeignKeyConstraint, ...])`` such that each
|
||||
:class:`.Table` follows its dependent :class:`.Table` objects.
|
||||
Remaining :class:`.ForeignKeyConstraint` objects that are separate due to
|
||||
dependency rules not satisifed by the sort are emitted afterwards
|
||||
as ``(None, [ForeignKeyConstraint ...])``.
|
||||
|
||||
Tables are dependent on another based on the presence of
|
||||
:class:`.ForeignKeyConstraint` objects, explicit dependencies
|
||||
added by :meth:`.Table.add_is_dependent_on`, as well as dependencies
|
||||
stated here using the :paramref:`~.sort_tables_and_constraints.skip_fn`
|
||||
and/or :paramref:`~.sort_tables_and_constraints.extra_dependencies`
|
||||
parameters.
|
||||
|
||||
:param tables: a sequence of :class:`.Table` objects.
|
||||
|
||||
:param filter_fn: optional callable which will be passed a
|
||||
:class:`.ForeignKeyConstraint` object, and returns a value based on
|
||||
whether this constraint should definitely be included or excluded as
|
||||
an inline constraint, or neither. If it returns False, the constraint
|
||||
will definitely be included as a dependency that cannot be subject
|
||||
to ALTER; if True, it will **only** be included as an ALTER result at
|
||||
the end. Returning None means the constraint is included in the
|
||||
table-based result unless it is detected as part of a dependency cycle.
|
||||
|
||||
:param extra_dependencies: a sequence of 2-tuples of tables which will
|
||||
also be considered as dependent on each other.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. seealso::
|
||||
|
||||
:func:`.sort_tables`
|
||||
|
||||
|
||||
"""
|
||||
|
||||
fixed_dependencies = set()
|
||||
mutable_dependencies = set()
|
||||
|
||||
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))
|
||||
fixed_dependencies.update(extra_dependencies)
|
||||
|
||||
remaining_fkcs = set()
|
||||
for table in tables:
|
||||
traverse(table,
|
||||
{'schema_visitor': True},
|
||||
{'foreign_key': visit_foreign_key})
|
||||
for fkc in table.foreign_key_constraints:
|
||||
if fkc.use_alter is True:
|
||||
remaining_fkcs.add(fkc)
|
||||
continue
|
||||
|
||||
tuples.extend(
|
||||
[parent, table] for parent in table._extra_dependencies
|
||||
if filter_fn:
|
||||
filtered = filter_fn(fkc)
|
||||
|
||||
if filtered is True:
|
||||
remaining_fkcs.add(fkc)
|
||||
continue
|
||||
|
||||
dependent_on = fkc.referred_table
|
||||
if dependent_on is not table:
|
||||
mutable_dependencies.add((dependent_on, table))
|
||||
|
||||
fixed_dependencies.update(
|
||||
(parent, table) for parent in table._extra_dependencies
|
||||
)
|
||||
|
||||
return list(topological.sort(tuples, tables))
|
||||
try:
|
||||
candidate_sort = list(
|
||||
topological.sort(
|
||||
fixed_dependencies.union(mutable_dependencies), tables,
|
||||
deterministic_order=True
|
||||
)
|
||||
)
|
||||
except exc.CircularDependencyError as err:
|
||||
for edge in err.edges:
|
||||
if edge in mutable_dependencies:
|
||||
table = edge[1]
|
||||
can_remove = [
|
||||
fkc for fkc in table.foreign_key_constraints
|
||||
if filter_fn is None or filter_fn(fkc) is not False]
|
||||
remaining_fkcs.update(can_remove)
|
||||
for fkc in can_remove:
|
||||
dependent_on = fkc.referred_table
|
||||
if dependent_on is not table:
|
||||
mutable_dependencies.discard((dependent_on, table))
|
||||
candidate_sort = list(
|
||||
topological.sort(
|
||||
fixed_dependencies.union(mutable_dependencies), tables,
|
||||
deterministic_order=True
|
||||
)
|
||||
)
|
||||
|
||||
return [
|
||||
(table, table.foreign_key_constraints.difference(remaining_fkcs))
|
||||
for table in candidate_sort
|
||||
] + [(None, list(remaining_fkcs))]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/default_comparator.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -9,8 +9,8 @@
|
|||
"""
|
||||
|
||||
from .. import exc, util
|
||||
from . import operators
|
||||
from . import type_api
|
||||
from . import operators
|
||||
from .elements import BindParameter, True_, False_, BinaryExpression, \
|
||||
Null, _const_expr, _clause_element_as_expr, \
|
||||
ClauseList, ColumnElement, TextClause, UnaryExpression, \
|
||||
|
|
@ -18,294 +18,271 @@ from .elements import BindParameter, True_, False_, BinaryExpression, \
|
|||
from .selectable import SelectBase, Alias, Selectable, ScalarSelect
|
||||
|
||||
|
||||
class _DefaultColumnComparator(operators.ColumnOperators):
|
||||
"""Defines comparison and math operations.
|
||||
def _boolean_compare(expr, op, obj, negate=None, reverse=False,
|
||||
_python_is_types=(util.NoneType, bool),
|
||||
result_type = None,
|
||||
**kwargs):
|
||||
|
||||
See :class:`.ColumnOperators` and :class:`.Operators` for descriptions
|
||||
of all operations.
|
||||
if result_type is None:
|
||||
result_type = type_api.BOOLEANTYPE
|
||||
|
||||
"""
|
||||
if isinstance(obj, _python_is_types + (Null, True_, False_)):
|
||||
|
||||
@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:
|
||||
# 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,
|
||||
obj,
|
||||
_literal_as_text(obj),
|
||||
op,
|
||||
type_=type_api.BOOLEANTYPE,
|
||||
type_=result_type,
|
||||
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 _conjunction_operate(self, expr, op, other, **kw):
|
||||
if op is operators.and_:
|
||||
return and_(expr, other)
|
||||
elif op is operators.or_:
|
||||
return or_(expr, other)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
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)
|
||||
elif isinstance(seq_or_selectable, ClauseElement):
|
||||
raise exc.InvalidRequestError(
|
||||
'in_() accepts'
|
||||
' either a list of expressions '
|
||||
'or a selectable: %r' % seq_or_selectable)
|
||||
|
||||
# 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_() accepts'
|
||||
' either a list of expressions '
|
||||
'or a selectable: %r' % o)
|
||||
elif o is None:
|
||||
o = Null()
|
||||
# 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:
|
||||
o = expr._bind_param(op, o)
|
||||
args.append(o)
|
||||
if len(args) == 0:
|
||||
raise exc.ArgumentError(
|
||||
"Only '=', '!=', 'is_()', 'isnot()' operators can "
|
||||
"be used with None/True/False")
|
||||
else:
|
||||
obj = _check_literal(expr, op, obj)
|
||||
|
||||
# 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.
|
||||
if reverse:
|
||||
return BinaryExpression(obj,
|
||||
expr,
|
||||
op,
|
||||
type_=result_type,
|
||||
negate=negate, modifiers=kwargs)
|
||||
else:
|
||||
return BinaryExpression(expr,
|
||||
obj,
|
||||
op,
|
||||
type_=result_type,
|
||||
negate=negate, modifiers=kwargs)
|
||||
|
||||
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 _binary_operate(expr, op, obj, reverse=False, result_type=None,
|
||||
**kw):
|
||||
obj = _check_literal(expr, op, obj)
|
||||
|
||||
def _unsupported_impl(self, expr, op, *arg, **kw):
|
||||
raise NotImplementedError("Operator '%s' is not supported on "
|
||||
"this expression" % op.__name__)
|
||||
if reverse:
|
||||
left, right = obj, expr
|
||||
else:
|
||||
left, right = expr, obj
|
||||
|
||||
def _inv_impl(self, expr, op, **kw):
|
||||
"""See :meth:`.ColumnOperators.__inv__`."""
|
||||
if hasattr(expr, 'negation_clause'):
|
||||
return expr.negation_clause
|
||||
if result_type is None:
|
||||
op, result_type = left.comparator._adapt_expression(
|
||||
op, right.comparator)
|
||||
|
||||
return BinaryExpression(
|
||||
left, right, op, type_=result_type, modifiers=kw)
|
||||
|
||||
|
||||
def _conjunction_operate(expr, op, other, **kw):
|
||||
if op is operators.and_:
|
||||
return and_(expr, other)
|
||||
elif op is operators.or_:
|
||||
return or_(expr, other)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _scalar(expr, op, fn, **kw):
|
||||
return fn(expr)
|
||||
|
||||
|
||||
def _in_impl(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 _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 _boolean_compare(
|
||||
expr, op, seq_or_selectable.as_scalar(),
|
||||
negate=negate_op, **kw)
|
||||
elif isinstance(seq_or_selectable, (Selectable, TextClause)):
|
||||
return _boolean_compare(expr, op, seq_or_selectable,
|
||||
negate=negate_op, **kw)
|
||||
elif isinstance(seq_or_selectable, ClauseElement):
|
||||
raise exc.InvalidRequestError(
|
||||
'in_() accepts'
|
||||
' either a list of expressions '
|
||||
'or a selectable: %r' % seq_or_selectable)
|
||||
|
||||
# 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_() accepts'
|
||||
' either a list of expressions '
|
||||
'or a selectable: %r' % o)
|
||||
elif o is None:
|
||||
o = Null()
|
||||
else:
|
||||
return expr._negate()
|
||||
o = expr._bind_param(op, o)
|
||||
args.append(o)
|
||||
if len(args) == 0:
|
||||
|
||||
def _neg_impl(self, expr, op, **kw):
|
||||
"""See :meth:`.ColumnOperators.__neg__`."""
|
||||
return UnaryExpression(expr, operator=operators.neg)
|
||||
# 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.
|
||||
|
||||
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),
|
||||
**kw)
|
||||
|
||||
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),
|
||||
op,
|
||||
negate=operators.notbetween_op
|
||||
if op is operators.between_op
|
||||
else operators.between_op,
|
||||
modifiers=kw)
|
||||
|
||||
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 = {
|
||||
"and_": (_conjunction_operate,),
|
||||
"or_": (_conjunction_operate,),
|
||||
"inv": (_inv_impl,),
|
||||
"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, ),
|
||||
"notbetween_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)
|
||||
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 other
|
||||
return expr == expr
|
||||
|
||||
return _boolean_compare(expr, op,
|
||||
ClauseList(*args).self_group(against=op),
|
||||
negate=negate_op)
|
||||
|
||||
|
||||
def _unsupported_impl(expr, op, *arg, **kw):
|
||||
raise NotImplementedError("Operator '%s' is not supported on "
|
||||
"this expression" % op.__name__)
|
||||
|
||||
|
||||
def _inv_impl(expr, op, **kw):
|
||||
"""See :meth:`.ColumnOperators.__inv__`."""
|
||||
if hasattr(expr, 'negation_clause'):
|
||||
return expr.negation_clause
|
||||
else:
|
||||
return expr._negate()
|
||||
|
||||
|
||||
def _neg_impl(expr, op, **kw):
|
||||
"""See :meth:`.ColumnOperators.__neg__`."""
|
||||
return UnaryExpression(expr, operator=operators.neg)
|
||||
|
||||
|
||||
def _match_impl(expr, op, other, **kw):
|
||||
"""See :meth:`.ColumnOperators.match`."""
|
||||
|
||||
return _boolean_compare(
|
||||
expr, operators.match_op,
|
||||
_check_literal(
|
||||
expr, operators.match_op, other),
|
||||
result_type=type_api.MATCHTYPE,
|
||||
negate=operators.notmatch_op
|
||||
if op is operators.match_op else operators.match_op,
|
||||
**kw
|
||||
)
|
||||
|
||||
|
||||
def _distinct_impl(expr, op, **kw):
|
||||
"""See :meth:`.ColumnOperators.distinct`."""
|
||||
return UnaryExpression(expr, operator=operators.distinct_op,
|
||||
type_=expr.type)
|
||||
|
||||
|
||||
def _between_impl(expr, op, cleft, cright, **kw):
|
||||
"""See :meth:`.ColumnOperators.between`."""
|
||||
return BinaryExpression(
|
||||
expr,
|
||||
ClauseList(
|
||||
_check_literal(expr, operators.and_, cleft),
|
||||
_check_literal(expr, operators.and_, cright),
|
||||
operator=operators.and_,
|
||||
group=False, group_contents=False),
|
||||
op,
|
||||
negate=operators.notbetween_op
|
||||
if op is operators.between_op
|
||||
else operators.between_op,
|
||||
modifiers=kw)
|
||||
|
||||
|
||||
def _collate_impl(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
|
||||
operator_lookup = {
|
||||
"and_": (_conjunction_operate,),
|
||||
"or_": (_conjunction_operate,),
|
||||
"inv": (_inv_impl,),
|
||||
"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,),
|
||||
"notmatch_op": (_match_impl,),
|
||||
"distinct_op": (_distinct_impl,),
|
||||
"between_op": (_between_impl, ),
|
||||
"notbetween_op": (_between_impl, ),
|
||||
"neg": (_neg_impl,),
|
||||
"getitem": (_unsupported_impl,),
|
||||
"lshift": (_unsupported_impl,),
|
||||
"rshift": (_unsupported_impl,),
|
||||
"contains": (_unsupported_impl,),
|
||||
}
|
||||
|
||||
|
||||
def _check_literal(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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/dml.py
|
||||
# Copyright (C) 2009-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2009-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -10,7 +10,8 @@ Provide :class:`.Insert`, :class:`.Update` and :class:`.Delete`.
|
|||
"""
|
||||
|
||||
from .base import Executable, _generative, _from_objects, DialectKWArgs
|
||||
from .elements import ClauseElement, _literal_as_text, Null, and_, _clone
|
||||
from .elements import ClauseElement, _literal_as_text, Null, and_, _clone, \
|
||||
_column_as_key
|
||||
from .selectable import _interpret_as_from, _interpret_as_select, HasPrefixes
|
||||
from .. import util
|
||||
from .. import exc
|
||||
|
|
@ -26,6 +27,7 @@ class UpdateBase(DialectKWArgs, HasPrefixes, Executable, ClauseElement):
|
|||
_execution_options = \
|
||||
Executable._execution_options.union({'autocommit': True})
|
||||
_hints = util.immutabledict()
|
||||
_parameter_ordering = None
|
||||
_prefixes = ()
|
||||
|
||||
def _process_colparams(self, parameters):
|
||||
|
|
@ -38,6 +40,16 @@ class UpdateBase(DialectKWArgs, HasPrefixes, Executable, ClauseElement):
|
|||
else:
|
||||
return p
|
||||
|
||||
if self._preserve_parameter_order and parameters is not None:
|
||||
if not isinstance(parameters, list) or \
|
||||
(parameters and not isinstance(parameters[0], tuple)):
|
||||
raise ValueError(
|
||||
"When preserve_parameter_order is True, "
|
||||
"values() only accepts a list of 2-tuples")
|
||||
self._parameter_ordering = [key for key, value in parameters]
|
||||
|
||||
return dict(parameters), False
|
||||
|
||||
if (isinstance(parameters, (list, tuple)) and parameters and
|
||||
isinstance(parameters[0], (list, tuple, dict))):
|
||||
|
||||
|
|
@ -177,6 +189,7 @@ class ValuesBase(UpdateBase):
|
|||
|
||||
_supports_multi_parameters = False
|
||||
_has_multi_parameters = False
|
||||
_preserve_parameter_order = False
|
||||
select = None
|
||||
|
||||
def __init__(self, table, values, prefixes):
|
||||
|
|
@ -213,23 +226,32 @@ class ValuesBase(UpdateBase):
|
|||
|
||||
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::
|
||||
:param \*args: As an alternative to passing key/value parameters,
|
||||
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 forms that are accepted vary
|
||||
based on whether this is an :class:`.Insert` or an :class:`.Update`
|
||||
construct.
|
||||
|
||||
For either an :class:`.Insert` or :class:`.Update` construct, a
|
||||
single dictionary can be passed, which works the same as that of
|
||||
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.update().values({"name": "some new name"})
|
||||
|
||||
Also for either form but more typically for the :class:`.Insert`
|
||||
construct, a tuple that contains an entry for every column in the
|
||||
table is also accepted::
|
||||
|
||||
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::
|
||||
The :class:`.Insert` construct also supports being passed a list
|
||||
of dictionaries or full-table-tuples, which on the server will
|
||||
render the less common SQL syntax of "multiple values" - this
|
||||
syntax is supported on backends such as SQLite, Postgresql, MySQL,
|
||||
but not necessarily others::
|
||||
|
||||
users.insert().values([
|
||||
{"name": "some name"},
|
||||
|
|
@ -237,38 +259,61 @@ class ValuesBase(UpdateBase):
|
|||
{"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.
|
||||
The above form would render a multiple VALUES statement similar to::
|
||||
|
||||
.. note::
|
||||
INSERT INTO users (name) VALUES
|
||||
(:name_1),
|
||||
(:name_2),
|
||||
(:name_3)
|
||||
|
||||
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::
|
||||
It is essential to note that **passing multiple values is
|
||||
NOT the same as using traditional executemany() form**. The above
|
||||
syntax is a **special** syntax not typically used. To emit an
|
||||
INSERT statement against multiple rows, the normal method is
|
||||
to pass a multiple values list to the :meth:`.Connection.execute`
|
||||
method, which is supported by all database backends and is generally
|
||||
more efficient for a very large number of parameters.
|
||||
|
||||
INSERT INTO table (col1, col2, col3) VALUES
|
||||
(col1_0, col2_0, col3_0),
|
||||
(col1_1, col2_1, col3_1),
|
||||
...
|
||||
.. seealso::
|
||||
|
||||
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.
|
||||
:ref:`execute_multiple` - an introduction to
|
||||
the traditional Core method of multiple parameter set
|
||||
invocation for INSERTs and other statements.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
Support for multiple-VALUES INSERT statements.
|
||||
.. versionchanged:: 1.0.0 an INSERT that uses a multiple-VALUES
|
||||
clause, even a list of length one,
|
||||
implies that the :paramref:`.Insert.inline` flag is set to
|
||||
True, indicating that the statement will not attempt to fetch
|
||||
the "last inserted primary key" or other defaults. The
|
||||
statement deals with an arbitrary number of rows, so the
|
||||
:attr:`.ResultProxy.inserted_primary_key` accessor does not
|
||||
apply.
|
||||
|
||||
.. versionchanged:: 1.0.0 A multiple-VALUES INSERT now supports
|
||||
columns with Python side default values and callables in the
|
||||
same way as that of an "executemany" style of invocation; the
|
||||
callable is invoked for each row. See :ref:`bug_3288`
|
||||
for other details.
|
||||
|
||||
The :class:`.Update` construct supports a special form which is a
|
||||
list of 2-tuples, which when provided must be passed in conjunction
|
||||
with the
|
||||
:paramref:`~sqlalchemy.sql.expression.update.preserve_parameter_order`
|
||||
parameter.
|
||||
This form causes the UPDATE statement to render the SET clauses
|
||||
using the order of parameters given to :meth:`.Update.values`, rather
|
||||
than the ordering of columns given in the :class:`.Table`.
|
||||
|
||||
.. versionadded:: 1.0.10 - added support for parameter-ordered
|
||||
UPDATE statements via the
|
||||
:paramref:`~sqlalchemy.sql.expression.update.preserve_parameter_order`
|
||||
flag.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`updates_order_parameters` - full example of the
|
||||
:paramref:`~sqlalchemy.sql.expression.update.preserve_parameter_order`
|
||||
flag
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
|
@ -358,7 +403,7 @@ class ValuesBase(UpdateBase):
|
|||
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
|
||||
by the statement is also consumed implicitly 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.
|
||||
|
|
@ -380,7 +425,7 @@ class ValuesBase(UpdateBase):
|
|||
:func:`.mapper`.
|
||||
|
||||
:param cols: optional list of column key names or :class:`.Column`
|
||||
objects. If omitted, all column expressions evaulated on the server
|
||||
objects. If omitted, all column expressions evaluated on the server
|
||||
are added to the returning list.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
|
@ -434,8 +479,13 @@ class Insert(ValuesBase):
|
|||
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.
|
||||
:param inline: if True, no attempt will be made to retrieve the
|
||||
SQL-generated default values to be provided within the statement;
|
||||
in particular,
|
||||
this allows SQL expressions to be rendered 'inline' within the
|
||||
statement without the need to pre-execute them beforehand; for
|
||||
backends that support "returning", this turns off the "implicit
|
||||
returning" feature for the statement.
|
||||
|
||||
If both `values` and compile-time bind parameters are present, the
|
||||
compile-time bind parameters override the information specified
|
||||
|
|
@ -463,6 +513,7 @@ class Insert(ValuesBase):
|
|||
ValuesBase.__init__(self, table, values, prefixes)
|
||||
self._bind = bind
|
||||
self.select = self.select_names = None
|
||||
self.include_insert_from_select_defaults = False
|
||||
self.inline = inline
|
||||
self._returning = returning
|
||||
self._validate_dialect_kwargs(dialect_kw)
|
||||
|
|
@ -475,7 +526,7 @@ class Insert(ValuesBase):
|
|||
return ()
|
||||
|
||||
@_generative
|
||||
def from_select(self, names, select):
|
||||
def from_select(self, names, select, include_defaults=True):
|
||||
"""Return a new :class:`.Insert` construct which represents
|
||||
an ``INSERT...FROM SELECT`` statement.
|
||||
|
||||
|
|
@ -494,25 +545,28 @@ class Insert(ValuesBase):
|
|||
is not checked before passing along to the database, the database
|
||||
would normally raise an exception if these column lists don't
|
||||
correspond.
|
||||
:param include_defaults: if True, non-server default values and
|
||||
SQL expressions as specified on :class:`.Column` objects
|
||||
(as documented in :ref:`metadata_defaults_toplevel`) not
|
||||
otherwise specified in the list of names will be rendered
|
||||
into the INSERT and SELECT statements, so that these values are also
|
||||
included in the data to be inserted.
|
||||
|
||||
.. note::
|
||||
.. note:: A Python-side default that uses a Python callable function
|
||||
will only be invoked **once** for the whole statement, and **not
|
||||
per row**.
|
||||
|
||||
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::
|
||||
.. versionadded:: 1.0.0 - :meth:`.Insert.from_select` now renders
|
||||
Python-side and SQL expression column defaults into the
|
||||
SELECT statement for columns otherwise not included in the
|
||||
list of column names.
|
||||
|
||||
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`.
|
||||
.. versionchanged:: 1.0.0 an INSERT that uses FROM SELECT
|
||||
implies that the :paramref:`.insert.inline` flag is set to
|
||||
True, indicating that the statement will not attempt to fetch
|
||||
the "last inserted primary key" or other defaults. The statement
|
||||
deals with an arbitrary number of rows, so the
|
||||
:attr:`.ResultProxy.inserted_primary_key` accessor does not apply.
|
||||
|
||||
.. versionadded:: 0.8.3
|
||||
|
||||
|
|
@ -522,9 +576,12 @@ class Insert(ValuesBase):
|
|||
"This construct already inserts value expressions")
|
||||
|
||||
self.parameters, self._has_multi_parameters = \
|
||||
self._process_colparams(dict((n, Null()) for n in names))
|
||||
self._process_colparams(
|
||||
dict((_column_as_key(n), Null()) for n in names))
|
||||
|
||||
self.select_names = names
|
||||
self.inline = True
|
||||
self.include_insert_from_select_defaults = include_defaults
|
||||
self.select = _interpret_as_select(select)
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
|
|
@ -552,6 +609,7 @@ class Update(ValuesBase):
|
|||
prefixes=None,
|
||||
returning=None,
|
||||
return_defaults=False,
|
||||
preserve_parameter_order=False,
|
||||
**dialect_kw):
|
||||
"""Construct an :class:`.Update` object.
|
||||
|
||||
|
|
@ -614,6 +672,19 @@ class Update(ValuesBase):
|
|||
be available in the dictionary returned from
|
||||
:meth:`.ResultProxy.last_updated_params`.
|
||||
|
||||
:param preserve_parameter_order: if True, the update statement is
|
||||
expected to receive parameters **only** via the :meth:`.Update.values`
|
||||
method, and they must be passed as a Python ``list`` of 2-tuples.
|
||||
The rendered UPDATE statement will emit the SET clause for each
|
||||
referenced column maintaining this order.
|
||||
|
||||
.. versionadded:: 1.0.10
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`updates_order_parameters` - full example of the
|
||||
:paramref:`~sqlalchemy.sql.expression.update.preserve_parameter_order` flag
|
||||
|
||||
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.
|
||||
|
|
@ -655,6 +726,7 @@ class Update(ValuesBase):
|
|||
|
||||
|
||||
"""
|
||||
self._preserve_parameter_order = preserve_parameter_order
|
||||
ValuesBase.__init__(self, table, values, prefixes)
|
||||
self._bind = bind
|
||||
self._returning = returning
|
||||
|
|
@ -728,10 +800,10 @@ class Delete(UpdateBase):
|
|||
:meth:`~.TableClause.delete` method on
|
||||
:class:`~.schema.Table`.
|
||||
|
||||
:param table: The table to be updated.
|
||||
:param table: The table to delete rows from.
|
||||
|
||||
:param whereclause: A :class:`.ClauseElement` describing the ``WHERE``
|
||||
condition of the ``UPDATE`` statement. Note that the
|
||||
condition of the ``DELETE`` statement. Note that the
|
||||
:meth:`~Delete.where()` generative method may be used instead.
|
||||
|
||||
.. seealso::
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/elements.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -19,7 +19,8 @@ from .visitors import Visitable, cloned_traverse, traverse
|
|||
from .annotation import Annotated
|
||||
import itertools
|
||||
from .base import Executable, PARSE_AUTOCOMMIT, Immutable, NO_ARG
|
||||
from .base import _generative, Generative
|
||||
from .base import _generative
|
||||
import numbers
|
||||
|
||||
import re
|
||||
import operator
|
||||
|
|
@ -227,6 +228,7 @@ class ClauseElement(Visitable):
|
|||
is_selectable = False
|
||||
is_clause_element = True
|
||||
|
||||
description = None
|
||||
_order_by_label_element = None
|
||||
_is_from_container = False
|
||||
|
||||
|
|
@ -539,7 +541,7 @@ class ClauseElement(Visitable):
|
|||
__nonzero__ = __bool__
|
||||
|
||||
def __repr__(self):
|
||||
friendly = getattr(self, 'description', None)
|
||||
friendly = self.description
|
||||
if friendly is None:
|
||||
return object.__repr__(self)
|
||||
else:
|
||||
|
|
@ -624,8 +626,73 @@ class ColumnElement(operators.ColumnOperators, ClauseElement):
|
|||
__visit_name__ = 'column'
|
||||
primary_key = False
|
||||
foreign_keys = []
|
||||
|
||||
_label = None
|
||||
_key_label = key = None
|
||||
"""The named label that can be used to target
|
||||
this column in a result set.
|
||||
|
||||
This label is almost always the label used when
|
||||
rendering <expr> AS <label> in a SELECT statement. It also
|
||||
refers to a name that this column expression can be located from
|
||||
in a result set.
|
||||
|
||||
For a regular Column bound to a Table, this is typically the label
|
||||
<tablename>_<columnname>. For other constructs, different rules
|
||||
may apply, such as anonymized labels and others.
|
||||
|
||||
"""
|
||||
|
||||
key = None
|
||||
"""the 'key' that in some circumstances refers to this object in a
|
||||
Python namespace.
|
||||
|
||||
This typically refers to the "key" of the column as present in the
|
||||
``.c`` collection of a selectable, e.g. sometable.c["somekey"] would
|
||||
return a Column with a .key of "somekey".
|
||||
|
||||
"""
|
||||
|
||||
_key_label = None
|
||||
"""A label-based version of 'key' that in some circumstances refers
|
||||
to this object in a Python namespace.
|
||||
|
||||
|
||||
_key_label comes into play when a select() statement is constructed with
|
||||
apply_labels(); in this case, all Column objects in the ``.c`` collection
|
||||
are rendered as <tablename>_<columnname> in SQL; this is essentially the
|
||||
value of ._label. But to locate those columns in the ``.c`` collection,
|
||||
the name is along the lines of <tablename>_<key>; that's the typical
|
||||
value of .key_label.
|
||||
|
||||
"""
|
||||
|
||||
_render_label_in_columns_clause = True
|
||||
"""A flag used by select._columns_plus_names that helps to determine
|
||||
we are actually going to render in terms of "SELECT <col> AS <label>".
|
||||
This flag can be returned as False for some Column objects that want
|
||||
to be rendered as simple "SELECT <col>"; typically columns that don't have
|
||||
any parent table and are named the same as what the label would be
|
||||
in any case.
|
||||
|
||||
"""
|
||||
|
||||
_resolve_label = None
|
||||
"""The name that should be used to identify this ColumnElement in a
|
||||
select() object when "label resolution" logic is used; this refers
|
||||
to using a string name in an expression like order_by() or group_by()
|
||||
that wishes to target a labeled expression in the columns clause.
|
||||
|
||||
The name is distinct from that of .name or ._label to account for the case
|
||||
where anonymizing logic may be used to change the name that's actually
|
||||
rendered at compile time; this attribute should hold onto the original
|
||||
name that was user-assigned when producing a .label() construct.
|
||||
|
||||
"""
|
||||
|
||||
_allow_label_resolve = True
|
||||
"""A flag that can be flipped to prevent a column from being resolvable
|
||||
by string label name."""
|
||||
|
||||
_alt_names = ()
|
||||
|
||||
def self_group(self, against=None):
|
||||
|
|
@ -648,7 +715,14 @@ class ColumnElement(operators.ColumnOperators, ClauseElement):
|
|||
|
||||
@util.memoized_property
|
||||
def comparator(self):
|
||||
return self.type.comparator_factory(self)
|
||||
try:
|
||||
comparator_factory = self.type.comparator_factory
|
||||
except AttributeError:
|
||||
raise TypeError(
|
||||
"Object %r associated with '.type' attribute "
|
||||
"is not a TypeEngine class or object" % self.type)
|
||||
else:
|
||||
return comparator_factory(self)
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
|
|
@ -770,6 +844,16 @@ class ColumnElement(operators.ColumnOperators, ClauseElement):
|
|||
else:
|
||||
return False
|
||||
|
||||
def cast(self, type_):
|
||||
"""Produce a type cast, i.e. ``CAST(<expression> AS <type>)``.
|
||||
|
||||
This is a shortcut to the :func:`~.expression.cast` function.
|
||||
|
||||
.. versionadded:: 1.0.7
|
||||
|
||||
"""
|
||||
return Cast(self, type_)
|
||||
|
||||
def label(self, name):
|
||||
"""Produce a column label, i.e. ``<columnname> AS <name>``.
|
||||
|
||||
|
|
@ -794,6 +878,9 @@ class ColumnElement(operators.ColumnOperators, ClauseElement):
|
|||
expressions and function calls.
|
||||
|
||||
"""
|
||||
while self._is_clone_of is not None:
|
||||
self = self._is_clone_of
|
||||
|
||||
return _anonymous_label(
|
||||
'%%(%d %s)s' % (id(self), getattr(self, 'name', 'anon'))
|
||||
)
|
||||
|
|
@ -1022,7 +1109,7 @@ class BindParameter(ColumnElement):
|
|||
"""
|
||||
if isinstance(key, ColumnClause):
|
||||
type_ = key.type
|
||||
key = key.name
|
||||
key = key.key
|
||||
if required is NO_ARG:
|
||||
required = (value is NO_ARG and callable_ is None)
|
||||
if value is NO_ARG:
|
||||
|
|
@ -1180,6 +1267,12 @@ class TextClause(Executable, ClauseElement):
|
|||
|
||||
_hide_froms = []
|
||||
|
||||
# help in those cases where text() is
|
||||
# interpreted in a column expression situation
|
||||
key = _label = _resolve_label = None
|
||||
|
||||
_allow_label_resolve = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text,
|
||||
|
|
@ -1203,7 +1296,7 @@ class TextClause(Executable, ClauseElement):
|
|||
|
||||
E.g.::
|
||||
|
||||
fom sqlalchemy import text
|
||||
from sqlalchemy import text
|
||||
|
||||
t = text("SELECT * FROM users")
|
||||
result = connection.execute(t)
|
||||
|
|
@ -1243,17 +1336,16 @@ class TextClause(Executable, ClauseElement):
|
|||
for id, name in connection.execute(t):
|
||||
print(id, name)
|
||||
|
||||
The :func:`.text` construct is used internally in cases when
|
||||
a literal string is specified for part of a larger query, such as
|
||||
when a string is specified to the :meth:`.Select.where` method of
|
||||
:class:`.Select`. In those cases, the same
|
||||
bind parameter syntax is applied::
|
||||
The :func:`.text` construct is used in cases when
|
||||
a literal string SQL fragment is specified as part of a larger query,
|
||||
such as for the WHERE clause of a SELECT statement::
|
||||
|
||||
s = select([users.c.id, users.c.name]).where("id=:user_id")
|
||||
s = select([users.c.id, users.c.name]).where(text("id=:user_id"))
|
||||
result = connection.execute(s, user_id=12)
|
||||
|
||||
Using :func:`.text` explicitly usually implies the construction
|
||||
of a full, standalone statement. As such, SQLAlchemy refers
|
||||
:func:`.text` is also used for the construction
|
||||
of a full, standalone statement using plain text.
|
||||
As such, SQLAlchemy refers
|
||||
to it as an :class:`.Executable` object, and it supports
|
||||
the :meth:`Executable.execution_options` method. For example,
|
||||
a :func:`.text` construct that should be subject to "autocommit"
|
||||
|
|
@ -1544,10 +1636,10 @@ class Null(ColumnElement):
|
|||
return type_api.NULLTYPE
|
||||
|
||||
@classmethod
|
||||
def _singleton(cls):
|
||||
def _instance(cls):
|
||||
"""Return a constant :class:`.Null` construct."""
|
||||
|
||||
return NULL
|
||||
return Null()
|
||||
|
||||
def compare(self, other):
|
||||
return isinstance(other, Null)
|
||||
|
|
@ -1568,11 +1660,11 @@ class False_(ColumnElement):
|
|||
return type_api.BOOLEANTYPE
|
||||
|
||||
def _negate(self):
|
||||
return TRUE
|
||||
return True_()
|
||||
|
||||
@classmethod
|
||||
def _singleton(cls):
|
||||
"""Return a constant :class:`.False_` construct.
|
||||
def _instance(cls):
|
||||
"""Return a :class:`.False_` construct.
|
||||
|
||||
E.g.::
|
||||
|
||||
|
|
@ -1606,7 +1698,7 @@ class False_(ColumnElement):
|
|||
|
||||
"""
|
||||
|
||||
return FALSE
|
||||
return False_()
|
||||
|
||||
def compare(self, other):
|
||||
return isinstance(other, False_)
|
||||
|
|
@ -1627,17 +1719,17 @@ class True_(ColumnElement):
|
|||
return type_api.BOOLEANTYPE
|
||||
|
||||
def _negate(self):
|
||||
return FALSE
|
||||
return False_()
|
||||
|
||||
@classmethod
|
||||
def _ifnone(cls, other):
|
||||
if other is None:
|
||||
return cls._singleton()
|
||||
return cls._instance()
|
||||
else:
|
||||
return other
|
||||
|
||||
@classmethod
|
||||
def _singleton(cls):
|
||||
def _instance(cls):
|
||||
"""Return a constant :class:`.True_` construct.
|
||||
|
||||
E.g.::
|
||||
|
|
@ -1672,15 +1764,11 @@ class True_(ColumnElement):
|
|||
|
||||
"""
|
||||
|
||||
return TRUE
|
||||
return True_()
|
||||
|
||||
def compare(self, other):
|
||||
return isinstance(other, True_)
|
||||
|
||||
NULL = Null()
|
||||
FALSE = False_()
|
||||
TRUE = True_()
|
||||
|
||||
|
||||
class ClauseList(ClauseElement):
|
||||
"""Describe a list of clauses, separated by an operator.
|
||||
|
|
@ -1694,13 +1782,16 @@ class ClauseList(ClauseElement):
|
|||
self.operator = kwargs.pop('operator', operators.comma_op)
|
||||
self.group = kwargs.pop('group', True)
|
||||
self.group_contents = kwargs.pop('group_contents', True)
|
||||
text_converter = kwargs.pop(
|
||||
'_literal_as_text',
|
||||
_expression_literal_as_text)
|
||||
if self.group_contents:
|
||||
self.clauses = [
|
||||
_literal_as_text(clause).self_group(against=self.operator)
|
||||
text_converter(clause).self_group(against=self.operator)
|
||||
for clause in clauses]
|
||||
else:
|
||||
self.clauses = [
|
||||
_literal_as_text(clause)
|
||||
text_converter(clause)
|
||||
for clause in clauses]
|
||||
|
||||
def __iter__(self):
|
||||
|
|
@ -1765,9 +1856,12 @@ class BooleanClauseList(ClauseList, ColumnElement):
|
|||
def _construct(cls, operator, continue_on, skip_on, *clauses, **kw):
|
||||
convert_clauses = []
|
||||
|
||||
clauses = util.coerce_generator_arg(clauses)
|
||||
clauses = [
|
||||
_expression_literal_as_text(clause)
|
||||
for clause in
|
||||
util.coerce_generator_arg(clauses)
|
||||
]
|
||||
for clause in clauses:
|
||||
clause = _literal_as_text(clause)
|
||||
|
||||
if isinstance(clause, continue_on):
|
||||
continue
|
||||
|
|
@ -2071,7 +2165,7 @@ class Case(ColumnElement):
|
|||
result of the ``CASE`` construct if all expressions within
|
||||
:paramref:`.case.whens` evaluate to false. When omitted, most
|
||||
databases will produce a result of NULL if none of the "when"
|
||||
expressions evaulate to true.
|
||||
expressions evaluate to true.
|
||||
|
||||
|
||||
"""
|
||||
|
|
@ -2133,14 +2227,15 @@ class Case(ColumnElement):
|
|||
|
||||
|
||||
def literal_column(text, type_=None):
|
||||
"""Return a textual column expression, as would be in the columns
|
||||
clause of a ``SELECT`` statement.
|
||||
"""Produce a :class:`.ColumnClause` object that has the
|
||||
:paramref:`.column.is_literal` flag set to True.
|
||||
|
||||
The object returned supports further expressions in the same way as any
|
||||
other column object, including comparison, math and string operations.
|
||||
The type\_ parameter is important to determine proper expression behavior
|
||||
(such as, '+' means string concatenation or numerical addition based on
|
||||
the type).
|
||||
:func:`.literal_column` is similar to :func:`.column`, except that
|
||||
it is more often used as a "standalone" column expression that renders
|
||||
exactly as stated; while :func:`.column` stores a string name that
|
||||
will be assumed to be part of a table and may be quoted as such,
|
||||
:func:`.literal_column` can be that, or any other arbitrary column-oriented
|
||||
expression.
|
||||
|
||||
:param text: the text of the expression; can be any SQL expression.
|
||||
Quoting rules will not be applied. To specify a column-name expression
|
||||
|
|
@ -2152,6 +2247,14 @@ def literal_column(text, type_=None):
|
|||
provide result-set translation and additional expression semantics for
|
||||
this column. If left as None the type will be NullType.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:func:`.column`
|
||||
|
||||
:func:`.text`
|
||||
|
||||
:ref:`sqlexpression_literal_column`
|
||||
|
||||
"""
|
||||
return ColumnClause(text, type_=type_, is_literal=True)
|
||||
|
||||
|
|
@ -2271,6 +2374,42 @@ class Extract(ColumnElement):
|
|||
return self.expr._from_objects
|
||||
|
||||
|
||||
class _label_reference(ColumnElement):
|
||||
"""Wrap a column expression as it appears in a 'reference' context.
|
||||
|
||||
This expression is any that inclues an _order_by_label_element,
|
||||
which is a Label, or a DESC / ASC construct wrapping a Label.
|
||||
|
||||
The production of _label_reference() should occur when an expression
|
||||
is added to this context; this includes the ORDER BY or GROUP BY of a
|
||||
SELECT statement, as well as a few other places, such as the ORDER BY
|
||||
within an OVER clause.
|
||||
|
||||
"""
|
||||
__visit_name__ = 'label_reference'
|
||||
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
self.element = clone(self.element, **kw)
|
||||
|
||||
@property
|
||||
def _from_objects(self):
|
||||
return ()
|
||||
|
||||
|
||||
class _textual_label_reference(ColumnElement):
|
||||
__visit_name__ = 'textual_label_reference'
|
||||
|
||||
def __init__(self, element):
|
||||
self.element = element
|
||||
|
||||
@util.memoized_property
|
||||
def _text_clause(self):
|
||||
return TextClause._create_text(self.element)
|
||||
|
||||
|
||||
class UnaryExpression(ColumnElement):
|
||||
"""Define a 'unary' expression.
|
||||
|
||||
|
|
@ -2287,13 +2426,14 @@ class UnaryExpression(ColumnElement):
|
|||
__visit_name__ = 'unary'
|
||||
|
||||
def __init__(self, element, operator=None, modifier=None,
|
||||
type_=None, negate=None):
|
||||
type_=None, negate=None, wraps_column_expression=False):
|
||||
self.operator = operator
|
||||
self.modifier = modifier
|
||||
self.element = element.self_group(
|
||||
against=self.operator or self.modifier)
|
||||
self.type = type_api.to_instance(type_)
|
||||
self.negate = negate
|
||||
self.wraps_column_expression = wraps_column_expression
|
||||
|
||||
@classmethod
|
||||
def _create_nullsfirst(cls, column):
|
||||
|
|
@ -2334,7 +2474,9 @@ class UnaryExpression(ColumnElement):
|
|||
|
||||
"""
|
||||
return UnaryExpression(
|
||||
_literal_as_text(column), modifier=operators.nullsfirst_op)
|
||||
_literal_as_label_reference(column),
|
||||
modifier=operators.nullsfirst_op,
|
||||
wraps_column_expression=False)
|
||||
|
||||
@classmethod
|
||||
def _create_nullslast(cls, column):
|
||||
|
|
@ -2374,7 +2516,9 @@ class UnaryExpression(ColumnElement):
|
|||
|
||||
"""
|
||||
return UnaryExpression(
|
||||
_literal_as_text(column), modifier=operators.nullslast_op)
|
||||
_literal_as_label_reference(column),
|
||||
modifier=operators.nullslast_op,
|
||||
wraps_column_expression=False)
|
||||
|
||||
@classmethod
|
||||
def _create_desc(cls, column):
|
||||
|
|
@ -2412,7 +2556,9 @@ class UnaryExpression(ColumnElement):
|
|||
|
||||
"""
|
||||
return UnaryExpression(
|
||||
_literal_as_text(column), modifier=operators.desc_op)
|
||||
_literal_as_label_reference(column),
|
||||
modifier=operators.desc_op,
|
||||
wraps_column_expression=False)
|
||||
|
||||
@classmethod
|
||||
def _create_asc(cls, column):
|
||||
|
|
@ -2449,7 +2595,9 @@ class UnaryExpression(ColumnElement):
|
|||
|
||||
"""
|
||||
return UnaryExpression(
|
||||
_literal_as_text(column), modifier=operators.asc_op)
|
||||
_literal_as_label_reference(column),
|
||||
modifier=operators.asc_op,
|
||||
wraps_column_expression=False)
|
||||
|
||||
@classmethod
|
||||
def _create_distinct(cls, expr):
|
||||
|
|
@ -2489,9 +2637,10 @@ class UnaryExpression(ColumnElement):
|
|||
"""
|
||||
expr = _literal_as_binds(expr)
|
||||
return UnaryExpression(
|
||||
expr, operator=operators.distinct_op, type_=expr.type)
|
||||
expr, operator=operators.distinct_op,
|
||||
type_=expr.type, wraps_column_expression=False)
|
||||
|
||||
@util.memoized_property
|
||||
@property
|
||||
def _order_by_label_element(self):
|
||||
if self.modifier in (operators.desc_op, operators.asc_op):
|
||||
return self.element._order_by_label_element
|
||||
|
|
@ -2526,7 +2675,8 @@ class UnaryExpression(ColumnElement):
|
|||
operator=self.negate,
|
||||
negate=self.operator,
|
||||
modifier=self.modifier,
|
||||
type_=self.type)
|
||||
type_=self.type,
|
||||
wraps_column_expression=self.wraps_column_expression)
|
||||
else:
|
||||
return ClauseElement._negate(self)
|
||||
|
||||
|
|
@ -2545,6 +2695,7 @@ class AsBoolean(UnaryExpression):
|
|||
self.operator = operator
|
||||
self.negate = negate
|
||||
self.modifier = None
|
||||
self.wraps_column_expression = True
|
||||
|
||||
def self_group(self, against=None):
|
||||
return self
|
||||
|
|
@ -2641,7 +2792,7 @@ class BinaryExpression(ColumnElement):
|
|||
self.right,
|
||||
self.negate,
|
||||
negate=self.operator,
|
||||
type_=type_api.BOOLEANTYPE,
|
||||
type_=self.type,
|
||||
modifiers=self.modifiers)
|
||||
else:
|
||||
return super(BinaryExpression, self)._negate()
|
||||
|
|
@ -2659,6 +2810,10 @@ class Grouping(ColumnElement):
|
|||
def self_group(self, against=None):
|
||||
return self
|
||||
|
||||
@property
|
||||
def _key_label(self):
|
||||
return self._label
|
||||
|
||||
@property
|
||||
def _label(self):
|
||||
return getattr(self.element, '_label', None) or self.anon_label
|
||||
|
|
@ -2733,9 +2888,13 @@ class Over(ColumnElement):
|
|||
"""
|
||||
self.func = func
|
||||
if order_by is not None:
|
||||
self.order_by = ClauseList(*util.to_list(order_by))
|
||||
self.order_by = ClauseList(
|
||||
*util.to_list(order_by),
|
||||
_literal_as_text=_literal_as_label_reference)
|
||||
if partition_by is not None:
|
||||
self.partition_by = ClauseList(*util.to_list(partition_by))
|
||||
self.partition_by = ClauseList(
|
||||
*util.to_list(partition_by),
|
||||
_literal_as_text=_literal_as_label_reference)
|
||||
|
||||
@util.memoized_property
|
||||
def type(self):
|
||||
|
|
@ -2762,6 +2921,120 @@ class Over(ColumnElement):
|
|||
))
|
||||
|
||||
|
||||
class FunctionFilter(ColumnElement):
|
||||
"""Represent a function FILTER clause.
|
||||
|
||||
This is a special operator against aggregate and window functions,
|
||||
which controls which rows are passed to it.
|
||||
It's supported only by certain database backends.
|
||||
|
||||
Invocation of :class:`.FunctionFilter` is via
|
||||
:meth:`.FunctionElement.filter`::
|
||||
|
||||
func.count(1).filter(True)
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.FunctionElement.filter`
|
||||
|
||||
"""
|
||||
__visit_name__ = 'funcfilter'
|
||||
|
||||
criterion = None
|
||||
|
||||
def __init__(self, func, *criterion):
|
||||
"""Produce a :class:`.FunctionFilter` object against a function.
|
||||
|
||||
Used against aggregate and window functions,
|
||||
for database backends that support the "FILTER" clause.
|
||||
|
||||
E.g.::
|
||||
|
||||
from sqlalchemy import funcfilter
|
||||
funcfilter(func.count(1), MyClass.name == 'some name')
|
||||
|
||||
Would produce "COUNT(1) FILTER (WHERE myclass.name = 'some name')".
|
||||
|
||||
This function is also available from the :data:`~.expression.func`
|
||||
construct itself via the :meth:`.FunctionElement.filter` method.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.FunctionElement.filter`
|
||||
|
||||
|
||||
"""
|
||||
self.func = func
|
||||
self.filter(*criterion)
|
||||
|
||||
def filter(self, *criterion):
|
||||
"""Produce an additional FILTER against the function.
|
||||
|
||||
This method adds additional criteria to the initial criteria
|
||||
set up by :meth:`.FunctionElement.filter`.
|
||||
|
||||
Multiple criteria are joined together at SQL render time
|
||||
via ``AND``.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
for criterion in list(criterion):
|
||||
criterion = _expression_literal_as_text(criterion)
|
||||
|
||||
if self.criterion is not None:
|
||||
self.criterion = self.criterion & criterion
|
||||
else:
|
||||
self.criterion = criterion
|
||||
|
||||
return self
|
||||
|
||||
def over(self, partition_by=None, order_by=None):
|
||||
"""Produce an OVER clause against this filtered function.
|
||||
|
||||
Used against aggregate or so-called "window" functions,
|
||||
for database backends that support window functions.
|
||||
|
||||
The expression::
|
||||
|
||||
func.rank().filter(MyClass.y > 5).over(order_by='x')
|
||||
|
||||
is shorthand for::
|
||||
|
||||
from sqlalchemy import over, funcfilter
|
||||
over(funcfilter(func.rank(), MyClass.y > 5), order_by='x')
|
||||
|
||||
See :func:`~.expression.over` for a full description.
|
||||
|
||||
"""
|
||||
return Over(self, partition_by=partition_by, order_by=order_by)
|
||||
|
||||
@util.memoized_property
|
||||
def type(self):
|
||||
return self.func.type
|
||||
|
||||
def get_children(self, **kwargs):
|
||||
return [c for c in
|
||||
(self.func, self.criterion)
|
||||
if c is not None]
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
self.func = clone(self.func, **kw)
|
||||
if self.criterion is not None:
|
||||
self.criterion = clone(self.criterion, **kw)
|
||||
|
||||
@property
|
||||
def _from_objects(self):
|
||||
return list(itertools.chain(
|
||||
*[c._from_objects for c in (self.func, self.criterion)
|
||||
if c is not None]
|
||||
))
|
||||
|
||||
|
||||
class Label(ColumnElement):
|
||||
"""Represents a column label (AS).
|
||||
|
||||
|
|
@ -2787,14 +3060,21 @@ class Label(ColumnElement):
|
|||
:param obj: a :class:`.ColumnElement`.
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(element, Label):
|
||||
self._resolve_label = element._label
|
||||
|
||||
while isinstance(element, Label):
|
||||
element = element.element
|
||||
|
||||
if name:
|
||||
self.name = name
|
||||
self._resolve_label = self.name
|
||||
else:
|
||||
self.name = _anonymous_label(
|
||||
'%%(%d %s)s' % (id(self), getattr(element, 'name', 'anon'))
|
||||
)
|
||||
|
||||
self.key = self._label = self._key_label = self.name
|
||||
self._element = element
|
||||
self._type = type_
|
||||
|
|
@ -2804,6 +3084,10 @@ class Label(ColumnElement):
|
|||
return self.__class__, (self.name, self._element, self._type)
|
||||
|
||||
@util.memoized_property
|
||||
def _allow_label_resolve(self):
|
||||
return self.element._allow_label_resolve
|
||||
|
||||
@property
|
||||
def _order_by_label_element(self):
|
||||
return self
|
||||
|
||||
|
|
@ -2837,8 +3121,16 @@ class Label(ColumnElement):
|
|||
def get_children(self, **kwargs):
|
||||
return self.element,
|
||||
|
||||
def _copy_internals(self, clone=_clone, **kw):
|
||||
self.element = clone(self.element, **kw)
|
||||
def _copy_internals(self, clone=_clone, anonymize_labels=False, **kw):
|
||||
self._element = clone(self._element, **kw)
|
||||
self.__dict__.pop('element', None)
|
||||
self.__dict__.pop('_allow_label_resolve', None)
|
||||
if anonymize_labels:
|
||||
self.name = self._resolve_label = _anonymous_label(
|
||||
'%%(%d %s)s' % (
|
||||
id(self), getattr(self.element, 'name', 'anon'))
|
||||
)
|
||||
self.key = self._label = self._key_label = self.name
|
||||
|
||||
@property
|
||||
def _from_objects(self):
|
||||
|
|
@ -2860,7 +3152,7 @@ class ColumnClause(Immutable, ColumnElement):
|
|||
:class:`.Column` class, is typically invoked using the
|
||||
:func:`.column` function, as in::
|
||||
|
||||
from sqlalchemy.sql import column
|
||||
from sqlalchemy import column
|
||||
|
||||
id, name = column("id"), column("name")
|
||||
stmt = select([id, name]).select_from("user")
|
||||
|
|
@ -2900,7 +3192,7 @@ class ColumnClause(Immutable, ColumnElement):
|
|||
:class:`.Column` class. The :func:`.column` function can
|
||||
be invoked with just a name alone, as in::
|
||||
|
||||
from sqlalchemy.sql import column
|
||||
from sqlalchemy import column
|
||||
|
||||
id, name = column("id"), column("name")
|
||||
stmt = select([id, name]).select_from("user")
|
||||
|
|
@ -2932,7 +3224,7 @@ class ColumnClause(Immutable, ColumnElement):
|
|||
(which is the lightweight analogue to :class:`.Table`) to produce
|
||||
a working table construct with minimal boilerplate::
|
||||
|
||||
from sqlalchemy.sql import table, column
|
||||
from sqlalchemy import table, column, select
|
||||
|
||||
user = table("user",
|
||||
column("id"),
|
||||
|
|
@ -2948,6 +3240,10 @@ class ColumnClause(Immutable, ColumnElement):
|
|||
:class:`.schema.MetaData`, DDL, or events, unlike its
|
||||
:class:`.Table` counterpart.
|
||||
|
||||
.. versionchanged:: 1.0.0 :func:`.expression.column` can now
|
||||
be imported from the plain ``sqlalchemy`` namespace like any
|
||||
other SQL element.
|
||||
|
||||
:param text: the text of the element.
|
||||
|
||||
:param type: :class:`.types.TypeEngine` object which can associate
|
||||
|
|
@ -2965,9 +3261,11 @@ class ColumnClause(Immutable, ColumnElement):
|
|||
|
||||
:func:`.literal_column`
|
||||
|
||||
:func:`.table`
|
||||
|
||||
:func:`.text`
|
||||
|
||||
:ref:`metadata_toplevel`
|
||||
:ref:`sqlexpression_literal_column`
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -3024,6 +3322,10 @@ class ColumnClause(Immutable, ColumnElement):
|
|||
def _label(self):
|
||||
return self._gen_label(self.name)
|
||||
|
||||
@_memoized_property
|
||||
def _render_label_in_columns_clause(self):
|
||||
return self.table is not None
|
||||
|
||||
def _gen_label(self, name):
|
||||
t = self.table
|
||||
|
||||
|
|
@ -3065,7 +3367,7 @@ class ColumnClause(Immutable, ColumnElement):
|
|||
return name
|
||||
|
||||
def _bind_param(self, operator, obj):
|
||||
return BindParameter(self.name, obj,
|
||||
return BindParameter(self.key, obj,
|
||||
_compared_to_operator=operator,
|
||||
_compared_to_type=self.type,
|
||||
unique=True)
|
||||
|
|
@ -3117,7 +3419,7 @@ class ReleaseSavepointClause(_IdentifiedClause):
|
|||
__visit_name__ = 'release_savepoint'
|
||||
|
||||
|
||||
class quoted_name(util.text_type):
|
||||
class quoted_name(util.MemoizedSlots, util.text_type):
|
||||
"""Represent a SQL identifier combined with quoting preferences.
|
||||
|
||||
:class:`.quoted_name` is a Python unicode/str subclass which
|
||||
|
|
@ -3161,6 +3463,8 @@ class quoted_name(util.text_type):
|
|||
|
||||
"""
|
||||
|
||||
__slots__ = 'quote', 'lower', 'upper'
|
||||
|
||||
def __new__(cls, value, quote):
|
||||
if value is None:
|
||||
return None
|
||||
|
|
@ -3180,15 +3484,13 @@ class quoted_name(util.text_type):
|
|||
def __reduce__(self):
|
||||
return quoted_name, (util.text_type(self), self.quote)
|
||||
|
||||
@util.memoized_instancemethod
|
||||
def lower(self):
|
||||
def _memoized_method_lower(self):
|
||||
if self.quote:
|
||||
return self
|
||||
else:
|
||||
return util.text_type(self).lower()
|
||||
|
||||
@util.memoized_instancemethod
|
||||
def upper(self):
|
||||
def _memoized_method_upper(self):
|
||||
if self.quote:
|
||||
return self
|
||||
else:
|
||||
|
|
@ -3205,6 +3507,8 @@ class _truncated_label(quoted_name):
|
|||
"""A unicode subclass used to identify symbolic "
|
||||
"names that may require truncation."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, value, quote=None):
|
||||
quote = getattr(value, "quote", quote)
|
||||
# return super(_truncated_label, cls).__new__(cls, value, quote, True)
|
||||
|
|
@ -3261,6 +3565,7 @@ class conv(_truncated_label):
|
|||
:ref:`constraint_naming_conventions`
|
||||
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class _defer_name(_truncated_label):
|
||||
|
|
@ -3268,6 +3573,8 @@ class _defer_name(_truncated_label):
|
|||
generation.
|
||||
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, value):
|
||||
if value is None:
|
||||
return _NONE_NAME
|
||||
|
|
@ -3276,9 +3583,13 @@ class _defer_name(_truncated_label):
|
|||
else:
|
||||
return super(_defer_name, cls).__new__(cls, value)
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, (util.text_type(self), )
|
||||
|
||||
|
||||
class _defer_none_name(_defer_name):
|
||||
"""indicate a 'deferred' name that was ultimately the value None."""
|
||||
__slots__ = ()
|
||||
|
||||
_NONE_NAME = _defer_none_name("_unnamed_")
|
||||
|
||||
|
|
@ -3293,6 +3604,8 @@ class _anonymous_label(_truncated_label):
|
|||
"""A unicode subclass used to identify anonymously
|
||||
generated names."""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __add__(self, other):
|
||||
return _anonymous_label(
|
||||
quoted_name(
|
||||
|
|
@ -3336,7 +3649,7 @@ def _string_or_unprintable(element):
|
|||
else:
|
||||
try:
|
||||
return str(element)
|
||||
except:
|
||||
except Exception:
|
||||
return "unprintable element %r" % element
|
||||
|
||||
|
||||
|
|
@ -3424,18 +3737,53 @@ def _clause_element_as_expr(element):
|
|||
return element
|
||||
|
||||
|
||||
def _literal_as_text(element):
|
||||
def _literal_as_label_reference(element):
|
||||
if isinstance(element, util.string_types):
|
||||
return _textual_label_reference(element)
|
||||
|
||||
elif hasattr(element, '__clause_element__'):
|
||||
element = element.__clause_element__()
|
||||
|
||||
return _literal_as_text(element)
|
||||
|
||||
|
||||
def _literal_and_labels_as_label_reference(element):
|
||||
if isinstance(element, util.string_types):
|
||||
return _textual_label_reference(element)
|
||||
|
||||
elif hasattr(element, '__clause_element__'):
|
||||
element = element.__clause_element__()
|
||||
|
||||
if isinstance(element, ColumnElement) and \
|
||||
element._order_by_label_element is not None:
|
||||
return _label_reference(element)
|
||||
else:
|
||||
return _literal_as_text(element)
|
||||
|
||||
|
||||
def _expression_literal_as_text(element):
|
||||
return _literal_as_text(element, warn=True)
|
||||
|
||||
|
||||
def _literal_as_text(element, warn=False):
|
||||
if isinstance(element, Visitable):
|
||||
return element
|
||||
elif hasattr(element, '__clause_element__'):
|
||||
return element.__clause_element__()
|
||||
elif isinstance(element, util.string_types):
|
||||
if warn:
|
||||
util.warn_limited(
|
||||
"Textual SQL expression %(expr)r should be "
|
||||
"explicitly declared as text(%(expr)r)",
|
||||
{"expr": util.ellipses_string(element)})
|
||||
|
||||
return TextClause(util.text_type(element))
|
||||
elif isinstance(element, (util.NoneType, bool)):
|
||||
return _const_expr(element)
|
||||
else:
|
||||
raise exc.ArgumentError(
|
||||
"SQL expression object or string expected."
|
||||
"SQL expression object or string expected, got object of type %r "
|
||||
"instead" % type(element)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -3484,6 +3832,8 @@ def _literal_as_binds(element, name=None, type_=None):
|
|||
else:
|
||||
return element
|
||||
|
||||
_guess_straight_column = re.compile(r'^\w\S*$', re.I)
|
||||
|
||||
|
||||
def _interpret_as_column_or_from(element):
|
||||
if isinstance(element, Visitable):
|
||||
|
|
@ -3498,7 +3848,31 @@ def _interpret_as_column_or_from(element):
|
|||
elif hasattr(insp, "selectable"):
|
||||
return insp.selectable
|
||||
|
||||
return ColumnClause(str(element), is_literal=True)
|
||||
# be forgiving as this is an extremely common
|
||||
# and known expression
|
||||
if element == "*":
|
||||
guess_is_literal = True
|
||||
elif isinstance(element, (numbers.Number)):
|
||||
return ColumnClause(str(element), is_literal=True)
|
||||
else:
|
||||
element = str(element)
|
||||
# give into temptation, as this fact we are guessing about
|
||||
# is not one we've previously ever needed our users tell us;
|
||||
# but let them know we are not happy about it
|
||||
guess_is_literal = not _guess_straight_column.match(element)
|
||||
util.warn_limited(
|
||||
"Textual column expression %(column)r should be "
|
||||
"explicitly declared with text(%(column)r), "
|
||||
"or use %(literal_column)s(%(column)r) "
|
||||
"for more specificity",
|
||||
{
|
||||
"column": util.ellipses_string(element),
|
||||
"literal_column": "literal_column"
|
||||
if guess_is_literal else "column"
|
||||
})
|
||||
return ColumnClause(
|
||||
element,
|
||||
is_literal=guess_is_literal)
|
||||
|
||||
|
||||
def _const_expr(element):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/expression.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -36,7 +36,7 @@ from .elements import ClauseElement, ColumnElement,\
|
|||
True_, False_, BinaryExpression, Tuple, TypeClause, Extract, \
|
||||
Grouping, not_, \
|
||||
collate, literal_column, between,\
|
||||
literal, outparam, type_coerce, ClauseList
|
||||
literal, outparam, type_coerce, ClauseList, FunctionFilter
|
||||
|
||||
from .elements import SavepointClause, RollbackToSavepointClause, \
|
||||
ReleaseSavepointClause
|
||||
|
|
@ -47,7 +47,7 @@ from .base import ColumnCollection, Generative, Executable, \
|
|||
from .selectable import Alias, Join, Select, Selectable, TableClause, \
|
||||
CompoundSelect, CTE, FromClause, FromGrouping, SelectBase, \
|
||||
alias, GenerativeSelect, \
|
||||
subquery, HasPrefixes, Exists, ScalarSelect, TextAsFrom
|
||||
subquery, HasPrefixes, HasSuffixes, Exists, ScalarSelect, TextAsFrom
|
||||
|
||||
|
||||
from .dml import Insert, Update, Delete, UpdateBase, ValuesBase
|
||||
|
|
@ -89,14 +89,16 @@ asc = public_factory(UnaryExpression._create_asc, ".expression.asc")
|
|||
desc = public_factory(UnaryExpression._create_desc, ".expression.desc")
|
||||
distinct = public_factory(
|
||||
UnaryExpression._create_distinct, ".expression.distinct")
|
||||
true = public_factory(True_._singleton, ".expression.true")
|
||||
false = public_factory(False_._singleton, ".expression.false")
|
||||
null = public_factory(Null._singleton, ".expression.null")
|
||||
true = public_factory(True_._instance, ".expression.true")
|
||||
false = public_factory(False_._instance, ".expression.false")
|
||||
null = public_factory(Null._instance, ".expression.null")
|
||||
join = public_factory(Join._create_join, ".expression.join")
|
||||
outerjoin = public_factory(Join._create_outerjoin, ".expression.outerjoin")
|
||||
insert = public_factory(Insert, ".expression.insert")
|
||||
update = public_factory(Update, ".expression.update")
|
||||
delete = public_factory(Delete, ".expression.delete")
|
||||
funcfilter = public_factory(
|
||||
FunctionFilter, ".expression.funcfilter")
|
||||
|
||||
|
||||
# internal functions still being called from tests and the ORM,
|
||||
|
|
@ -106,7 +108,8 @@ from .elements import _literal_as_text, _clause_element_as_expr,\
|
|||
_is_column, _labeled, _only_column_elements, _string_or_unprintable, \
|
||||
_truncated_label, _clone, _cloned_difference, _cloned_intersection,\
|
||||
_column_as_key, _literal_as_binds, _select_iterables, \
|
||||
_corresponding_column_or_error
|
||||
_corresponding_column_or_error, _literal_as_label_reference, \
|
||||
_expression_literal_as_text
|
||||
from .selectable import _interpret_as_from
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/functions.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
"""
|
||||
from . import sqltypes, schema
|
||||
from .base import Executable
|
||||
from .base import Executable, ColumnCollection
|
||||
from .elements import ClauseList, Cast, Extract, _literal_as_binds, \
|
||||
literal_column, _type_from_args, ColumnElement, _clone,\
|
||||
Over, BindParameter
|
||||
from .selectable import FromClause, Select
|
||||
Over, BindParameter, FunctionFilter
|
||||
from .selectable import FromClause, Select, Alias
|
||||
|
||||
from . import operators
|
||||
from .visitors import VisitableType
|
||||
|
|
@ -67,12 +67,24 @@ class FunctionElement(Executable, ColumnElement, FromClause):
|
|||
|
||||
@property
|
||||
def columns(self):
|
||||
"""Fulfill the 'columns' contract of :class:`.ColumnElement`.
|
||||
"""The set of columns exported by this :class:`.FunctionElement`.
|
||||
|
||||
Function objects currently have no result column names built in;
|
||||
this method returns a single-element column collection with
|
||||
an anonymously named column.
|
||||
|
||||
An interim approach to providing named columns for a function
|
||||
as a FROM clause is to build a :func:`.select` with the
|
||||
desired columns::
|
||||
|
||||
from sqlalchemy.sql import column
|
||||
|
||||
stmt = select([column('x'), column('y')]).\
|
||||
select_from(func.myfunction())
|
||||
|
||||
Returns a single-element list consisting of this object.
|
||||
|
||||
"""
|
||||
return [self]
|
||||
return ColumnCollection(self.label(None))
|
||||
|
||||
@util.memoized_property
|
||||
def clauses(self):
|
||||
|
|
@ -104,6 +116,35 @@ class FunctionElement(Executable, ColumnElement, FromClause):
|
|||
"""
|
||||
return Over(self, partition_by=partition_by, order_by=order_by)
|
||||
|
||||
def filter(self, *criterion):
|
||||
"""Produce a FILTER clause against this function.
|
||||
|
||||
Used against aggregate and window functions,
|
||||
for database backends that support the "FILTER" clause.
|
||||
|
||||
The expression::
|
||||
|
||||
func.count(1).filter(True)
|
||||
|
||||
is shorthand for::
|
||||
|
||||
from sqlalchemy import funcfilter
|
||||
funcfilter(func.count(1), True)
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.FunctionFilter`
|
||||
|
||||
:func:`.funcfilter`
|
||||
|
||||
|
||||
"""
|
||||
if not criterion:
|
||||
return self
|
||||
return FunctionFilter(self, *criterion)
|
||||
|
||||
@property
|
||||
def _from_objects(self):
|
||||
return self.clauses._from_objects
|
||||
|
|
@ -116,6 +157,36 @@ class FunctionElement(Executable, ColumnElement, FromClause):
|
|||
self._reset_exported()
|
||||
FunctionElement.clauses._reset(self)
|
||||
|
||||
def alias(self, name=None, flat=False):
|
||||
"""Produce a :class:`.Alias` construct against this
|
||||
:class:`.FunctionElement`.
|
||||
|
||||
This construct wraps the function in a named alias which
|
||||
is suitable for the FROM clause.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy.sql import column
|
||||
|
||||
stmt = select([column('data')]).select_from(
|
||||
func.unnest(Table.data).alias('data_view')
|
||||
)
|
||||
|
||||
Would produce:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
SELECT data
|
||||
FROM unnest(sometable.data) AS data_view
|
||||
|
||||
.. versionadded:: 0.9.8 The :meth:`.FunctionElement.alias` method
|
||||
is now supported. Previously, this method's behavior was
|
||||
undefined and did not behave consistently across versions.
|
||||
|
||||
"""
|
||||
|
||||
return Alias(self, name)
|
||||
|
||||
def select(self):
|
||||
"""Produce a :func:`~.expression.select` construct
|
||||
against this :class:`.FunctionElement`.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sqlalchemy/naming.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -113,10 +113,12 @@ def _constraint_name_for_table(const, table):
|
|||
|
||||
if isinstance(const.name, conv):
|
||||
return const.name
|
||||
elif convention is not None and (
|
||||
const.name is None or not isinstance(const.name, conv) and
|
||||
"constraint_name" in convention
|
||||
):
|
||||
elif convention is not None and \
|
||||
not isinstance(const.name, conv) and \
|
||||
(
|
||||
const.name is None or
|
||||
"constraint_name" in convention or
|
||||
isinstance(const.name, _defer_name)):
|
||||
return conv(
|
||||
convention % ConventionDict(const, table,
|
||||
metadata.naming_convention)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/operators.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -15,7 +15,7 @@ from .. import util
|
|||
|
||||
from operator import (
|
||||
and_, or_, inv, add, mul, sub, mod, truediv, lt, le, ne, gt, ge, eq, neg,
|
||||
getitem, lshift, rshift
|
||||
getitem, lshift, rshift, contains
|
||||
)
|
||||
|
||||
if util.py2k:
|
||||
|
|
@ -38,6 +38,7 @@ class Operators(object):
|
|||
:class:`.ColumnOperators`.
|
||||
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __and__(self, other):
|
||||
"""Implement the ``&`` operator.
|
||||
|
|
@ -137,7 +138,7 @@ class Operators(object):
|
|||
.. versionadded:: 0.8 - added the 'precedence' argument.
|
||||
|
||||
:param is_comparison: if True, the operator will be considered as a
|
||||
"comparison" operator, that is which evaulates to a boolean
|
||||
"comparison" operator, that is which evaluates to a boolean
|
||||
true/false value, like ``==``, ``>``, etc. This flag should be set
|
||||
so that ORM relationships can establish that the operator is a
|
||||
comparison operator when used in a custom join condition.
|
||||
|
|
@ -267,6 +268,8 @@ class ColumnOperators(Operators):
|
|||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
timetuple = None
|
||||
"""Hack, allows datetime objects to be compared on the LHS."""
|
||||
|
||||
|
|
@ -330,6 +333,9 @@ class ColumnOperators(Operators):
|
|||
"""
|
||||
return self.operate(neg)
|
||||
|
||||
def __contains__(self, other):
|
||||
return self.operate(contains, other)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Implement the [] operator.
|
||||
|
||||
|
|
@ -529,8 +535,10 @@ class ColumnOperators(Operators):
|
|||
* Postgresql - renders ``x @@ to_tsquery(y)``
|
||||
* MySQL - renders ``MATCH (x) AGAINST (y IN BOOLEAN MODE)``
|
||||
* Oracle - renders ``CONTAINS(x, y)``
|
||||
* other backends may provide special implementations;
|
||||
some backends such as SQLite have no support.
|
||||
* other backends may provide special implementations.
|
||||
* Backends without any special implementation will emit
|
||||
the operator as "MATCH". This is compatible with SQlite, for
|
||||
example.
|
||||
|
||||
"""
|
||||
return self.operate(match_op, other, **kwargs)
|
||||
|
|
@ -592,6 +600,14 @@ class ColumnOperators(Operators):
|
|||
"""
|
||||
return self.reverse_operate(div, other)
|
||||
|
||||
def __rmod__(self, other):
|
||||
"""Implement the ``%`` operator in reverse.
|
||||
|
||||
See :meth:`.ColumnOperators.__mod__`.
|
||||
|
||||
"""
|
||||
return self.reverse_operate(mod, other)
|
||||
|
||||
def between(self, cleft, cright, symmetric=False):
|
||||
"""Produce a :func:`~.expression.between` clause against
|
||||
the parent object, given the lower and upper range.
|
||||
|
|
@ -767,6 +783,10 @@ def match_op(a, b, **kw):
|
|||
return a.match(b, **kw)
|
||||
|
||||
|
||||
def notmatch_op(a, b, **kw):
|
||||
return a.notmatch(b, **kw)
|
||||
|
||||
|
||||
def comma_op(a, b):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -834,6 +854,7 @@ _PRECEDENCE = {
|
|||
|
||||
concat_op: 6,
|
||||
match_op: 6,
|
||||
notmatch_op: 6,
|
||||
|
||||
ilike_op: 6,
|
||||
notilike_op: 6,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
# sql/sqltypes.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -14,7 +14,6 @@ import codecs
|
|||
|
||||
from .type_api import TypeEngine, TypeDecorator, to_instance
|
||||
from .elements import quoted_name, type_coerce, _defer_name
|
||||
from .default_comparator import _DefaultColumnComparator
|
||||
from .. import exc, util, processors
|
||||
from .base import _bind_or_error, SchemaEventTarget
|
||||
from . import operators
|
||||
|
|
@ -180,8 +179,10 @@ class String(Concatenable, TypeEngine):
|
|||
if self._warn_on_bytestring:
|
||||
def process(value):
|
||||
if isinstance(value, util.binary_type):
|
||||
util.warn("Unicode type received non-unicode"
|
||||
"bind param value.")
|
||||
util.warn_limited(
|
||||
"Unicode type received non-unicode "
|
||||
"bind param value %r.",
|
||||
(util.ellipses_string(value),))
|
||||
return value
|
||||
return process
|
||||
else:
|
||||
|
|
@ -194,8 +195,10 @@ class String(Concatenable, TypeEngine):
|
|||
if isinstance(value, util.text_type):
|
||||
return encoder(value, self.unicode_error)[0]
|
||||
elif warn_on_bytestring and value is not None:
|
||||
util.warn("Unicode type received non-unicode bind "
|
||||
"param value")
|
||||
util.warn_limited(
|
||||
"Unicode type received non-unicode bind "
|
||||
"param value %r.",
|
||||
(util.ellipses_string(value),))
|
||||
return value
|
||||
return process
|
||||
else:
|
||||
|
|
@ -212,9 +215,6 @@ class String(Concatenable, TypeEngine):
|
|||
self.convert_unicode != 'force_nocheck'
|
||||
)
|
||||
if needs_convert:
|
||||
to_unicode = processors.to_unicode_processor_factory(
|
||||
dialect.encoding, self.unicode_error)
|
||||
|
||||
if needs_isinstance:
|
||||
return processors.to_conditional_unicode_processor_factory(
|
||||
dialect.encoding, self.unicode_error)
|
||||
|
|
@ -876,9 +876,9 @@ class LargeBinary(_Binary):
|
|||
|
||||
"""A type for large binary byte data.
|
||||
|
||||
The Binary type generates BLOB or BYTEA when tables are created,
|
||||
and also converts incoming values using the ``Binary`` callable
|
||||
provided by each DB-API.
|
||||
The :class:`.LargeBinary` type corresponds to a large and/or unlengthed
|
||||
binary type for the target platform, such as BLOB on MySQL and BYTEA for
|
||||
Postgresql. It also handles the necessary conversions for the DBAPI.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -889,13 +889,8 @@ class LargeBinary(_Binary):
|
|||
Construct a LargeBinary type.
|
||||
|
||||
:param length: optional, a length for the column for use in
|
||||
DDL statements, for those BLOB types that accept a length
|
||||
(i.e. MySQL). It does *not* produce a small BINARY/VARBINARY
|
||||
type - use the BINARY/VARBINARY types specifically for those.
|
||||
May be safely omitted if no ``CREATE
|
||||
TABLE`` will be issued. Certain databases may require a
|
||||
*length* for use in DDL, and will raise an exception when
|
||||
the ``CREATE TABLE`` DDL is issued.
|
||||
DDL statements, for those binary types that accept a length,
|
||||
such as the MySQL BLOB type.
|
||||
|
||||
"""
|
||||
_Binary.__init__(self, length=length)
|
||||
|
|
@ -935,7 +930,7 @@ class SchemaType(SchemaEventTarget):
|
|||
"""
|
||||
|
||||
def __init__(self, name=None, schema=None, metadata=None,
|
||||
inherit_schema=False, quote=None):
|
||||
inherit_schema=False, quote=None, _create_events=True):
|
||||
if name is not None:
|
||||
self.name = quoted_name(name, quote)
|
||||
else:
|
||||
|
|
@ -943,8 +938,9 @@ class SchemaType(SchemaEventTarget):
|
|||
self.schema = schema
|
||||
self.metadata = metadata
|
||||
self.inherit_schema = inherit_schema
|
||||
self._create_events = _create_events
|
||||
|
||||
if self.metadata:
|
||||
if _create_events and self.metadata:
|
||||
event.listen(
|
||||
self.metadata,
|
||||
"before_create",
|
||||
|
|
@ -963,6 +959,9 @@ class SchemaType(SchemaEventTarget):
|
|||
if self.inherit_schema:
|
||||
self.schema = table.schema
|
||||
|
||||
if not self._create_events:
|
||||
return
|
||||
|
||||
event.listen(
|
||||
table,
|
||||
"before_create",
|
||||
|
|
@ -989,19 +988,18 @@ class SchemaType(SchemaEventTarget):
|
|||
)
|
||||
|
||||
def copy(self, **kw):
|
||||
return self.adapt(self.__class__)
|
||||
return self.adapt(self.__class__, _create_events=True)
|
||||
|
||||
def adapt(self, impltype, **kw):
|
||||
schema = kw.pop('schema', self.schema)
|
||||
metadata = kw.pop('metadata', self.metadata)
|
||||
_create_events = kw.pop('_create_events', False)
|
||||
|
||||
# don't associate with MetaData as the hosting type
|
||||
# is already associated with it, avoid creating event
|
||||
# listeners
|
||||
metadata = kw.pop('metadata', None)
|
||||
return impltype(name=self.name,
|
||||
schema=schema,
|
||||
metadata=metadata,
|
||||
inherit_schema=self.inherit_schema,
|
||||
metadata=metadata,
|
||||
_create_events=_create_events,
|
||||
**kw)
|
||||
|
||||
@property
|
||||
|
|
@ -1145,6 +1143,7 @@ class Enum(String, SchemaType):
|
|||
|
||||
def __repr__(self):
|
||||
return util.generic_repr(self,
|
||||
additional_kw=[('native_enum', True)],
|
||||
to_inspect=[Enum, SchemaType],
|
||||
)
|
||||
|
||||
|
|
@ -1161,13 +1160,15 @@ class Enum(String, SchemaType):
|
|||
type_coerce(column, self).in_(self.enums),
|
||||
name=_defer_name(self.name),
|
||||
_create_rule=util.portable_instancemethod(
|
||||
self._should_create_constraint)
|
||||
self._should_create_constraint),
|
||||
_type_bound=True
|
||||
)
|
||||
assert e.table is table
|
||||
|
||||
def adapt(self, impltype, **kw):
|
||||
schema = kw.pop('schema', self.schema)
|
||||
metadata = kw.pop('metadata', None)
|
||||
metadata = kw.pop('metadata', self.metadata)
|
||||
_create_events = kw.pop('_create_events', False)
|
||||
if issubclass(impltype, Enum):
|
||||
return impltype(name=self.name,
|
||||
schema=schema,
|
||||
|
|
@ -1175,9 +1176,11 @@ class Enum(String, SchemaType):
|
|||
convert_unicode=self.convert_unicode,
|
||||
native_enum=self.native_enum,
|
||||
inherit_schema=self.inherit_schema,
|
||||
_create_events=_create_events,
|
||||
*self.enums,
|
||||
**kw)
|
||||
else:
|
||||
# TODO: why would we be here?
|
||||
return super(Enum, self).adapt(impltype, **kw)
|
||||
|
||||
|
||||
|
|
@ -1273,7 +1276,8 @@ class Boolean(TypeEngine, SchemaType):
|
|||
|
||||
__visit_name__ = 'boolean'
|
||||
|
||||
def __init__(self, create_constraint=True, name=None):
|
||||
def __init__(
|
||||
self, create_constraint=True, name=None, _create_events=True):
|
||||
"""Construct a Boolean.
|
||||
|
||||
:param create_constraint: defaults to True. If the boolean
|
||||
|
|
@ -1286,6 +1290,7 @@ class Boolean(TypeEngine, SchemaType):
|
|||
"""
|
||||
self.create_constraint = create_constraint
|
||||
self.name = name
|
||||
self._create_events = _create_events
|
||||
|
||||
def _should_create_constraint(self, compiler):
|
||||
return not compiler.dialect.supports_native_boolean
|
||||
|
|
@ -1299,7 +1304,8 @@ class Boolean(TypeEngine, SchemaType):
|
|||
type_coerce(column, self).in_([0, 1]),
|
||||
name=_defer_name(self.name),
|
||||
_create_rule=util.portable_instancemethod(
|
||||
self._should_create_constraint)
|
||||
self._should_create_constraint),
|
||||
_type_bound=True
|
||||
)
|
||||
assert e.table is table
|
||||
|
||||
|
|
@ -1650,10 +1656,26 @@ class NullType(TypeEngine):
|
|||
comparator_factory = Comparator
|
||||
|
||||
|
||||
class MatchType(Boolean):
|
||||
"""Refers to the return type of the MATCH operator.
|
||||
|
||||
As the :meth:`.ColumnOperators.match` is probably the most open-ended
|
||||
operator in generic SQLAlchemy Core, we can't assume the return type
|
||||
at SQL evaluation time, as MySQL returns a floating point, not a boolean,
|
||||
and other backends might do something different. So this type
|
||||
acts as a placeholder, currently subclassing :class:`.Boolean`.
|
||||
The type allows dialects to inject result-processing functionality
|
||||
if needed, and on MySQL will return floating-point values.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
"""
|
||||
|
||||
NULLTYPE = NullType()
|
||||
BOOLEANTYPE = Boolean()
|
||||
STRINGTYPE = String()
|
||||
INTEGERTYPE = Integer()
|
||||
MATCHTYPE = MatchType()
|
||||
|
||||
_type_map = {
|
||||
int: Integer(),
|
||||
|
|
@ -1681,21 +1703,7 @@ type_api.BOOLEANTYPE = BOOLEANTYPE
|
|||
type_api.STRINGTYPE = STRINGTYPE
|
||||
type_api.INTEGERTYPE = INTEGERTYPE
|
||||
type_api.NULLTYPE = NULLTYPE
|
||||
type_api.MATCHTYPE = MATCHTYPE
|
||||
type_api._type_map = _type_map
|
||||
|
||||
# this one, there's all kinds of ways to play it, but at the EOD
|
||||
# there's just a giant dependency cycle between the typing system and
|
||||
# the expression element system, as you might expect. We can use
|
||||
# importlaters or whatnot, but the typing system just necessarily has
|
||||
# to have some kind of connection like this. right now we're injecting the
|
||||
# _DefaultColumnComparator implementation into the TypeEngine.Comparator
|
||||
# interface. Alternatively TypeEngine.Comparator could have an "impl"
|
||||
# injected, though just injecting the base is simpler, error free, and more
|
||||
# performant.
|
||||
|
||||
|
||||
class Comparator(_DefaultColumnComparator):
|
||||
BOOLEANTYPE = BOOLEANTYPE
|
||||
|
||||
TypeEngine.Comparator.__bases__ = (
|
||||
Comparator, ) + TypeEngine.Comparator.__bases__
|
||||
TypeEngine.Comparator.BOOLEANTYPE = BOOLEANTYPE
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/types_api.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -12,13 +12,14 @@
|
|||
|
||||
from .. import exc, util
|
||||
from . import operators
|
||||
from .visitors import Visitable
|
||||
from .visitors import Visitable, VisitableType
|
||||
|
||||
# these are back-assigned by sqltypes.
|
||||
BOOLEANTYPE = None
|
||||
INTEGERTYPE = None
|
||||
NULLTYPE = None
|
||||
STRINGTYPE = None
|
||||
MATCHTYPE = None
|
||||
|
||||
|
||||
class TypeEngine(Visitable):
|
||||
|
|
@ -45,9 +46,51 @@ class TypeEngine(Visitable):
|
|||
|
||||
|
||||
"""
|
||||
__slots__ = 'expr', 'type'
|
||||
|
||||
default_comparator = None
|
||||
|
||||
def __init__(self, expr):
|
||||
self.expr = expr
|
||||
self.type = expr.type
|
||||
|
||||
@util.dependencies('sqlalchemy.sql.default_comparator')
|
||||
def operate(self, default_comparator, op, *other, **kwargs):
|
||||
o = default_comparator.operator_lookup[op.__name__]
|
||||
return o[0](self.expr, op, *(other + o[1:]), **kwargs)
|
||||
|
||||
@util.dependencies('sqlalchemy.sql.default_comparator')
|
||||
def reverse_operate(self, default_comparator, op, other, **kwargs):
|
||||
o = default_comparator.operator_lookup[op.__name__]
|
||||
return o[0](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 __reduce__(self):
|
||||
return _reconstitute_comparator, (self.expr, )
|
||||
|
|
@ -85,6 +128,33 @@ class TypeEngine(Visitable):
|
|||
|
||||
"""
|
||||
|
||||
def compare_against_backend(self, dialect, conn_type):
|
||||
"""Compare this type against the given backend type.
|
||||
|
||||
This function is currently not implemented for SQLAlchemy
|
||||
types, and for all built in types will return ``None``. However,
|
||||
it can be implemented by a user-defined type
|
||||
where it can be consumed by schema comparison tools such as
|
||||
Alembic autogenerate.
|
||||
|
||||
A future release of SQLAlchemy will potentially impement this method
|
||||
for builtin types as well.
|
||||
|
||||
The function should return True if this type is equivalent to the
|
||||
given type; the type is typically reflected from the database
|
||||
so should be database specific. The dialect in use is also
|
||||
passed. It can also return False to assert that the type is
|
||||
not equivalent.
|
||||
|
||||
:param dialect: a :class:`.Dialect` that is involved in the comparison.
|
||||
|
||||
:param conn_type: the type object reflected from the backend.
|
||||
|
||||
.. versionadded:: 1.0.3
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def copy_value(self, value):
|
||||
return value
|
||||
|
||||
|
|
@ -252,7 +322,7 @@ class TypeEngine(Visitable):
|
|||
The construction of :meth:`.TypeEngine.with_variant` is always
|
||||
from the "fallback" type to that which is dialect specific.
|
||||
The returned type is an instance of :class:`.Variant`, which
|
||||
itself provides a :meth:`~sqlalchemy.types.Variant.with_variant`
|
||||
itself provides a :meth:`.Variant.with_variant`
|
||||
that can be called repeatedly.
|
||||
|
||||
:param type_: a :class:`.TypeEngine` that will be selected
|
||||
|
|
@ -417,7 +487,11 @@ class TypeEngine(Visitable):
|
|||
return util.generic_repr(self)
|
||||
|
||||
|
||||
class UserDefinedType(TypeEngine):
|
||||
class VisitableCheckKWArg(util.EnsureKWArgType, VisitableType):
|
||||
pass
|
||||
|
||||
|
||||
class UserDefinedType(util.with_metaclass(VisitableCheckKWArg, TypeEngine)):
|
||||
"""Base for user defined types.
|
||||
|
||||
This should be the base of new types. Note that
|
||||
|
|
@ -430,7 +504,7 @@ class UserDefinedType(TypeEngine):
|
|||
def __init__(self, precision = 8):
|
||||
self.precision = precision
|
||||
|
||||
def get_col_spec(self):
|
||||
def get_col_spec(self, **kw):
|
||||
return "MYTYPE(%s)" % self.precision
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
|
|
@ -450,10 +524,26 @@ class UserDefinedType(TypeEngine):
|
|||
Column('data', MyType(16))
|
||||
)
|
||||
|
||||
The ``get_col_spec()`` method will in most cases receive a keyword
|
||||
argument ``type_expression`` which refers to the owning expression
|
||||
of the type as being compiled, such as a :class:`.Column` or
|
||||
:func:`.cast` construct. This keyword is only sent if the method
|
||||
accepts keyword arguments (e.g. ``**kw``) in its argument signature;
|
||||
introspection is used to check for this in order to support legacy
|
||||
forms of this function.
|
||||
|
||||
.. versionadded:: 1.0.0 the owning expression is passed to
|
||||
the ``get_col_spec()`` method via the keyword argument
|
||||
``type_expression``, if it receives ``**kw`` in its signature.
|
||||
|
||||
"""
|
||||
__visit_name__ = "user_defined"
|
||||
|
||||
ensure_kwarg = 'get_col_spec'
|
||||
|
||||
class Comparator(TypeEngine.Comparator):
|
||||
__slots__ = ()
|
||||
|
||||
def _adapt_expression(self, op, other_comparator):
|
||||
if hasattr(self.type, 'adapt_operator'):
|
||||
util.warn_deprecated(
|
||||
|
|
@ -566,6 +656,26 @@ class TypeDecorator(TypeEngine):
|
|||
else:
|
||||
return self
|
||||
|
||||
.. warning::
|
||||
|
||||
Note that the **behavior of coerce_compared_value is not inherited
|
||||
by default from that of the base type**.
|
||||
If the :class:`.TypeDecorator` is augmenting a
|
||||
type that requires special logic for certain types of operators,
|
||||
this method **must** be overridden. A key example is when decorating
|
||||
the :class:`.postgresql.JSON` and :class:`.postgresql.JSONB` types;
|
||||
the default rules of :meth:`.TypeEngine.coerce_compared_value` should
|
||||
be used in order to deal with operators like index operations::
|
||||
|
||||
class MyJsonType(TypeDecorator):
|
||||
impl = postgresql.JSON
|
||||
|
||||
def coerce_compared_value(self, op, value):
|
||||
return self.impl.coerce_compared_value(op, value)
|
||||
|
||||
Without the above step, index operations such as ``mycol['foo']``
|
||||
will cause the index value ``'foo'`` to be JSON encoded.
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = "type_decorator"
|
||||
|
|
@ -617,6 +727,7 @@ class TypeDecorator(TypeEngine):
|
|||
"""
|
||||
|
||||
class Comparator(TypeEngine.Comparator):
|
||||
__slots__ = ()
|
||||
|
||||
def operate(self, op, *other, **kwargs):
|
||||
kwargs['_python_is_types'] = self.expr.type.coerce_to_is_types
|
||||
|
|
@ -630,9 +741,13 @@ class TypeDecorator(TypeEngine):
|
|||
|
||||
@property
|
||||
def comparator_factory(self):
|
||||
return type("TDComparator",
|
||||
(TypeDecorator.Comparator, self.impl.comparator_factory),
|
||||
{})
|
||||
if TypeDecorator.Comparator in self.impl.comparator_factory.__mro__:
|
||||
return self.impl.comparator_factory
|
||||
else:
|
||||
return type("TDComparator",
|
||||
(TypeDecorator.Comparator,
|
||||
self.impl.comparator_factory),
|
||||
{})
|
||||
|
||||
def _gen_dialect_impl(self, dialect):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/util.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -16,7 +16,8 @@ from itertools import chain
|
|||
from collections import deque
|
||||
|
||||
from .elements import BindParameter, ColumnClause, ColumnElement, \
|
||||
Null, UnaryExpression, literal_column, Label
|
||||
Null, UnaryExpression, literal_column, Label, _label_reference, \
|
||||
_textual_label_reference
|
||||
from .selectable import ScalarSelect, Join, FromClause, FromGrouping
|
||||
from .schema import Column
|
||||
|
||||
|
|
@ -161,6 +162,10 @@ def unwrap_order_by(clause):
|
|||
not isinstance(t, UnaryExpression) or
|
||||
not operators.is_ordering_modifier(t.modifier)
|
||||
):
|
||||
if isinstance(t, _label_reference):
|
||||
t = t.element
|
||||
if isinstance(t, (_textual_label_reference)):
|
||||
continue
|
||||
cols.add(t)
|
||||
else:
|
||||
for c in t.get_children():
|
||||
|
|
@ -428,36 +433,6 @@ def criterion_as_pairs(expression, consider_as_foreign_keys=None,
|
|||
return pairs
|
||||
|
||||
|
||||
class AliasedRow(object):
|
||||
"""Wrap a RowProxy with a translation map.
|
||||
|
||||
This object allows a set of keys to be translated
|
||||
to those present in a RowProxy.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, row, map):
|
||||
# AliasedRow objects don't nest, so un-nest
|
||||
# if another AliasedRow was passed
|
||||
if isinstance(row, AliasedRow):
|
||||
self.row = row.row
|
||||
else:
|
||||
self.row = row
|
||||
self.map = map
|
||||
|
||||
def __contains__(self, key):
|
||||
return self.map[key] in self.row
|
||||
|
||||
def has_key(self, key):
|
||||
return key in self
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.row[self.map[key]]
|
||||
|
||||
def keys(self):
|
||||
return self.row.keys()
|
||||
|
||||
|
||||
class ClauseAdapter(visitors.ReplacingCloningVisitor):
|
||||
"""Clones and modifies clauses based on column correspondence.
|
||||
|
||||
|
|
@ -486,21 +461,14 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
|
|||
"""
|
||||
|
||||
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]}
|
||||
adapt_on_names=False, anonymize_labels=False):
|
||||
self.__traverse_options__ = {
|
||||
'stop_on': [selectable],
|
||||
'anonymize_labels': anonymize_labels}
|
||||
self.selectable = selectable
|
||||
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.include_fn = include_fn
|
||||
self.exclude_fn = exclude_fn
|
||||
self.equivalents = util.column_dict(equivalents or {})
|
||||
self.adapt_on_names = adapt_on_names
|
||||
|
||||
|
|
@ -520,10 +488,8 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
|
|||
newcol = self.selectable.c.get(col.name)
|
||||
return newcol
|
||||
|
||||
magic_flag = False
|
||||
|
||||
def replace(self, col):
|
||||
if not self.magic_flag and isinstance(col, FromClause) and \
|
||||
if isinstance(col, FromClause) and \
|
||||
self.selectable.is_derived_from(col):
|
||||
return self.selectable
|
||||
elif not isinstance(col, ColumnElement):
|
||||
|
|
@ -539,62 +505,102 @@ class ClauseAdapter(visitors.ReplacingCloningVisitor):
|
|||
class ColumnAdapter(ClauseAdapter):
|
||||
"""Extends ClauseAdapter with extra utility functions.
|
||||
|
||||
Provides the ability to "wrap" this ClauseAdapter
|
||||
around another, a columns dictionary which returns
|
||||
adapted elements given an original, and an
|
||||
adapted_row() factory.
|
||||
Key aspects of ColumnAdapter include:
|
||||
|
||||
* Expressions that are adapted are stored in a persistent
|
||||
.columns collection; so that an expression E adapted into
|
||||
an expression E1, will return the same object E1 when adapted
|
||||
a second time. This is important in particular for things like
|
||||
Label objects that are anonymized, so that the ColumnAdapter can
|
||||
be used to present a consistent "adapted" view of things.
|
||||
|
||||
* Exclusion of items from the persistent collection based on
|
||||
include/exclude rules, but also independent of hash identity.
|
||||
This because "annotated" items all have the same hash identity as their
|
||||
parent.
|
||||
|
||||
* "wrapping" capability is added, so that the replacement of an expression
|
||||
E can proceed through a series of adapters. This differs from the
|
||||
visitor's "chaining" feature in that the resulting object is passed
|
||||
through all replacing functions unconditionally, rather than stopping
|
||||
at the first one that returns non-None.
|
||||
|
||||
* An adapt_required option, 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
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, selectable, equivalents=None,
|
||||
chain_to=None, include=None,
|
||||
exclude=None, adapt_required=False):
|
||||
chain_to=None, adapt_required=False,
|
||||
include_fn=None, exclude_fn=None,
|
||||
adapt_on_names=False,
|
||||
allow_label_resolve=True,
|
||||
anonymize_labels=False):
|
||||
ClauseAdapter.__init__(self, selectable, equivalents,
|
||||
include, exclude)
|
||||
include_fn=include_fn, exclude_fn=exclude_fn,
|
||||
adapt_on_names=adapt_on_names,
|
||||
anonymize_labels=anonymize_labels)
|
||||
|
||||
if chain_to:
|
||||
self.chain(chain_to)
|
||||
self.columns = util.populate_column_dict(self._locate_col)
|
||||
if self.include_fn or self.exclude_fn:
|
||||
self.columns = self._IncludeExcludeMapping(self, self.columns)
|
||||
self.adapt_required = adapt_required
|
||||
self.allow_label_resolve = allow_label_resolve
|
||||
self._wrap = None
|
||||
|
||||
class _IncludeExcludeMapping(object):
|
||||
def __init__(self, parent, columns):
|
||||
self.parent = parent
|
||||
self.columns = columns
|
||||
|
||||
def __getitem__(self, key):
|
||||
if (
|
||||
self.parent.include_fn and not self.parent.include_fn(key)
|
||||
) or (
|
||||
self.parent.exclude_fn and self.parent.exclude_fn(key)
|
||||
):
|
||||
if self.parent._wrap:
|
||||
return self.parent._wrap.columns[key]
|
||||
else:
|
||||
return key
|
||||
return self.columns[key]
|
||||
|
||||
def wrap(self, adapter):
|
||||
ac = self.__class__.__new__(self.__class__)
|
||||
ac.__dict__ = self.__dict__.copy()
|
||||
ac._locate_col = ac._wrap(ac._locate_col, adapter._locate_col)
|
||||
ac.adapt_clause = ac._wrap(ac.adapt_clause, adapter.adapt_clause)
|
||||
ac.adapt_list = ac._wrap(ac.adapt_list, adapter.adapt_list)
|
||||
ac.__dict__.update(self.__dict__)
|
||||
ac._wrap = adapter
|
||||
ac.columns = util.populate_column_dict(ac._locate_col)
|
||||
if ac.include_fn or ac.exclude_fn:
|
||||
ac.columns = self._IncludeExcludeMapping(ac, ac.columns)
|
||||
|
||||
return ac
|
||||
|
||||
adapt_clause = ClauseAdapter.traverse
|
||||
def traverse(self, obj):
|
||||
return self.columns[obj]
|
||||
|
||||
adapt_clause = traverse
|
||||
adapt_list = ClauseAdapter.copy_and_process
|
||||
|
||||
def _wrap(self, local, wrapped):
|
||||
def locate(col):
|
||||
col = local(col)
|
||||
return wrapped(col)
|
||||
return locate
|
||||
|
||||
def _locate_col(self, col):
|
||||
c = self._corresponding_column(col, True)
|
||||
if c is None:
|
||||
c = self.adapt_clause(col)
|
||||
|
||||
# anonymize labels in case they have a hardcoded name
|
||||
if isinstance(c, Label):
|
||||
c = c.label(None)
|
||||
c = ClauseAdapter.traverse(self, col)
|
||||
|
||||
if self._wrap:
|
||||
c2 = self._wrap._locate_col(c)
|
||||
if c2 is not None:
|
||||
c = c2
|
||||
|
||||
# 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
|
||||
|
||||
return c
|
||||
c._allow_label_resolve = self.allow_label_resolve
|
||||
|
||||
def adapted_row(self, row):
|
||||
return AliasedRow(row, self.columns)
|
||||
return c
|
||||
|
||||
def __getstate__(self):
|
||||
d = self.__dict__.copy()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# sql/visitors.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
|
|
@ -51,6 +51,7 @@ class VisitableType(type):
|
|||
|
||||
Classes having no __visit_name__ attribute will remain unaffected.
|
||||
"""
|
||||
|
||||
def __init__(cls, clsname, bases, clsdict):
|
||||
if clsname != 'Visitable' and \
|
||||
hasattr(cls, '__visit_name__'):
|
||||
|
|
@ -212,12 +213,19 @@ def iterate(obj, opts):
|
|||
traversal is configured to be breadth-first.
|
||||
|
||||
"""
|
||||
# fasttrack for atomic elements like columns
|
||||
children = obj.get_children(**opts)
|
||||
if not children:
|
||||
return [obj]
|
||||
|
||||
traversal = deque()
|
||||
stack = deque([obj])
|
||||
while stack:
|
||||
t = stack.popleft()
|
||||
yield t
|
||||
traversal.append(t)
|
||||
for c in t.get_children(**opts):
|
||||
stack.append(c)
|
||||
return iter(traversal)
|
||||
|
||||
|
||||
def iterate_depthfirst(obj, opts):
|
||||
|
|
@ -226,6 +234,11 @@ def iterate_depthfirst(obj, opts):
|
|||
traversal is configured to be depth-first.
|
||||
|
||||
"""
|
||||
# fasttrack for atomic elements like columns
|
||||
children = obj.get_children(**opts)
|
||||
if not children:
|
||||
return [obj]
|
||||
|
||||
stack = deque([obj])
|
||||
traversal = deque()
|
||||
while stack:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue