aminghadersohi commented on code in PR #45681:
URL: https://github.com/apache/superset/pull/45681#discussion_r4381899188
##########
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:
Fixed in 9da337ea2de9464e92c5db61477a82e451cf62e6. I changed the collector
to render and strip adhoc column references before saved-column lookup,
independently of `expressionType`, while keeping SQL metrics literal.
Regression test:
`tests/unit_tests/connectors/sqla/virtual_dataset_connections_test.py::test_adhoc_saved_column_cache_keys_are_user_scoped`
executes with two users and two different RLS rules, then asserts different
full cache keys. Against the incoming head, 15 cases fail with equal keys and 3
existing-behavior controls pass; all 18 pass after the change.
`test_expression_cache_key_ignores_shadowed_references` and
`test_groupby_alias_retains_saved_column_rls` also fail before and pass after
for namespace/label siblings. The complete focused module passes 230 tests.
Siblings checked:
- superset/models/helpers.py:4054 — fixed in
9da337ea2de9464e92c5db61477a82e451cf62e6
- superset/models/helpers.py:4070 — fixed in
9da337ea2de9464e92c5db61477a82e451cf62e6
- superset/models/helpers.py:4085 — fixed in
9da337ea2de9464e92c5db61477a82e451cf62e6
- superset/connectors/sqla/models.py:2402 — not affected: SELECT/GROUP BY
and adhoc filters already share the rendered, stripped saved-column lookup; the
regression exercises these execution paths.
- superset/connectors/sqla/models.py:2286 — not affected: SQL metrics stay
literal and SIMPLE metrics resolve their column namespace;
test_sql_metric_cache_key_does_not_resolve_saved_column and
test_expression_cache_key_sibling_fields cover both.
- superset/models/sql_lab.py:484 — not affected: query-backed columns
execute raw expressions rather than saved calculated columns; the existing
Query expression/cache tests pass.
- superset/common/query_context_processor.py:461 — not affected: chart cache
consumers use the shared datasource collector, with no separate lookup
implementation.
- superset/semantic_layers/models.py:886 — not affected: separate cache
implementation that does not inherit ExploreMixin.
##########
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:
Fixed in 9da337ea2de9464e92c5db61477a82e451cf62e6. I pass the
execution-normalized granularity into expression collection and only collect
the primary time column additionally when `always_filter_main_dttm` uses it. A
valid SQL Lab Query time column never evaluates `main_dttm_col`.
Regression tests:
`tests/unit_tests/connectors/sqla/virtual_dataset_connections_test.py::test_cache_key_uses_only_executed_time_column`
first builds executable SQL, then checks cache keys. Both incoming-head cases
fail (Query: AttributeError; SqlaTable: unused template division-by-zero); both
pass after the change. `test_cache_key_collects_effective_primary_time_column`
also pins invalid-granularity fallback and always-filter-primary RLS
contributions: the unused non-temporal expression case fails before and passes
after; the primary-filter case is a passing control.
Siblings checked:
- superset/models/helpers.py:4120 — fixed in
9da337ea2de9464e92c5db61477a82e451cf62e6
- superset/models/helpers.py:4180 — fixed in
9da337ea2de9464e92c5db61477a82e451cf62e6
- superset/models/helpers.py:5203 — not affected: execution already
normalizes invalid granularity and gates the additional primary filter with
always_filter_main_dttm; the new tests compare against this builder.
- superset/models/sql_lab.py:364 — not affected: valid Query granularity no
longer reaches this property during collection; invalid granularity already
reaches it during execution and is not a valid execution path repaired here.
- superset/connectors/sqla/models.py:2871 — not affected: SqlaTable
delegates to the same collector; test_cache_key_uses_only_executed_time_column
covers its unused primary column.
- superset/models/sql_lab.py:395 — not affected: Query delegates to the same
collector; the Query parameter of the regression covers its TableColumn
metadata.
- superset/common/query_context_processor.py:461 — not affected:
test_cache_hit_schema_discovery_connection_budget retains exactly one discovery
connection and test_cache_hit_series_limit_does_not_execute_prequery retains
zero prequeries.
##########
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:
Fixed in 9da337ea2de9464e92c5db61477a82e451cf62e6. The text says the
execution schema takes precedence when discoverable, and the configured dataset
schema remains the fallback for RLS lookup and cache keys when connection
parameters and the inspector yield no schema.
Regression test:
`tests/unit_tests/connectors/sqla/virtual_dataset_connections_test.py::test_rls_schema_fallback_documented`
checks the empty-discovery fallback and its documentation. It fails on the
incoming wording and passes after this edit. The existing unresolved-schema
execution, retry and cache-key cases also pass.
Siblings checked:
- docs/admin_docs/security/security.mdx:668 — fixed in
9da337ea2de9464e92c5db61477a82e451cf62e6
- superset/utils/rls.py:58 — not affected: runtime already retains the
configured schema after empty discovery; no policy change is needed.
- superset/models/helpers.py:4022 — not affected: get_from_clause and
expression cache collection share this schema resolver, covered by the
unresolved-schema and connection-schema tests.
- superset/connectors/sqla/models.py:2871 — not affected: dataset cache
collection uses the same resolver and retains the fallback.
- superset/models/sql_lab.py:395 — not affected: query-backed cache
collection uses the same resolver and retains the fallback.
--
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]