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


##########
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 earlier reply missed the GROUP BY raw-SQL path. The statement that 
columns/GROUP BY use real names only was incorrect.
   
   Fixed in aa1813a84f789496e71aa86ed6bc8e5643dc06fb. The collector 
distinguishes raw GROUP BY SQL from saved bare names and quote-before-render 
detail selections. It also follows ORDER BY-enabled grouping for series metrics 
and the datasource-specific adhoc-column fallback. It does not render 
already-rendered SQL again, and the earlier bare-name and removed_filters 
regressions remain passing.
   
   Regression tests in 
`tests/unit_tests/connectors/sqla/virtual_dataset_connections_test.py`:
   - `test_raw_string_groupby_cache_keys_are_user_scoped`: 22 SqlaTable/Query 
cases covering columns with a metric, direct legacy groupby, grouping enabled 
by adhoc/saved ORDER BY metrics, JOIN/prequery series paths, and 
literal-template output. Execution returns `[1]` and `[2]`; the incoming source 
contributes `[[], []]` and equal full keys. The new collector contributes each 
exact predicate, yields different keys, and renders each selection source once.
   - `test_raw_string_detail_selection_matches_execution`: unknown detail 
strings are quoted before one template pass, rather than treated like saved 
bare names. Both datasource types assert exact render inputs and parsed SQL, 
including a template whose rendered expression needs RLS.
   - `test_orderby_grouping_collects_series_metric_rls`: active series metrics 
contribute their predicates when ORDER BY enables grouping without selected 
metrics.
   - `test_quoted_adhoc_reference_fallback_cache_keys_are_user_scoped`: missing 
`isColumnReference` metadata uses quoted original source, covering selected 
columns, groupby, filters, ORDER BY labels and BASE_AXIS.
   - `test_adhoc_sql_looking_saved_name_matches_datasource_execution`: 
SqlaTable quotes a matching physical metadata name, while Query executes 
literal adhoc SQL even on a metadata match.
   
   Docker proof: with these final tests retained and production 
helpers.py/models.py restored to bac4307d0cd0444a7f5883a80aedf49582984fbe 
(SHA256 checked against `git show`), **34 failed, 347 passed**. At 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb, **381 passed** in the complete 
module, retaining all 346 earlier cases. The one new before-pass is the Query 
metadata-match compatibility control. Full `tests/unit_tests/connectors` and 
`tests/unit_tests/models`, plus the affected schema-parameter tests, RLS 
utilities and query-context processor tests: **1,966 passed** 
(`manual`/`llm_compliance` deselected). Staged-file and final 
branch-changed-file pre-commit pass; Docker changed-file Ruff passes. `docker 
rm -fv pr45681-raw-tests` completed; `docker ps -a --filter name=pr45681` shows 
only the header.
   
   Siblings checked:
   - superset/models/helpers.py:4066 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:4135 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:4118 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:4184 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:4024 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/connectors/sqla/models.py:2389 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:5474 — not affected: execution already renders 
unknown GROUP BY strings as SQL; the collector is aligned with it, including 
grouping activated by ORDER BY.
   - superset/models/helpers.py:5534 — not affected: detail execution quotes 
unknown strings before rendering; the new detail regression pins both literal 
identifiers and SQL produced by that template pass.
   - superset/models/helpers.py:5440 — not affected: unknown ORDER BY strings 
are rejected rather than treated as SQL; adhoc SQL metrics and label-referenced 
columns follow their separate tested namespaces.
   - superset/models/helpers.py:5748 — not affected: unknown filter strings are 
skipped, while saved columns, metrics and adhoc labels have explicit 
resolution; existing filter/verbose-label/removed_filters tests pass.
   - superset/connectors/sqla/models.py:2446 — not affected: execution already 
renders an adhoc lookup, resolves saved metadata, then quotes the original 
source for a missing isColumnReference fallback; the new collector override 
mirrors those choices.
   - superset/models/sql_lab.py:512 — not affected: Query executes adhoc SQL 
