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


##########
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:
   The cache-key collector skips the saved-column lookup whenever 
`expressionType` is present, but execution (`adhoc_column_to_sqla`) resolves 
the rendered, stripped `sqlExpression` against saved columns regardless of 
`expressionType`. The frontend always sends `expressionType: 'SQL'` on adhoc 
columns, including every time-series x-axis. So with `ALLOW_ADHOC_SUBQUERY` on, 
an adhoc column naming a saved calculated column like `(SELECT ... FROM 
protected)` executes with each user's RLS on `protected`, but the collector 
sees a bare identifier and contributes no predicates. Two users with different 
RLS on `protected` then share a cache key and the second can be served the 
first user's result. The same mismatch occurs without `expressionType` when the 
name carries surrounding whitespace (`columns.get(sql)` is unstripped). Should 
this lookup follow the sink's rule (strip, then resolve saved columns for 
non-metric values) instead of keying on `expressionType`?



##########
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
+                    )
+                add(value.get("column"))
+                add(value.get("column_name"))
+
+        for value in [
+            *(query_obj.get("columns") or []),
+            *(query_obj.get("groupby") or []),
+        ]:
+            add(value)
+        for metric_value in query_obj.get("metrics") or []:
+            add(metric_value, metric=True)
+        for orderby_value, _ascending in query_obj.get("orderby") or []:
+            add(
+                orderby_value,
+                metric=isinstance(orderby_value, str) and orderby_value in 
metrics,
+            )
+        for flt in query_obj.get("filter") or []:
+            filter_column = flt.get("col")
+            add(
+                filter_column,
+                metric=isinstance(filter_column, str) and filter_column not in 
columns,
+            )
+        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(query_obj.get("granularity"))
+        add(self.main_dttm_col if query_obj.get("granularity") else None)

Review Comment:
   `self.main_dttm_col` is evaluated here whenever `granularity` is set. For 
SQL Lab `Query` datasources it calls `col.get('is_dttm')` on `TableColumn` 
objects (`Query.columns`), which have no `get`, so with `ALLOW_ADHOC_SUBQUERY` 
on, Explore-from-SQL-Lab with `granularity: 'ds'` raises `AttributeError` 
during cache-key generation, before any cache lookup or execution. Previously a 
valid granularity never reached that property. Separately, on `SqlaTable` this 
adds the primary datetime column's expression even when execution uses a 
different time column (execution only falls back to `main_dttm_col` with 
`always_filter_main_dttm`), so an unused column with a failing template or 
macro can break cache-key generation. Could this collect only the column 
execution would actually use, and avoid `main_dttm_col` for `Query`?



##########
docs/admin_docs/security/security.mdx:
##########
@@ -662,6 +662,13 @@ has important implications when working with virtual 
(SQL-based) datasets:
   Additionally, RLS filters on the **underlying physical datasets** referenced 
by the
   virtual dataset's SQL are injected into the inner subquery for each 
referenced table.
 
+When `ALLOW_ADHOC_SUBQUERY` is enabled, subqueries in chart columns, metrics 
and filters
+also receive the referenced datasets' RLS filters, and those filters 
contribute to the
+chart's cache key. Unqualified table names use the connection's execution 
schema on
+engines that cannot change schema per query, not an ignored dataset schema 
setting.

Review Comment:
   This reads as if the dataset schema is ignored on engines that cannot change 
schema per query, but `get_rls_schema` falls back to the configured dataset 
schema when neither the URI nor the inspector yields a default schema (e.g. 
Druid via pydruid). An operator who clears the dataset schema believing it 
irrelevant would drop the fallback used to match physical-dataset RLS rules and 
build cache keys. Could the text say the execution schema takes precedence when 
it can be discovered, and that the dataset schema remains the lookup fallback 
otherwise?



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