Repository: googleapis/python-bigquery

Issue:
When parsing parameter types, underscores aren't handled in struct field names



Discussion:

Resolving patch:
diff --git a/google/cloud/bigquery/dbapi/_helpers.py b/google/cloud/bigquery/dbapi/_helpers.py
--- a/google/cloud/bigquery/dbapi/_helpers.py
+++ b/google/cloud/bigquery/dbapi/_helpers.py
@@ -173,7 +173,7 @@ def _parse_type(
         \s*
         (ARRAY|STRUCT|RECORD)  # Type
         \s*
-        <([A-Z0-9<> ,()]+)>      # Subtype(s)
+        <([A-Z0-9_<> ,()]+)>   # Subtype(s)
         \s*$
         """,
         re.IGNORECASE | re.VERBOSE,
diff --git a/google/cloud/bigquery/dbapi/cursor.py b/google/cloud/bigquery/dbapi/cursor.py
--- a/google/cloud/bigquery/dbapi/cursor.py
+++ b/google/cloud/bigquery/dbapi/cursor.py
@@ -494,7 +494,7 @@ def _extract_types(
         ([^:)]*)      # name
         (?::          # ':' introduces type
           (             # start of type group
-            [a-zA-Z0-9<>, ]+  # First part, no parens
+            [a-zA-Z0-9_<>, ]+ # First part, no parens
 
             (?:               # start sets of parens + non-paren text
               \([0-9 ,]+\)      # comma-separated groups of digits in parens


Test patch:
diff --git a/tests/unit/test_dbapi__helpers.py b/tests/unit/test_dbapi__helpers.py
--- a/tests/unit/test_dbapi__helpers.py
+++ b/tests/unit/test_dbapi__helpers.py
@@ -612,8 +612,8 @@ def test_complex_query_parameter_type_errors(type_, value, expect):
     "parameters,parameter_types,expect",
     [
         (
-            [[], dict(name="ch1", bdate=datetime.date(2021, 1, 1))],
-            ["ARRAY<INT64>", "struct<name string, bdate date>"],
+            [[], dict(name="ch1", b_date=datetime.date(2021, 1, 1))],
+            ["ARRAY<INT64>", "struct<name string, b_date date>"],
             [
                 {
                     "parameterType": {"arrayType": {"type": "INT64"}, "type": "ARRAY"},
@@ -623,13 +623,13 @@ def test_complex_query_parameter_type_errors(type_, value, expect):
                     "parameterType": {
                         "structTypes": [
                             {"name": "name", "type": {"type": "STRING"}},
-                            {"name": "bdate", "type": {"type": "DATE"}},
+                            {"name": "b_date", "type": {"type": "DATE"}},
                         ],
                         "type": "STRUCT",
                     },
                     "parameterValue": {
                         "structValues": {
-                            "bdate": {"value": "2021-01-01"},
+                            "b_date": {"value": "2021-01-01"},
                             "name": {"value": "ch1"},
                         }
                     },
diff --git a/tests/unit/test_dbapi_cursor.py b/tests/unit/test_dbapi_cursor.py
--- a/tests/unit/test_dbapi_cursor.py
+++ b/tests/unit/test_dbapi_cursor.py
@@ -809,6 +809,10 @@ def test__format_operation_no_placeholders(self):
             "values(%%%%%(foo:INT64)s, %(bar)s)",
             ("values(%%%%%(foo)s, %(bar)s)", dict(foo="INT64")),
         ),
+        (
+            "values(%%%%%(foo:struct<x_1 string, y_ int64>)s, %(bar)s)",
+            ("values(%%%%%(foo)s, %(bar)s)", dict(foo="struct<x_1 string, y_ int64>")),
+        ),
         (
             "values(%%%%%(foo:struct<x string, y int64>)s, %(bar)s)",
             ("values(%%%%%(foo)s, %(bar)s)", dict(foo="struct<x string, y int64>")),