From 50866d2f857dd45bb2d186a0fa076768437d62a3 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 10 Mar 2015 15:24:28 -0400 Subject: - copyright 2015 --- lib/sqlalchemy/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index fbbe15da3..bec5b5824 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -1,5 +1,5 @@ # sql/util.py -# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors +# Copyright (C) 2005-2015 the SQLAlchemy authors and contributors # # # This module is part of SQLAlchemy and is released under -- cgit v1.2.1 From 34f98a63b54a17c06e48eab5a29e9c090488b4bd Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 27 Apr 2015 17:32:05 -0400 Subject: - altered part of the use contract first set up in #2992; we now skip textual label references when copying ORDER BY elements to the joined-eager-load subquery, as we can't know that these expressions are compatible with this placement; either because they are meant for text(), or because they refer to label names already stated and aren't bound to a table. fixes #3392 --- lib/sqlalchemy/sql/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index bec5b5824..8f502fc86 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -16,7 +16,8 @@ from itertools import chain from collections import deque from .elements import BindParameter, ColumnClause, ColumnElement, \ - Null, UnaryExpression, literal_column, Label, _label_reference + Null, UnaryExpression, literal_column, Label, _label_reference, \ + _textual_label_reference from .selectable import ScalarSelect, Join, FromClause, FromGrouping from .schema import Column @@ -163,6 +164,8 @@ def unwrap_order_by(clause): ): 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(): -- cgit v1.2.1 From 7c4512cbeb1cf9e4e988e833589ddc6377b5e525 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 26 Aug 2015 16:58:13 -0400 Subject: - Added support for "set-aggregate" functions of the form `` WITHIN GROUP (ORDER BY )``, using the method :class:`.FunctionElement.within_group`. A series of common set-aggregate functions with return types derived from the set have been added. This includes functions like :class:`.percentile_cont`, :class:`.dense_rank` and others. fixes #1370 - make sure we use func.name for all _literal_as_binds in functions.py so we get consistent naming behavior for parameters. --- lib/sqlalchemy/sql/util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 8f502fc86..cbd74faac 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -154,6 +154,7 @@ def unwrap_order_by(clause): without DESC/ASC/NULLS FIRST/NULLS LAST""" cols = util.column_set() + result = [] stack = deque([clause]) while stack: t = stack.popleft() @@ -166,11 +167,13 @@ def unwrap_order_by(clause): t = t.element if isinstance(t, (_textual_label_reference)): continue - cols.add(t) + if t not in cols: + cols.add(t) + result.append(t) else: for c in t.get_children(): stack.append(c) - return cols + return result def clause_is_present(clause, search): -- cgit v1.2.1 From 33c378f768c699f3590f168f6c3c86448239268c Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 11 Nov 2015 12:57:32 -0500 Subject: - Fixed bug where the "single table inheritance" criteria would be added onto the end of a query in some inappropriate situations, such as when querying from an exists() of a single-inheritance subclass. fixes #3582 --- lib/sqlalchemy/sql/util.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index cbd74faac..5def70444 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -203,6 +203,21 @@ def surface_selectables(clause): stack.append(elem.element) +def surface_column_elements(clause): + """traverse and yield only outer-exposed column elements, such as would + be addressable in the WHERE clause of a SELECT if this element were + in the columns clause.""" + + stack = deque([clause]) + while stack: + elem = stack.popleft() + yield elem + for sub in elem.get_children(): + if isinstance(elem, FromGrouping): + continue + stack.append(sub) + + def selectables_overlap(left, right): """Return True if left/right have some overlapping selectable""" -- cgit v1.2.1 From 3370cbde509b3e230c46a503956f11f78df74b88 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 11 Nov 2015 14:21:02 -0500 Subject: - correct the commit from ref #3582 to refer to the correct sub-element --- lib/sqlalchemy/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 5def70444..676e45a24 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -213,7 +213,7 @@ def surface_column_elements(clause): elem = stack.popleft() yield elem for sub in elem.get_children(): - if isinstance(elem, FromGrouping): + if isinstance(sub, FromGrouping): continue stack.append(sub) -- cgit v1.2.1 From c90f0a49f332867f6b337c79ddf192299788667f Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 28 Nov 2015 14:30:05 -0500 Subject: - Added support for parameter-ordered SET clauses in an UPDATE statement. This feature is available by passing the :paramref:`~.sqlalchemy.sql.expression.update.preserve_parameter_order` flag either to the core :class:`.Update` construct or alternatively adding it to the :paramref:`.Query.update.update_args` dictionary at the ORM-level, also passing the parameters themselves as a list of 2-tuples. Thanks to Gorka Eguileor for implementation and tests. adapted from pullreq github:200 --- lib/sqlalchemy/sql/util.py | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 676e45a24..f5aa9f228 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -451,7 +451,6 @@ def criterion_as_pairs(expression, consider_as_foreign_keys=None, return pairs - class ClauseAdapter(visitors.ReplacingCloningVisitor): """Clones and modifies clauses based on column correspondence. -- cgit v1.2.1 From 859379e2fcc4506d036700ba1eca4c0ae526a8ee Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 29 Jan 2016 11:20:22 -0500 Subject: - happy new year --- lib/sqlalchemy/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index f5aa9f228..1dcf0ee66 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -1,5 +1,5 @@ # sql/util.py -# Copyright (C) 2005-2015 the SQLAlchemy authors and contributors +# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors # # # This module is part of SQLAlchemy and is released under -- cgit v1.2.1 From ff3be95620b6505943b2d7e4688abc29dca3e493 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 9 Feb 2016 17:49:38 -0500 Subject: - A refinement to the logic which adds columns to the resulting SQL when :meth:`.Query.distinct` is combined with :meth:`.Query.order_by` such that columns which are already present will not be added a second time, even if they are labeled with a different name. Regardless of this change, the extra columns added to the SQL have never been returned in the final result, so this change only impacts the string form of the statement as well as its behavior when used in a Core execution context. Additionally, columns are no longer added when the DISTINCT ON format is used, provided the query is not wrapped inside a subquery due to joined eager loading. fixes #3641 --- lib/sqlalchemy/sql/util.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 1dcf0ee66..7e294d85f 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -176,6 +176,28 @@ def unwrap_order_by(clause): return result +def expand_column_list_from_order_by(collist, order_by): + """Given the columns clause and ORDER BY of a selectable, + return a list of column expressions that can be added to the collist + corresponding to the ORDER BY, without repeating those already + in the collist. + + """ + cols_already_present = set([ + col.element if col._order_by_label_element is not None + else col for col in collist + ]) + + return [ + col for col in + chain(*[ + unwrap_order_by(o) + for o in order_by + ]) + if col not in cols_already_present + ] + + def clause_is_present(clause, search): """Given a target clause and a second to search within, return True if the target is plainly present in the search without any -- cgit v1.2.1 From 591e0cf08a798fb16e0ee9b56df5c3141aa48959 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 17 Feb 2016 13:31:29 -0500 Subject: - All string formatting of bound parameter sets and result rows for logging, exception, and ``repr()`` purposes now truncate very large scalar values within each collection, including an "N characters truncated" notation, similar to how the display for large multiple-parameter sets are themselves truncated. fixes #2837 --- lib/sqlalchemy/sql/util.py | 117 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 10 deletions(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 7e294d85f..98d9cc944 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -279,28 +279,125 @@ def _quote_ddl_expr(element): return repr(element) -class _repr_params(object): - """A string view of bound parameters, truncating - display to the given number of 'multi' parameter sets. +class _repr_base(object): + _LIST = 0 + _TUPLE = 1 + _DICT = 2 + + __slots__ = 'max_chars', + + def trunc(self, value): + rep = repr(value) + lenrep = len(rep) + if lenrep > self.max_chars: + segment_length = self.max_chars // 2 + rep = ( + rep[0:segment_length] + + (" ... (%d characters truncated) ... " + % (lenrep - self.max_chars)) + + rep[-segment_length:] + ) + return rep + + +class _repr_row(_repr_base): + """Provide a string view of a row.""" + + __slots__ = 'row', + + def __init__(self, row, max_chars=300): + self.row = row + self.max_chars = max_chars + + def __repr__(self): + trunc = self.trunc + return "(%s)" % ( + "".join(trunc(value) + "," for value in self.row) + ) + + +class _repr_params(_repr_base): + """Provide a string view of bound parameters. + + Truncates display to a given numnber of 'multi' parameter sets, + as well as long values to a given number of characters. """ - def __init__(self, params, batches): + __slots__ = 'params', 'batches', + + def __init__(self, params, batches, max_chars=300): self.params = params self.batches = batches + self.max_chars = max_chars def __repr__(self): - if isinstance(self.params, (list, tuple)) and \ - len(self.params) > self.batches and \ - isinstance(self.params[0], (list, dict, tuple)): + if isinstance(self.params, list): + typ = self._LIST + ismulti = self.params and isinstance( + self.params[0], (list, dict, tuple)) + elif isinstance(self.params, tuple): + typ = self._TUPLE + ismulti = self.params and isinstance( + self.params[0], (list, dict, tuple)) + elif isinstance(self.params, dict): + typ = self._DICT + ismulti = False + else: + assert False, "Unknown parameter type %s" % (type(self.params), ) + + if ismulti and len(self.params) > self.batches: msg = " ... displaying %i of %i total bound parameter sets ... " return ' '.join(( - repr(self.params[:self.batches - 2])[0:-1], + self._repr_multi(self.params[:self.batches - 2], typ)[0:-1], msg % (self.batches, len(self.params)), - repr(self.params[-2:])[1:] + self._repr_multi(self.params[-2:], typ)[1:] )) + elif ismulti: + return self._repr_multi(self.params, typ) + else: + return self._repr_params(self.params, typ) + + def _repr_multi(self, multi_params, typ): + if multi_params: + if isinstance(multi_params[0], list): + elem_type = self._LIST + elif isinstance(multi_params[0], tuple): + elem_type = self._TUPLE + elif isinstance(multi_params[0], dict): + elem_type = self._DICT + else: + assert False, \ + "Unknown parameter type %s" % (type(multi_params[0])) + + elements = ", ".join( + self._repr_params(params, elem_type) + for params in multi_params) + else: + elements = "" + + if typ == self._LIST: + return "[%s]" % elements + else: + return "(%s)" % elements + + def _repr_params(self, params, typ): + trunc = self.trunc + if typ is self._DICT: + return "{%s}" % ( + ", ".join( + "%r: %s" % (key, trunc(value)) + for key, value in params.items() + ) + ) + elif typ is self._TUPLE: + return "(%s)" % ( + "".join(trunc(value) + "," for value in params) + ) else: - return repr(self.params) + return "[%s]" % ( + ", ".join(trunc(value) for value in params) + ) def adapt_criterion_to_null(crit, nulls): -- cgit v1.2.1 From bf1d03a9e58a0256db0b1f7389e23a6d11c4a964 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 17 Feb 2016 15:21:00 -0500 Subject: - do the trailing comma logic of tuple repr() exactly --- lib/sqlalchemy/sql/util.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 98d9cc944..d40bf48f1 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -311,8 +311,9 @@ class _repr_row(_repr_base): def __repr__(self): trunc = self.trunc - return "(%s)" % ( - "".join(trunc(value) + "," for value in self.row) + return "(%s%s)" % ( + ", ".join(trunc(value) for value in self.row), + "," if len(self.row) == 1 else "" ) @@ -391,8 +392,10 @@ class _repr_params(_repr_base): ) ) elif typ is self._TUPLE: - return "(%s)" % ( - "".join(trunc(value) + "," for value in params) + return "(%s%s)" % ( + ", ".join(trunc(value) for value in params), + "," if len(params) == 1 else "" + ) else: return "[%s]" % ( -- cgit v1.2.1 From 2965da0a5d89119787bd45ac6f5459a7b755656d Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 17 Feb 2016 16:53:01 -0500 Subject: - handle parameter sets that aren't correctly formed, so that for example an exception object made within a test suite can still repr (error seen in Keystone) --- lib/sqlalchemy/sql/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/sql/util.py') diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index d40bf48f1..5f180646c 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -345,7 +345,7 @@ class _repr_params(_repr_base): typ = self._DICT ismulti = False else: - assert False, "Unknown parameter type %s" % (type(self.params), ) + return self.trunc(self.params) if ismulti and len(self.params) > self.batches: msg = " ... displaying %i of %i total bound parameter sets ... " -- cgit v1.2.1