From 08b7e3231aa76e7f488e18bfc7df7129a9070f29 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 17:04:51 +0900 Subject: [PATCH 1/4] Add test for #494 --- tests/default.cnf | 2 +- tests/test_cursor.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/default.cnf b/tests/default.cnf index 2aeda7cf..e4d56274 100644 --- a/tests/default.cnf +++ b/tests/default.cnf @@ -4,7 +4,7 @@ [MySQLdb-tests] host = 127.0.0.1 -user = test +user = root database = test #password = default-character-set = utf8 diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 5cb98910..1dbd90e7 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -222,3 +222,24 @@ def test_cursor_discard_result(Cursor): "SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 31 AND 40" ) assert cursor.fetchone() == (31, "row 31") + + +def test_binary_prefix(): + # https://github.com/PyMySQL/mysqlclient/issues/494 + conn = connect(binary_prefix=True) + cursor = conn.cursor() + + cursor.execute("DROP TABLE IF EXISTS test_binary_prefix") + cursor.execute( + """\ +CREATE TABLE test_binary_prefix ( + id INTEGER NOT NULL AUTO_INCREMENT, + json JSON NOT NULL, + PRIMARY KEY (id) +) CHARSET=utf8mb4""" + ) + + cursor.executemany( + "INSERT INTO test_binary_prefix (id, json) VALUES (%(id)s, %(json)s)", + ({"id": 1, "json": "{}"}, {"id": 2, "json": "{}"}), + ) From aea0ae5a087b57c843965b66a8a019d7ae5a843b Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 17:28:21 +0900 Subject: [PATCH 2/4] Update default test config --- tests/default.cnf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/default.cnf b/tests/default.cnf index e4d56274..1d6c9421 100644 --- a/tests/default.cnf +++ b/tests/default.cnf @@ -2,9 +2,10 @@ # http://dev.mysql.com/doc/refman/5.1/en/option-files.html # and set TESTDB in your environment to the name of the file +# $ docker run -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -p 3306:3306 --rm --name mysqld mysql:latest [MySQLdb-tests] host = 127.0.0.1 user = root database = test #password = -default-character-set = utf8 +default-character-set = utf8mb4 From d2b003cf4581917e3ebd5e3a030d4373e4838998 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 17:45:31 +0900 Subject: [PATCH 3/4] Use _mogrify in executemany --- src/MySQLdb/cursors.py | 4 ++-- tests/test_cursor.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MySQLdb/cursors.py b/src/MySQLdb/cursors.py index 7851359f..385eafbe 100644 --- a/src/MySQLdb/cursors.py +++ b/src/MySQLdb/cursors.py @@ -289,11 +289,11 @@ def _do_execute_many( postfix = postfix.encode(encoding) sql = bytearray(prefix) args = iter(args) - v = values % escape(next(args), conn) + v = self._mogrify(values, next(args)) sql += v rows = 0 for arg in args: - v = values % escape(arg, conn) + v = self._mogrify(values, arg) if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length: rows += self.execute(sql + postfix) sql = bytearray(prefix) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index 1dbd90e7..1d2c3655 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -72,7 +72,7 @@ def test_executemany(): # values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9) # """ # list args - data = range(10) + data = [(i,) for i in range(10)] cursor.executemany("insert into test (data) values (%s)", data) assert cursor._executed.endswith( b",(7),(8),(9)" From a7c27c523028c876c6213c3a1f2e8013d7c6d876 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Thu, 18 May 2023 17:47:16 +0900 Subject: [PATCH 4/4] Use Cursor._mogrify instead of _escape_args --- src/MySQLdb/cursors.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/MySQLdb/cursors.py b/src/MySQLdb/cursors.py index 385eafbe..785fa9a1 100644 --- a/src/MySQLdb/cursors.py +++ b/src/MySQLdb/cursors.py @@ -110,34 +110,6 @@ def __exit__(self, *exc_info): del exc_info self.close() - def _escape_args(self, args, conn): - encoding = conn.encoding - literal = conn.literal - - def ensure_bytes(x): - if isinstance(x, str): - return x.encode(encoding) - elif isinstance(x, tuple): - return tuple(map(ensure_bytes, x)) - elif isinstance(x, list): - return list(map(ensure_bytes, x)) - return x - - if isinstance(args, (tuple, list)): - ret = tuple(literal(ensure_bytes(arg)) for arg in args) - elif isinstance(args, dict): - ret = { - ensure_bytes(key): literal(ensure_bytes(val)) - for (key, val) in args.items() - } - else: - # If it's not a dictionary let's try escaping it anyways. - # Worst case it will throw a Value error - ret = literal(ensure_bytes(args)) - - ensure_bytes = None # break circular reference - return ret - def _check_executed(self): if not self._executed: raise ProgrammingError("execute() first") @@ -279,8 +251,6 @@ def executemany(self, query, args): def _do_execute_many( self, prefix, values, postfix, args, max_stmt_length, encoding ): - conn = self._get_db() - escape = self._escape_args if isinstance(prefix, str): prefix = prefix.encode(encoding) if isinstance(values, str):