diff options
author | mike bayer <mike_mp@zzzcomputing.com> | 2021-07-13 14:25:13 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@ci3.zzzcomputing.com> | 2021-07-13 14:25:13 +0000 |
commit | 673ca806b323f47ef7064dd64ffc98240818b930 (patch) | |
tree | 56fbe5fde5a3892475949ce16e84793364bf0eb2 /lib/sqlalchemy/sql/selectable.py | |
parent | 326e3f5dc26f22ca51f4aa2509856d8077e76dec (diff) | |
parent | 707e5d70fcdcfaaddcd0aaee51f4f1b881e5e3e2 (diff) | |
download | sqlalchemy-673ca806b323f47ef7064dd64ffc98240818b930.tar.gz |
Merge "labeling refactor"
Diffstat (limited to 'lib/sqlalchemy/sql/selectable.py')
-rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 263 |
1 files changed, 140 insertions, 123 deletions
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index d947aed37..30a613089 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -1132,7 +1132,7 @@ class Join(roles.DMLTableRole, FromClause): ) ) self._columns._populate_separate_keys( - (col._key_label, col) for col in columns + (col._tq_key_label, col) for col in columns ) self.foreign_keys.update( itertools.chain(*[col.foreign_keys for col in columns]) @@ -4222,49 +4222,41 @@ class SelectState(util.MemoizedSlots, CompileState): @classmethod def _column_naming_convention(cls, label_style): - # note: these functions won't work for TextClause objects, - # which should be omitted when iterating through - # _raw_columns. - if label_style is LABEL_STYLE_NONE: + table_qualified = label_style is LABEL_STYLE_TABLENAME_PLUS_COL + dedupe = label_style is not LABEL_STYLE_NONE - def go(c, col_name=None): - return c._proxy_key + pa = prefix_anon_map() + names = set() - elif label_style is LABEL_STYLE_TABLENAME_PLUS_COL: - names = set() - pa = [] # late-constructed as needed, python 2 has no "nonlocal" - - def go(c, col_name=None): - # we use key_label since this name is intended for targeting - # within the ColumnCollection only, it's not related to SQL - # rendering which always uses column name for SQL label names - - name = c._key_label - - if name in names: - if not pa: - pa.append(prefix_anon_map()) - - name = c._label_anon_key_label % pa[0] - else: - names.add(name) + def go(c, col_name=None): + if c._is_text_clause: + return None + elif not dedupe: + name = c._proxy_key + if name is None: + name = "_no_label" return name - else: - names = set() - pa = [] # late-constructed as needed, python 2 has no "nonlocal" + name = c._tq_key_label if table_qualified else c._proxy_key - def go(c, col_name=None): - name = c._proxy_key + if name is None: + name = "_no_label" if name in names: - if not pa: - pa.append(prefix_anon_map()) - name = c._anon_key_label % pa[0] + return c._anon_label(name) % pa else: names.add(name) + return name + elif name in names: + return ( + c._anon_tq_key_label % pa + if table_qualified + else c._anon_key_label % pa + ) + else: + names.add(name) return name return go @@ -4394,7 +4386,7 @@ class SelectState(util.MemoizedSlots, CompileState): def _memoized_attr__label_resolve_dict(self): with_cols = dict( - (c._resolve_label or c._label or c.key, c) + (c._resolve_label or c._tq_label or c.key, c) for c in self.statement._all_selected_columns if c._allow_label_resolve ) @@ -5853,60 +5845,70 @@ class Select( """Generate column names as rendered in a SELECT statement by the compiler. - This is distinct from other name generators that are intended for - population of .c collections and similar, which may have slightly - different rules. + This is distinct from the _column_naming_convention generator that's + intended for population of .c collections and similar, which has + different rules. the collection returned here calls upon the + _column_naming_convention as well. """ cols = self._all_selected_columns - # when use_labels is on: - # in all cases == if we see the same label name, use _label_anon_label - # for subsequent occurrences of that label - # - # anon_for_dupe_key == if we see the same column object multiple - # times under a particular name, whether it's the _label name or the - # anon label, apply _dedupe_label_anon_label to the subsequent - # occurrences of it. - if self._label_style is LABEL_STYLE_NONE: - # don't generate any labels - same_cols = set() + key_naming_convention = SelectState._column_naming_convention( + self._label_style + ) - return [ - (None, c, c in same_cols or same_cols.add(c)) for c in cols - ] - else: - names = {} + names = {} - use_tablename_labels = ( - self._label_style is LABEL_STYLE_TABLENAME_PLUS_COL - ) + result = [] + result_append = result.append - def name_for_col(c): - if not c._render_label_in_columns_clause: - return (None, c, False) - elif use_tablename_labels: - if c._label is None: - repeated = c._anon_name_label in names - names[c._anon_name_label] = c - return (None, c, repeated) - else: - name = effective_name = c._label - elif getattr(c, "name", None) is None: - # this is a scalar_select(). need to improve this case + table_qualified = self._label_style is LABEL_STYLE_TABLENAME_PLUS_COL + label_style_none = self._label_style is LABEL_STYLE_NONE + + for c in cols: + repeated = False + + if not c._render_label_in_columns_clause: + effective_name = ( + required_label_name + ) = fallback_label_name = None + elif label_style_none: + effective_name = required_label_name = None + fallback_label_name = c._non_anon_label or c._anon_name_label + else: + if table_qualified: + required_label_name = ( + effective_name + ) = fallback_label_name = c._tq_label + else: + effective_name = fallback_label_name = c._non_anon_label + required_label_name = None + + if effective_name is None: + # it seems like this could be _proxy_key and we would + # not need _expression_label but it isn't + # giving us a clue when to use anon_label instead expr_label = c._expression_label if expr_label is None: repeated = c._anon_name_label in names names[c._anon_name_label] = c - return (None, c, repeated) - else: - name = effective_name = expr_label - else: - name = None - effective_name = c.name + effective_name = required_label_name = None - repeated = False + if repeated: + # here, "required_label_name" is sent as + # "None" and "fallback_label_name" is sent. + if table_qualified: + fallback_label_name = c._dedupe_anon_tq_label + else: + fallback_label_name = c._dedupe_anon_label + else: + fallback_label_name = c._anon_name_label + else: + required_label_name = ( + effective_name + ) = fallback_label_name = expr_label + if effective_name is not None: if effective_name in names: # when looking to see if names[name] is the same column as # c, use hash(), so that an annotated version of the column @@ -5915,82 +5917,97 @@ class Select( # different column under the same name. apply # disambiguating label - if use_tablename_labels: - name = c._label_anon_label + if table_qualified: + required_label_name = ( + fallback_label_name + ) = c._anon_tq_label else: - name = c._anon_name_label + required_label_name = ( + fallback_label_name + ) = c._anon_name_label - if anon_for_dupe_key and name in names: - # here, c._label_anon_label is definitely unique to + if anon_for_dupe_key and required_label_name in names: + # here, c._anon_tq_label is definitely unique to # that column identity (or annotated version), so # this should always be true. # this is also an infrequent codepath because # you need two levels of duplication to be here - assert hash(names[name]) == hash(c) + assert hash(names[required_label_name]) == hash(c) # the column under the disambiguating label is # already present. apply the "dedupe" label to # subsequent occurrences of the column so that the # original stays non-ambiguous - if use_tablename_labels: - name = c._dedupe_label_anon_label + if table_qualified: + required_label_name = ( + fallback_label_name + ) = c._dedupe_anon_tq_label else: - name = c._dedupe_anon_label + required_label_name = ( + fallback_label_name + ) = c._dedupe_anon_label repeated = True else: - names[name] = c + names[required_label_name] = c elif anon_for_dupe_key: # same column under the same name. apply the "dedupe" # label so that the original stays non-ambiguous - if use_tablename_labels: - name = c._dedupe_label_anon_label + if table_qualified: + required_label_name = ( + fallback_label_name + ) = c._dedupe_anon_tq_label else: - name = c._dedupe_anon_label + required_label_name = ( + fallback_label_name + ) = c._dedupe_anon_label repeated = True else: names[effective_name] = c - return name, c, repeated - return [name_for_col(c) for c in cols] + result_append( + ( + # string label name, if non-None, must be rendered as a + # label, i.e. "AS <name>" + required_label_name, + # proxy_key that is to be part of the result map for this + # col. this is also the key in a fromclause.c or + # select.selected_columns collection + key_naming_convention(c), + # name that can be used to render an "AS <name>" when + # we have to render a label even though + # required_label_name was not given + fallback_label_name, + # the ColumnElement itself + c, + # True if this is a duplicate of a previous column + # in the list of columns + repeated, + ) + ) + + return result def _generate_fromclause_column_proxies(self, subquery): """Generate column proxies to place in the exported ``.c`` collection of a subquery.""" - keys_seen = set() - prox = [] - - pa = None - - tablename_plus_col = ( - self._label_style is LABEL_STYLE_TABLENAME_PLUS_COL - ) - disambiguate_only = self._label_style is LABEL_STYLE_DISAMBIGUATE_ONLY - - for name, c, repeated in self._generate_columns_plus_names(False): - if c._is_text_clause: - continue - elif tablename_plus_col: - key = c._key_label - if key is not None and key in keys_seen: - if pa is None: - pa = prefix_anon_map() - key = c._label_anon_key_label % pa - keys_seen.add(key) - elif disambiguate_only: - key = c._proxy_key - if key is not None and key in keys_seen: - if pa is None: - pa = prefix_anon_map() - key = c._anon_key_label % pa - keys_seen.add(key) - else: - key = c._proxy_key - prox.append( - c._make_proxy( - subquery, key=key, name=name, name_is_truncatable=True - ) + prox = [ + c._make_proxy( + subquery, + key=proxy_key, + name=required_label_name, + name_is_truncatable=True, ) + for ( + required_label_name, + proxy_key, + fallback_label_name, + c, + repeated, + ) in (self._generate_columns_plus_names(False)) + if not c._is_text_clause + ] + subquery._columns._populate_separate_keys(prox) def _needs_parens_for_grouping(self): |