From 08b7e3231aa76e7f488e18bfc7df7129a9070f29 Mon Sep 17 00:00:00 2001
From: Inada Naoki <songofacandy@gmail.com>
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 <songofacandy@gmail.com>
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 <songofacandy@gmail.com>
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 <songofacandy@gmail.com>
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):