Skip to content

Commit

Permalink
fix: consistent percents handling in DB API query (#619)
Browse files Browse the repository at this point in the history
Fixes #608.

Percents in the query string are now always de-escaped, regardless of whether any query parameters are passed or not.

In addition, misformatting placeholders that don't match parameter values now consistently raise `ProgrammingError`.

**PR checklist:**
- [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/python-bigquery/issues/new/choose) before writing your code!  That way we can discuss the change, evaluate designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
  • Loading branch information
plamut committed Apr 16, 2021
1 parent e0b373d commit 6502a60
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 3 deletions.
6 changes: 3 additions & 3 deletions google/cloud/bigquery/dbapi/cursor.py
Expand Up @@ -393,7 +393,7 @@ def _format_operation_list(operation, parameters):

try:
return operation % tuple(formatted_params)
except TypeError as exc:
except (TypeError, ValueError) as exc:
raise exceptions.ProgrammingError(exc)


Expand Down Expand Up @@ -423,7 +423,7 @@ def _format_operation_dict(operation, parameters):

try:
return operation % formatted_params
except KeyError as exc:
except (KeyError, ValueError, TypeError) as exc:
raise exceptions.ProgrammingError(exc)


Expand All @@ -445,7 +445,7 @@ def _format_operation(operation, parameters=None):
``parameters`` argument.
"""
if parameters is None or len(parameters) == 0:
return operation
return operation.replace("%%", "%") # Still do percent de-escaping.

if isinstance(parameters, collections_abc.Mapping):
return _format_operation_dict(operation, parameters)
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/test_dbapi_cursor.py
Expand Up @@ -657,6 +657,14 @@ def test__format_operation_w_wrong_dict(self):
{"somevalue-not-here": "hi", "othervalue": "world"},
)

def test__format_operation_w_redundant_dict_key(self):
from google.cloud.bigquery.dbapi import cursor

formatted_operation = cursor._format_operation(
"SELECT %(somevalue)s;", {"somevalue": "foo", "value-not-used": "bar"}
)
self.assertEqual(formatted_operation, "SELECT @`somevalue`;")

def test__format_operation_w_sequence(self):
from google.cloud.bigquery.dbapi import cursor

Expand All @@ -676,8 +684,53 @@ def test__format_operation_w_too_short_sequence(self):
("hello",),
)

def test__format_operation_w_too_long_sequence(self):
from google.cloud.bigquery import dbapi
from google.cloud.bigquery.dbapi import cursor

self.assertRaises(
dbapi.ProgrammingError,
cursor._format_operation,
"SELECT %s, %s;",
("hello", "world", "everyone"),
)

def test__format_operation_w_empty_dict(self):
from google.cloud.bigquery.dbapi import cursor

formatted_operation = cursor._format_operation("SELECT '%f'", {})
self.assertEqual(formatted_operation, "SELECT '%f'")

def test__format_operation_wo_params_single_percent(self):
from google.cloud.bigquery.dbapi import cursor

formatted_operation = cursor._format_operation("SELECT '%'", {})
self.assertEqual(formatted_operation, "SELECT '%'")

def test__format_operation_wo_params_double_percents(self):
from google.cloud.bigquery.dbapi import cursor

formatted_operation = cursor._format_operation("SELECT '%%'", {})
self.assertEqual(formatted_operation, "SELECT '%'")

def test__format_operation_unescaped_percent_w_dict_param(self):
from google.cloud.bigquery import dbapi
from google.cloud.bigquery.dbapi import cursor

self.assertRaises(
dbapi.ProgrammingError,
cursor._format_operation,
"SELECT %(foo)s, '100 %';",
{"foo": "bar"},
)

def test__format_operation_unescaped_percent_w_list_param(self):
from google.cloud.bigquery import dbapi
from google.cloud.bigquery.dbapi import cursor

self.assertRaises(
dbapi.ProgrammingError,
cursor._format_operation,
"SELECT %s, %s, '100 %';",
["foo", "bar"],
)

0 comments on commit 6502a60

Please sign in to comment.