diff --git a/packages/pandas-gbq/pandas_gbq/core/resource_references.py b/packages/pandas-gbq/pandas_gbq/core/resource_references.py index afa180f1859e..93627b587731 100644 --- a/packages/pandas-gbq/pandas_gbq/core/resource_references.py +++ b/packages/pandas-gbq/pandas_gbq/core/resource_references.py @@ -53,6 +53,14 @@ def parse_table_id(table_id: str) -> Union[BigLakeTableId, BigQueryTableId]: if any(part == "" for part in inner_parts): raise ValueError(f"Invalid table ID: {table_id}") + # The parsed parts are interpolated into backtick-quoted table references in + # generated SQL (see core/biglake.py and core/sample.py). A backtick can't + # appear in a real project/dataset/table name, and one here would close the + # identifier quoting and let the rest of the string run as SQL, so reject it + # while we're validating the table ID rather than downstream. + if "`" in table_id: + raise ValueError(f"Invalid table ID: {table_id}") + if len(inner_parts) == 1: return BigQueryTableId( project_id=regex_match.group("project"), diff --git a/packages/pandas-gbq/tests/unit/core/test_core_resource_references.py b/packages/pandas-gbq/tests/unit/core/test_core_resource_references.py index e8f21f09ec21..8749f4aa7a5d 100644 --- a/packages/pandas-gbq/tests/unit/core/test_core_resource_references.py +++ b/packages/pandas-gbq/tests/unit/core/test_core_resource_references.py @@ -67,6 +67,11 @@ def test_parse_table_id_valid(table_id, expected): ".my_dataset.my_table", "my-project.my_dataset.", "my-project..my_table", + # A backtick would close the identifier quoting in the generated SQL and + # let the rest of the string run as SQL, so it must be rejected here. + "my-project.my_dataset.my_table` ORDER BY (SELECT 1) -- ", + "my-project.my_catalog.my_namespace.evil` UNION ALL SELECT 1 -- ", + "my-project.my_dataset.`", ], ) def test_parse_table_id_invalid(table_id):