fix(plugin-mysql): set the session character set on connect and decode MySQL text by its declared charset (#2725) - #2740
Merged
Conversation
…e MySQL text by its declared charset (#2725) Claude-Session: https://claude.ai/code/session_01U7kkDGsR4M4v9NWiycc97U
…truncated column buffer Claude-Session: https://claude.ai/code/session_01U7kkDGsR4M4v9NWiycc97U
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2725
Root cause
The reported comment,
メール・記事ç´^P付ã^A‘, is UTF-8 that was stored through a session the server treated as Latin 1, then read back through UTF-8. The^Pand^Aare the giveaway: MySQL'slatin1is cp1252 with five bytes passed through as C1 controls, and the stored bytes areC3A3C692C2A1…. Every correct UTF-8 client shows the same string. TablePro was reading what the server holds.TablePro could write exactly those bytes itself, and could not read such data any other way:
utf8mb4only through the handshake's collation byte (MYSQL_SET_CHARSET_NAME) and never checked the result. A server withinit_connect='SET NAMES latin1'(any user withoutCONNECTION_ADMIN), MariaDB withskip-character-set-client-handshake, or a server withoututf8mb4leaves the session latin1 while libmariadb still reportsutf8mb4. TablePro kept sending UTF-8, so every comment or value it saved there was stored double-encoded. The handshake'sutf8mb4_general_cialso madeWHERE col = @varfail withERROR 1267 Illegal mix of collationson MySQL 8.SET NAMES. Restoring it with a client whose default is Latin 1 (amysqlCLI without a UTF-8 locale, the MySQL 5.7 client,docker-entrypoint-initdb.d) produces byte-identical Characters are incorrectly decoded in table comments #2725 mojibake.SET NAMES latin1,’and€came back as invisible C1 controls.Fix
establishSessionCharacterSetcallsmysql_set_character_set("utf8mb4")on every connect path (main connection, metadata pool, reconnect, idle-release reacquire). It falls back to a plainSET NAMES utf8, and if the server refuses both it keeps the server's own session rather than failing the connect, so an engine that rejectsSET NAMESstill connects. Sessions now get the server's default utf8mb4 collation, the same as themysql8.4 CLI.MySQLColumnDecoding, keyed on each field'scharsetnrthroughmariadb_get_charset_by_nr, serves the text, prepared and streaming paths, column names and error messages.latin1reads UTF-8 first and then MySQL's real latin1 table (MySQLLatin1), which keeps the existingSET NAMES latin1trick working. The ISO-8859-1 fallback is gone. The Foundation encodings inMySQLCharacterSetare limited to the 19 charsets that decode byte-for-byte like the server.scripts/check-mysql-charset-decoding.shre-checks that against a live server. sjis, ujis, big5, euckr, greek, hebrew, koi8, cp866, latin7 and tis620 each disagree on some characters and stay out.mysqlclient sends them. Following a mid-sessionSET NAMESon the way out was considered and rejected: a legacy dump that declaresSET NAMES latin1over UTF-8 bytes restores byte-for-byte today only because nothing re-encodes it.character_set_client = latin1, so writes are stored the way a latin1 client stored them. Results stayutf8mb4and each value is repaired client-side. So correctly stored text never comes back as?, and the handshake staysutf8mb4, which keeps non-ASCII logins and database names working.SET NAMES utf8mb4prologue and@OLD_*restore epilogue into every split part. Each DDL statement is now written in one write, so a part can never end mid-statement, and the epilogue counts toward the split size.MariaDBFieldMetadatamoves the pure type mapping out of the CMariaDB file. That revivesMariaDBFieldClassifierTestsandMariaDBTypeNameTests(27 cases behind acanImportthat was never true) and deletes the parser copyGeometryWKBParserTestscarried.Measured
A swiftc harness ran the real plugin sources against a throwaway MySQL 8.4.11, as a user with
init_connect='SET NAMES latin1':latin1 / latin1 / latin1utf8mb4 / utf8mb4 / utf8mb4ALTER TABLE … COMMENT 'メール・記事紐付け'stored asC3A3C692C2A1…(#2725)E383A1E383BC…it’s 5€afterSET NAMES latin1its 5(C1 controls)it’s 5€WHERE note = @von MySQL 8ERROR 1267SELECTof GEOMETRYPOINT(1.0 2.0)a…,b…,c…,short)a…four timesdbl/goodcommentsメール・記事紐付け/メール・記事紐付けCOMMENT '新規'C3A6E28093C2B0…(what a latin1 client stores)scripts/check-mysql-charset-decoding.shpasses against MySQL 8.4.11 and MariaDB 12.3.3 (gb18030 skipped there, the server lacks it).Before / After
Sidebar of a database whose
dbltable comment was written through a Latin 1 client. Before: the comment shows as stored. After: the same connection with Encoding set to UTF-8 via Latin 1, whilegood(stored correctly) is unchanged.The new Encoding row in the Options pane:
Tests
MySQLLatin1Tests(all 256 bytes against the server's own conversion, the Characters are incorrectly decoded in table comments #2725 string repaired),MySQLCharacterSetTests,MySQLColumnDecodingTests(including Databend's boolean and hex-binary shapes, which moved into the decoder during the rebase onto feat(plugin-mysql): add TiDB and Databend connection types #2711),MySQLConnectionEncodingTests(the field matches the curated MySQL and MariaDB entries byte-for-byte as JSON, and the plugin declares it),SQLExportEncodingTests(every part is wrapped, the epilogue counts toward the cap).MariaDBFieldClassifierTests,MariaDBTypeNameTests;GeometryWKBParserTestsnow runs against the real parser.Review
Codex could not review: the account hit its usage limit (resets Sep 15). The
code-reviewskill reviewed instead. A security review found that reading column names byname_lengthcould run past a name containing a NUL, which libmariadb copies withstrdup. It is now bounded withstrnlen.Follow-ups
The iOS MySQL driver has the same connect-time gap and ignores the synced Encoding field. It follows in a stacked PR. Separate PRs follow for the other encoding defects found during this investigation: PostgreSQL
client_encoding, ClickHouse whole-body decoding, SQL Server non-Nliterals, SQL import encodings, and MySQL session replay.https://claude.ai/code/session_01U7kkDGsR4M4v9NWiycc97U