aminghadersohi commented on code in PR #45681:
URL: https://github.com/apache/superset/pull/45681#discussion_r4396607712


##########
superset/models/helpers.py:
##########
@@ -3994,6 +4019,156 @@ def get_rendered_sql(
     def text(self, clause: str) -> TextClause:
         return self.db_engine_spec.get_text_clause(clause)
 
+    def _get_rls_schema(self) -> str:
+        """Resolve RLS tables with the same schema policy as SQL 
expressions."""
+        return get_rls_schema(
+            self.database,
+            self.catalog,
+            self.schema,
+            self.sql or "",
+            self.template_params_dict,
+        )
+
+    def _get_query_sql_expressions(
+        self, query_obj: QueryObjectDict, template_processor: 
BaseTemplateProcessor
+    ) -> list[str]:
+        """Find SQL-bearing chart fields without building a chart or its 
prequeries."""
+        # Real column names take precedence over verbose names in filter 
lookup.
+        columns = {col.verbose_name: col.expression for col in self.columns}
+        columns.update({col.column_name: col.expression for col in 
self.columns})
+        metrics = {metric.metric_name: metric.expression for metric in 
self.metrics}
+        expressions: list[str] = []
+
+        def add(value: Any, metric: bool = False) -> None:
+            """Resolve stored references in the namespace used by the query 
sink."""
+            if isinstance(value, str):
+                references = metrics if metric else columns
+                expressions.append(references.get(value) or value)
+            elif isinstance(value, dict):
+                if sql := value.get("sqlExpression"):
+                    sql = template_processor.process_template(sql)
+                    # Adhoc columns can name a saved calculated column after
+                    # rendering. SQL metrics use their SQL literally instead.
+                    expressions.append(
+                        sql if "expressionType" in value else columns.get(sql) 
or sql

Review Comment:
   My previous commit 1a121cb71cf0dc0c7154dbc96b8220f78e38a9f9 reopened the 
shared-key case: an adhoc reference to a saved column with `(FROM physical)` 
executed each viewer's RLS but omitted it from the cache key. This includes 
`expressionType: SQL` and surrounding whitespace.
   
   Fixed in 0c4fa24d190053c1b7f104a76cfe71f5eebb76ed: the side-effect-free 
prefilter uses the same SQL parser's subquery/table extraction as execution 
instead of requiring a SELECT keyword. FROM-first expressions (including 
lowercase, comments, whitespace, nested parentheses, and literal-template 
outputs) therefore contribute the executed table's per-viewer RLS predicates. 
Table-free expressions still do not render templates before cache lookup; 
opaque or unparseable template output remains viewer-scoped without executing 
its macros.
   
   Tests: `test_from_first_subquery_cache_keys_are_user_scoped`, 
`test_from_first_saved_expression_cache_keys_are_user_scoped`, 
`test_from_first_dialect_subquery_cache_keys_are_user_scoped`, and the 
FROM-first parameter of `test_expression_cache_key_sibling_fields`. Restoring 
the prefilter verbatim from 1a121cb71cf0dc0c7154dbc96b8220f78e38a9f9 gives 237 
failures and 34 compatibility passes; the complete affected file passes 757 
tests with this commit. These assert distinct final cache keys for two viewers 
with different physical-table RLS, and real SQLite results for column, SQL 
metric, WHERE and HAVING on both datasource types. 
`test_cache_key_skips_partition_templates_without_subqueries` and 
`test_swapped_table_rules_have_distinct_cache_keys` also pass. 
`test_cache_key_uses_only_executed_time_column` includes unused subqueries 
alongside `{{ 1 / 0 }}`; an eager-primary-column mutation fails both new 
SqlaTable variants under lazy rendering.
   
   Siblings checked:
   - `superset/models/helpers.py:4204` (the shared prefilter) — fixed in 
0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/models/helpers.py:4232` (add_expression, adhoc/saved columns, 
SQL/SIMPLE/saved metrics, ORDER BY, filters, WHERE/HAVING, fetch predicate, 
time-column and series-limit gates) — fixed in 
0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/connectors/sqla/models.py:2572` (saved-column RLS expression 
lookup) — fixed in 0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/models/helpers.py:4144` (literal Jinja sources) — not affected: 
it inspects literal Jinja inputs/outputs without running macros and has no 
SQL-keyword gate.
   - `superset/models/helpers.py:4513` (post-render collector) — not affected: 
SQLStatement.has_subquery already recognizes FROM-first subqueries and the 
ambiguous-output fallback remains per-viewer.
   - `superset/connectors/sqla/models.py:3034` (has_extra_cache_key_calls) — 
not affected: it gates ExtraCache macro extraction, not RLS collection, which 
is called independently.
   - `superset/connectors/sqla/models.py:3196` (dataset cache keys) — not 
affected: expression RLS delegates to the corrected shared collector and 
dataset SQL collection has no SELECT-keyword prefilter.
   - `superset/models/sql_lab.py:395` (Query cache keys) — not affected: 
expression RLS delegates to the corrected shared collector and query SQL 
collection has no SELECT-keyword prefilter.
   - `superset/models/helpers.py:4135` (Query adhoc-column adapter) — not 
affected: Query executes adhoc SQL literally rather than expanding saved 
expressions.
   - `superset/models/sql_lab.py:268` (Query columns) — not affected: result 
metadata has no saved-column expressions, pinned by 
test_query_column_metadata_does_not_expand_saved_expressions.
   
   Remaining: no known uncovered case in this regression. I have left the 
thread open for review.
   



##########
superset/models/helpers.py:
##########
@@ -3994,6 +4019,201 @@ def get_rendered_sql(
     def text(self, clause: str) -> TextClause:
         return self.db_engine_spec.get_text_clause(clause)
 
+    def _get_rls_schema(self) -> str:
+        """Resolve RLS tables with the same schema policy as SQL 
expressions."""
+        return get_rls_schema(
+            self.database,
+            self.catalog,
+            self.schema,
+            self.sql or "",
+            self.template_params_dict,
+        )
+
+    def _get_filter_column(
+        self,
+        name: str,
+        columns_by_name: dict[str, "TableColumn"],
+    ) -> Optional["TableColumn"]:
+        """Resolve real names before the first matching verbose filter 
label."""
+        col = columns_by_name.get(name)
+        if col is not None:
+            return col
+        return next((col for col in self.columns if col.verbose_name == name), 
None)
+
+    def _get_query_sql_expressions(  # noqa: C901
+        self,
+        query_obj: QueryObjectDict,
+        template_processor: BaseTemplateProcessor,
+        granularity: str | None,
+    ) -> list[str]:
+        """Find SQL-bearing chart fields without building a chart or its 
prequeries."""
+        columns_by_name = {col.column_name: col for col in self.columns}
+        columns = {name: col.expression for name, col in 
columns_by_name.items()}
+        metrics = {metric.metric_name: metric.expression for metric in 
self.metrics}
+        selected_columns = query_obj.get("columns") or []
+        selected_metrics = query_obj.get("metrics") or []
+        adhoc_columns_by_label = {
+            col.get("label"): col
+            for col in selected_columns
+            if utils.is_adhoc_column(col)
+        }
+        metric_labels = {utils.get_metric_name(metric) for metric in 
selected_metrics}
+        expressions: list[str] = []
+
+        def add(value: Any, metric: bool = False) -> None:
+            """Resolve stored references in the namespace used by the query 
sink."""
+            if isinstance(value, str):
+                references = metrics if metric else columns
+                expressions.append(references.get(value) or value)
+            elif isinstance(value, dict):
+                if sql := value.get("sqlExpression"):
+                    sql = template_processor.process_template(sql)
+                    # Adhoc columns can name a saved calculated column after
+                    # rendering. SQL metrics use their SQL literally instead.
+                    expressions.append(
+                        sql if metric else columns.get(sql.strip()) or sql
+                    )
+                add(value.get("column"))
+                add(value.get("column_name"))
+
+        def add_orderby(value: Any) -> None:
+            """ORDER BY prefers selected labels before stored metric/column 
names."""
+            if isinstance(value, str):
+                if value in metric_labels:
+                    # Selected metrics have already been collected.
+                    return
+                if value in adhoc_columns_by_label:
+                    add(adhoc_columns_by_label[value])
+                    return
+            add(
+                value,
+                metric=utils.is_adhoc_metric(value)
+                or (isinstance(value, str) and value in metrics),
+            )
+
+        def add_filter(value: Any) -> None:
+            """Filters accept verbose column names before stored metric 
names."""
+            if not isinstance(value, str):
+                add(value)
+            elif value == utils.DTTM_ALIAS and query_obj.get("is_timeseries", 
True):
+                add(granularity)
+            elif (col := self._get_filter_column(value, columns_by_name)) is 
not None:
+                expressions.append(col.expression or value)
+            elif value in metrics:
+                add(value, metric=True)
+            elif value in adhoc_columns_by_label:
+                add(adhoc_columns_by_label[value])
+
+        for value in query_obj.get("groupby") or selected_columns:
+            add(value)
+        for metric_value in selected_metrics:
+            add(metric_value, metric=True)
+        for orderby_value, _ascending in query_obj.get("orderby") or []:
+            add_orderby(orderby_value)
+        for flt in query_obj.get("filter") or []:
+            add_filter(flt.get("col"))
+        series_limit = query_obj.get("series_limit") or (
+            query_obj.get("timeseries_limit")
+            if query_obj.get("is_timeseries", True)
+            else 0
+        )
+        add(
+            (
+                query_obj.get("series_limit_metric")
+                or query_obj.get("timeseries_limit_metric")
+            )
+            if series_limit
+            else None,
+            metric=True,
+        )
+        add(granularity)
+        if (
+            granularity
+            and self.always_filter_main_dttm
+            and self.main_dttm_col in self.dttm_cols
+            and self.main_dttm_col != granularity
+        ):
+            add(self.main_dttm_col)
+        extras = query_obj.get("extras") or {}
+        expressions.extend(
+            extras[key] for key in ("where", "having") if extras.get(key)
+        )
+        if (
+            query_obj.get("apply_fetch_values_predicate")
+            and self.fetch_values_predicate
+        ):
+            expressions.append(self.fetch_values_predicate)
+        return expressions
+
+    def _get_query_rls_cache_keys(
+        self,
+        query_obj: QueryObjectDict,
+        default_schema: str | None = None,
+        resolve_catalog: Callable[[], str | None] | None = None,
+    ) -> list[str]:
+        """Collect expression-only RLS rules without building or executing SQL.
+
+        Only subquery expressions need table discovery. Reuse the
+        datasource SQL's resolved schema, or resolve it once for physical 
datasets.
+        Do not exclude the dataset's own id: an independent expression 
subquery is
+        not constrained by the outer query's RLS.
+        """
+        from superset.utils.rls import collect_rls_predicates_for_sql
+
+        if not query_obj or not is_feature_enabled("ALLOW_ADHOC_SUBQUERY"):
+            return []
+        resolve_catalog = resolve_catalog or 
cache(self.database.get_default_catalog)
+        extras = query_obj.get("extras") or {}
+        granularity = query_obj.get("granularity")
+        if granularity is not None and granularity not in self.dttm_cols:
+            granularity = self.main_dttm_col
+        template_kwargs = {
+            key: query_obj.get(key)
+            for key in (
+                "columns",
+                "groupby",
+                "metrics",
+                "row_limit",
+                "row_offset",
+                "filter",
+            )
+        }
+        template_kwargs.update(
+            time_column=granularity,
+            time_grain=extras.get("time_grain_sqla"),
+            table_columns=[col.column_name for col in self.columns],
+        )
+        template_kwargs.update(self.template_params_dict)
+        template_processor = self.get_template_processor(**template_kwargs)
+        predicates: set[str] = set()
+        for expression in self._get_query_sql_expressions(
+            query_obj, template_processor, granularity
+        ):
+            expression = template_processor.process_template(expression)

Review Comment:
   My previous commit 1a121cb71cf0dc0c7154dbc96b8220f78e38a9f9 reopened shared 
RLS cache keys by skipping expressions without SELECT. This corrective commit 
restores those predicates without introducing another template render; the 
existing literal-template and single-render regressions remain covered.
   
   Fixed in 0c4fa24d190053c1b7f104a76cfe71f5eebb76ed: the side-effect-free 
prefilter uses the same SQL parser's subquery/table extraction as execution 
instead of requiring a SELECT keyword. FROM-first expressions (including 
lowercase, comments, whitespace, nested parentheses, and literal-template 
outputs) therefore contribute the executed table's per-viewer RLS predicates. 
Table-free expressions still do not render templates before cache lookup; 
opaque or unparseable template output remains viewer-scoped without executing 
its macros.
   
   Tests: `test_from_first_subquery_cache_keys_are_user_scoped`, 
`test_from_first_saved_expression_cache_keys_are_user_scoped`, 
`test_from_first_dialect_subquery_cache_keys_are_user_scoped`, and the 
FROM-first parameter of `test_expression_cache_key_sibling_fields`. Restoring 
the prefilter verbatim from 1a121cb71cf0dc0c7154dbc96b8220f78e38a9f9 gives 237 
failures and 34 compatibility passes; the complete affected file passes 757 
tests with this commit. These assert distinct final cache keys for two viewers 
with different physical-table RLS, and real SQLite results for column, SQL 
metric, WHERE and HAVING on both datasource types. 
`test_cache_key_skips_partition_templates_without_subqueries` and 
`test_swapped_table_rules_have_distinct_cache_keys` also pass. 
`test_cache_key_uses_only_executed_time_column` includes unused subqueries 
alongside `{{ 1 / 0 }}`; an eager-primary-column mutation fails both new 
SqlaTable variants under lazy rendering.
   
   Siblings checked:
   - `superset/models/helpers.py:4204` (the shared prefilter) — fixed in 
0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/models/helpers.py:4232` (add_expression, adhoc/saved columns, 
SQL/SIMPLE/saved metrics, ORDER BY, filters, WHERE/HAVING, fetch predicate, 
time-column and series-limit gates) — fixed in 
0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/connectors/sqla/models.py:2572` (saved-column RLS expression 
lookup) — fixed in 0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/models/helpers.py:4144` (literal Jinja sources) — not affected: 
it inspects literal Jinja inputs/outputs without running macros and has no 
SQL-keyword gate.
   - `superset/models/helpers.py:4513` (post-render collector) — not affected: 
SQLStatement.has_subquery already recognizes FROM-first subqueries and the 
ambiguous-output fallback remains per-viewer.
   - `superset/connectors/sqla/models.py:3034` (has_extra_cache_key_calls) — 
not affected: it gates ExtraCache macro extraction, not RLS collection, which 
is called independently.
   - `superset/connectors/sqla/models.py:3196` (dataset cache keys) — not 
affected: expression RLS delegates to the corrected shared collector and 
dataset SQL collection has no SELECT-keyword prefilter.
   - `superset/models/sql_lab.py:395` (Query cache keys) — not affected: 
expression RLS delegates to the corrected shared collector and query SQL 
collection has no SELECT-keyword prefilter.
   - `superset/models/helpers.py:4135` (Query adhoc-column adapter) — not 
affected: Query executes adhoc SQL literally rather than expanding saved 
expressions.
   - `superset/models/sql_lab.py:268` (Query columns) — not affected: result 
metadata has no saved-column expressions, pinned by 
test_query_column_metadata_does_not_expand_saved_expressions.
   
   Remaining: no known uncovered case in this regression. I have left the 
thread open for review.
   



##########
superset/models/helpers.py:
##########
@@ -4104,6 +4110,286 @@ def get_rendered_sql(
     def text(self, clause: str) -> TextClause:
         return self.db_engine_spec.get_text_clause(clause)
 
+    def _get_rls_schema(self) -> str:
+        """Resolve RLS tables with the same schema policy as SQL 
expressions."""
+        return get_rls_schema(
+            self.database,
+            self.catalog,
+            self.schema,
+            self.sql or "",
+            self.template_params_dict,
+        )
+
+    def _get_filter_column(
+        self,
+        name: str,
+        columns_by_name: dict[str, "TableColumn"],
+    ) -> Optional["TableColumn"]:
+        """Resolve real names before the first matching verbose filter 
label."""
+        col = columns_by_name.get(name)
+        if col is not None:
+            return col
+        return next((col for col in self.columns if col.verbose_name == name), 
None)
+
+    def _get_adhoc_column_rls_expression(
+        self,
+        col: AdhocColumn,
+        rendered_sql: str,
+        template_processor: BaseTemplateProcessor,
+    ) -> str | None:
+        """Query-backed adhoc columns execute their rendered SQL literally."""
+        return rendered_sql
+
+    def _get_query_sql_expressions(  # noqa: C901
+        self,
+        query_obj: QueryObjectDict,
+        template_processor: BaseTemplateProcessor,
+        granularity: str | None,
+        removed_filters: list[str],
+    ) -> list[str]:
+        """Render SQL-bearing chart fields without building a chart or its 
prequeries.
+
+        Render each source expression once, matching execution. An adhoc column
+        that resolves to saved metadata also renders that column's expression.
+        """
+        columns_by_name = {col.column_name: col for col in self.columns}
+        columns = {name: col.expression for name, col in 
columns_by_name.items()}
+        metrics = {metric.metric_name: metric.expression for metric in 
self.metrics}
+        selected_columns = query_obj.get("columns") or []
+        selected_metrics = query_obj.get("metrics") or []
+        adhoc_columns_by_label = {
+            col.get("label"): col
+            for col in selected_columns
+            if utils.is_adhoc_column(col)
+        }
+        metric_labels = {utils.get_metric_name(metric) for metric in 
selected_metrics}
+        expressions: list[str] = []
+
+        def add_expression(expression: str) -> None:
+            """Render source SQL, preserving literal Jinja in its output."""
+            expressions.append(template_processor.process_template(expression))
+
+        def add(value: Any, metric: bool = False) -> None:
+            """Resolve stored references in the namespace used by the query 
sink."""
+            if isinstance(value, str):
+                references = metrics if metric else columns
+                # Saved columns without expressions are quoted identifiers.
+                # Unknown selection strings have a separate SQL fallback below.
+                if expression := references.get(value):
+                    add_expression(expression)
+            elif isinstance(value, dict):
+                if sql := value.get("sqlExpression"):
+                    sql = template_processor.process_template(sql)
+                    if not metric:
+                        sql = self._get_adhoc_column_rls_expression(
+                            cast(AdhocColumn, value), sql, template_processor
+                        )
+                    if sql:
+                        expressions.append(sql)
+                add(value.get("column"))
+                add(value.get("column_name"))
+
+        def add_orderby(value: Any) -> None:
+            """ORDER BY prefers selected labels before stored metric/column 
names."""
+            if isinstance(value, str):
+                if value in metric_labels:
+                    # Selected metrics have already been collected.
+                    return
+                if value in adhoc_columns_by_label:
+                    add(adhoc_columns_by_label[value])
+                    return
+            add(
+                value,
+                metric=utils.is_adhoc_metric(value)
+                or (isinstance(value, str) and value in metrics),
+            )
+
+        def add_filter(value: Any) -> None:
+            """Filters accept verbose column names before stored metric 
names."""
+            # Execution renders adhoc/metric SQL before checking removal, but
+            # delays saved-column SQL until after that check.
+            if not isinstance(value, str):
+                add(value)
+            elif value == utils.DTTM_ALIAS and query_obj.get("is_timeseries", 
True):
+                if value not in removed_filters and granularity not in 
removed_filters:
+                    add(granularity)
+            elif (col := self._get_filter_column(value, columns_by_name)) is 
not None:
+                if value not in removed_filters and col.expression:
+                    add_expression(col.expression)
+            elif value in metrics:
+                add(value, metric=True)
+            elif value in adhoc_columns_by_label:
+                add(adhoc_columns_by_label[value])
+
+        effective_columns = query_obj.get("groupby") or [
+            col for col in selected_columns if col != utils.DTTM_ALIAS
+        ]
+        # ORDER BY metrics can enable grouping even without selected metrics.
+        need_groupby = bool(
+            query_obj.get("metrics") is not None
+            or query_obj.get("groupby")
+            or any(
+                utils.is_adhoc_metric(cast(Metric, value))
+                or (
+                    isinstance(value, str)
+                    and (
+                        value in metric_labels
+                        or (value in metrics and value not in 
adhoc_columns_by_label)
+                    )
+                )
+                for value, _ascending in query_obj.get("orderby") or []
+            )
+        )
+        for value in effective_columns:
+            if isinstance(value, str) and value not in columns:
+                # GROUP BY executes unknown strings as SQL. Detail selections
+                # quote them before rendering, unlike saved bare column names.
+                add_expression(
+                    value if need_groupby else 
self.database.quote_identifier(value)
+                )
+            else:
+                add(value)
+        for metric_value in selected_metrics:
+            add(metric_value, metric=True)
+        for orderby_value, _ascending in query_obj.get("orderby") or []:
+            add_orderby(orderby_value)
+        # FROM templates remove filters before execution renders time/filter 
columns.
+        # Render with the same processor so its removal state reaches this 
collector.
+        if self.sql:
+            self.get_rendered_sql(template_processor)

Review Comment:
   My previous commit 1a121cb71cf0dc0c7154dbc96b8220f78e38a9f9 avoided 
unnecessary template rendering, but its SELECT-keyword shortcut reopened shared 
RLS cache keys for `(FROM physical)`. This corrective commit retains the 
lazy-rendering behavior while detecting those table subqueries.
   
   Fixed in 0c4fa24d190053c1b7f104a76cfe71f5eebb76ed: the side-effect-free 
prefilter uses the same SQL parser's subquery/table extraction as execution 
instead of requiring a SELECT keyword. FROM-first expressions (including 
lowercase, comments, whitespace, nested parentheses, and literal-template 
outputs) therefore contribute the executed table's per-viewer RLS predicates. 
Table-free expressions still do not render templates before cache lookup; 
opaque or unparseable template output remains viewer-scoped without executing 
its macros.
   
   Tests: `test_from_first_subquery_cache_keys_are_user_scoped`, 
`test_from_first_saved_expression_cache_keys_are_user_scoped`, 
`test_from_first_dialect_subquery_cache_keys_are_user_scoped`, and the 
FROM-first parameter of `test_expression_cache_key_sibling_fields`. Restoring 
the prefilter verbatim from 1a121cb71cf0dc0c7154dbc96b8220f78e38a9f9 gives 237 
failures and 34 compatibility passes; the complete affected file passes 757 
tests with this commit. These assert distinct final cache keys for two viewers 
with different physical-table RLS, and real SQLite results for column, SQL 
metric, WHERE and HAVING on both datasource types. 
`test_cache_key_skips_partition_templates_without_subqueries` and 
`test_swapped_table_rules_have_distinct_cache_keys` also pass. 
`test_cache_key_uses_only_executed_time_column` includes unused subqueries 
alongside `{{ 1 / 0 }}`; an eager-primary-column mutation fails both new 
SqlaTable variants under lazy rendering.
   
   Siblings checked:
   - `superset/models/helpers.py:4204` (the shared prefilter) — fixed in 
0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/models/helpers.py:4232` (add_expression, adhoc/saved columns, 
SQL/SIMPLE/saved metrics, ORDER BY, filters, WHERE/HAVING, fetch predicate, 
time-column and series-limit gates) — fixed in 
0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/connectors/sqla/models.py:2572` (saved-column RLS expression 
lookup) — fixed in 0c4fa24d190053c1b7f104a76cfe71f5eebb76ed
   - `superset/models/helpers.py:4144` (literal Jinja sources) — not affected: 
it inspects literal Jinja inputs/outputs without running macros and has no 
SQL-keyword gate.
   - `superset/models/helpers.py:4513` (post-render collector) — not affected: 
SQLStatement.has_subquery already recognizes FROM-first subqueries and the 
ambiguous-output fallback remains per-viewer.
   - `superset/connectors/sqla/models.py:3034` (has_extra_cache_key_calls) — 
not affected: it gates ExtraCache macro extraction, not RLS collection, which 
is called independently.
   - `superset/connectors/sqla/models.py:3196` (dataset cache keys) — not 
affected: expression RLS delegates to the corrected shared collector and 
dataset SQL collection has no SELECT-keyword prefilter.
   - `superset/models/sql_lab.py:395` (Query cache keys) — not affected: 
expression RLS delegates to the corrected shared collector and query SQL 
collection has no SELECT-keyword prefilter.
   - `superset/models/helpers.py:4135` (Query adhoc-column adapter) — not 
affected: Query executes adhoc SQL literally rather than expanding saved 
expressions.
   - `superset/models/sql_lab.py:268` (Query columns) — not affected: result 
metadata has no saved-column expressions, pinned by 
test_query_column_metadata_does_not_expand_saved_expressions.
   
   Remaining: no known uncovered case in this regression. I have left the 
thread open for review.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to