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


##########
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:
   Fixed in 90d1e8a992f2dc98d2652cf9ad53b4d757fbe5f3. I made FROM rendering 
lazy: it runs only when a calculated time/filter column needs filter-removal 
state, rather than for every expression-cache-key collection.
   
   `test_cache_key_does_not_render_unneeded_partition_macro` covers both 
SqlaTable and Query, plain selections/filters/time columns and an independent 
expression, with successful and failing Presto partition discovery. All 16 
cases fail with the helper restored to 24670f522f1b8870ba56ec0a3d465e8c24dc1bec 
and pass after the change. The test also checks that an explicit execution-time 
render still invokes the macro. Existing swapped-RLS and removed-filter 
regressions pass.
   
   Siblings checked:
   - superset/models/helpers.py:4143 — fixed in 
90d1e8a992f2dc98d2652cf9ad53b4d757fbe5f3
   - superset/connectors/sqla/models.py:3193 — fixed in 
90d1e8a992f2dc98d2652cf9ad53b4d757fbe5f3 (uses the shared collector)
   - superset/models/sql_lab.py:395 — fixed in 
90d1e8a992f2dc98d2652cf9ad53b4d757fbe5f3 (uses the shared collector)
   - superset/connectors/sqla/models.py:3031 — not affected: ExtraCache 
detection only scans text; its SQL-build path remains gated on needing extra 
keys.
   - superset/connectors/sqla/models.py:2460 — not affected: physical FROM 
skips templating; virtual FROM delegates to execution-time generation.
   - superset/connectors/sqla/models.py:2572 — not affected: renders the 
specific chart expression for metadata resolution, not the dataset FROM 
template.
   - superset/models/helpers.py:4414 — not affected: actual SQL generation must 
render FROM; its ExtraCache pre-cache caller remains gated.
   - superset/utils/rls.py:309 — not affected: the base-SQL collector parses 
SQL rather than rendering the dataset template.
   - superset/sql/parse.py:3883 — not affected: the validation render used by 
PostgreSQL schema discovery neutralizes/stubs partition macros.
   



-- 
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