diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-06-22 13:27:18 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2021-06-22 17:25:37 -0400 |
commit | 2cf8c5868cb83185001755d86aa0f79e0318b53f (patch) | |
tree | 7eadd810494b7d888f0522e38b7fdbabd349a032 /lib/sqlalchemy/sql/selectable.py | |
parent | 2f100a7d4bb143e1f8674a388d8d305be3051bd6 (diff) | |
download | sqlalchemy-2cf8c5868cb83185001755d86aa0f79e0318b53f.tar.gz |
Export deferred columns but not col props; fix CTE labeling
Refined the behavior of ORM subquery rendering with regards to deferred
columns and column properties to be more compatible with that of 1.3 while
also providing for 1.4's newer features. As a subquery in 1.4 does not make
use of loader options, including :func:`_orm.deferred`, a subquery that is
against an ORM entity with deferred attributes will now render those
deferred attributes that refer directly to mapped table columns, as these
are needed in the outer SELECT if that outer SELECT makes use of these
columns; however a deferred attribute that refers to a composed SQL
expression as we normally do with :func:`_orm.column_property` will not be
part of the subquery, as these can be selected explicitly if needed in the
subquery. If the entity is being SELECTed from this subquery, the column
expression can still render on "the outside" in terms of the derived
subquery columns. This produces essentially the same behavior as when
working with 1.3. However in this case the fix has to also make sure that
the ``.selected_columns`` collection of an ORM-enabled :func:`_sql.select`
also follows these rules, which in particular allows recursive CTEs to
render correctly in this scenario, which were previously failing to render
correctly due to this issue.
As part of this change the _exported_columns_iterator() method has been
removed and logic simplified to use ._all_selected_columns from any
SelectBase object where _exported_columns_iterator() was used before.
Additionally sets up UpdateBase to include ReturnsRows in its hierarchy;
the literal point of ReturnsRows was to be a common base for UpdateBase
and SelectBase so it was kind of weird it wasn't there.
Fixes: #6661
Fixed issue in CTE constructs mostly relevant to ORM use cases where a
recursive CTE against "anonymous" labels such as those seen in ORM
``column_property()`` mappings would render in the
``WITH RECURSIVE xyz(...)`` section as their raw internal label and not a
cleanly anonymized name.
Fixes: #6663
Change-Id: I26219d4d8e6c0915b641426e9885540f74fae4d2
Diffstat (limited to 'lib/sqlalchemy/sql/selectable.py')
-rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 62 |
1 files changed, 22 insertions, 40 deletions
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index e1dee091b..557c443bf 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -109,23 +109,19 @@ class ReturnsRows(roles.ReturnsRowsRole, ClauseElement): @property def selectable(self): - raise NotImplementedError() - - def _exported_columns_iterator(self): - """An iterator of column objects that represents the "exported" - columns of this :class:`_expression.ReturnsRows`. + return self - This is the same set of columns as are returned by - :meth:`_expression.ReturnsRows.exported_columns` - except they are returned - as a simple iterator or sequence, rather than as a - :class:`_expression.ColumnCollection` namespace. + @property + def _all_selected_columns(self): + """A sequence of column expression objects that represents the + "selected" columns of this :class:`_expression.ReturnsRows`. - Subclasses should re-implement this method to bypass the interim - creation of the :class:`_expression.ColumnCollection` if appropriate. + This is typically equivalent to .exported_columns except it is + delivered in the form of a straight sequence and not keyed + :class:`_expression.ColumnCollection`. """ - return iter(self.exported_columns) + raise NotImplementedError() @property def exported_columns(self): @@ -161,10 +157,6 @@ class Selectable(ReturnsRows): is_selectable = True - @property - def selectable(self): - return self - def _refresh_for_new_column(self, column): raise NotImplementedError() @@ -3113,9 +3105,6 @@ class SelectStatementGrouping(GroupedElement, SelectBase): def _generate_proxy_for_new_column(self, column, subquery): return self.element._generate_proxy_for_new_column(subquery) - def _exported_columns_iterator(self): - return self.element._exported_columns_iterator() - @property def _all_selected_columns(self): return self.element._all_selected_columns @@ -3935,9 +3924,6 @@ class CompoundSelect(HasCompileState, GenerativeSelect): for select in self.selects: select._refresh_for_new_column(column) - def _exported_columns_iterator(self): - return self.selects[0]._exported_columns_iterator() - @property def _all_selected_columns(self): return self.selects[0]._all_selected_columns @@ -4335,7 +4321,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) - for c in self.statement._exported_columns_iterator() + for c in self.statement._all_selected_columns if c._allow_label_resolve ) only_froms = dict( @@ -4357,14 +4343,6 @@ class SelectState(util.MemoizedSlots, CompileState): return None @classmethod - def exported_columns_iterator(cls, statement): - return [ - c - for c in _select_iterables(statement._raw_columns) - if not c._is_text_clause - ] - - @classmethod def all_selected_columns(cls, statement): return [c for c in _select_iterables(statement._raw_columns)] @@ -5318,7 +5296,7 @@ class Select( """ - return self._exported_columns_iterator() + return iter(self._all_selected_columns) def is_derived_from(self, fromclause): if self in fromclause._cloned_set: @@ -5470,7 +5448,7 @@ class Select( """ return self.with_only_columns( *util.preloaded.sql_util.reduce_columns( - self._exported_columns_iterator(), + self._all_selected_columns, only_synonyms=only_synonyms, *(self._where_criteria + self._from_obj) ) @@ -5779,7 +5757,11 @@ class Select( conv = SelectState._column_naming_convention(self._label_style) return ColumnCollection( - [(conv(c), c) for c in self._exported_columns_iterator()] + [ + (conv(c), c) + for c in self._all_selected_columns + if not c._is_text_clause + ] ).as_immutable() @HasMemoized.memoized_attribute @@ -5787,10 +5769,6 @@ class Select( meth = SelectState.get_plugin_class(self).all_selected_columns return list(meth(self)) - def _exported_columns_iterator(self): - meth = SelectState.get_plugin_class(self).exported_columns_iterator - return meth(self) - def _ensure_disambiguated_names(self): if self._label_style is LABEL_STYLE_NONE: self = self.set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY) @@ -5912,7 +5890,7 @@ class Select( disambiguate_only = self._label_style is LABEL_STYLE_DISAMBIGUATE_ONLY for name, c, repeated in self._generate_columns_plus_names(False): - if not hasattr(c, "_make_proxy"): + if c._is_text_clause: continue elif tablename_plus_col: key = c._key_label @@ -6405,6 +6383,10 @@ class TextualSelect(SelectBase): (c.key, c) for c in self.column_args ).as_immutable() + @property + def _all_selected_columns(self): + return self.column_args + def _set_label_style(self, style): return self |