literally even when its metadata name matches; the new default collector hook 
preserves that contract and its Query control passes.
   - superset/connectors/sqla/models.py:2562 — not affected: series metrics 
remain SQL metrics or saved metric names, not column references; grouping 
activation in the caller is the changed condition. Both JOIN and prequery 
execution variants are tested.
   - superset/models/helpers.py:4593 — not affected: the Query series-orderby 
wrapper uses the same metric namespace; Query grouping and series tests cover 
its caller.
   - superset/connectors/sqla/models.py:2891 — not affected: the SqlaTable 
cache wrapper delegates to the changed common collector and does not interpret 
raw selection strings independently.
   - superset/models/sql_lab.py:395 — not affected: the Query cache wrapper 
delegates to the changed common collector; the raw-string two-user tests cover 
both wrappers.
   - superset/models/helpers.py:2146 — not affected: SQL metric execution 
renders source once and disables rendering in the validation stage; the 
existing single-render metric tests pass unchanged.
   - superset/models/helpers.py:2180 — not affected: generic SQL execution 
renders its own source, not collector output; no second pass was added to 
expression predicate parsing.
   - superset/models/helpers.py:3947 — not affected: datasource SQL is a 
separate source collected independently from selected expressions; the new 
probes deliberately use table-free datasource SQL.
   - superset/models/helpers.py:5067 — not affected: expression-validation SQL 
is execution/validation-only, not a chart cache consumer.
   - superset/models/helpers.py:5160 — not affected: the legacy dict timestamp 
helper is not used for TableColumn-based chart selections; the existing 
temporal-column and removal regressions pass.
   - superset/models/helpers.py:5182 — not affected: saved calculated-column 
execution renders stored expressions, not their quoted names; existing 
stored-expression and bare-name tests pass.
   - superset/models/helpers.py:6253 — not affected: WHERE and HAVING enter the 
collector as source SQL and remain single-rendered; the prior two-user controls 
pass.
   - superset/models/helpers.py:6296 — not affected: ORDER BY alias 
literal_column wraps an already-collected expression label without rendering 
new SQL.
   - superset/connectors/sqla/models.py:1170 — not affected: dataset RLS clause 
SQL is collected through the existing per-dataset RLS key, not through raw 
selection fallback.
   - superset/connectors/sqla/models.py:1182 — not affected: guest RLS clause 
SQL remains on the existing guest/dataset RLS path.
   - superset/connectors/sqla/models.py:1507 — not affected: saved column 
source SQL is rendered separately; uncalculated saved names remain quoted, 
never templated by the collector.
   - superset/connectors/sqla/models.py:1598 — not affected: saved timestamp 
expressions use their stored source; the existing effective-time and 
removed-time tests pass.
   - superset/connectors/sqla/models.py:1752 — not affected: saved metric 
source SQL is rendered in the metric namespace, not the new adhoc-column hook.
   - superset/connectors/sqla/models.py:1793 — not affected: normalized metric 
SQL is derived from the stored metric source, not an unknown column-name 
fallback.
   - superset/connectors/sqla/models.py:2207 — not affected: fetch predicates 
remain explicit SQL sources collected only when enabled; the prior 
stored-expression control passes.
   - superset/connectors/sqla/models.py:2352 — not affected: adhoc SQL metrics 
stay literal; the column-specific hook is not called for them.
   - superset/common/query_context_processor.py:461 — not affected: synchronous 
chart cache consumers call the datasource wrapper with the same query dict; 
there is no separate selection renderer.
   - superset/common/query_context.py:157 — not affected: delegates cache-key 
creation to QueryContextProcessor.
   - superset/tasks/async_queries.py:372 — not affected: asynchronous keys use 
the same QueryContext/datasource collector.
   - superset/semantic_layers/models.py:886 — not affected: separate cache 
implementation that does not inherit ExploreMixin.
   



