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


##########
providers/common/ai/tests/unit/common/ai/toolsets/test_sql.py:
##########
@@ -548,6 +548,42 @@ def 
test_a_row_carrying_the_connection_password_reaches_the_model_masked(self, r
 
 
 class TestSQLToolsetCheckQuery:
+    @pytest.mark.parametrize(
+        ("allow_writes", "expected_valid"),
+        [(False, False), (True, True)],
+    )
+    def test_write_statement_validity_follows_allow_writes(self, allow_writes, 
expected_valid):

Review Comment:
   Nothing pins `require_parse`: flipping `check_query`'s `True` to `False`, or 
`query`'s `False` to `True`, leaves every test in this file passing. Could you 
add one case with `allow_writes=True` and a malformed write like `INSERT INTO 
users VALUES (`, asserting `check_query` returns `valid: false` and `query` 
still reaches the hook (`_assert_executed`)? That covers both halves of the 
behavior described in the PR.



##########
providers/common/ai/src/airflow/providers/common/ai/toolsets/sql.py:
##########
@@ -546,15 +535,36 @@ def _query(self, sql: str) -> str:
             total_rows=fetch.total_rows,
         )
 
+    def _validate_for_execution(self, sql: str, *, dialect: str | None, 
require_parse: bool) -> None:
+        """
+        Apply the checks ``query`` runs before executing ``sql``, raising when 
it would be refused.
+
+        ``check_query`` shares this so it never reports valid for a statement 
``query`` refuses.
+        ``require_parse`` makes ``check_query`` syntax-check writes too; 
``query`` leaves a write
+        unparsed unless ``allowed_tables`` needs the AST, so a statement 
sqlglot cannot parse
+        still reaches the database.
+        """
+        statements: list[Any] | None = None
+        if not self._allow_writes:
+            # allow_read_only_metadata lets agents inspect schemas with 
DESCRIBE/SHOW
+            # (a common first move) instead of hard-failing; the deep scan 
still
+            # rejects any data-modifying statement, including EXPLAIN <write>.
+            statements = _validate_sql(sql, dialect=dialect, 
allow_read_only_metadata=True)
+        elif require_parse or self._allowed_canonical is not None:
+            # Writes are allowed, so only parse: the allow-list, when set, 
still
+            # governs which tables a write may touch.
+            statements = _parse_sql(sql, dialect=dialect)

Review Comment:
   With `allow_writes=True` and no `allowed_tables`, only `check_query` reaches 
this branch, and `_parse_sql` defaults to `allow_multiple_statements=False`. So 
`INSERT INTO t VALUES (1); DELETE FROM t` comes back `valid: false` ("Multiple 
statements detected") while `query` sends the same string to the hook, which is 
the mismatch this PR set out to remove and contradicts the new "same statement 
rules as `query`" line in `sql.rst`. Passing 
`allow_multiple_statements=self._allowed_canonical is None` here would fix it 
without touching `query`, since `query` only gets here when the allow-list is 
set. If rejecting multi-statement is intended, the docs row should say so 
instead.



##########
providers/common/ai/tests/unit/common/ai/toolsets/test_sql.py:
##########
@@ -548,6 +548,42 @@ def 
test_a_row_carrying_the_connection_password_reaches_the_model_masked(self, r
 
 
 class TestSQLToolsetCheckQuery:
+    @pytest.mark.parametrize(
+        ("allow_writes", "expected_valid"),
+        [(False, False), (True, True)],
+    )
+    def test_write_statement_validity_follows_allow_writes(self, allow_writes, 
expected_valid):
+        ts = SQLToolset("pg_default", allow_writes=allow_writes)
+        ts._hook = _make_mock_db_hook()
+
+        result = asyncio.run(
+            ts.call_tool(
+                "check_query",
+                {"sql": "INSERT INTO users VALUES (3, 'Eve')"},
+                ctx=MagicMock(),
+                tool=MagicMock(),
+            )
+        )
+        assert json.loads(result)["valid"] is expected_valid
+
+    @pytest.mark.parametrize(
+        ("table", "expected_valid"),
+        [("orders", True), ("secret_salaries", False)],

Review Comment:
   The `secret_salaries` case also passes without this change, since the old 
`check_query` rejected every INSERT by statement type. Asserting 
`"secret_salaries" in data["error"]` (as 
`test_check_query_reports_disallowed_table_as_invalid` does) would make it fail 
if writes ever stopped going through the allow-list.



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