Update sqlalchemy
This commit is contained in:
+156
-23
@@ -154,9 +154,10 @@ class _CompileLabel(visitors.Visitable):
|
||||
__visit_name__ = 'label'
|
||||
__slots__ = 'element', 'name'
|
||||
|
||||
def __init__(self, col, name):
|
||||
def __init__(self, col, name, alt_names=()):
|
||||
self.element = col
|
||||
self.name = name
|
||||
self._alt_names = alt_names
|
||||
|
||||
@property
|
||||
def proxy_set(self):
|
||||
@@ -251,6 +252,10 @@ class SQLCompiler(engine.Compiled):
|
||||
# column targeting
|
||||
self.result_map = {}
|
||||
|
||||
# collect CTEs to tack on top of a SELECT
|
||||
self.ctes = util.OrderedDict()
|
||||
self.ctes_recursive = False
|
||||
|
||||
# true if the paramstyle is positional
|
||||
self.positional = dialect.positional
|
||||
if self.positional:
|
||||
@@ -354,14 +359,16 @@ class SQLCompiler(engine.Compiled):
|
||||
# or ORDER BY clause of a select. dialect-specific compilers
|
||||
# can modify this behavior.
|
||||
if within_columns_clause and not within_label_clause:
|
||||
if isinstance(label.name, sql._generated_label):
|
||||
if isinstance(label.name, sql._truncated_label):
|
||||
labelname = self._truncated_identifier("colident", label.name)
|
||||
else:
|
||||
labelname = label.name
|
||||
|
||||
if result_map is not None:
|
||||
result_map[labelname.lower()] = \
|
||||
(label.name, (label, label.element, labelname),\
|
||||
result_map[labelname.lower()] = (
|
||||
label.name,
|
||||
(label, label.element, labelname, ) +
|
||||
label._alt_names,
|
||||
label.type)
|
||||
|
||||
return label.element._compiler_dispatch(self,
|
||||
@@ -376,17 +383,19 @@ class SQLCompiler(engine.Compiled):
|
||||
**kw)
|
||||
|
||||
def visit_column(self, column, result_map=None, **kwargs):
|
||||
name = column.name
|
||||
name = orig_name = column.name
|
||||
if name is None:
|
||||
raise exc.CompileError("Cannot compile Column object until "
|
||||
"it's 'name' is assigned.")
|
||||
|
||||
is_literal = column.is_literal
|
||||
if not is_literal and isinstance(name, sql._generated_label):
|
||||
if not is_literal and isinstance(name, sql._truncated_label):
|
||||
name = self._truncated_identifier("colident", name)
|
||||
|
||||
if result_map is not None:
|
||||
result_map[name.lower()] = (name, (column, ), column.type)
|
||||
result_map[name.lower()] = (orig_name,
|
||||
(column, name, column.key),
|
||||
column.type)
|
||||
|
||||
if is_literal:
|
||||
name = self.escape_literal_column(name)
|
||||
@@ -404,7 +413,7 @@ class SQLCompiler(engine.Compiled):
|
||||
else:
|
||||
schema_prefix = ''
|
||||
tablename = table.name
|
||||
if isinstance(tablename, sql._generated_label):
|
||||
if isinstance(tablename, sql._truncated_label):
|
||||
tablename = self._truncated_identifier("alias", tablename)
|
||||
|
||||
return schema_prefix + \
|
||||
@@ -646,7 +655,8 @@ class SQLCompiler(engine.Compiled):
|
||||
if name in self.binds:
|
||||
existing = self.binds[name]
|
||||
if existing is not bindparam:
|
||||
if existing.unique or bindparam.unique:
|
||||
if (existing.unique or bindparam.unique) and \
|
||||
not existing.proxy_set.intersection(bindparam.proxy_set):
|
||||
raise exc.CompileError(
|
||||
"Bind parameter '%s' conflicts with "
|
||||
"unique bind parameter of the same name" %
|
||||
@@ -703,7 +713,7 @@ class SQLCompiler(engine.Compiled):
|
||||
return self.bind_names[bindparam]
|
||||
|
||||
bind_name = bindparam.key
|
||||
if isinstance(bind_name, sql._generated_label):
|
||||
if isinstance(bind_name, sql._truncated_label):
|
||||
bind_name = self._truncated_identifier("bindparam", bind_name)
|
||||
|
||||
# add to bind_names for translation
|
||||
@@ -715,7 +725,7 @@ class SQLCompiler(engine.Compiled):
|
||||
if (ident_class, name) in self.truncated_names:
|
||||
return self.truncated_names[(ident_class, name)]
|
||||
|
||||
anonname = name % self.anon_map
|
||||
anonname = name.apply_map(self.anon_map)
|
||||
|
||||
if len(anonname) > self.label_length:
|
||||
counter = self.truncated_names.get(ident_class, 1)
|
||||
@@ -744,10 +754,49 @@ class SQLCompiler(engine.Compiled):
|
||||
else:
|
||||
return self.bindtemplate % {'name':name}
|
||||
|
||||
def visit_cte(self, cte, asfrom=False, ashint=False,
|
||||
fromhints=None, **kwargs):
|
||||
if isinstance(cte.name, sql._truncated_label):
|
||||
cte_name = self._truncated_identifier("alias", cte.name)
|
||||
else:
|
||||
cte_name = cte.name
|
||||
if cte.cte_alias:
|
||||
if isinstance(cte.cte_alias, sql._truncated_label):
|
||||
cte_alias = self._truncated_identifier("alias", cte.cte_alias)
|
||||
else:
|
||||
cte_alias = cte.cte_alias
|
||||
if not cte.cte_alias and cte not in self.ctes:
|
||||
if cte.recursive:
|
||||
self.ctes_recursive = True
|
||||
text = self.preparer.format_alias(cte, cte_name)
|
||||
if cte.recursive:
|
||||
if isinstance(cte.original, sql.Select):
|
||||
col_source = cte.original
|
||||
elif isinstance(cte.original, sql.CompoundSelect):
|
||||
col_source = cte.original.selects[0]
|
||||
else:
|
||||
assert False
|
||||
recur_cols = [c.key for c in util.unique_list(col_source.inner_columns)
|
||||
if c is not None]
|
||||
|
||||
text += "(%s)" % (", ".join(recur_cols))
|
||||
text += " AS \n" + \
|
||||
cte.original._compiler_dispatch(
|
||||
self, asfrom=True, **kwargs
|
||||
)
|
||||
self.ctes[cte] = text
|
||||
if asfrom:
|
||||
if cte.cte_alias:
|
||||
text = self.preparer.format_alias(cte, cte_alias)
|
||||
text += " AS " + cte_name
|
||||
else:
|
||||
return self.preparer.format_alias(cte, cte_name)
|
||||
return text
|
||||
|
||||
def visit_alias(self, alias, asfrom=False, ashint=False,
|
||||
fromhints=None, **kwargs):
|
||||
if asfrom or ashint:
|
||||
if isinstance(alias.name, sql._generated_label):
|
||||
if isinstance(alias.name, sql._truncated_label):
|
||||
alias_name = self._truncated_identifier("alias", alias.name)
|
||||
else:
|
||||
alias_name = alias.name
|
||||
@@ -775,8 +824,14 @@ class SQLCompiler(engine.Compiled):
|
||||
if isinstance(column, sql._Label):
|
||||
return column
|
||||
|
||||
elif select is not None and select.use_labels and column._label:
|
||||
return _CompileLabel(column, column._label)
|
||||
elif select is not None and \
|
||||
select.use_labels and \
|
||||
column._label:
|
||||
return _CompileLabel(
|
||||
column,
|
||||
column._label,
|
||||
alt_names=(column._key_label, )
|
||||
)
|
||||
|
||||
elif \
|
||||
asfrom and \
|
||||
@@ -784,7 +839,8 @@ class SQLCompiler(engine.Compiled):
|
||||
not column.is_literal and \
|
||||
column.table is not None and \
|
||||
not isinstance(column.table, sql.Select):
|
||||
return _CompileLabel(column, sql._generated_label(column.name))
|
||||
return _CompileLabel(column, sql._as_truncated(column.name),
|
||||
alt_names=(column.key,))
|
||||
elif not isinstance(column,
|
||||
(sql._UnaryExpression, sql._TextClause)) \
|
||||
and (not hasattr(column, 'name') or \
|
||||
@@ -799,6 +855,9 @@ class SQLCompiler(engine.Compiled):
|
||||
def get_from_hint_text(self, table, text):
|
||||
return None
|
||||
|
||||
def get_crud_hint_text(self, table, text):
|
||||
return None
|
||||
|
||||
def visit_select(self, select, asfrom=False, parens=True,
|
||||
iswrapper=False, fromhints=None,
|
||||
compound_index=1, **kwargs):
|
||||
@@ -897,6 +956,15 @@ class SQLCompiler(engine.Compiled):
|
||||
if select.for_update:
|
||||
text += self.for_update_clause(select)
|
||||
|
||||
if self.ctes and \
|
||||
compound_index==1 and not entry:
|
||||
cte_text = self.get_cte_preamble(self.ctes_recursive) + " "
|
||||
cte_text += ", \n".join(
|
||||
[txt for txt in self.ctes.values()]
|
||||
)
|
||||
cte_text += "\n "
|
||||
text = cte_text + text
|
||||
|
||||
self.stack.pop(-1)
|
||||
|
||||
if asfrom and parens:
|
||||
@@ -904,6 +972,12 @@ class SQLCompiler(engine.Compiled):
|
||||
else:
|
||||
return text
|
||||
|
||||
def get_cte_preamble(self, recursive):
|
||||
if recursive:
|
||||
return "WITH RECURSIVE"
|
||||
else:
|
||||
return "WITH"
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
"""Called when building a ``SELECT`` statement, position is just
|
||||
before column list.
|
||||
@@ -977,12 +1051,26 @@ class SQLCompiler(engine.Compiled):
|
||||
|
||||
text = "INSERT"
|
||||
|
||||
|
||||
prefixes = [self.process(x) for x in insert_stmt._prefixes]
|
||||
if prefixes:
|
||||
text += " " + " ".join(prefixes)
|
||||
|
||||
text += " INTO " + preparer.format_table(insert_stmt.table)
|
||||
|
||||
if insert_stmt._hints:
|
||||
dialect_hints = dict([
|
||||
(table, hint_text)
|
||||
for (table, dialect), hint_text in
|
||||
insert_stmt._hints.items()
|
||||
if dialect in ('*', self.dialect.name)
|
||||
])
|
||||
if insert_stmt.table in dialect_hints:
|
||||
text += " " + self.get_crud_hint_text(
|
||||
insert_stmt.table,
|
||||
dialect_hints[insert_stmt.table]
|
||||
)
|
||||
|
||||
if colparams or not supports_default_values:
|
||||
text += " (%s)" % ', '.join([preparer.format_column(c[0])
|
||||
for c in colparams])
|
||||
@@ -1014,21 +1102,25 @@ class SQLCompiler(engine.Compiled):
|
||||
extra_froms, **kw):
|
||||
"""Provide a hook to override the initial table clause
|
||||
in an UPDATE statement.
|
||||
|
||||
|
||||
MySQL overrides this.
|
||||
|
||||
"""
|
||||
return self.preparer.format_table(from_table)
|
||||
|
||||
def update_from_clause(self, update_stmt, from_table, extra_froms, **kw):
|
||||
def update_from_clause(self, update_stmt,
|
||||
from_table, extra_froms,
|
||||
from_hints,
|
||||
**kw):
|
||||
"""Provide a hook to override the generation of an
|
||||
UPDATE..FROM clause.
|
||||
|
||||
|
||||
MySQL overrides this.
|
||||
|
||||
"""
|
||||
return "FROM " + ', '.join(
|
||||
t._compiler_dispatch(self, asfrom=True, **kw)
|
||||
t._compiler_dispatch(self, asfrom=True,
|
||||
fromhints=from_hints, **kw)
|
||||
for t in extra_froms)
|
||||
|
||||
def visit_update(self, update_stmt, **kw):
|
||||
@@ -1045,6 +1137,21 @@ class SQLCompiler(engine.Compiled):
|
||||
update_stmt.table,
|
||||
extra_froms, **kw)
|
||||
|
||||
if update_stmt._hints:
|
||||
dialect_hints = dict([
|
||||
(table, hint_text)
|
||||
for (table, dialect), hint_text in
|
||||
update_stmt._hints.items()
|
||||
if dialect in ('*', self.dialect.name)
|
||||
])
|
||||
if update_stmt.table in dialect_hints:
|
||||
text += " " + self.get_crud_hint_text(
|
||||
update_stmt.table,
|
||||
dialect_hints[update_stmt.table]
|
||||
)
|
||||
else:
|
||||
dialect_hints = None
|
||||
|
||||
text += ' SET '
|
||||
if extra_froms and self.render_table_with_column_in_update_from:
|
||||
text += ', '.join(
|
||||
@@ -1067,7 +1174,8 @@ class SQLCompiler(engine.Compiled):
|
||||
extra_from_text = self.update_from_clause(
|
||||
update_stmt,
|
||||
update_stmt.table,
|
||||
extra_froms, **kw)
|
||||
extra_froms,
|
||||
dialect_hints, **kw)
|
||||
if extra_from_text:
|
||||
text += " " + extra_from_text
|
||||
|
||||
@@ -1133,7 +1241,6 @@ class SQLCompiler(engine.Compiled):
|
||||
for k, v in stmt.parameters.iteritems():
|
||||
parameters.setdefault(sql._column_as_key(k), v)
|
||||
|
||||
|
||||
# create a list of column assignment clauses as tuples
|
||||
values = []
|
||||
|
||||
@@ -1192,7 +1299,7 @@ class SQLCompiler(engine.Compiled):
|
||||
# "defaults", "primary key cols", etc.
|
||||
for c in stmt.table.columns:
|
||||
if c.key in parameters and c.key not in check_columns:
|
||||
value = parameters[c.key]
|
||||
value = parameters.pop(c.key)
|
||||
if sql._is_literal(value):
|
||||
value = self._create_crud_bind_param(
|
||||
c, value, required=value is required)
|
||||
@@ -1288,6 +1395,17 @@ class SQLCompiler(engine.Compiled):
|
||||
self.prefetch.append(c)
|
||||
elif c.server_onupdate is not None:
|
||||
self.postfetch.append(c)
|
||||
|
||||
if parameters and stmt.parameters:
|
||||
check = set(parameters).intersection(
|
||||
sql._column_as_key(k) for k in stmt.parameters
|
||||
).difference(check_columns)
|
||||
if check:
|
||||
util.warn(
|
||||
"Unconsumed column names: %s" %
|
||||
(", ".join(check))
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
def visit_delete(self, delete_stmt):
|
||||
@@ -1296,6 +1414,21 @@ class SQLCompiler(engine.Compiled):
|
||||
|
||||
text = "DELETE FROM " + self.preparer.format_table(delete_stmt.table)
|
||||
|
||||
if delete_stmt._hints:
|
||||
dialect_hints = dict([
|
||||
(table, hint_text)
|
||||
for (table, dialect), hint_text in
|
||||
delete_stmt._hints.items()
|
||||
if dialect in ('*', self.dialect.name)
|
||||
])
|
||||
if delete_stmt.table in dialect_hints:
|
||||
text += " " + self.get_crud_hint_text(
|
||||
delete_stmt.table,
|
||||
dialect_hints[delete_stmt.table]
|
||||
)
|
||||
else:
|
||||
dialect_hints = None
|
||||
|
||||
if delete_stmt._returning:
|
||||
self.returning = delete_stmt._returning
|
||||
if self.returning_precedes_values:
|
||||
@@ -1445,7 +1578,7 @@ class DDLCompiler(engine.Compiled):
|
||||
return "\nDROP TABLE " + self.preparer.format_table(drop.element)
|
||||
|
||||
def _index_identifier(self, ident):
|
||||
if isinstance(ident, sql._generated_label):
|
||||
if isinstance(ident, sql._truncated_label):
|
||||
max = self.dialect.max_index_name_length or \
|
||||
self.dialect.max_identifier_length
|
||||
if len(ident) > max:
|
||||
|
||||
@@ -832,6 +832,14 @@ def tuple_(*expr):
|
||||
[(1, 2), (5, 12), (10, 19)]
|
||||
)
|
||||
|
||||
.. warning::
|
||||
|
||||
The composite IN construct is not supported by all backends,
|
||||
and is currently known to work on Postgresql and MySQL,
|
||||
but not SQLite. Unsupported backends will raise
|
||||
a subclass of :class:`~sqlalchemy.exc.DBAPIError` when such
|
||||
an expression is invoked.
|
||||
|
||||
"""
|
||||
return _Tuple(*expr)
|
||||
|
||||
@@ -1275,14 +1283,48 @@ func = _FunctionGenerator()
|
||||
# TODO: use UnaryExpression for this instead ?
|
||||
modifier = _FunctionGenerator(group=False)
|
||||
|
||||
class _generated_label(unicode):
|
||||
"""A unicode subclass used to identify dynamically generated names."""
|
||||
class _truncated_label(unicode):
|
||||
"""A unicode subclass used to identify symbolic "
|
||||
"names that may require truncation."""
|
||||
|
||||
def _escape_for_generated(x):
|
||||
if isinstance(x, _generated_label):
|
||||
return x
|
||||
def apply_map(self, map_):
|
||||
return self
|
||||
|
||||
# for backwards compatibility in case
|
||||
# someone is re-implementing the
|
||||
# _truncated_identifier() sequence in a custom
|
||||
# compiler
|
||||
_generated_label = _truncated_label
|
||||
|
||||
class _anonymous_label(_truncated_label):
|
||||
"""A unicode subclass used to identify anonymously
|
||||
generated names."""
|
||||
|
||||
def __add__(self, other):
|
||||
return _anonymous_label(
|
||||
unicode(self) +
|
||||
unicode(other))
|
||||
|
||||
def __radd__(self, other):
|
||||
return _anonymous_label(
|
||||
unicode(other) +
|
||||
unicode(self))
|
||||
|
||||
def apply_map(self, map_):
|
||||
return self % map_
|
||||
|
||||
def _as_truncated(value):
|
||||
"""coerce the given value to :class:`._truncated_label`.
|
||||
|
||||
Existing :class:`._truncated_label` and
|
||||
:class:`._anonymous_label` objects are passed
|
||||
unchanged.
|
||||
"""
|
||||
|
||||
if isinstance(value, _truncated_label):
|
||||
return value
|
||||
else:
|
||||
return x.replace('%', '%%')
|
||||
return _truncated_label(value)
|
||||
|
||||
def _string_or_unprintable(element):
|
||||
if isinstance(element, basestring):
|
||||
@@ -1466,6 +1508,7 @@ class ClauseElement(Visitable):
|
||||
supports_execution = False
|
||||
_from_objects = []
|
||||
bind = None
|
||||
_is_clone_of = None
|
||||
|
||||
def _clone(self):
|
||||
"""Create a shallow copy of this ClauseElement.
|
||||
@@ -1514,7 +1557,7 @@ class ClauseElement(Visitable):
|
||||
f = self
|
||||
while f is not None:
|
||||
s.add(f)
|
||||
f = getattr(f, '_is_clone_of', None)
|
||||
f = f._is_clone_of
|
||||
return s
|
||||
|
||||
def __getstate__(self):
|
||||
@@ -2063,6 +2106,8 @@ class ColumnElement(ClauseElement, _CompareMixin):
|
||||
foreign_keys = []
|
||||
quote = None
|
||||
_label = None
|
||||
_key_label = None
|
||||
_alt_names = ()
|
||||
|
||||
@property
|
||||
def _select_iterable(self):
|
||||
@@ -2109,9 +2154,14 @@ class ColumnElement(ClauseElement, _CompareMixin):
|
||||
else:
|
||||
key = name
|
||||
|
||||
co = ColumnClause(name, selectable, type_=getattr(self,
|
||||
co = ColumnClause(_as_truncated(name),
|
||||
selectable,
|
||||
type_=getattr(self,
|
||||
'type', None))
|
||||
co.proxies = [self]
|
||||
if selectable._is_clone_of is not None:
|
||||
co._is_clone_of = \
|
||||
selectable._is_clone_of.columns[key]
|
||||
selectable._columns[key] = co
|
||||
return co
|
||||
|
||||
@@ -2157,7 +2207,7 @@ class ColumnElement(ClauseElement, _CompareMixin):
|
||||
expressions and function calls.
|
||||
|
||||
"""
|
||||
return _generated_label('%%(%d %s)s' % (id(self), getattr(self,
|
||||
return _anonymous_label('%%(%d %s)s' % (id(self), getattr(self,
|
||||
'name', 'anon')))
|
||||
|
||||
class ColumnCollection(util.OrderedProperties):
|
||||
@@ -2420,6 +2470,13 @@ class FromClause(Selectable):
|
||||
|
||||
"""
|
||||
|
||||
def embedded(expanded_proxy_set, target_set):
|
||||
for t in target_set.difference(expanded_proxy_set):
|
||||
if not set(_expand_cloned([t])
|
||||
).intersection(expanded_proxy_set):
|
||||
return False
|
||||
return True
|
||||
|
||||
# dont dig around if the column is locally present
|
||||
if self.c.contains_column(column):
|
||||
return column
|
||||
@@ -2427,10 +2484,10 @@ class FromClause(Selectable):
|
||||
target_set = column.proxy_set
|
||||
cols = self.c
|
||||
for c in cols:
|
||||
i = target_set.intersection(itertools.chain(*[p._cloned_set
|
||||
for p in c.proxy_set]))
|
||||
expanded_proxy_set = set(_expand_cloned(c.proxy_set))
|
||||
i = target_set.intersection(expanded_proxy_set)
|
||||
if i and (not require_embedded
|
||||
or c.proxy_set.issuperset(target_set)):
|
||||
or embedded(expanded_proxy_set, target_set)):
|
||||
if col is None:
|
||||
|
||||
# no corresponding column yet, pick this one.
|
||||
@@ -2580,10 +2637,10 @@ class _BindParamClause(ColumnElement):
|
||||
|
||||
"""
|
||||
if unique:
|
||||
self.key = _generated_label('%%(%d %s)s' % (id(self), key
|
||||
self.key = _anonymous_label('%%(%d %s)s' % (id(self), key
|
||||
or 'param'))
|
||||
else:
|
||||
self.key = key or _generated_label('%%(%d param)s'
|
||||
self.key = key or _anonymous_label('%%(%d param)s'
|
||||
% id(self))
|
||||
|
||||
# identifiying key that won't change across
|
||||
@@ -2631,14 +2688,14 @@ class _BindParamClause(ColumnElement):
|
||||
def _clone(self):
|
||||
c = ClauseElement._clone(self)
|
||||
if self.unique:
|
||||
c.key = _generated_label('%%(%d %s)s' % (id(c), c._orig_key
|
||||
c.key = _anonymous_label('%%(%d %s)s' % (id(c), c._orig_key
|
||||
or 'param'))
|
||||
return c
|
||||
|
||||
def _convert_to_unique(self):
|
||||
if not self.unique:
|
||||
self.unique = True
|
||||
self.key = _generated_label('%%(%d %s)s' % (id(self),
|
||||
self.key = _anonymous_label('%%(%d %s)s' % (id(self),
|
||||
self._orig_key or 'param'))
|
||||
|
||||
def compare(self, other, **kw):
|
||||
@@ -3607,7 +3664,7 @@ class Alias(FromClause):
|
||||
if name is None:
|
||||
if self.original.named_with_column:
|
||||
name = getattr(self.original, 'name', None)
|
||||
name = _generated_label('%%(%d %s)s' % (id(self), name
|
||||
name = _anonymous_label('%%(%d %s)s' % (id(self), name
|
||||
or 'anon'))
|
||||
self.name = name
|
||||
|
||||
@@ -3662,6 +3719,47 @@ class Alias(FromClause):
|
||||
def bind(self):
|
||||
return self.element.bind
|
||||
|
||||
class CTE(Alias):
|
||||
"""Represent a Common Table Expression.
|
||||
|
||||
The :class:`.CTE` object is obtained using the
|
||||
:meth:`._SelectBase.cte` method from any selectable.
|
||||
See that method for complete examples.
|
||||
|
||||
New in 0.7.6.
|
||||
|
||||
"""
|
||||
__visit_name__ = 'cte'
|
||||
def __init__(self, selectable,
|
||||
name=None,
|
||||
recursive=False,
|
||||
cte_alias=False):
|
||||
self.recursive = recursive
|
||||
self.cte_alias = cte_alias
|
||||
super(CTE, self).__init__(selectable, name=name)
|
||||
|
||||
def alias(self, name=None):
|
||||
return CTE(
|
||||
self.original,
|
||||
name=name,
|
||||
recursive=self.recursive,
|
||||
cte_alias = self.name
|
||||
)
|
||||
|
||||
def union(self, other):
|
||||
return CTE(
|
||||
self.original.union(other),
|
||||
name=self.name,
|
||||
recursive=self.recursive
|
||||
)
|
||||
|
||||
def union_all(self, other):
|
||||
return CTE(
|
||||
self.original.union_all(other),
|
||||
name=self.name,
|
||||
recursive=self.recursive
|
||||
)
|
||||
|
||||
|
||||
class _Grouping(ColumnElement):
|
||||
"""Represent a grouping within a column expression"""
|
||||
@@ -3807,9 +3905,12 @@ class _Label(ColumnElement):
|
||||
def __init__(self, name, element, type_=None):
|
||||
while isinstance(element, _Label):
|
||||
element = element.element
|
||||
self.name = self.key = self._label = name \
|
||||
or _generated_label('%%(%d %s)s' % (id(self),
|
||||
if name:
|
||||
self.name = 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_
|
||||
self.quote = element.quote
|
||||
@@ -3956,8 +4057,18 @@ class ColumnClause(_Immutable, ColumnElement):
|
||||
return self.name.encode('ascii', 'backslashreplace')
|
||||
# end Py2K
|
||||
|
||||
@_memoized_property
|
||||
def _key_label(self):
|
||||
if self.key != self.name:
|
||||
return self._gen_label(self.key)
|
||||
else:
|
||||
return self._label
|
||||
|
||||
@_memoized_property
|
||||
def _label(self):
|
||||
return self._gen_label(self.name)
|
||||
|
||||
def _gen_label(self, name):
|
||||
t = self.table
|
||||
if self.is_literal:
|
||||
return None
|
||||
@@ -3965,11 +4076,9 @@ class ColumnClause(_Immutable, ColumnElement):
|
||||
elif t is not None and t.named_with_column:
|
||||
if getattr(t, 'schema', None):
|
||||
label = t.schema.replace('.', '_') + "_" + \
|
||||
_escape_for_generated(t.name) + "_" + \
|
||||
_escape_for_generated(self.name)
|
||||
t.name + "_" + name
|
||||
else:
|
||||
label = _escape_for_generated(t.name) + "_" + \
|
||||
_escape_for_generated(self.name)
|
||||
label = t.name + "_" + name
|
||||
|
||||
# ensure the label name doesn't conflict with that
|
||||
# of an existing column
|
||||
@@ -3981,10 +4090,10 @@ class ColumnClause(_Immutable, ColumnElement):
|
||||
counter += 1
|
||||
label = _label
|
||||
|
||||
return _generated_label(label)
|
||||
return _as_truncated(label)
|
||||
|
||||
else:
|
||||
return self.name
|
||||
return name
|
||||
|
||||
def label(self, name):
|
||||
# currently, anonymous labels don't occur for
|
||||
@@ -4010,12 +4119,15 @@ class ColumnClause(_Immutable, ColumnElement):
|
||||
# otherwise its considered to be a label
|
||||
is_literal = self.is_literal and (name is None or name == self.name)
|
||||
c = self._constructor(
|
||||
name or self.name,
|
||||
_as_truncated(name or self.name),
|
||||
selectable=selectable,
|
||||
type_=self.type,
|
||||
is_literal=is_literal
|
||||
)
|
||||
c.proxies = [self]
|
||||
if selectable._is_clone_of is not None:
|
||||
c._is_clone_of = \
|
||||
selectable._is_clone_of.columns[c.name]
|
||||
|
||||
if attach:
|
||||
selectable._columns[c.name] = c
|
||||
@@ -4218,6 +4330,125 @@ class _SelectBase(Executable, FromClause):
|
||||
"""
|
||||
return self.as_scalar().label(name)
|
||||
|
||||
def cte(self, name=None, recursive=False):
|
||||
"""Return a new :class:`.CTE`, or Common Table Expression instance.
|
||||
|
||||
Common table expressions are a SQL standard whereby SELECT
|
||||
statements can draw upon secondary statements specified along
|
||||
with the primary statement, using a clause called "WITH".
|
||||
Special semantics regarding UNION can also be employed to
|
||||
allow "recursive" queries, where a SELECT statement can draw
|
||||
upon the set of rows that have previously been selected.
|
||||
|
||||
SQLAlchemy detects :class:`.CTE` objects, which are treated
|
||||
similarly to :class:`.Alias` objects, as special elements
|
||||
to be delivered to the FROM clause of the statement as well
|
||||
as to a WITH clause at the top of the statement.
|
||||
|
||||
The :meth:`._SelectBase.cte` method is new in 0.7.6.
|
||||
|
||||
:param name: name given to the common table expression. Like
|
||||
:meth:`._FromClause.alias`, the name can be left as ``None``
|
||||
in which case an anonymous symbol will be used at query
|
||||
compile time.
|
||||
:param recursive: if ``True``, will render ``WITH RECURSIVE``.
|
||||
A recursive common table expression is intended to be used in
|
||||
conjunction with UNION ALL in order to derive rows
|
||||
from those already selected.
|
||||
|
||||
The following examples illustrate two examples from
|
||||
Postgresql's documentation at
|
||||
http://www.postgresql.org/docs/8.4/static/queries-with.html.
|
||||
|
||||
Example 1, non recursive::
|
||||
|
||||
from sqlalchemy import Table, Column, String, Integer, MetaData, \\
|
||||
select, func
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
orders = Table('orders', metadata,
|
||||
Column('region', String),
|
||||
Column('amount', Integer),
|
||||
Column('product', String),
|
||||
Column('quantity', Integer)
|
||||
)
|
||||
|
||||
regional_sales = select([
|
||||
orders.c.region,
|
||||
func.sum(orders.c.amount).label('total_sales')
|
||||
]).group_by(orders.c.region).cte("regional_sales")
|
||||
|
||||
|
||||
top_regions = select([regional_sales.c.region]).\\
|
||||
where(
|
||||
regional_sales.c.total_sales >
|
||||
select([
|
||||
func.sum(regional_sales.c.total_sales)/10
|
||||
])
|
||||
).cte("top_regions")
|
||||
|
||||
statement = select([
|
||||
orders.c.region,
|
||||
orders.c.product,
|
||||
func.sum(orders.c.quantity).label("product_units"),
|
||||
func.sum(orders.c.amount).label("product_sales")
|
||||
]).where(orders.c.region.in_(
|
||||
select([top_regions.c.region])
|
||||
)).group_by(orders.c.region, orders.c.product)
|
||||
|
||||
result = conn.execute(statement).fetchall()
|
||||
|
||||
Example 2, WITH RECURSIVE::
|
||||
|
||||
from sqlalchemy import Table, Column, String, Integer, MetaData, \\
|
||||
select, func
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
parts = Table('parts', metadata,
|
||||
Column('part', String),
|
||||
Column('sub_part', String),
|
||||
Column('quantity', Integer),
|
||||
)
|
||||
|
||||
included_parts = select([
|
||||
parts.c.sub_part,
|
||||
parts.c.part,
|
||||
parts.c.quantity]).\\
|
||||
where(parts.c.part=='our part').\\
|
||||
cte(recursive=True)
|
||||
|
||||
|
||||
incl_alias = included_parts.alias()
|
||||
parts_alias = parts.alias()
|
||||
included_parts = included_parts.union_all(
|
||||
select([
|
||||
parts_alias.c.part,
|
||||
parts_alias.c.sub_part,
|
||||
parts_alias.c.quantity
|
||||
]).
|
||||
where(parts_alias.c.part==incl_alias.c.sub_part)
|
||||
)
|
||||
|
||||
statement = select([
|
||||
included_parts.c.sub_part,
|
||||
func.sum(included_parts.c.quantity).label('total_quantity')
|
||||
]).\
|
||||
select_from(included_parts.join(parts,
|
||||
included_parts.c.part==parts.c.part)).\\
|
||||
group_by(included_parts.c.sub_part)
|
||||
|
||||
result = conn.execute(statement).fetchall()
|
||||
|
||||
|
||||
See also:
|
||||
|
||||
:meth:`.orm.query.Query.cte` - ORM version of :meth:`._SelectBase.cte`.
|
||||
|
||||
"""
|
||||
return CTE(self, name=name, recursive=recursive)
|
||||
|
||||
@_generative
|
||||
@util.deprecated('0.6',
|
||||
message=":func:`.autocommit` is deprecated. Use "
|
||||
@@ -4602,7 +4833,7 @@ class Select(_SelectBase):
|
||||
The text of the hint is rendered in the appropriate
|
||||
location for the database backend in use, relative
|
||||
to the given :class:`.Table` or :class:`.Alias` passed as the
|
||||
*selectable* argument. The dialect implementation
|
||||
``selectable`` argument. The dialect implementation
|
||||
typically uses Python string substitution syntax
|
||||
with the token ``%(name)s`` to render the name of
|
||||
the table or alias. E.g. when using Oracle, the
|
||||
@@ -4999,7 +5230,9 @@ class Select(_SelectBase):
|
||||
def _populate_column_collection(self):
|
||||
for c in self.inner_columns:
|
||||
if hasattr(c, '_make_proxy'):
|
||||
c._make_proxy(self, name=self.use_labels and c._label or None)
|
||||
c._make_proxy(self,
|
||||
name=self.use_labels
|
||||
and c._label or None)
|
||||
|
||||
def self_group(self, against=None):
|
||||
"""return a 'grouping' construct as per the ClauseElement
|
||||
@@ -5086,6 +5319,7 @@ class UpdateBase(Executable, ClauseElement):
|
||||
_execution_options = \
|
||||
Executable._execution_options.union({'autocommit': True})
|
||||
kwargs = util.immutabledict()
|
||||
_hints = util.immutabledict()
|
||||
|
||||
def _process_colparams(self, parameters):
|
||||
if isinstance(parameters, (list, tuple)):
|
||||
@@ -5166,6 +5400,45 @@ class UpdateBase(Executable, ClauseElement):
|
||||
"""
|
||||
self._returning = cols
|
||||
|
||||
@_generative
|
||||
def with_hint(self, text, selectable=None, dialect_name="*"):
|
||||
"""Add a table hint for a single table to this
|
||||
INSERT/UPDATE/DELETE statement.
|
||||
|
||||
.. note::
|
||||
|
||||
:meth:`.UpdateBase.with_hint` currently applies only to
|
||||
Microsoft SQL Server. For MySQL INSERT hints, use
|
||||
:meth:`.Insert.prefix_with`. UPDATE/DELETE hints for
|
||||
MySQL will be added in a future release.
|
||||
|
||||
The text of the hint is rendered in the appropriate
|
||||
location for the database backend in use, relative
|
||||
to the :class:`.Table` that is the subject of this
|
||||
statement, or optionally to that of the given
|
||||
:class:`.Table` passed as the ``selectable`` argument.
|
||||
|
||||
The ``dialect_name`` option will limit the rendering of a particular
|
||||
hint to a particular backend. Such as, to add a hint
|
||||
that only takes effect for SQL Server::
|
||||
|
||||
mytable.insert().with_hint("WITH (PAGLOCK)", dialect_name="mssql")
|
||||
|
||||
New in 0.7.6.
|
||||
|
||||
:param text: Text of the hint.
|
||||
:param selectable: optional :class:`.Table` that specifies
|
||||
an element of the FROM clause within an UPDATE or DELETE
|
||||
to be the subject of the hint - applies only to certain backends.
|
||||
:param dialect_name: defaults to ``*``, if specified as the name
|
||||
of a particular dialect, will apply these hints only when
|
||||
that dialect is in use.
|
||||
"""
|
||||
if selectable is None:
|
||||
selectable = self.table
|
||||
|
||||
self._hints = self._hints.union({(selectable, dialect_name):text})
|
||||
|
||||
class ValuesBase(UpdateBase):
|
||||
"""Supplies support for :meth:`.ValuesBase.values` to INSERT and UPDATE constructs."""
|
||||
|
||||
|
||||
@@ -34,11 +34,19 @@ __all__ = ['VisitableType', 'Visitable', 'ClauseVisitor',
|
||||
'cloned_traverse', 'replacement_traverse']
|
||||
|
||||
class VisitableType(type):
|
||||
"""Metaclass which checks for a `__visit_name__` attribute and
|
||||
applies `_compiler_dispatch` method to classes.
|
||||
"""Metaclass which assigns a `_compiler_dispatch` method to classes
|
||||
having a `__visit_name__` attribute.
|
||||
|
||||
The _compiler_dispatch attribute becomes an instance method which
|
||||
looks approximately like the following::
|
||||
|
||||
def _compiler_dispatch (self, visitor, **kw):
|
||||
'''Look for an attribute named "visit_" + self.__visit_name__
|
||||
on the visitor, and call it with the same kw params.'''
|
||||
return getattr(visitor, 'visit_%s' % self.__visit_name__)(self, **kw)
|
||||
|
||||
Classes having no __visit_name__ attribute will remain unaffected.
|
||||
"""
|
||||
|
||||
def __init__(cls, clsname, bases, clsdict):
|
||||
if cls.__name__ == 'Visitable' or not hasattr(cls, '__visit_name__'):
|
||||
super(VisitableType, cls).__init__(clsname, bases, clsdict)
|
||||
@@ -48,19 +56,31 @@ class VisitableType(type):
|
||||
|
||||
super(VisitableType, cls).__init__(clsname, bases, clsdict)
|
||||
|
||||
|
||||
def _generate_dispatch(cls):
|
||||
# set up an optimized visit dispatch function
|
||||
# for use by the compiler
|
||||
"""Return an optimized visit dispatch function for the cls
|
||||
for use by the compiler.
|
||||
"""
|
||||
if '__visit_name__' in cls.__dict__:
|
||||
visit_name = cls.__visit_name__
|
||||
if isinstance(visit_name, str):
|
||||
# There is an optimization opportunity here because the
|
||||
# the string name of the class's __visit_name__ is known at
|
||||
# this early stage (import time) so it can be pre-constructed.
|
||||
getter = operator.attrgetter("visit_%s" % visit_name)
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return getter(visitor)(self, **kw)
|
||||
else:
|
||||
# The optimization opportunity is lost for this case because the
|
||||
# __visit_name__ is not yet a string. As a result, the visit
|
||||
# string has to be recalculated with each compilation.
|
||||
def _compiler_dispatch(self, visitor, **kw):
|
||||
return getattr(visitor, 'visit_%s' % self.__visit_name__)(self, **kw)
|
||||
|
||||
_compiler_dispatch.__doc__ = \
|
||||
"""Look for an attribute named "visit_" + self.__visit_name__
|
||||
on the visitor, and call it with the same kw params.
|
||||
"""
|
||||
cls._compiler_dispatch = _compiler_dispatch
|
||||
|
||||
class Visitable(object):
|
||||
|
||||
Reference in New Issue
Block a user