summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-04-22 10:45:01 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2021-04-22 15:09:27 -0400
commit17072f8b04c6a3a989673e85ace163620f9130cd (patch)
tree559661c76a4ad19ce957b4c9248108169cf94b9b /lib/sqlalchemy/sql
parent72f43a8b49803cd4d4befe91635f6691965f34fb (diff)
downloadsqlalchemy-17072f8b04c6a3a989673e85ace163620f9130cd.tar.gz
omit text from selected_columns; clear memoizations
Fixed regression where usage of the :func:`_sql.text` construct inside the columns clause of a :class:`_sql.Select` construct, which is better handled by using a :func:`_sql.literal_column` construct, would nonetheless prevent constructs like :func:`_sql.union` from working correctly. Other use cases, such as constructing subuqeries, continue to work the same as in prior versions where the :func:`_sql.text` construct is silently omitted from the collection of exported columns. Also repairs similar use within the ORM. This adds a new internal method _all_selected_columns. The existing "selected_columns" collection can't store a TextClause and this never worked, so they are omitted. The TextClause is also not "exported", i.e. available for SELECT from a subquery, as was already the case in 1.3, so the "exported_columns" and "exported_columns_iterator" accessors are where we now omit TextClause. Fixed regression involving legacy methods such as :meth:`_sql.Select.append_column` where internal assertions would fail. Fixes: #6343 Fixes: #6261 Change-Id: I7c2e5b9ae5d94131c77599a020f4310dcf812bcf
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/compiler.py4
-rw-r--r--lib/sqlalchemy/sql/selectable.py102
2 files changed, 88 insertions, 18 deletions
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 4c591a87f..6168248ff 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -3156,7 +3156,7 @@ class SQLCompiler(Compiled):
entry["select_0"] = select
elif compound_index:
select_0 = entry["select_0"]
- numcols = len(select_0.selected_columns)
+ numcols = len(select_0._all_selected_columns)
if len(compile_state.columns_plus_names) != numcols:
raise exc.CompileError(
@@ -3168,7 +3168,7 @@ class SQLCompiler(Compiled):
1,
numcols,
compound_index + 1,
- len(select.selected_columns),
+ len(select._all_selected_columns),
)
)
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 45fd26e89..f3c500852 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -2807,16 +2807,45 @@ class SelectBase(
statement; a subquery must be applied first which provides for the
necessary parenthesization required by SQL.
+ .. note::
+
+ The :attr:`_sql.SelectBase.selected_columns` collection does not
+ include expressions established in the columns clause using the
+ :func:`_sql.text` construct; these are silently omitted from the
+ collection. To use plain textual column expressions inside of a
+ :class:`_sql.Select` construct, use the :func:`_sql.literal_column`
+ construct.
+
+ .. seealso::
+
+ :attr:`_sql.Select.selected_columns`
+
.. versionadded:: 1.4
"""
raise NotImplementedError()
@property
+ def _all_selected_columns(self):
+ """A sequence of expressions that correspond to what is rendered
+ in the columns clause, including :class:`_sql.TextClause`
+ constructs.
+
+ .. versionadded:: 1.4.12
+
+ .. seealso::
+
+ :attr:`_sql.SelectBase.exported_columns`
+
+ """
+ raise NotImplementedError()
+
+ @property
def exported_columns(self):
"""A :class:`_expression.ColumnCollection`
that represents the "exported"
- columns of this :class:`_expression.Selectable`.
+ columns of this :class:`_expression.Selectable`, not including
+ :class:`_sql.TextClause` constructs.
The "exported" columns for a :class:`_expression.SelectBase`
object are synonymous
@@ -2826,6 +2855,8 @@ class SelectBase(
.. seealso::
+ :attr:`_expression.Select.exported_columns`
+
:attr:`_expression.Selectable.exported_columns`
:attr:`_expression.FromClause.exported_columns`
@@ -3082,16 +3113,21 @@ class SelectStatementGrouping(GroupedElement, SelectBase):
return self.element._exported_columns_iterator()
@property
+ def _all_selected_columns(self):
+ return self.element._all_selected_columns
+
+ @property
def selected_columns(self):
"""A :class:`_expression.ColumnCollection`
representing the columns that
- the embedded SELECT statement returns in its result set.
+ the embedded SELECT statement returns in its result set, not including
+ :class:`_sql.TextClause` constructs.
.. versionadded:: 1.4
.. seealso::
- :ref:`.SelectBase.selected_columns`
+ :attr:`_sql.Select.selected_columns`
"""
return self.element.selected_columns
@@ -3881,6 +3917,7 @@ class CompoundSelect(HasCompileState, GenerativeSelect):
# to how low in the list of select()s the column occurs, so
# that the corresponding_column() operation can resolve
# conflicts
+
for subq_col, select_cols in zip(
subquery.c._all_columns,
zip(*[s.selected_columns for s in self.selects]),
@@ -3899,10 +3936,15 @@ class CompoundSelect(HasCompileState, GenerativeSelect):
return self.selects[0]._exported_columns_iterator()
@property
+ def _all_selected_columns(self):
+ return self.selects[0]._all_selected_columns
+
+ @property
def selected_columns(self):
"""A :class:`_expression.ColumnCollection`
representing the columns that
- this SELECT statement or similar construct returns in its result set.
+ this SELECT statement or similar construct returns in its result set,
+ not including :class:`_sql.TextClause` constructs.
For a :class:`_expression.CompoundSelect`, the
:attr:`_expression.CompoundSelect.selected_columns`
@@ -3910,6 +3952,10 @@ class CompoundSelect(HasCompileState, GenerativeSelect):
columns of the first SELECT statement contained within the series of
statements within the set operation.
+ .. seealso::
+
+ :attr:`_sql.Select.selected_columns`
+
.. versionadded:: 1.4
"""
@@ -4108,6 +4154,10 @@ 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:
def go(c, col_name=None):
@@ -4281,7 +4331,15 @@ class SelectState(util.MemoizedSlots, CompileState):
@classmethod
def exported_columns_iterator(cls, statement):
- return _select_iterables(statement._raw_columns)
+ 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)]
def _setup_joins(self, args):
for (right, onclause, left, flags) in args:
@@ -5241,10 +5299,7 @@ class Select(
clone=clone, omit_attrs=("_from_obj",), **kw
)
- # memoizations should be cleared here as of
- # I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this
- # is the case for now.
- self._assert_no_memoizations()
+ self._reset_memoizations()
def get_children(self, **kwargs):
return itertools.chain(
@@ -5269,10 +5324,7 @@ class Select(
:class:`_expression.Select` object.
"""
- # memoizations should be cleared here as of
- # I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this
- # is the case for now.
- self._assert_no_memoizations()
+ self._reset_memoizations()
self._raw_columns = self._raw_columns + [
coercions.expect(
@@ -5602,7 +5654,8 @@ class Select(
def selected_columns(self):
"""A :class:`_expression.ColumnCollection`
representing the columns that
- this SELECT statement or similar construct returns in its result set.
+ this SELECT statement or similar construct returns in its result set,
+ not including :class:`_sql.TextClause` constructs.
This collection differs from the :attr:`_expression.FromClause.columns`
collection of a :class:`_expression.FromClause` in that the columns
@@ -5626,6 +5679,16 @@ class Select(
:class:`_expression.ColumnElement` objects that are in the
:attr:`_expression.FromClause.c` collection of the from element.
+ .. note::
+
+ The :attr:`_sql.Select.selected_columns` collection does not
+ include expressions established in the columns clause using the
+ :func:`_sql.text` construct; these are silently omitted from the
+ collection. To use plain textual column expressions inside of a
+ :class:`_sql.Select` construct, use the :func:`_sql.literal_column`
+ construct.
+
+
.. versionadded:: 1.4
"""
@@ -5640,6 +5703,11 @@ class Select(
[(conv(c), c) for c in self._exported_columns_iterator()]
).as_immutable()
+ @HasMemoized.memoized_attribute
+ def _all_selected_columns(self):
+ meth = SelectState.get_plugin_class(self).all_selected_columns
+ return meth(self)
+
def _exported_columns_iterator(self):
meth = SelectState.get_plugin_class(self).exported_columns_iterator
return meth(self)
@@ -5658,7 +5726,7 @@ class Select(
different rules.
"""
- cols = self._exported_columns_iterator()
+ cols = self._all_selected_columns
# when use_labels is on:
# in all cases == if we see the same label name, use _label_anon_label
@@ -6237,7 +6305,8 @@ class TextualSelect(SelectBase):
def selected_columns(self):
"""A :class:`_expression.ColumnCollection`
representing the columns that
- this SELECT statement or similar construct returns in its result set.
+ this SELECT statement or similar construct returns in its result set,
+ not including :class:`_sql.TextClause` constructs.
This collection differs from the :attr:`_expression.FromClause.columns`
collection of a :class:`_expression.FromClause` in that the columns
@@ -6250,6 +6319,7 @@ class TextualSelect(SelectBase):
passed to the constructor, typically via the
:meth:`_expression.TextClause.columns` method.
+
.. versionadded:: 1.4
"""