Skip to content

Commit 91a86c0

Browse files
author
Jesse Whitehouse
committed
Fix: native parameters failed if a query included the SQL LIKE wildcard
Signed-off-by: Jesse Whitehouse <jesse.whitehouse@databricks.com>
1 parent 13dae9e commit 91a86c0

File tree

4 files changed

+40
-3
lines changed

4 files changed

+40
-3
lines changed

docs/parameters.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ The result of the above two examples is identical.
212212

213213
**Note**: `%s` is not compliant with PEP-249 and only works due to the specific implementation of our inline renderer.
214214

215-
**Note:** This `%s` syntax overlaps with valid SQL syntax around the usage of `LIKE` DML. For example if your query includes a clause like `WHERE field LIKE '%sequence'`, the parameter inlining function will raise an exception because this string appears to include an inline marker but none is provided. When `use_inline_params=False`, we will pass `%s` occurrences along to the database, allowing it to be used as expected in `LIKE` statements.
215+
**Note:** This `%s` syntax overlaps with valid SQL syntax around the usage of `LIKE` DML. For example if your query includes a clause like `WHERE field LIKE '%sequence'`, the parameter inlining function will raise an exception because this string appears to include an inline marker but none is provided. This means that connector versionss below 3.0.0 it has been impossible to execute a query that included both parameters and LIKE wildcards. When `use_inline_params=False`, we will pass `%s` occurrences along to the database, allowing it to be used as expected in `LIKE` statements.
216216

217217
### Passing sequences as parameter values
218218

@@ -238,7 +238,7 @@ SELECT field FROM table WHERE field IN (1,2,3,4,5)
238238

239239
### Migrating to native parameters
240240

241-
Native parameters are meant to be a drop-in replacement for inline parameters. In most use-cases, upgrading to `databricks-sql-connector>=3.0.0` will grant an immediate improvement to safety. And future improvements to parameterization (such as support for binding complex types like `STRUCT`, `MAP`, and `ARRAY`) will only be available when `use_inline_params=False`.
241+
Native parameters are meant to be a drop-in replacement for inline parameters. In most use-cases, upgrading to `databricks-sql-connector>=3.0.0` will grant an immediate improvement to safety. Plus, native parameters allow you to use SQL LIKE wildcards (`%`) in your queries which is impossible with inline parametrs. Future improvements to parameterization (such as support for binding complex types like `STRUCT`, `MAP`, and `ARRAY`) will only be available when `use_inline_params=False`.
242242

243243
To completely migrate, you need to [revise your SQL queries](#legacy-pyformat-paramstyle-usage-example) to use the new paramstyles.
244244

src/databricks/sql/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from collections.abc import Iterable
88
from decimal import Decimal
99
from enum import Enum
10-
from typing import Any, Dict, List, Union, TypeVar
10+
from typing import Any, Dict, List, Union
11+
import re
1112

1213
import lz4.frame
1314
import pyarrow
@@ -429,6 +430,9 @@ def _interpolate_named_markers(
429430
except TypeError:
430431
# TypeError occurs if there are no %(param)s markers in the operation
431432
return operation
433+
except ValueError:
434+
# ValueError occurs if a query contains % signs that are not part of a %(param)s marker
435+
return operation
432436

433437

434438
def transform_paramstyle(

tests/e2e/test_parameterized_queries.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,24 @@ def test_native_ordinals_dont_break_sql(self):
429429

430430
assert result.samsonite == "samsonite"
431431
assert result.luggage == "luggage"
432+
433+
def test_inline_like_wildcard_breaks(self):
434+
"""One flaw with the ParameterEscaper is that it fails if a query contains
435+
a SQL LIKE wildcard %. This test proves that's the case.
436+
"""
437+
query = "SELECT 1 `col` WHERE 'foo' LIKE '%'"
438+
params ={"param": 'bar'}
439+
with self.cursor(extra_params={"use_inline_params": True}) as cursor:
440+
with pytest.raises(ValueError, match="unsupported format character"):
441+
result = cursor.execute(query, parameters=params).fetchone()
442+
443+
def test_native_like_wildcard_works(self):
444+
"""This is a mirror of test_inline_like_wildcard_breaks that proves that LIKE
445+
wildcards work under the native approach.
446+
"""
447+
query = "SELECT 1 `col` WHERE 'foo' LIKE '%'"
448+
params ={"param": 'bar'}
449+
with self.cursor(extra_params={"use_inline_params": False}) as cursor:
450+
result = cursor.execute(query, parameters=params).fetchone()
451+
452+
assert result.col == 1

tests/unit/test_param_escaper.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,18 @@ class TestInlineToNativeTransformer(object):
196196
{"param": None, "list": None},
197197
"SELECT * FROM table WHERE field = :param AND other_field IN (:list)",
198198
),
199+
(
200+
"query with like wildcard",
201+
'select * from table where field like "%"',
202+
{},
203+
'select * from table where field like "%"'
204+
),
205+
(
206+
"query with named param and like wildcard",
207+
'select :param from table where field like "%"',
208+
{"param": None},
209+
'select :param from table where field like "%"'
210+
)
199211
),
200212
)
201213
def test_transformer(

0 commit comments

Comments
 (0)