Skip to content

Commit a46e60e

Browse files
committed
BUG#34984850: Fix binary conversion with NO_BACKSLASH_ESCAPES mode
The default converter used by the pure Python implementation does not escape the single quotes correctly when using NO_BACKSLASH_ESCAPES SQL mode. This patch is an extension of the initial fix for BUG#34984850. Change-Id: I46be753bb34f21dc6eff1a559906f6e50257ce6b
1 parent 0eed7e2 commit a46e60e

File tree

4 files changed

+36
-16
lines changed

4 files changed

+36
-16
lines changed

lib/mysql/connector/connection_cext.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,9 @@ def prepare_for_mysql(
770770
if self.converter:
771771
result = [
772772
self.converter.quote(
773-
self.converter.escape(self.converter.to_mysql(value))
773+
self.converter.escape(
774+
self.converter.to_mysql(value), self._sql_mode
775+
)
774776
)
775777
for value in params
776778
]
@@ -781,7 +783,9 @@ def prepare_for_mysql(
781783
if self.converter:
782784
for key, value in params.items():
783785
result[key] = self.converter.quote(
784-
self.converter.escape(self.converter.to_mysql(value))
786+
self.converter.escape(
787+
self.converter.to_mysql(value), self._sql_mode
788+
)
785789
)
786790
else:
787791
for key, value in params.items():

lib/mysql/connector/conversion.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2009, 2022, Oracle and/or its affiliates.
1+
# Copyright (c) 2009, 2023, Oracle and/or its affiliates.
22
#
33
# This program is free software; you can redistribute it and/or modify
44
# it under the terms of the GNU General Public License, version 2.0, as
@@ -132,7 +132,10 @@ def to_python(
132132
return value
133133

134134
@staticmethod
135-
def escape(value: Any) -> Any:
135+
def escape(
136+
value: Any,
137+
sql_mode: Optional[str] = None, # pylint: disable=unused-argument
138+
) -> Any:
136139
"""Escape buffer for sending to MySQL"""
137140
return value
138141

@@ -169,7 +172,7 @@ def __init__(
169172
] = {}
170173

171174
@staticmethod
172-
def escape(value: Any) -> Any:
175+
def escape(value: Any, sql_mode: Optional[str] = None) -> Any:
173176
"""
174177
Escapes special characters as they are expected to by when MySQL
175178
receives them.
@@ -178,13 +181,17 @@ def escape(value: Any) -> Any:
178181
Returns the value if not a string, or the escaped string.
179182
"""
180183
if isinstance(value, (bytes, bytearray)):
184+
if sql_mode == "NO_BACKSLASH_ESCAPES":
185+
return value.replace(b"'", b"''")
181186
value = value.replace(b"\\", b"\\\\")
182187
value = value.replace(b"\n", b"\\n")
183188
value = value.replace(b"\r", b"\\r")
184189
value = value.replace(b"\047", b"\134\047") # single quotes
185190
value = value.replace(b"\042", b"\134\042") # double quotes
186191
value = value.replace(b"\032", b"\134\032") # for Win32
187192
elif isinstance(value, str) and not isinstance(value, HexLiteral):
193+
if sql_mode == "NO_BACKSLASH_ESCAPES":
194+
return value.replace("'", "''")
188195
value = value.replace("\\", "\\\\")
189196
value = value.replace("\n", "\\n")
190197
value = value.replace("\r", "\\r")
@@ -270,8 +277,8 @@ def _int_to_mysql(value: int) -> int:
270277
def _long_to_mysql(value: int) -> int:
271278
"""Convert value to int
272279
273-
Note: there is not type "long" in Python 3 since integers (int) are of unlimited size.
274-
Since Python 2 is no longer supported, this method should be deprecated.
280+
Note: There is no type "long" in Python 3 since integers are of unlimited size.
281+
Since Python 2 is no longer supported, this method should be deprecated.
275282
"""
276283
return int(value)
277284

lib/mysql/connector/cursor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ def _process_params_dict(
442442
for key, value in params.items():
443443
conv = value
444444
conv = to_mysql(conv)
445-
conv = escape(conv)
445+
conv = escape(conv, self._connection.sql_mode)
446446
if not isinstance(value, Decimal):
447447
conv = quote(conv)
448448
res[key.encode()] = conv
@@ -462,7 +462,7 @@ def _process_params(
462462
escape = self._connection.converter.escape
463463
quote = self._connection.converter.quote
464464
res = [to_mysql(value) for value in res]
465-
res = [escape(value) for value in res]
465+
res = [escape(value, self._connection.sql_mode) for value in res]
466466
res = [
467467
quote(value) if not isinstance(params[i], Decimal) else value
468468
for i, value in enumerate(res)

tests/test_bugs.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7390,7 +7390,8 @@ def setUp(self):
73907390
f"""
73917391
CREATE TABLE {self.table_name} (
73927392
id INT AUTO_INCREMENT PRIMARY KEY,
7393-
data LONGBLOB
7393+
data LONGBLOB,
7394+
text VARCHAR(50)
73947395
)
73957396
"""
73967397
)
@@ -7401,16 +7402,24 @@ def tearDown(self):
74017402
with mysql.connector.connect(**config) as cnx:
74027403
cnx.cmd_query(f"DROP TABLE IF EXISTS {self.table_name}")
74037404

7404-
@foreach_cnx()
7405-
def test_no_backslash_escapes(self):
7406-
data = b"\x01\x02\x00\x01"
7405+
def _run_test(self):
7406+
data = (b"\x01\x02\x00\x01", "abc'cba")
74077407
for use_backslash_escapes in (True, False):
74087408
if use_backslash_escapes:
74097409
self.cnx.sql_mode = [constants.SQLMode.NO_BACKSLASH_ESCAPES]
74107410
with self.cnx.cursor() as cur:
74117411
cur.execute(
7412-
f"INSERT INTO {self.table_name} (data) VALUES (%s)", (data,)
7412+
f"INSERT INTO {self.table_name} (data, text) VALUES (%s, %s)", data
74137413
)
7414-
cur.execute(f"SELECT data FROM {self.table_name} LIMIT 1")
7414+
cur.execute(f"SELECT data, text FROM {self.table_name} LIMIT 1")
74157415
res = cur.fetchone()
7416-
self.assertEqual(data, res[0])
7416+
self.assertEqual(data, res)
7417+
7418+
@foreach_cnx()
7419+
def test_no_backslash_escapes(self):
7420+
self._run_test()
7421+
7422+
@cnx_config(converter_class=conversion.MySQLConverter)
7423+
@foreach_cnx()
7424+
def test_no_backslash_escapes_with_converter_class(self):
7425+
self._run_test()

0 commit comments

Comments
 (0)