##########
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 earlier reply missed the GROUP BY raw-SQL path. The comment about quoting 
bare column names incorrectly covered unknown GROUP BY strings; I replaced that 
comment with the specific saved-name rule.
   
   Fixed in aa1813a84f789496e71aa86ed6bc8e5643dc06fb. The collector 
distinguishes raw GROUP BY SQL from saved bare names and quote-before-render 
detail selections. It also follows ORDER BY-enabled grouping for series metrics 
and the datasource-specific adhoc-column fallback. It does not render 
already-rendered SQL again, and the earlier bare-name and removed_filters 
regressions remain passing.
   
   Regression tests in 
`tests/unit_tests/connectors/sqla/virtual_dataset_connections_test.py`:
   - `test_raw_string_groupby_cache_keys_are_user_scoped`: 22 SqlaTable/Query 
cases covering columns with a metric, direct legacy groupby, grouping enabled 
by adhoc/saved ORDER BY metrics, JOIN/prequery series paths, and 
literal-template output. Execution returns `[1]` and `[2]`; the incoming source 
contributes `[[], []]` and equal full keys. The new collector contributes each 
exact predicate, yields different keys, and renders each selection source once.
   - `test_raw_string_detail_selection_matches_execution`: unknown detail 
strings are quoted before one template pass, rather than treated like saved 
bare names. Both datasource types assert exact render inputs and parsed SQL, 
including a template whose rendered expression needs RLS.
   - `test_orderby_grouping_collects_series_metric_rls`: active series metrics 
contribute their predicates when ORDER BY enables grouping without selected 
metrics.
   - `test_quoted_adhoc_reference_fallback_cache_keys_are_user_scoped`: missing 
`isColumnReference` metadata uses quoted original source, covering selected 
columns, groupby, filters, ORDER BY labels and BASE_AXIS.
   - `test_adhoc_sql_looking_saved_name_matches_datasource_execution`: 
SqlaTable quotes a matching physical metadata name, while Query executes 
literal adhoc SQL even on a metadata match.
   
   Docker proof: with these final tests retained and production 
helpers.py/models.py restored to bac4307d0cd0444a7f5883a80aedf49582984fbe 
(SHA256 checked against `git show`), **34 failed, 347 passed**. At 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb, **381 passed** in the complete 
module, retaining all 346 earlier cases. The one new before-pass is the Query 
metadata-match compatibility control. Full `tests/unit_tests/connectors` and 
`tests/unit_tests/models`, plus the affected schema-parameter tests, RLS 
utilities and query-context processor tests: **1,966 passed** 
(`manual`/`llm_compliance` deselected). Staged-file and final 
branch-changed-file pre-commit pass; Docker changed-file Ruff passes. `docker 
rm -fv pr45681-raw-tests` completed; `docker ps -a --filter name=pr45681` shows 
only the header.
   
   Siblings checked:
   - superset/models/helpers.py:4066 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:4135 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:4118 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:4184 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:4024 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/connectors/sqla/models.py:2389 — fixed in 
aa1813a84f789496e71aa86ed6bc8e5643dc06fb
   - superset/models/helpers.py:5474 — not affected: execution already renders 
unknown GROUP BY strings as SQL; the collector is aligned with it, including 
grouping activated by ORDER BY.
   - superset/models/helpers.py:5534 — not affected: detail execution quotes 
unknown strings before rendering; the new detail regression pins both literal 
identifiers and SQL produced by that template pass.
   - superset/models/helpers.py:5440 — not affected: unknown ORDER BY strings 
are rejected rather than treated as SQL; adhoc SQL metrics and label-referenced 
columns follow their separate tested namespaces.
   - superset/models/helpers.py:5748 — not affected: unknown filter strings are 
skipped, while saved columns, metrics and adhoc labels have explicit 
resolution; existing filter/verbose-label/removed_filters tests pass.
   - superset/connectors/sqla/models.py:2446 — not affected: execution already 
renders an adhoc lookup, resolves saved metadata, then quotes the original 
source for a missing isColumnReference fallback; the new collector override 
mirrors those choices.
   - superset/models/sql_lab.py:512 — not affected: Query executes adhoc SQL 
