This is an automated email from the ASF dual-hosted git repository.
kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new ae4343cad45 Reject empty allowed_tables in SQLToolset instead of
allowing all tables (#73381)
ae4343cad45 is described below
commit ae4343cad4551da310575121749ce53723b46d27
Author: Jyun-An Chen <[email protected]>
AuthorDate: Mon Sep 21 19:38:52 2026 +0800
Reject empty allowed_tables in SQLToolset instead of allowing all tables
(#73381)
* Reject empty allowed_tables in SQLToolset instead of allowing all tables
* Note the empty allowed_tables rejection in the common-ai docs and
changelog
Rejecting an empty allowed_tables breaks construction that 0.8.0 and 0.9.0
accepted, so an upgrading Dag that builds the list dynamically now fails at
import and needs to be told that None is the way to ask for allow-all. The
toolsets page carries the same parameter list as the docstring and is where
users of this toolset land, so it has to describe the guard as well.
---
providers/common/ai/docs/changelog.rst | 8 ++++++++
providers/common/ai/docs/toolsets.rst | 3 ++-
.../ai/src/airflow/providers/common/ai/toolsets/sql.py | 12 +++++++++---
.../common/ai/tests/unit/common/ai/toolsets/test_sql.py | 4 ++++
4 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/providers/common/ai/docs/changelog.rst
b/providers/common/ai/docs/changelog.rst
index 1eb0b82aca7..dd5daeb3047 100644
--- a/providers/common/ai/docs/changelog.rst
+++ b/providers/common/ai/docs/changelog.rst
@@ -36,6 +36,14 @@ Changelog
or inspect its ``.exceptions`` attribute for the original per-model errors.
See
:doc:`retry_policies`, "When the connection also carries a fallback chain".
+.. note::
+ ``SQLToolset(allowed_tables=[])`` now raises ``ValueError``. Up to 0.9.0 an
empty list
+ was accepted and exposed every table in the schema -- the same as
``allowed_tables=None``
+ -- so a Dag that builds the list dynamically (a ``Variable.get``, a config
file, a
+ filtered comprehension) silently handed the agent the whole schema whenever
the list
+ came back empty. Such a Dag now fails at import instead. Pass ``None``
explicitly if
+ exposing every table is what you meant.
+
0.9.0
.....
diff --git a/providers/common/ai/docs/toolsets.rst
b/providers/common/ai/docs/toolsets.rst
index 2db6edc5b5b..3461935fe62 100644
--- a/providers/common/ai/docs/toolsets.rst
+++ b/providers/common/ai/docs/toolsets.rst
@@ -207,7 +207,8 @@ Parameters
- ``db_conn_id``: Airflow connection ID for the database.
- ``allowed_tables``: Restrict the agent to a fixed set of tables. ``None``
- (default) exposes all tables in ``schema``. Entries may be schema-qualified
+ (default) exposes all tables in ``schema``; an empty list raises
``ValueError``
+ rather than silently exposing them all. Entries may be schema-qualified
(``"SCHEMA.TABLE"``) to span multiple schemas; see above. Matching is
case-insensitive. When set, the list is enforced on ``query`` and
``check_query`` as well as discovery -- every table a query references must
be
diff --git
a/providers/common/ai/src/airflow/providers/common/ai/toolsets/sql.py
b/providers/common/ai/src/airflow/providers/common/ai/toolsets/sql.py
index 584fd475c93..5011ba99c20 100644
--- a/providers/common/ai/src/airflow/providers/common/ai/toolsets/sql.py
+++ b/providers/common/ai/src/airflow/providers/common/ai/toolsets/sql.py
@@ -171,9 +171,10 @@ class SQLToolset(AbstractToolset[Any]):
:param db_conn_id: Airflow connection ID for the database.
:param allowed_tables: Restrict the agent to a fixed set of tables.
``None``
- (default) exposes every table in ``schema``. Entries may be
schema-qualified
- (``"SCHEMA.TABLE"``) to span multiple schemas in one database --
common on
- warehouses such as Snowflake. ``list_tables`` introspects each
referenced
+ (default) exposes every table in ``schema``; an empty list raises
+ ``ValueError`` rather than silently exposing every table. Entries may
be
+ schema-qualified (``"SCHEMA.TABLE"``) to span multiple schemas in one
database
+ -- common on warehouses such as Snowflake. ``list_tables`` introspects
each referenced
schema and returns the matching tables fully qualified, and
``get_schema``
routes to the table's own schema. Unqualified entries use ``schema``.
Matching is case-insensitive, since databases reflect identifiers in
their
@@ -246,6 +247,11 @@ class SQLToolset(AbstractToolset[Any]):
max_rows: int = 50,
max_result_bytes: int = DEFAULT_MAX_RESULT_BYTES,
) -> None:
+ if allowed_tables is not None and not allowed_tables:
+ raise ValueError(
+ "allowed_tables must not be empty. Pass None to allow every
table in the schema, "
+ "or list the tables the agent may access."
+ )
self._db_conn_id = db_conn_id
self._allowed_tables: frozenset[str] | None =
frozenset(allowed_tables) if allowed_tables else None
# Case-folded so matching a query's function names (also case-folded)
is
diff --git a/providers/common/ai/tests/unit/common/ai/toolsets/test_sql.py
b/providers/common/ai/tests/unit/common/ai/toolsets/test_sql.py
index 0a00ef9c04a..455805d1043 100644
--- a/providers/common/ai/tests/unit/common/ai/toolsets/test_sql.py
+++ b/providers/common/ai/tests/unit/common/ai/toolsets/test_sql.py
@@ -96,6 +96,10 @@ class TestSQLToolsetInit:
ts = SQLToolset("my_pg")
assert ts.id == "sql-my_pg"
+ def test_empty_allowed_tables_raises(self):
+ with pytest.raises(ValueError, match="allowed_tables must not be
empty"):
+ SQLToolset("my_pg", allowed_tables=[])
+
class TestSQLToolsetGetTools:
def test_returns_four_tools(self):