Skip to content

Commit 669f6e4

Browse files
authored
SNOW-638845 ValueError: unsupported format character with version 1.4.0 (#355)
1 parent c67bcc8 commit 669f6e4

File tree

5 files changed

+59
-17
lines changed

5 files changed

+59
-17
lines changed

DESCRIPTION.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ Source code is also available at:
99

1010
# Release Notes
1111

12+
- v1.4.4(Unreleased)
13+
14+
- Fixed a bug that percent signs in a non-compiled statement should not be interpolated with emtpy sequence when executed.
15+
1216
- v1.4.3(Oct 17, 2022)
1317

1418
- Fixed a bug that `SnowflakeDialect.normalize_name` and `SnowflakeDialect.denormalize_name` could not handle empty string.

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ universal = 1
33

44
[metadata]
55
name = snowflake-sqlalchemy
6-
version = 1.4.3
6+
version = 1.4.4
77
description = Snowflake SQLAlchemy Dialect
88
long_description = file: DESCRIPTION.md
99
long_description_content_type = text/markdown

src/snowflake/sqlalchemy/base.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from sqlalchemy.util.compat import string_types
1414

1515
from .custom_commands import AWSBucket, AzureContainer, ExternalStage
16+
from .util import _set_connection_interpolate_empty_sequences
1617

1718
RESERVED_WORDS = frozenset(
1819
[
@@ -416,34 +417,23 @@ def should_autocommit(self):
416417
def pre_exec(self):
417418
if self.compiled and self.identifier_preparer._double_percents:
418419
# for compiled statements, percent is doubled for escape, we turn on _interpolate_empty_sequences
419-
if hasattr(self._dbapi_connection, "driver_connection"):
420-
# _dbapi_connection is a _ConnectionFairy which proxies raw SnowflakeConnection
421-
self._dbapi_connection.driver_connection._interpolate_empty_sequences = (
422-
True
423-
)
424-
else:
425-
# _dbapi_connection is a raw SnowflakeConnection
426-
self._dbapi_connection._interpolate_empty_sequences = True
420+
_set_connection_interpolate_empty_sequences(self._dbapi_connection, True)
427421

428422
# if the statement is executemany insert, setting _interpolate_empty_sequences to True is not enough,
429423
# because executemany pre-processes the param binding and then pass None params to execute so
430424
# _interpolate_empty_sequences condition not getting met for the command.
431425
# Therefore, we manually revert the escape percent in the command here
432426
if self.executemany and self.INSERT_SQL_RE.match(self.statement):
433427
self.statement = self.statement.replace("%%", "%")
428+
else:
429+
# for other cases, do no interpolate empty sequences as "%" is not double escaped
430+
_set_connection_interpolate_empty_sequences(self._dbapi_connection, False)
434431

435432
def post_exec(self):
436433
if self.compiled and self.identifier_preparer._double_percents:
437434
# for compiled statements, percent is doubled for escapeafter execution
438435
# we reset _interpolate_empty_sequences to false which is turned on in pre_exec
439-
if hasattr(self._dbapi_connection, "driver_connection"):
440-
# _dbapi_connection is a _ConnectionFairy which proxies raw SnowflakeConnection
441-
self._dbapi_connection.driver_connection._interpolate_empty_sequences = (
442-
False
443-
)
444-
else:
445-
# _dbapi_connection is a raw SnowflakeConnection
446-
self._dbapi_connection._interpolate_empty_sequences = False
436+
_set_connection_interpolate_empty_sequences(self._dbapi_connection, False)
447437

448438
@property
449439
def rowcount(self):

src/snowflake/sqlalchemy/util.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from sqlalchemy import exc
99

1010
from snowflake.connector.compat import IS_STR
11+
from snowflake.connector.connection import SnowflakeConnection
1112

1213

1314
def _rfc_1738_quote(text):
@@ -72,3 +73,15 @@ def sep(is_first_parameter):
7273
ret += sep(is_first_parameter) + p + "=" + encoded_value
7374
is_first_parameter = False
7475
return ret
76+
77+
78+
def _set_connection_interpolate_empty_sequences(
79+
dbapi_connection: SnowflakeConnection, flag: bool
80+
) -> None:
81+
"""set the _interpolate_empty_sequences config of the underlying connection"""
82+
if hasattr(dbapi_connection, "driver_connection"):
83+
# _dbapi_connection is a _ConnectionFairy which proxies raw SnowflakeConnection
84+
dbapi_connection.driver_connection._interpolate_empty_sequences = flag
85+
else:
86+
# _dbapi_connection is a raw SnowflakeConnection
87+
dbapi_connection._interpolate_empty_sequences = flag

tests/test_pandas.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,38 @@ def test_pandas_invalid_make_pd_writer(engine_testaccount):
411411
index=False,
412412
method=make_pd_writer(df=test_df),
413413
)
414+
415+
416+
def test_percent_signs(engine_testaccount, run_v20_sqlalchemy):
417+
if run_v20_sqlalchemy and sys.version_info < (3, 8):
418+
pytest.skip(
419+
"In Python 3.7, this test depends on pandas features of which the implementation is incompatible with sqlachemy 2.0, and pandas does not support Python 3.7 anymore."
420+
)
421+
422+
table_name = f"test_table_{uuid.uuid4().hex}".upper()
423+
with engine_testaccount.connect() as conn:
424+
with conn.begin():
425+
conn.exec_driver_sql(
426+
f"CREATE OR REPLACE TEMP TABLE {table_name}(c1 int, c2 string)"
427+
)
428+
conn.exec_driver_sql(
429+
f"""
430+
INSERT INTO {table_name}(c1, c2) values
431+
(1, 'abc'),
432+
(2, 'def'),
433+
(3, 'ghi')
434+
"""
435+
)
436+
437+
df = pd.read_sql(
438+
f"select * from {table_name} where c2 not like '%b%'", conn
439+
)
440+
assert list(df.itertuples(index=False, name=None)) == [
441+
(2, "def"),
442+
(3, "ghi"),
443+
]
444+
445+
df = pd.read_sql(f"select * from {table_name} where c2 like '%b%'", conn)
446+
assert list(df.itertuples(index=False, name=None)) == [
447+
(1, "abc"),
448+
]

0 commit comments

Comments
 (0)