From ffa563c173f547459c347440a7a4bf67d50ee98d Mon Sep 17 00:00:00 2001 From: Subham Sinha Date: Tue, 6 Jan 2026 11:49:59 +0530 Subject: [PATCH 1/2] ci: add cryptography to prerelease dependencies --- noxfile.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/noxfile.py b/noxfile.py index 62d67d0be1..e85fba3c54 100644 --- a/noxfile.py +++ b/noxfile.py @@ -555,6 +555,9 @@ def prerelease_deps(session, protobuf_implementation, database_dialect): "google-cloud-testutils", # dependencies of google-cloud-testutils" "click", + # dependency of google-auth + "cffi", + "cryptography", ] for dep in prerel_deps: From cc772af31d0cea3e4ff843b3601a67339721ce53 Mon Sep 17 00:00:00 2001 From: Subham Sinha Date: Fri, 2 Jan 2026 16:56:49 +0530 Subject: [PATCH 2/2] fix(spanner/dbapi): bypass sqlparse for RUN PARTITION commands --- google/cloud/spanner_dbapi/parse_utils.py | 5 +++++ tests/unit/spanner_dbapi/test_parse_utils.py | 23 ++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/google/cloud/spanner_dbapi/parse_utils.py b/google/cloud/spanner_dbapi/parse_utils.py index 66741eb264..d99caa7e8c 100644 --- a/google/cloud/spanner_dbapi/parse_utils.py +++ b/google/cloud/spanner_dbapi/parse_utils.py @@ -233,6 +233,11 @@ def classify_statement(query, args=None): :rtype: ParsedStatement :returns: parsed statement attributes. """ + # Check for RUN PARTITION command to avoid sqlparse processing it. + # sqlparse fails with "Maximum grouping depth exceeded" on long partition IDs. + if re.match(r"^\s*RUN\s+PARTITION\s+.+", query, re.IGNORECASE): + return client_side_statement_parser.parse_stmt(query.strip()) + # sqlparse will strip Cloud Spanner comments, # still, special commenting styles, like # PostgreSQL dollar quoted comments are not diff --git a/tests/unit/spanner_dbapi/test_parse_utils.py b/tests/unit/spanner_dbapi/test_parse_utils.py index f63dbb78e4..ec612d9ebd 100644 --- a/tests/unit/spanner_dbapi/test_parse_utils.py +++ b/tests/unit/spanner_dbapi/test_parse_utils.py @@ -200,6 +200,29 @@ def test_run_partition_classify_stmt(self): ), ) + def test_run_partition_classify_stmt_long_id(self): + # Regression test for "Maximum grouping depth exceeded" with sqlparse + long_id = "a" * 5000 + query = f"RUN PARTITION {long_id}" + parsed_statement = classify_statement(query) + self.assertEqual( + parsed_statement, + ParsedStatement( + StatementType.CLIENT_SIDE, + Statement(query), + ClientSideStatementType.RUN_PARTITION, + [long_id], + ), + ) + + def test_run_partition_classify_stmt_incomplete(self): + # "RUN PARTITION" without ID should be classified as UNKNOWN (not None) + # because it falls through the specific check and sqlparse handles it. + query = "RUN PARTITION" + parsed_statement = classify_statement(query) + self.assertEqual(parsed_statement.statement_type, StatementType.UNKNOWN) + self.assertEqual(parsed_statement.statement.sql, query) + def test_run_partitioned_query_classify_stmt(self): parsed_statement = classify_statement( " RUN PARTITIONED QUERY SELECT s.SongName FROM Songs AS s "