8000 fix(mysql): give a json_table text column the database's collation by colinhacks · Pull Request #77 · colinhacks/reylo · GitHub
[go: up one dir, main page]

Skip to content

fix(mysql): give a json_table text column the database's collation - #77

Merged
colinhacks merged 4 commits into
mainfrom
mysql-json-table-collation
Oct 23, 2026
Merged

colinhacks merged 4 commits into
mainfrom
mysql-json-table-collation

Conversation

@colinhacks
Copy link
Copy Markdown
Owner

JSON_TABLE gives a column of a character type the connection's character set and collation, with the coercibility of a table's column. mysql2 connects with utf8mb4_unicode_ci, and MySQL 8 makes its databases utf8mb4_0900_ai_ci, so out of the box every json_table text column compared with a table's text column failed with Illegal mix of collations (1267), in the type check and at run time:

select id from users
where name in (select j.name from json_table(:names::json('{"type": "array", "items": {"type": "string"}}'), '$[*]' columns (name varchar(40) path '$')) as j)

json_table is how the docs read every list, bulk insert, nested insert and JSON object on MySQL, and the docs' nested insert worked around it by hard-coding collate utf8mb4_0900_ai_ci, which is wrong on a database with another default.

What changes

  • The compiled statement names the database's character set and collation (@@collation_database, asked once a session) in each json_table column of a character type that names neither: name varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci path '$'. The expansion of a rows(…) parameter already did this for its own JSON_TABLE, which is left as it was. MariaDB is left alone.
  • The column keeps a column's coercibility, so nothing compares under a collation it did not before: a _bin column still wins as MySQL decides. One case changes: a column of the connection's collation that is not the database's (utf8mb4_unicode_ci in a utf8mb4_0900_ai_ci database) compared before and is now refused, until the json_table column names that collation. The oracle pins both (mysql_json_table_collation).
  • A CAST(… AS CHAR) is not rewritten: a trailing COLLATE has explicit coercibility and would change what an equality on a _bin column matches. Reylo's own ::text is already the bare placeholder, which takes the column's collation; the docs say to write that.
  • The rewrite records each text it adds (Rewritten.inserted), so error offsets, placeholder spans and a rows(…) insert's lists, which the run time writes again by offset, all still map back to the author's text.
  • Docs: the MySQL page states the rule, and its Casts table now says ::text, ::varchar(n) and ::uuid are sent as the bare ?; the nested insert drops its hard-coded collation.

Copilot AI balanced review requested due to automatic review settings October 23, 2026 18:13
@vercel
vercel Bot commented Oct 23, 2026 •
Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
reylo Ready Ready Preview Oct 23, 2026 6:36pm UTC

Request Review

Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@pullfrog pullfrog Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The added CHARACTER SET … COLLATE … text cannot be parsed 8000 by the engine's own sqlparser. Any MySQL statement with a json_table text column now loses its parse-tree analysis.

Reviewed changes

Reviewed both commits: the MySQL json_table collation rewrite, the new Rewritten.inserted offset bookkeeping, the oracle case, and the docs.

  • mysql_collation.rs: scans the text for each json_table column of a char or text type that names no character set or collation, and writes the database's character set and collation before its path. The rewrite is skipped on MariaDB and on the reylo. tables the rows(…) expansion writes.
  • Rewritten.inserted: records the added text so that to_original, the rows(…) list offsets in structured.rs and vectors_as_text still map back to the author's text.
  • Oracle mysql_json_table_collation: covers the IN, join, LIKE, NESTED, _bin, connection-collation and named-charset forms.
  • Docs: the casts table says ::text is sent as the bare ?, a new paragraph explains the collation rule, and the nested insert no longer hard-codes collate utf8mb4_0900_ai_ci.

ℹ️ The compiled text uses the character set and collation of the database the type check ran against

The clause is written into the module's sql from the type check's @@character_set_database and @@collation_database. It applies at run time, whatever database the app connects to. The rows(…) expansion already works this way, but this PR extends it to every hand-written json_table. The character set is the new part. Take a database whose default is latin1 but whose tables are utf8mb4: before, a json_table text column was utf8mb4 (the connection's). Now it is latin1, so JSON strings outside latin1 are converted on the way into an insert … select … from json_table(…). Consider whether to write only COLLATE when the character set already matches the connection's, or to skip the rewrite when the database's character set is not utf8mb4. This is a design choice, so no change is required.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread engine/mysql/src/mysql.rs Resolved
JSON_TABLE gives a character column the connection's collation with a column's coercibility, so with mysql2 (utf8mb4_unicode_ci) on a MySQL 8 database (utf8mb4_0900_ai_ci) comparing it with a table's text column failed with Illegal mix of collations. The compiled statement now names the database's character set and collation in each such column's definition, as the expansion of a rows parameter already does; the rewrite records the text it adds so every offset still maps back.
The engine's parser refused CHARACTER SET and COLLATE after a JSON_TABLE column's type, so a statement that named one, or that the collation pass wrote one into, lost its tree: its row count and JSON shapes. It now reads and prints them. The pass writes only COLLATE, and only where the database's character set is utf8mb4, JSON's own.
@colinhacks
colinhacks force-pushed the mysql-json-table-collation branch from 8d2e369 to 34ff5d8 Compare October 23, 2026 18:35
@colinhacks
Copy link
Copy Markdown
Owner Author

On the character set (the review's note): agreed, writing the database's character set would convert the JSON's text where the database isn't utf8mb4. Since b6c124e the pass writes only COLLATE <@@collation_database> (a column's collation implies its character set), and only where the database's character set is utf8mb4, JSON's own; elsewhere the column is left as written. The MySQL page says so.

@pullfrog pullfrog Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. Both points from the prior review are addressed.

Reviewed changes

Reviewed the two commits since 8d2e369: the parser fix and the narrowing of the rewrite to utf8mb4 databases.

  • Vendored sqlparser reads a json_table column's charset and collation: parse_json_table_column_def now takes an optional CHARACTER SET/CHARSET and COLLATE after the type. JsonTableNamedColumn keeps them as character_set/collation and prints them back, so the rewritten statement and the probes made from it keep their tree. The crate's README records the change.
  • New oracle statements: key_and_list.sql stays maybeOne and shape.sql gives { o: { n: text? } }. Both would have regressed to many and unknown if the parse had failed.
  • Rewrite writes only COLLATE and only on a utf8mb4 database: mysql.rs:266 now requires @@character_set_database to be utf8mb4. written writes COLLATE <collation>, so a database with another default character set no longer converts JSON text into that character set. The module doc, the oracle setup.sql comment and the MySQL docs paragraph now say this.
  • Unit test parses the written statement and one that names a character set, then checks that the printed form parses back unchanged.
  • Rebuilt artifacts and recaptured docs: tests/fingerprint.test.ts passes against the committed engine.wasm files.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

@colinhacks
colinhacks merged commit a8537a6 into main Oct 23, 2026
6 checks passed
@colinhacks
colinhacks deleted the mysql-json-table-collation branch October 23, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0