diff options
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 51 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/expression.py | 185 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/util.py | 7 |
3 files changed, 163 insertions, 80 deletions
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 59e46de12..b902f9ffc 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -423,7 +423,7 @@ class SQLCompiler(engine.Compiled): name = orig_name = column.name if name is None: raise exc.CompileError("Cannot compile Column object until " - "it's 'name' is assigned.") + "its 'name' is assigned.") is_literal = column.is_literal if not is_literal and isinstance(name, sql._truncated_label): @@ -787,14 +787,14 @@ class SQLCompiler(engine.Compiled): existing = self.binds[name] if existing is not bindparam: if (existing.unique or bindparam.unique) and \ - not existing.proxy_set.intersection(bindparam.proxy_set): + not existing.proxy_set.intersection( + bindparam.proxy_set): raise exc.CompileError( "Bind parameter '%s' conflicts with " "unique bind parameter of the same name" % bindparam.key ) - elif getattr(existing, '_is_crud', False) or \ - getattr(bindparam, '_is_crud', False): + elif existing._is_crud or bindparam._is_crud: raise exc.CompileError( "bindparam() name '%s' is reserved " "for automatic usage in the VALUES or SET " @@ -992,13 +992,15 @@ class SQLCompiler(engine.Compiled): else: self.result_map[keyname] = name, objects, type_ - def _label_select_column(self, select, column, populate_result_map, + def _label_select_column(self, select, column, + populate_result_map, asfrom, column_clause_args, + name=None, within_columns_clause=True): """produce labeled columns present in a select().""" if column.type._has_column_expression and \ - populate_result_map: + populate_result_map: col_expr = column.type.column_expression(column) add_to_result_map = lambda keyname, name, objects, type_: \ self._add_to_result_map( @@ -1023,13 +1025,11 @@ class SQLCompiler(engine.Compiled): else: result_expr = col_expr - elif select is not None and \ - select.use_labels and \ - column._label: + elif select is not None and name: result_expr = _CompileLabel( col_expr, - column._label, - alt_names=(column._key_label, ) + name, + alt_names=(column._key_label,) ) elif \ @@ -1037,7 +1037,7 @@ class SQLCompiler(engine.Compiled): isinstance(column, sql.ColumnClause) and \ not column.is_literal and \ column.table is not None and \ - not isinstance(column.table, sql.Select): + not isinstance(column.table, sql.Select): result_expr = _CompileLabel(col_expr, sql._as_truncated(column.name), alt_names=(column.key,)) @@ -1086,14 +1086,9 @@ class SQLCompiler(engine.Compiled): positional_names=None, **kwargs): entry = self.stack and self.stack[-1] or {} - if not asfrom: - existingfroms = entry.get('from', None) - else: - # don't render correlations if we're rendering a FROM list - # entry - existingfroms = [] + existingfroms = entry.get('from', None) - froms = select._get_display_froms(existingfroms) + froms = select._get_display_froms(existingfroms, asfrom=asfrom) correlate_froms = set(sql._from_objects(*froms)) @@ -1103,11 +1098,11 @@ class SQLCompiler(engine.Compiled): # correlate_froms.union(existingfroms) populate_result_map = force_result_map or ( - compound_index == 0 and ( - not entry or \ - entry.get('iswrapper', False) - ) - ) + compound_index == 0 and ( + not entry or \ + entry.get('iswrapper', False) + ) + ) self.stack.append({'from': correlate_froms, 'iswrapper': iswrapper}) @@ -1122,10 +1117,12 @@ class SQLCompiler(engine.Compiled): # the actual list of columns to print in the SELECT column list. inner_columns = [ c for c in [ - self._label_select_column(select, column, + self._label_select_column(select, + column, populate_result_map, asfrom, - column_clause_args) - for column in util.unique_list(select.inner_columns) + column_clause_args, + name=name) + for name, column in select._columns_plus_names ] if c is not None ] diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 490004e39..28b1c6ddd 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -181,10 +181,10 @@ def select(columns=None, whereclause=None, from_obj=[], **kwargs): string arguments, which will be converted as appropriate into either :func:`text()` or :func:`literal_column()` constructs. - See also: + .. seealso:: - :ref:`coretutorial_selecting` - Core Tutorial description of - :func:`.select`. + :ref:`coretutorial_selecting` - Core Tutorial description of + :func:`.select`. :param columns: A list of :class:`.ClauseElement` objects, typically @@ -464,7 +464,7 @@ def update(table, whereclause=None, values=None, inline=False, **kwargs): as_scalar() ) - See also: + .. seealso:: :ref:`inserts_and_updates` - SQL Expression Language Tutorial @@ -493,7 +493,7 @@ def delete(table, whereclause=None, **kwargs): condition of the ``UPDATE`` statement. Note that the :meth:`~Delete.where()` generative method may be used instead. - See also: + .. seealso:: :ref:`deletes` - SQL Expression Tutorial @@ -2873,6 +2873,8 @@ class BindParameter(ColumnElement): __visit_name__ = 'bindparam' quote = None + _is_crud = False + def __init__(self, key, value, type_=None, unique=False, callable_=None, isoutparam=False, required=False, @@ -3073,7 +3075,7 @@ class Executable(Generative): See :meth:`.Connection.execution_options` for a full list of possible options. - See also: + .. seealso:: :meth:`.Connection.execution_options()` @@ -3444,15 +3446,15 @@ class Case(ColumnElement): class FunctionElement(Executable, ColumnElement, FromClause): """Base for SQL function-oriented constructs. - See also: + .. seealso:: - :class:`.Function` - named SQL function. + :class:`.Function` - named SQL function. - :data:`.func` - namespace which produces registered or ad-hoc - :class:`.Function` instances. + :data:`.func` - namespace which produces registered or ad-hoc + :class:`.Function` instances. - :class:`.GenericFunction` - allows creation of registered function - types. + :class:`.GenericFunction` - allows creation of registered function + types. """ @@ -3571,15 +3573,13 @@ class Function(FunctionElement): See the superclass :class:`.FunctionElement` for a description of public methods. - See also: - - See also: + .. seealso:: - :data:`.func` - namespace which produces registered or ad-hoc - :class:`.Function` instances. + :data:`.func` - namespace which produces registered or ad-hoc + :class:`.Function` instances. - :class:`.GenericFunction` - allows creation of registered function - types. + :class:`.GenericFunction` - allows creation of registered function + types. """ @@ -4725,7 +4725,9 @@ class SelectBase(Executable, FromClause): """return a 'scalar' representation of this selectable, embedded as a subquery with a label. - See also :meth:`~.SelectBase.as_scalar`. + .. seealso:: + + :meth:`~.SelectBase.as_scalar`. """ return self.as_scalar().label(name) @@ -4843,9 +4845,9 @@ class SelectBase(Executable, FromClause): result = conn.execute(statement).fetchall() - See also: + .. seealso:: - :meth:`.orm.query.Query.cte` - ORM version of :meth:`.SelectBase.cte`. + :meth:`.orm.query.Query.cte` - ORM version of :meth:`.SelectBase.cte`. """ return CTE(self, name=name, recursive=recursive) @@ -4914,6 +4916,10 @@ class SelectBase(Executable, FromClause): The criterion will be appended to any pre-existing ORDER BY criterion. + This is an **in-place** mutation method; the + :meth:`~.SelectBase.order_by` method is preferred, as it provides standard + :term:`method chaining`. + """ if len(clauses) == 1 and clauses[0] is None: self._order_by_clause = ClauseList() @@ -4927,6 +4933,10 @@ class SelectBase(Executable, FromClause): The criterion will be appended to any pre-existing GROUP BY criterion. + This is an **in-place** mutation method; the + :meth:`~.SelectBase.group_by` method is preferred, as it provides standard + :term:`method chaining`. + """ if len(clauses) == 1 and clauses[0] is None: self._group_by_clause = ClauseList() @@ -4980,7 +4990,7 @@ class CompoundSelect(SelectBase): INTERSECT_ALL = util.symbol('INTERSECT ALL') def __init__(self, keyword, *selects, **kwargs): - self._should_correlate = kwargs.pop('correlate', False) + self._auto_correlate = kwargs.pop('correlate', False) self.keyword = keyword self.selects = [] @@ -5120,13 +5130,13 @@ class HasPrefixes(object): class Select(HasPrefixes, SelectBase): """Represents a ``SELECT`` statement. - See also: + .. seealso:: - :func:`~.expression.select` - the function which creates - a :class:`.Select` object. + :func:`~.expression.select` - the function which creates + a :class:`.Select` object. - :ref:`coretutorial_selecting` - Core Tutorial description - of :func:`.select`. + :ref:`coretutorial_selecting` - Core Tutorial description + of :func:`.select`. """ @@ -5159,7 +5169,7 @@ class Select(HasPrefixes, SelectBase): :class:`SelectBase` superclass. """ - self._should_correlate = correlate + self._auto_correlate = correlate if distinct is not False: if distinct is True: self._distinct = True @@ -5232,7 +5242,7 @@ class Select(HasPrefixes, SelectBase): return froms - def _get_display_froms(self, existing_froms=None): + def _get_display_froms(self, existing_froms=None, asfrom=False): """Return the full list of 'from' clauses to be displayed. Takes into account a set of existing froms which may be @@ -5258,18 +5268,29 @@ class Select(HasPrefixes, SelectBase): # using a list to maintain ordering froms = [f for f in froms if f not in toremove] - if len(froms) > 1 or self._correlate or self._correlate_except: + if not asfrom: if self._correlate: - froms = [f for f in froms if f not in - _cloned_intersection(froms, - self._correlate)] + froms = [ + f for f in froms if f not in + _cloned_intersection( + _cloned_intersection(froms, existing_froms or ()), + self._correlate + ) + ] if self._correlate_except: - froms = [f for f in froms if f in _cloned_intersection(froms, - self._correlate_except)] - if self._should_correlate and existing_froms: - froms = [f for f in froms if f not in - _cloned_intersection(froms, - existing_froms)] + froms = [ + f for f in froms if f in + _cloned_intersection( + froms, + self._correlate_except + ) + ] + + if self._auto_correlate and existing_froms and len(froms) > 1: + froms = [ + f for f in froms if f not in + _cloned_intersection(froms, existing_froms) + ] if not len(froms): raise exc.InvalidRequestError("Select statement '%s" @@ -5642,7 +5663,7 @@ class Select(HasPrefixes, SelectBase): :ref:`correlated_subqueries` """ - self._should_correlate = False + self._auto_correlate = False if fromclauses and fromclauses[0] is None: self._correlate = () else: @@ -5662,7 +5683,7 @@ class Select(HasPrefixes, SelectBase): :ref:`correlated_subqueries` """ - self._should_correlate = False + self._auto_correlate = False if fromclauses and fromclauses[0] is None: self._correlate_except = () else: @@ -5671,9 +5692,15 @@ class Select(HasPrefixes, SelectBase): def append_correlation(self, fromclause): """append the given correlation expression to this select() - construct.""" + construct. + + This is an **in-place** mutation method; the + :meth:`~.Select.correlate` method is preferred, as it provides standard + :term:`method chaining`. - self._should_correlate = False + """ + + self._auto_correlate = False self._correlate = set(self._correlate).union( _interpret_as_from(f) for f in fromclause) @@ -5681,6 +5708,10 @@ class Select(HasPrefixes, SelectBase): """append the given column expression to the columns clause of this select() construct. + This is an **in-place** mutation method; the + :meth:`~.Select.column` method is preferred, as it provides standard + :term:`method chaining`. + """ self._reset_exported() column = _interpret_as_column_or_from(column) @@ -5694,6 +5725,10 @@ class Select(HasPrefixes, SelectBase): """append the given columns clause prefix expression to this select() construct. + This is an **in-place** mutation method; the + :meth:`~.Select.prefix_with` method is preferred, as it provides standard + :term:`method chaining`. + """ clause = _literal_as_text(clause) self._prefixes = self._prefixes + (clause,) @@ -5704,6 +5739,10 @@ class Select(HasPrefixes, SelectBase): The expression will be joined to existing WHERE criterion via AND. + This is an **in-place** mutation method; the + :meth:`~.Select.where` method is preferred, as it provides standard + :term:`method chaining`. + """ self._reset_exported() whereclause = _literal_as_text(whereclause) @@ -5719,6 +5758,10 @@ class Select(HasPrefixes, SelectBase): The expression will be joined to existing HAVING criterion via AND. + This is an **in-place** mutation method; the + :meth:`~.Select.having` method is preferred, as it provides standard + :term:`method chaining`. + """ if self._having is not None: self._having = and_(self._having, _literal_as_text(having)) @@ -5729,18 +5772,56 @@ class Select(HasPrefixes, SelectBase): """append the given FromClause expression to this select() construct's FROM clause. + This is an **in-place** mutation method; the + :meth:`~.Select.select_from` method is preferred, as it provides standard + :term:`method chaining`. + """ self._reset_exported() fromclause = _interpret_as_from(fromclause) self._from_obj = self._from_obj.union([fromclause]) + + @_memoized_property + def _columns_plus_names(self): + if self.use_labels: + names = set() + def name_for_col(c): + if c._label is None: + return (None, c) + name = c._label + if name in names: + name = c.anon_label + else: + names.add(name) + return name, c + + return [ + name_for_col(c) + for c in util.unique_list(_select_iterables(self._raw_columns)) + ] + else: + return [ + (None, c) + for c in util.unique_list(_select_iterables(self._raw_columns)) + ] + def _populate_column_collection(self): - for c in self.inner_columns: - if hasattr(c, '_make_proxy'): - c._make_proxy(self, - name=c._label if self.use_labels else None, - key=c._key_label if self.use_labels else None, - name_is_truncatable=True) + for name, c in self._columns_plus_names: + if not hasattr(c, '_make_proxy'): + continue + if name is None: + key = None + elif self.use_labels: + key = c._key_label + if key is not None and key in self.c: + key = c.anon_label + else: + key = None + + c._make_proxy(self, key=key, + name=name, + name_is_truncatable=True) def _refresh_for_new_column(self, column): for fromclause in self._froms: @@ -6124,9 +6205,9 @@ class Insert(ValuesBase): The :class:`.Insert` object is created using the :func:`~.expression.insert()` function. - See also: + .. seealso:: - :ref:`coretutorial_insert_expressions` + :ref:`coretutorial_insert_expressions` """ __visit_name__ = 'insert' diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index fd138cfec..520c90f99 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -13,12 +13,14 @@ from collections import deque """Utility functions that build upon SQL and Schema constructs.""" -def sort_tables(tables, skip_fn=None): +def sort_tables(tables, skip_fn=None, extra_dependencies=None): """sort a collection of Table objects in order of their foreign-key dependency.""" tables = list(tables) tuples = [] + if extra_dependencies is not None: + tuples.extend(extra_dependencies) def visit_foreign_key(fkey): if fkey.use_alter: @@ -507,6 +509,9 @@ class AnnotatedColumnElement(Annotated): """pull 'key' from parent, if not present""" return self._Annotated__element.key + @util.memoized_property + def info(self): + return self._Annotated__element.info # hard-generate Annotated subclasses. this technique # is used instead of on-the-fly types (i.e. type.__new__()) |