summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/selectable.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-06-22 13:27:18 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2021-06-22 17:25:37 -0400
commit2cf8c5868cb83185001755d86aa0f79e0318b53f (patch)
tree7eadd810494b7d888f0522e38b7fdbabd349a032 /lib/sqlalchemy/sql/selectable.py
parent2f100a7d4bb143e1f8674a388d8d305be3051bd6 (diff)
downloadsqlalchemy-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.py62
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