literally even when its metadata name matches; the new default collector hook 
preserves that contract and its Query control passes.
   - superset/connectors/sqla/models.py:2562 — not affected: series metrics 
remain SQL metrics or saved metric names, not column references; grouping 
activation in the caller is the changed condition. Both JOIN and prequery 
execution variants are tested.
   - superset/models/helpers.py:4593 — not affected: the Query series-orderby 
wrapper uses the same metric namespace; Query grouping and series tests cover 
its caller.
   - superset/connectors/sqla/models.py:2891 — not affected: the SqlaTable 
cache wrapper delegates to the changed common collector and does not interpret 
raw selection strings independently.
   - superset/models/sql_lab.py:395 — not affected: the Query cache wrapper 
delegates to the changed common collector; the raw-string two-user tests cover 
both wrappers.
   - superset/models/helpers.py:2146 — not affected: SQL metric execution 
renders source once and disables rendering in the validation stage; the 
existing single-render metric tests pass unchanged.
   - superset/models/helpers.py:2180 — not affected: generic SQL execution 
renders its own source, not collector output; no second pass was added to 
expression predicate parsing.
   - superset/models/helpers.py:3947 — not affected: datasource SQL is a 
separate source collected independently from selected expressions; the new 
probes deliberately use table-free datasource SQL.
   - superset/models/helpers.py:5067 — not affected: expression-validation SQL 
is execution/validation-only, not a chart cache consumer.
   - superset/models/helpers.py:5160 — not affected: the legacy dict timestamp 
helper is not used for TableColumn-based chart selections; the existing 
temporal-column and removal regressions pass.
   - superset/models/helpers.py:5182 — not affected: saved calculated-column 
execution renders stored expressions, not their quoted names; existing 
stored-expression and bare-name tests pass.
   - superset/models/helpers.py:6253 — not affected: WHERE and HAVING enter the 
collector as source SQL and remain single-rendered; the prior two-user controls 
pass.
   - superset/models/helpers.py:6296 — not affected: ORDER BY alias 
literal_column wraps an already-collected expression label without rendering 
new SQL.
   - superset/connectors/sqla/models.py:1170 — not affected: dataset RLS clause 
SQL is collected through the existing per-dataset RLS key, not through raw 
selection fallback.
   - superset/connectors/sqla/models.py:1182 — not affected: guest RLS clause 
SQL remains on the existing guest/dataset RLS path.
   - superset/connectors/sqla/models.py:1507 — not affected: saved column 
source SQL is rendered separately; uncalculated saved names remain quoted, 
never templated by the collector.
   - superset/connectors/sqla/models.py:1598 — not affected: saved timestamp 
expressions use their stored source; the existing effective-time and 
removed-time tests pass.
   - superset/connectors/sqla/models.py:1752 — not affected: saved metric 
source SQL is rendered in the metric namespace, not the new adhoc-column hook.
   - superset/connectors/sqla/models.py:1793 — not affected: normalized metric 
SQL is derived from the stored metric source, not an unknown column-name 
fallback.
   - superset/connectors/sqla/models.py:2207 — not affected: fetch predicates 
remain explicit SQL sources collected only when enabled; the prior 
stored-expression control passes.
   - superset/connectors/sqla/models.py:2352 — not affected: adhoc SQL metrics 
stay literal; the column-specific hook is not called for them.
   - superset/common/query_context_processor.py:461 — not affected: synchronous 
chart cache consumers call the datasource wrapper with the same query dict; 
there is no separate selection renderer.
   - superset/common/query_context.py:157 — not affected: delegates cache-key 
creation to QueryContextProcessor.
   - superset/tasks/async_queries.py:372 — not affected: asynchronous keys use 
the same QueryContext/datasource collector.
   - superset/semantic_layers/models.py:886 — not affected: separate cache 
implementation that does not inherit ExploreMixin.
   



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