Bug #121235 mysql-connector-python mis-splits statements containing the word “delimiter”
Submitted: 4 Sep 21:26
Reporter: Steven Wilson Email Updates:
Status: Open Impact on me:
None 
Category:Connector / Python Severity:S2 (Serious)
Version:9.2.0, 9.7.0, 26.7.0 OS:MacOS (also reproduces on Linux)
Assigned to: CPU Architecture:Any
Tags: mysql-connector-python

[4 Sep 21:26] Steven Wilson
Description:
cursor.execute() corrupts or rejects valid SQL when a string literal contains the word “delimiter”
Severity / impact:
Data integrity. Worst case is silent corruption of a multi-row INSERT: the client rewrites the statement before sending it, so rows are truncated or dropped without the application seeing anything wrong beyond an eventual syntax error.

Version
Affected: 9.2.0, 9.7.0, 26.7.0 (all verified by running the reproducers below). Not affected: 9.0.0, 9.1.0 — these predate mysql/connector/_scripting.py.

The defect is in the pure-Python layer, so it reproduces with and without the C extension (use_pure=True and use_pure=False behave identically).

How to repeat:
Reproducer 1 — minimal, public API:
import mysql.connector
cnx = mysql.connector.connect(user=..., password=..., host=..., database=...)
cur = cnx.cursor()
cur.execute("SELECT %s", ("it's a separator don't",))   # OK
cur.execute("SELECT %s", ("it's a delimiter don't",))   # raises

The two calls differ by one word. The second raises:
InterfaceError: The backslash (\) character is not a valid delimiter.
The value is ordinary user data passed as a bound parameter. Nothing in it is a DELIMITER command.

Reproducer 2 — no server required, shows the corruption
from mysql.connector._scripting import MySQLScriptSplitter, split_multi_statement
stmt = (b"INSERT INTO t (id, txt) VALUES "
        b"(1,'row one doesn\\'t matter'),"
        b"(2,'csv sniffer does delimiter detection'),"
        b"(3,'row three doesn\\'t matter')")

print(MySQLScriptSplitter.has_delimiter(stmt))          # True  -- expected False
print(next(split_multi_statement(sql_code=stmt)))
Output:

True
{'single_stmts': deque([
    b"INSERT INTO t (id, txt) VALUES (1,'row one doesn\\'t matter'),(2,'csv sniffer does",
    b"three doesn\\'t matter')"]),
 'mappable_stmt':
    b"INSERT INTO t (id, txt) VALUES (1,'row one doesn\\'t matter'),(2,'csv sniffer does;\nthree doesn\\'t matter')"}

The splitter reads delimiter detection inside the string literal as a DELIMITER command, adopts the literal text detection as the new statement delimiter, and then splits the statement on every occurrence of it. Row 2’s value is truncated mid-literal and row 3’s opening is destroyed.

Root cause:
mysql/connector/_scripting.py:

DELIMITER_PATTERN: re.Pattern = re.compile(
    rb"""(delimiter\s+)(?=(?:[^"'`]*(?:"[^"]*"|'[^']*'|`[^`]*`))*[^"'`]*$)""",
    flags=re.IGNORECASE | re.MULTILINE,
)
cursor.execute() calls split_multi_statement() on the fully interpolated statement (cursor_cext.py:339, cursor.py:403), i.e. after parameters have been substituted, so caller data is parsed as if it were SQL.

Suggested fix:
• Do not run the script splitter on statements that were not submitted as scripts, or at minimum skip it when the statement contains no ; outside a literal.
• Recognise DELIMITER only as a command, i.e. at the start of a statement or line and outside any quoted literal — not by substring match anywhere.
• Replace the quote-parity lookahead with a real scan that tracks quoting state left to right and honours backslash escapes (and NO_BACKSLASH_ESCAPES), rather than a regex over the remaining text.