diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-28 17:12:09 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-07-28 17:12:09 -0400 |
| commit | f839b8927099b64b2d120ffd93d5f444b8951e59 (patch) | |
| tree | c4e6c15d586f0ddd4d76753f0427285e1a3ceb4d /lib/sqlalchemy/sql | |
| parent | 22ba1c43b792953ae6f791512d276739c8c09eae (diff) | |
| download | sqlalchemy-f839b8927099b64b2d120ffd93d5f444b8951e59.tar.gz | |
- [feature] Added reduce_columns() method
to select() construct, replaces columns inline
using the util.reduce_columns utility function
to remove equivalent columns. reduce_columns()
also adds "with_only_synonyms" to limit the
reduction just to those columns which have the same
name. The deprecated fold_equivalents() feature is
removed [ticket:1729].
- [feature] Added with_labels and
reduce_columns keyword arguments to
Query.subquery(), to provide two alternate
strategies for producing queries with uniquely-
named columns. [ticket:1729].
Diffstat (limited to 'lib/sqlalchemy/sql')
| -rw-r--r-- | lib/sqlalchemy/sql/expression.py | 48 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/util.py | 59 |
2 files changed, 45 insertions, 62 deletions
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index e9786cbe1..b403ea88b 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -3687,7 +3687,6 @@ class Join(FromClause): self.onclause = onclause self.isouter = isouter - self.__folded_equivalents = None @property def description(self): @@ -3720,7 +3719,6 @@ class Join(FromClause): self.left = clone(self.left, **kw) self.right = clone(self.right, **kw) self.onclause = clone(self.onclause, **kw) - self.__folded_equivalents = None def get_children(self, **kwargs): return self.left, self.right, self.onclause @@ -3732,7 +3730,7 @@ class Join(FromClause): left_right = None return sqlutil.join_condition(left, right, a_subset=left_right) - def select(self, whereclause=None, fold_equivalents=False, **kwargs): + def select(self, whereclause=None, **kwargs): """Create a :class:`.Select` from this :class:`.Join`. The equivalent long-hand form, given a :class:`.Join` object @@ -3746,22 +3744,11 @@ class Join(FromClause): :param whereclause: the WHERE criterion that will be sent to the :func:`select()` function - :param fold_equivalents: based on the join criterion of this - :class:`.Join`, do not include - repeat column names in the column list of the resulting - select, for columns that are calculated to be "equivalent" - based on the join criterion of this :class:`.Join`. This will - recursively apply to any joins directly nested by this one - as well. - :param \**kwargs: all other kwargs are sent to the underlying :func:`select()` function. """ - if fold_equivalents: - collist = sqlutil.folded_equivalents(self) - else: - collist = [self.left, self.right] + collist = [self.left, self.right] return select(collist, whereclause, from_obj=[self], **kwargs) @@ -5149,6 +5136,37 @@ class Select(SelectBase): """ self.append_column(column) + def reduce_columns(self, only_synonyms=True): + """Return a new :func`.select` construct with redundantly + named, equivalently-valued columns removed from the columns clause. + + "Redundant" here means two columns where one refers to the + other either based on foreign key, or via a simple equality + comparison in the WHERE clause of the statement. The primary purpose + of this method is to automatically construct a select statement + with all uniquely-named columns, without the need to use table-qualified + labels as :meth:`.apply_labels` does. + + When columns are omitted based on foreign key, the referred-to + column is the one that's kept. When columns are omitted based on + WHERE eqivalence, the first column in the columns clause is the + one that's kept. + + :param only_synonyms: when True, limit the removal of columns + to those which have the same name as the equivalent. Otherwise, + all columns that are equivalent to another are removed. + + .. versionadded:: 0.8 + + """ + return self.with_only_columns( + sqlutil.reduce_columns( + self.inner_columns, + *(self._whereclause, ) + tuple(self._from_obj), + only_synonyms=only_synonyms + ) + ) + @_generative def with_only_columns(self, columns): """Return a new :func:`.select` construct with its columns diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index 0727f0537..6bfaf4b8c 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -585,6 +585,7 @@ def reduce_columns(columns, *clauses, **kw): """ ignore_nonexistent_tables = kw.pop('ignore_nonexistent_tables', False) + only_synonyms = kw.pop('only_synonyms', False) columns = util.ordered_column_set(columns) @@ -610,21 +611,27 @@ def reduce_columns(columns, *clauses, **kw): continue else: raise - if fk_col.shares_lineage(c): + if fk_col.shares_lineage(c) and \ + (not only_synonyms or \ + c.name == col.name): omit.add(col) break if clauses: def visit_binary(binary): if binary.operator == operators.eq: - cols = util.column_set(chain(*[c.proxy_set for c in columns.difference(omit)])) + cols = util.column_set(chain(*[c.proxy_set + for c in columns.difference(omit)])) if binary.left in cols and binary.right in cols: - for c in columns: - if c.shares_lineage(binary.right): + for c in reversed(columns): + if c.shares_lineage(binary.right) and \ + (not only_synonyms or \ + c.name == binary.left.name): omit.add(c) break for clause in clauses: - visitors.traverse(clause, {}, {'binary':visit_binary}) + if clause is not None: + visitors.traverse(clause, {}, {'binary': visit_binary}) return expression.ColumnSet(columns.difference(omit)) @@ -677,48 +684,6 @@ def criterion_as_pairs(expression, consider_as_foreign_keys=None, visitors.traverse(expression, {}, {'binary':visit_binary}) return pairs -def folded_equivalents(join, equivs=None): - """Return a list of uniquely named columns. - - The column list of the given Join will be narrowed - down to a list of all equivalently-named, - equated columns folded into one column, where 'equated' means they are - equated to each other in the ON clause of this join. - - This function is used by Join.select(fold_equivalents=True). - - Deprecated. This function is used for a certain kind of - "polymorphic_union" which is designed to achieve joined - table inheritance where the base table has no "discriminator" - column; [ticket:1131] will provide a better way to - achieve this. - - """ - if equivs is None: - equivs = set() - def visit_binary(binary): - if binary.operator == operators.eq and binary.left.name == binary.right.name: - equivs.add(binary.right) - equivs.add(binary.left) - visitors.traverse(join.onclause, {}, {'binary':visit_binary}) - collist = [] - if isinstance(join.left, expression.Join): - left = folded_equivalents(join.left, equivs) - else: - left = list(join.left.columns) - if isinstance(join.right, expression.Join): - right = folded_equivalents(join.right, equivs) - else: - right = list(join.right.columns) - used = set() - for c in left + right: - if c in equivs: - if c.name not in used: - collist.append(c) - used.add(c.name) - else: - collist.append(c) - return collist class AliasedRow(object): """Wrap a RowProxy with a translation map. |
