Skip to content

Commit 34a00b1

Browse files
committed
BUG#35278365: Fix UnicodeDecodeError with a long field name alias (c-ext)
An UnicodeDecodeError is raised when using a complex query that produces a long field name alias. It fails to create an Unicode object using `PyUnicode_Decode()` from the resulting `MYSQL_FIELD.name` returned by `mysql_fetch_fields()` MySQL C API. This patch uses "replace" in `PyUnicode_Decode()` to set how decoding errors are handled, which uses a replace marker. Change-Id: Ifbeb8572e3c906fccbdb0b291e903775d132b494
1 parent ec87ba4 commit 34a00b1

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ v8.1.0
1919
- BUG#35349093: Compression doesn't work with C extension API
2020
- BUG#35338384: PIP installs incompatible Connector/Python packages
2121
- BUG#35318413: Fix charset mapping for MySQL 8.1.0
22+
- BUG#35278365: Fix UnicodeDecodeError with a long field name alias (c-ext)
2223
- BUG#35212199: Check for identifier quotes in the database name
2324
- BUG#35140271: Regex split hanging in cursor.execute(..., multi=True) for complex queries
2425
- BUG#29115406: CONTRIBUTION - FIX RECV COMPRESS BUG

src/mysql_capi_conversion.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2022, Oracle and/or its affiliates.
2+
* Copyright (c) 2014, 2023, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -676,7 +676,7 @@ mytopy_string(const char *data, enum_field_types field_type,
676676

677677
/* 'binary' charset = 63 */
678678
if (use_unicode && (field_type == MYSQL_TYPE_JSON || field_charsetnr != 63)) {
679-
return PyUnicode_Decode(data, field_length, charset, NULL);
679+
return PyUnicode_Decode(data, field_length, charset, "replace");
680680
}
681681

682682
return PyByteArray_FromStringAndSize(data, field_length);

tests/test_bugs.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7587,3 +7587,83 @@ def test_set_compress_cext_false(self):
75877587
"""Test setting `compress=False` in the C extension directly."""
75887588
res = self._test_compression_status_cext(False)
75897589
self.assertEqual(res, ("Compression", "OFF"))
7590+
7591+
7592+
class BugOra35278365(tests.MySQLConnectorTests):
7593+
"""BUG#35278365: Fix UnicodeDecodeError with a long field name alias (c-ext)
7594+
7595+
An UnicodeDecodeError is raised when using a complex query that produces
7596+
a long field name alias.
7597+
It fails to create an Unicode object using `PyUnicode_Decode()` from the
7598+
resulting `MYSQL_FIELD.name` returned by `mysql_fetch_fields()` MySQL
7599+
C API.
7600+
7601+
This patch uses "replace" in `PyUnicode_Decode()` to set how decoding
7602+
errors are handled, which uses a replace marker.
7603+
"""
7604+
7605+
tbl_prefix = "BugOra35278365"
7606+
7607+
def setUp(self):
7608+
config = tests.get_mysql_config()
7609+
with mysql.connector.connect(**config) as cnx:
7610+
with cnx.cursor() as cur:
7611+
cnx.cmd_query(f"DROP TABLE IF EXISTS {self.tbl_prefix}_table1")
7612+
cnx.cmd_query(f"DROP TABLE IF EXISTS {self.tbl_prefix}_table2")
7613+
cnx.cmd_query(f"DROP TABLE IF EXISTS {self.tbl_prefix}_table3")
7614+
cur.execute(
7615+
f"""
7616+
CREATE TABLE {self.tbl_prefix}_table1 (
7617+
id int(11) NOT NULL,
7618+
ort_id int(11) DEFAULT NULL
7619+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
7620+
"""
7621+
)
7622+
cur.execute(
7623+
f"""
7624+
CREATE TABLE {self.tbl_prefix}_table2 (
7625+
id int(11) NOT NULL
7626+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
7627+
"""
7628+
)
7629+
cur.execute(
7630+
f"""
7631+
CREATE TABLE {self.tbl_prefix}_table3 (
7632+
besuch_id int(11) NOT NULL,
7633+
taxon varchar(30) NOT NULL,
7634+
epitheton varchar(30) DEFAULT NULL,
7635+
rang int(1) NOT NULL
7636+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
7637+
"""
7638+
)
7639+
cnx.commit()
7640+
7641+
def tearDown(self):
7642+
config = tests.get_mysql_config()
7643+
with mysql.connector.connect(**config) as cnx:
7644+
cnx.cmd_query(f"DROP TABLE IF EXISTS {self.tbl_prefix}_table1")
7645+
cnx.cmd_query(f"DROP TABLE IF EXISTS {self.tbl_prefix}_table2")
7646+
cnx.cmd_query(f"DROP TABLE IF EXISTS {self.tbl_prefix}_table3")
7647+
cnx.commit()
7648+
7649+
@foreach_cnx()
7650+
def test_long_field_names(self):
7651+
with mysql.connector.connect(**tests.get_mysql_config()) as cnx:
7652+
with cnx.cursor() as cur:
7653+
query = f"""
7654+
SELECT (SELECT Group_concat(DISTINCT Concat_ws(' ', taxon,
7655+
Ifnull(t3.epitheton, 'sp.'))
7656+
ORDER BY
7657+
t3.taxon, Isnull(t3.epitheton), t3.epitheton SEPARATOR ', ')
7658+
FROM {self.tbl_prefix}_table3 t3
7659+
WHERE t3.besuch_id = {self.tbl_prefix}_table1.id
7660+
AND t3.rang IN (1, 2)
7661+
AND NOT EXISTS(
7662+
SELECT 0 FROM {self.tbl_prefix}_table3 b3)), 1, 1
7663+
FROM {self.tbl_prefix}_table1,{self.tbl_prefix}_table2
7664+
WHERE {self.tbl_prefix}_table2.id = {self.tbl_prefix}_table1.ort_id
7665+
ORDER BY {self.tbl_prefix}_table1.id
7666+
"""
7667+
cur.execute(query) # No error is success
7668+
res = cur.fetchall()
7669+
self.assertEqual(res, [])

0 commit comments

Comments
 (0)