kaxil commented on code in PR #76688:
URL: https://github.com/apache/airflow/pull/76688#discussion_r4504510304


##########
providers/common/ai/src/airflow/providers/common/ai/utils/sql_validation.py:
##########
@@ -433,7 +433,8 @@ def validate_sql(
     :param allowed_types: Tuple of sqlglot expression types to permit.
         Defaults to ``(Select, Union, Intersect, Except)``. When supplied, the
         caller takes full control of the allow-list and 
``allow_read_only_metadata``
-        is ignored.
+        is ignored. The deep scan for data-modifying nodes still runs unless 
one of
+        the supplied types is itself a data-modifying type (e.g. 
``exp.Insert``).

Review Comment:
   The comment above `_DATA_MODIFYING_NODES` still says custom `allowed_types` 
bypass the deep scan entirely, and `docs/stability.rst` still scopes the 
nested-write check to the default types. Can you update both to the new rule?



##########
providers/common/ai/src/airflow/providers/common/ai/utils/sql_validation.py:
##########
@@ -445,18 +446,17 @@ def validate_sql(
     :raises SQLSafetyError: If the SQL is empty, contains disallowed statement 
types,
         or has multiple statements when not permitted.
     """
-    # A caller-supplied ``allowed_types`` is an explicit opt-out of the curated
-    # read-only defaults (and the data-modifying deep scan). Otherwise we use 
the
-    # read-only defaults, optionally widened with metadata statements, and keep
-    # the deep scan on.
     if allowed_types is None:
         types: tuple[type[exp.Expr], ...] = DEFAULT_ALLOWED_TYPES
         if allow_read_only_metadata:
             types = types + READ_ONLY_METADATA_TYPES
-        run_data_modifying_scan = True
     else:
         types = allowed_types
-        run_data_modifying_scan = types == DEFAULT_ALLOWED_TYPES
+    # Only an allow-list that admits a write opts out of the deep scan. 
Comparing to
+    # DEFAULT_ALLOWED_TYPES would also switch it off for a narrower or 
reordered
+    # read-only list such as ``(exp.Select,)``, letting ``WITH d AS (DELETE 
...)
+    # SELECT ...`` through.
+    run_data_modifying_scan = not any(issubclass(t, _DATA_MODIFYING_NODES) for 
t in types)

Review Comment:
   This only checks one direction. `(exp.Select, exp.DML)` admits an INSERT at 
the `isinstance` check below, but `issubclass(exp.DML, _DATA_MODIFYING_NODES)` 
is False, so the scan stays on and rejects that same INSERT as "read-only 
mode". That config works on 1.1.0. Could the scan look only for the nodes the 
list doesn't admit, e.g. `denied = tuple(n for n in _DATA_MODIFYING_NODES if 
not issubclass(n, types))`? That would also stop `(exp.Select, exp.Insert)` 
from letting a DELETE CTE through, which passes both before and after this 
change.



##########
providers/common/ai/tests/unit/common/ai/utils/test_sql_validation.py:
##########
@@ -220,6 +220,26 @@ def test_deep_scan_runs_with_explicit_default_types(self):
                 dialect="postgres",
             )
 
+    @pytest.mark.parametrize(
+        "allowed_types",
+        [
+            pytest.param((exp.Select,), id="narrower"),

Review Comment:
   Could you add rows where the list admits a write, like `(exp.Select, 
exp.Insert)` and `(exp.Select, exp.DML)`, and assert the INSERT passes? Nothing 
covers the opt-out side today, so changing `any` to `all` keeps every test 
green. `DEFAULT_ALLOWED_TYPES` could also be a param here and replace 
`test_deep_scan_runs_with_explicit_default_types`.



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

Reply via email to