This repository was archived by the owner on Jun 8, 2026. It is now read-only.
File tree
Expand file treeCollapse file tree
Open diff view settings
google/cloud/spanner_dbapi
Expand file treeCollapse file tree
Open diff view settings
Collapse file
Copy file name to clipboardExpand all lines: google/cloud/spanner_dbapi/parse_utils.py +5Lines changed: 5 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -233,6 +233,11 @@ def classify_statement(query, args=None): |
233 | 233 | :rtype: ParsedStatement |
234 | 234 | :returns: parsed statement attributes. |
235 | 235 | """ |
| 236 | + # Check for RUN PARTITION command to avoid sqlparse processing it. |
| 237 | + # sqlparse fails with "Maximum grouping depth exceeded" on long partition IDs. |
| 238 | + if re.match(r"^\s*RUN\s+PARTITION\s+.+", query, re.IGNORECASE): |
| 239 | + return client_side_statement_parser.parse_stmt(query.strip()) |
| 240 | + |
236 | 241 | # sqlparse will strip Cloud Spanner comments, |
237 | 242 | # still, special commenting styles, like |
238 | 243 | # PostgreSQL dollar quoted comments are not |
|
Collapse file
Copy file name to clipboardExpand all lines: noxfile.py +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -555,6 +555,9 @@ def prerelease_deps(session, protobuf_implementation, database_dialect): |
555 | 555 | "google-cloud-testutils", |
556 | 556 | # dependencies of google-cloud-testutils" |
557 | 557 | "click", |
| 558 | + # dependency of google-auth |
| 559 | + "cffi", |
| 560 | + "cryptography", |
558 | 561 | ] |
559 | 562 |
|
560 | 563 | for dep in prerel_deps: |
|
Collapse file
Copy file name to clipboardExpand all lines: tests/unit/spanner_dbapi/test_parse_utils.py +23Lines changed: 23 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -200,6 +200,29 @@ def test_run_partition_classify_stmt(self): |
200 | 200 | ), |
201 | 201 | ) |
202 | 202 |
|
| 203 | + def test_run_partition_classify_stmt_long_id(self): |
| 204 | + # Regression test for "Maximum grouping depth exceeded" with sqlparse |
| 205 | + long_id = "a" * 5000 |
| 206 | + query = f"RUN PARTITION {long_id}" |
| 207 | + parsed_statement = classify_statement(query) |
| 208 | + self.assertEqual( |
| 209 | + parsed_statement, |
| 210 | + ParsedStatement( |
| 211 | + StatementType.CLIENT_SIDE, |
| 212 | + Statement(query), |
| 213 | + ClientSideStatementType.RUN_PARTITION, |
| 214 | + [long_id], |
| 215 | + ), |
| 216 | + ) |
| 217 | + |
| 218 | + def test_run_partition_classify_stmt_incomplete(self): |
| 219 | + # "RUN PARTITION" without ID should be classified as UNKNOWN (not None) |
| 220 | + # because it falls through the specific check and sqlparse handles it. |
| 221 | + query = "RUN PARTITION" |
| 222 | + parsed_statement = classify_statement(query) |
| 223 | + self.assertEqual(parsed_statement.statement_type, StatementType.UNKNOWN) |
| 224 | + self.assertEqual(parsed_statement.statement.sql, query) |
| 225 | + |
203 | 226 | def test_run_partitioned_query_classify_stmt(self): |
204 | 227 | parsed_statement = classify_statement( |
205 | 228 | " RUN PARTITIONED QUERY SELECT s.SongName FROM Songs AS s " |
|