Skip to main content

spg_sql/
charset.rs

1//! MySQL charset names and the collation each one defaults to.
2//!
3//! v7.39.2 — moved here from `spg-engine`'s `collate` because the
4//! PARSER needs it: `_utf8mb4'x'` is a charset introducer only when the
5//! word after the underscore names a charset, and deciding that is
6//! parsing. The engine reads the same table through this module rather
7//! than keeping a second copy — two lists of one fact is how the
8//! surfaces in this release came to disagree in the first place.
9
10/// Do trailing spaces count in a comparison under this collation?
11///
12/// v7.38.18 — the pad attribute is a property of the collation NAME,
13/// and SPG read a name for whether to FOLD and never for whether to
14/// PAD. `utf8mb4_bin` and `utf8mb4_general_ci` are PAD SPACE in BOTH
15/// engines, which is the part that is easy to get backwards: byte-wise
16/// does not mean padding counts.
17///
18/// The rule is measured, not inferred. Over every collation both
19/// oracles list — 286 names on MySQL 9.7.2 and 552 on MariaDB 12.3.2,
20/// 838 in all — `NO PAD` holds exactly when the name is `binary`, or
21/// contains `_0900_`, or contains `nopad`. **Zero counter-examples on
22/// either engine.**
23///
24/// `None` — nothing declared — is the session default, and SPG
25/// advertises `8.0.0-spg-v…` on the MySQL wire, so that is MySQL 8.0's
26/// `utf8mb4_0900_ai_ci`: NO PAD.
27/// v7.39 — the collation a MySQL session compares under when the
28/// session has not said otherwise.
29///
30/// One constant, because the last time a name SPG reports and a rule SPG
31/// applies lived in two places they disagreed for months
32/// (`server_version`, v7.38.25) -- and they disagreed here too:
33/// `@@collation_connection` answered `utf8mb4_general_ci` (PAD SPACE)
34/// while `e2e_mysql_pad_space_round375` pinned the NO PAD behaviour of
35/// `utf8mb4_0900_ai_ci`, re-measured on MySQL 9.7.2 in v7.38.17. Both
36/// were defensible on their own and could not both be right.
37///
38/// `utf8mb4_0900_ai_ci` is the value: it is what SPG's advertised 8.0
39/// lineage implies, what `show.rs` already reported for
40/// `collation_server`, what MySQL 9.7.2 answers for a bare
41/// `SET NAMES utf8mb4` (measured -- and `'a' = 'a '` is 0 there), and
42/// what those pins were re-measured against. `pads_space` reads
43/// `_0900_` and says NO PAD, so the reported name and the applied rule
44/// now agree by construction.
45/// v7.39 — `SET NAMES <charset>` with no `COLLATE` takes the charset's
46/// DEFAULT collation, and that decides whether the session pads.
47/// Measured, not inferred: read from MySQL 9.7.2's
48/// `information_schema.CHARACTER_SETS`, all 41 rows of it, so
49/// the two that matter most are visible beside the rest --
50/// `utf8mb4` -> `utf8mb4_0900_ai_ci` (NO PAD) and `latin1` ->
51/// `latin1_swedish_ci` (PAD SPACE). Confirmed on the wire: a bare
52/// `SET NAMES utf8mb4` answers `'a' = 'a '` with 0 there and a bare
53/// `SET NAMES latin1` answers 1.
54pub const MYSQL_CHARSET_DEFAULT_COLLATION: &[(&str, &str)] = &[
55    ("armscii8", "armscii8_general_ci"),
56    ("ascii", "ascii_general_ci"),
57    ("big5", "big5_chinese_ci"),
58    ("binary", "binary"),
59    ("cp1250", "cp1250_general_ci"),
60    ("cp1251", "cp1251_general_ci"),
61    ("cp1256", "cp1256_general_ci"),
62    ("cp1257", "cp1257_general_ci"),
63    ("cp850", "cp850_general_ci"),
64    ("cp852", "cp852_general_ci"),
65    ("cp866", "cp866_general_ci"),
66    ("cp932", "cp932_japanese_ci"),
67    ("dec8", "dec8_swedish_ci"),
68    ("eucjpms", "eucjpms_japanese_ci"),
69    ("euckr", "euckr_korean_ci"),
70    ("gb18030", "gb18030_chinese_ci"),
71    ("gb2312", "gb2312_chinese_ci"),
72    ("gbk", "gbk_chinese_ci"),
73    ("geostd8", "geostd8_general_ci"),
74    ("greek", "greek_general_ci"),
75    ("hebrew", "hebrew_general_ci"),
76    ("hp8", "hp8_english_ci"),
77    ("keybcs2", "keybcs2_general_ci"),
78    ("koi8r", "koi8r_general_ci"),
79    ("koi8u", "koi8u_general_ci"),
80    ("latin1", "latin1_swedish_ci"),
81    ("latin2", "latin2_general_ci"),
82    ("latin5", "latin5_turkish_ci"),
83    ("latin7", "latin7_general_ci"),
84    ("macce", "macce_general_ci"),
85    ("macroman", "macroman_general_ci"),
86    ("sjis", "sjis_japanese_ci"),
87    ("swe7", "swe7_swedish_ci"),
88    ("tis620", "tis620_thai_ci"),
89    ("ucs2", "ucs2_general_ci"),
90    ("ujis", "ujis_japanese_ci"),
91    ("utf16", "utf16_general_ci"),
92    ("utf16le", "utf16le_general_ci"),
93    ("utf32", "utf32_general_ci"),
94    ("utf8mb3", "utf8mb3_general_ci"),
95    ("utf8mb4", "utf8mb4_0900_ai_ci"),
96];
97
98/// The collation `SET NAMES <charset>` selects, or `None` for a charset
99/// this build does not know. An unknown name is not silently mapped to
100/// a default: that is how a session ends up padding differently from
101/// what it was told.
102#[must_use]
103pub fn charset_default_collation(charset: &str) -> Option<&'static str> {
104    let lower = charset.trim().to_ascii_lowercase();
105    MYSQL_CHARSET_DEFAULT_COLLATION
106        .iter()
107        .find(|(c, _)| *c == lower)
108        .map(|(_, coll)| *coll)
109}
110
111/// Every collation MySQL 9.7.2 **or MariaDB 12.3.3** serves, read from
112/// their own `information_schema.collations` — 575 names, sorted for
113/// `binary_search`.
114///
115/// The union, because SPG answers to both. The first version was MySQL's
116/// alone, and MariaDB's UCA-14.0.0 family — which MySQL does not have,
117/// and which the drop-in acceptance suite declares its MariaDB column
118/// with — became "no such collation". Every query against that table
119/// then returned nothing: five drop-in cases failed at once, in CI, on a
120/// suite the local gates do not run. 289 of these names are MariaDB's
121/// alone and 23 are MySQL's.
122///
123/// v7.39.2 — the check this replaces was a SUFFIX: any name ending in
124/// `_ci`, `_cs` or `_bin` counted as known. `SELECT 'a' COLLATE
125/// nosuch_ci` was therefore accepted and then silently ignored — the
126/// comparison did whatever the session would have done anyway, and a
127/// client that named a collation got no signal that it had not been
128/// used. MySQL 9.7.2 answers `ERROR 1273 (HY000): Unknown collation:
129/// 'nosuch_ci'`.
130pub const MYSQL_COLLATIONS: &[&str] = &[
131    "armscii8_bin",
132    "armscii8_general_ci",
133    "armscii8_general_nopad_ci",
134    "armscii8_nopad_bin",
135    "ascii_bin",
136    "ascii_general_ci",
137    "ascii_general_nopad_ci",
138    "ascii_nopad_bin",
139    "big5_bin",
140    "big5_chinese_ci",
141    "big5_chinese_nopad_ci",
142    "big5_nopad_bin",
143    "binary",
144    "cp1250_bin",
145    "cp1250_croatian_ci",
146    "cp1250_czech_cs",
147    "cp1250_general_ci",
148    "cp1250_general_nopad_ci",
149    "cp1250_nopad_bin",
150    "cp1250_polish_ci",
151    "cp1251_bin",
152    "cp1251_bulgarian_ci",
153    "cp1251_general_ci",
154    "cp1251_general_cs",
155    "cp1251_general_nopad_ci",
156    "cp1251_nopad_bin",
157    "cp1251_ukrainian_ci",
158    "cp1256_bin",
159    "cp1256_general_ci",
160    "cp1256_general_nopad_ci",
161    "cp1256_nopad_bin",
162    "cp1257_bin",
163    "cp1257_general_ci",
164    "cp1257_general_nopad_ci",
165    "cp1257_lithuanian_ci",
166    "cp1257_nopad_bin",
167    "cp850_bin",
168    "cp850_general_ci",
169    "cp850_general_nopad_ci",
170    "cp850_nopad_bin",
171    "cp852_bin",
172    "cp852_general_ci",
173    "cp852_general_nopad_ci",
174    "cp852_nopad_bin",
175    "cp866_bin",
176    "cp866_general_ci",
177    "cp866_general_nopad_ci",
178    "cp866_nopad_bin",
179    "cp932_bin",
180    "cp932_japanese_ci",
181    "cp932_japanese_nopad_ci",
182    "cp932_nopad_bin",
183    "dec8_bin",
184    "dec8_nopad_bin",
185    "dec8_swedish_ci",
186    "dec8_swedish_nopad_ci",
187    "eucjpms_bin",
188    "eucjpms_japanese_ci",
189    "eucjpms_japanese_nopad_ci",
190    "eucjpms_nopad_bin",
191    "euckr_bin",
192    "euckr_korean_ci",
193    "euckr_korean_nopad_ci",
194    "euckr_nopad_bin",
195    "gb18030_bin",
196    "gb18030_chinese_ci",
197    "gb18030_unicode_520_ci",
198    "gb2312_bin",
199    "gb2312_chinese_ci",
200    "gb2312_chinese_nopad_ci",
201    "gb2312_nopad_bin",
202    "gbk_bin",
203    "gbk_chinese_ci",
204    "gbk_chinese_nopad_ci",
205    "gbk_nopad_bin",
206    "geostd8_bin",
207    "geostd8_general_ci",
208    "geostd8_general_nopad_ci",
209    "geostd8_nopad_bin",
210    "greek_bin",
211    "greek_general_ci",
212    "greek_general_nopad_ci",
213    "greek_nopad_bin",
214    "hebrew_bin",
215    "hebrew_general_ci",
216    "hebrew_general_nopad_ci",
217    "hebrew_nopad_bin",
218    "hp8_bin",
219    "hp8_english_ci",
220    "hp8_english_nopad_ci",
221    "hp8_nopad_bin",
222    "keybcs2_bin",
223    "keybcs2_general_ci",
224    "keybcs2_general_nopad_ci",
225    "keybcs2_nopad_bin",
226    "koi8r_bin",
227    "koi8r_general_ci",
228    "koi8r_general_nopad_ci",
229    "koi8r_nopad_bin",
230    "koi8u_bin",
231    "koi8u_general_ci",
232    "koi8u_general_nopad_ci",
233    "koi8u_nopad_bin",
234    "latin1_bin",
235    "latin1_danish_ci",
236    "latin1_general_ci",
237    "latin1_general_cs",
238    "latin1_german1_ci",
239    "latin1_german2_ci",
240    "latin1_nopad_bin",
241    "latin1_spanish_ci",
242    "latin1_swedish_ci",
243    "latin1_swedish_nopad_ci",
244    "latin2_bin",
245    "latin2_croatian_ci",
246    "latin2_czech_cs",
247    "latin2_general_ci",
248    "latin2_general_nopad_ci",
249    "latin2_hungarian_ci",
250    "latin2_nopad_bin",
251    "latin5_bin",
252    "latin5_nopad_bin",
253    "latin5_turkish_ci",
254    "latin5_turkish_nopad_ci",
255    "latin7_bin",
256    "latin7_estonian_cs",
257    "latin7_general_ci",
258    "latin7_general_cs",
259    "latin7_general_nopad_ci",
260    "latin7_nopad_bin",
261    "macce_bin",
262    "macce_general_ci",
263    "macce_general_nopad_ci",
264    "macce_nopad_bin",
265    "macroman_bin",
266    "macroman_general_ci",
267    "macroman_general_nopad_ci",
268    "macroman_nopad_bin",
269    "sjis_bin",
270    "sjis_japanese_ci",
271    "sjis_japanese_nopad_ci",
272    "sjis_nopad_bin",
273    "swe7_bin",
274    "swe7_nopad_bin",
275    "swe7_swedish_ci",
276    "swe7_swedish_nopad_ci",
277    "tis620_bin",
278    "tis620_nopad_bin",
279    "tis620_thai_ci",
280    "tis620_thai_nopad_ci",
281    "uca1400_ai_ci",
282    "uca1400_ai_cs",
283    "uca1400_as_ci",
284    "uca1400_as_cs",
285    "uca1400_croatian_ai_ci",
286    "uca1400_croatian_ai_cs",
287    "uca1400_croatian_as_ci",
288    "uca1400_croatian_as_cs",
289    "uca1400_croatian_nopad_ai_ci",
290    "uca1400_croatian_nopad_ai_cs",
291    "uca1400_croatian_nopad_as_ci",
292    "uca1400_croatian_nopad_as_cs",
293    "uca1400_czech_ai_ci",
294    "uca1400_czech_ai_cs",
295    "uca1400_czech_as_ci",
296    "uca1400_czech_as_cs",
297    "uca1400_czech_nopad_ai_ci",
298    "uca1400_czech_nopad_ai_cs",
299    "uca1400_czech_nopad_as_ci",
300    "uca1400_czech_nopad_as_cs",
301    "uca1400_danish_ai_ci",
302    "uca1400_danish_ai_cs",
303    "uca1400_danish_as_ci",
304    "uca1400_danish_as_cs",
305    "uca1400_danish_nopad_ai_ci",
306    "uca1400_danish_nopad_ai_cs",
307    "uca1400_danish_nopad_as_ci",
308    "uca1400_danish_nopad_as_cs",
309    "uca1400_esperanto_ai_ci",
310    "uca1400_esperanto_ai_cs",
311    "uca1400_esperanto_as_ci",
312    "uca1400_esperanto_as_cs",
313    "uca1400_esperanto_nopad_ai_ci",
314    "uca1400_esperanto_nopad_ai_cs",
315    "uca1400_esperanto_nopad_as_ci",
316    "uca1400_esperanto_nopad_as_cs",
317    "uca1400_estonian_ai_ci",
318    "uca1400_estonian_ai_cs",
319    "uca1400_estonian_as_ci",
320    "uca1400_estonian_as_cs",
321    "uca1400_estonian_nopad_ai_ci",
322    "uca1400_estonian_nopad_ai_cs",
323    "uca1400_estonian_nopad_as_ci",
324    "uca1400_estonian_nopad_as_cs",
325    "uca1400_german2_ai_ci",
326    "uca1400_german2_ai_cs",
327    "uca1400_german2_as_ci",
328    "uca1400_german2_as_cs",
329    "uca1400_german2_nopad_ai_ci",
330    "uca1400_german2_nopad_ai_cs",
331    "uca1400_german2_nopad_as_ci",
332    "uca1400_german2_nopad_as_cs",
333    "uca1400_hungarian_ai_ci",
334    "uca1400_hungarian_ai_cs",
335    "uca1400_hungarian_as_ci",
336    "uca1400_hungarian_as_cs",
337    "uca1400_hungarian_nopad_ai_ci",
338    "uca1400_hungarian_nopad_ai_cs",
339    "uca1400_hungarian_nopad_as_ci",
340    "uca1400_hungarian_nopad_as_cs",
341    "uca1400_icelandic_ai_ci",
342    "uca1400_icelandic_ai_cs",
343    "uca1400_icelandic_as_ci",
344    "uca1400_icelandic_as_cs",
345    "uca1400_icelandic_nopad_ai_ci",
346    "uca1400_icelandic_nopad_ai_cs",
347    "uca1400_icelandic_nopad_as_ci",
348    "uca1400_icelandic_nopad_as_cs",
349    "uca1400_latvian_ai_ci",
350    "uca1400_latvian_ai_cs",
351    "uca1400_latvian_as_ci",
352    "uca1400_latvian_as_cs",
353    "uca1400_latvian_nopad_ai_ci",
354    "uca1400_latvian_nopad_ai_cs",
355    "uca1400_latvian_nopad_as_ci",
356    "uca1400_latvian_nopad_as_cs",
357    "uca1400_lithuanian_ai_ci",
358    "uca1400_lithuanian_ai_cs",
359    "uca1400_lithuanian_as_ci",
360    "uca1400_lithuanian_as_cs",
361    "uca1400_lithuanian_nopad_ai_ci",
362    "uca1400_lithuanian_nopad_ai_cs",
363    "uca1400_lithuanian_nopad_as_ci",
364    "uca1400_lithuanian_nopad_as_cs",
365    "uca1400_nopad_ai_ci",
366    "uca1400_nopad_ai_cs",
367    "uca1400_nopad_as_ci",
368    "uca1400_nopad_as_cs",
369    "uca1400_persian_ai_ci",
370    "uca1400_persian_ai_cs",
371    "uca1400_persian_as_ci",
372    "uca1400_persian_as_cs",
373    "uca1400_persian_nopad_ai_ci",
374    "uca1400_persian_nopad_ai_cs",
375    "uca1400_persian_nopad_as_ci",
376    "uca1400_persian_nopad_as_cs",
377    "uca1400_polish_ai_ci",
378    "uca1400_polish_ai_cs",
379    "uca1400_polish_as_ci",
380    "uca1400_polish_as_cs",
381    "uca1400_polish_nopad_ai_ci",
382    "uca1400_polish_nopad_ai_cs",
383    "uca1400_polish_nopad_as_ci",
384    "uca1400_polish_nopad_as_cs",
385    "uca1400_roman_ai_ci",
386    "uca1400_roman_ai_cs",
387    "uca1400_roman_as_ci",
388    "uca1400_roman_as_cs",
389    "uca1400_roman_nopad_ai_ci",
390    "uca1400_roman_nopad_ai_cs",
391    "uca1400_roman_nopad_as_ci",
392    "uca1400_roman_nopad_as_cs",
393    "uca1400_romanian_ai_ci",
394    "uca1400_romanian_ai_cs",
395    "uca1400_romanian_as_ci",
396    "uca1400_romanian_as_cs",
397    "uca1400_romanian_nopad_ai_ci",
398    "uca1400_romanian_nopad_ai_cs",
399    "uca1400_romanian_nopad_as_ci",
400    "uca1400_romanian_nopad_as_cs",
401    "uca1400_sinhala_ai_ci",
402    "uca1400_sinhala_ai_cs",
403    "uca1400_sinhala_as_ci",
404    "uca1400_sinhala_as_cs",
405    "uca1400_sinhala_nopad_ai_ci",
406    "uca1400_sinhala_nopad_ai_cs",
407    "uca1400_sinhala_nopad_as_ci",
408    "uca1400_sinhala_nopad_as_cs",
409    "uca1400_slovak_ai_ci",
410    "uca1400_slovak_ai_cs",
411    "uca1400_slovak_as_ci",
412    "uca1400_slovak_as_cs",
413    "uca1400_slovak_nopad_ai_ci",
414    "uca1400_slovak_nopad_ai_cs",
415    "uca1400_slovak_nopad_as_ci",
416    "uca1400_slovak_nopad_as_cs",
417    "uca1400_slovenian_ai_ci",
418    "uca1400_slovenian_ai_cs",
419    "uca1400_slovenian_as_ci",
420    "uca1400_slovenian_as_cs",
421    "uca1400_slovenian_nopad_ai_ci",
422    "uca1400_slovenian_nopad_ai_cs",
423    "uca1400_slovenian_nopad_as_ci",
424    "uca1400_slovenian_nopad_as_cs",
425    "uca1400_spanish2_ai_ci",
426    "uca1400_spanish2_ai_cs",
427    "uca1400_spanish2_as_ci",
428    "uca1400_spanish2_as_cs",
429    "uca1400_spanish2_nopad_ai_ci",
430    "uca1400_spanish2_nopad_ai_cs",
431    "uca1400_spanish2_nopad_as_ci",
432    "uca1400_spanish2_nopad_as_cs",
433    "uca1400_spanish_ai_ci",
434    "uca1400_spanish_ai_cs",
435    "uca1400_spanish_as_ci",
436    "uca1400_spanish_as_cs",
437    "uca1400_spanish_nopad_ai_ci",
438    "uca1400_spanish_nopad_ai_cs",
439    "uca1400_spanish_nopad_as_ci",
440    "uca1400_spanish_nopad_as_cs",
441    "uca1400_swedish_ai_ci",
442    "uca1400_swedish_ai_cs",
443    "uca1400_swedish_as_ci",
444    "uca1400_swedish_as_cs",
445    "uca1400_swedish_nopad_ai_ci",
446    "uca1400_swedish_nopad_ai_cs",
447    "uca1400_swedish_nopad_as_ci",
448    "uca1400_swedish_nopad_as_cs",
449    "uca1400_turkish_ai_ci",
450    "uca1400_turkish_ai_cs",
451    "uca1400_turkish_as_ci",
452    "uca1400_turkish_as_cs",
453    "uca1400_turkish_nopad_ai_ci",
454    "uca1400_turkish_nopad_ai_cs",
455    "uca1400_turkish_nopad_as_ci",
456    "uca1400_turkish_nopad_as_cs",
457    "uca1400_vietnamese_ai_ci",
458    "uca1400_vietnamese_ai_cs",
459    "uca1400_vietnamese_as_ci",
460    "uca1400_vietnamese_as_cs",
461    "uca1400_vietnamese_nopad_ai_ci",
462    "uca1400_vietnamese_nopad_ai_cs",
463    "uca1400_vietnamese_nopad_as_ci",
464    "uca1400_vietnamese_nopad_as_cs",
465    "ucs2_bin",
466    "ucs2_croatian_ci",
467    "ucs2_croatian_mysql561_ci",
468    "ucs2_czech_ci",
469    "ucs2_danish_ci",
470    "ucs2_esperanto_ci",
471    "ucs2_estonian_ci",
472    "ucs2_general_ci",
473    "ucs2_general_mysql500_ci",
474    "ucs2_general_nopad_ci",
475    "ucs2_german2_ci",
476    "ucs2_hungarian_ci",
477    "ucs2_icelandic_ci",
478    "ucs2_latvian_ci",
479    "ucs2_lithuanian_ci",
480    "ucs2_myanmar_ci",
481    "ucs2_nopad_bin",
482    "ucs2_persian_ci",
483    "ucs2_polish_ci",
484    "ucs2_roman_ci",
485    "ucs2_romanian_ci",
486    "ucs2_sinhala_ci",
487    "ucs2_slovak_ci",
488    "ucs2_slovenian_ci",
489    "ucs2_spanish2_ci",
490    "ucs2_spanish_ci",
491    "ucs2_swedish_ci",
492    "ucs2_thai_520_w2",
493    "ucs2_turkish_ci",
494    "ucs2_unicode_520_ci",
495    "ucs2_unicode_520_nopad_ci",
496    "ucs2_unicode_ci",
497    "ucs2_unicode_nopad_ci",
498    "ucs2_vietnamese_ci",
499    "ujis_bin",
500    "ujis_japanese_ci",
501    "ujis_japanese_nopad_ci",
502    "ujis_nopad_bin",
503    "utf16_bin",
504    "utf16_croatian_ci",
505    "utf16_croatian_mysql561_ci",
506    "utf16_czech_ci",
507    "utf16_danish_ci",
508    "utf16_esperanto_ci",
509    "utf16_estonian_ci",
510    "utf16_general_ci",
511    "utf16_general_nopad_ci",
512    "utf16_german2_ci",
513    "utf16_hungarian_ci",
514    "utf16_icelandic_ci",
515    "utf16_latvian_ci",
516    "utf16_lithuanian_ci",
517    "utf16_myanmar_ci",
518    "utf16_nopad_bin",
519    "utf16_persian_ci",
520    "utf16_polish_ci",
521    "utf16_roman_ci",
522    "utf16_romanian_ci",
523    "utf16_sinhala_ci",
524    "utf16_slovak_ci",
525    "utf16_slovenian_ci",
526    "utf16_spanish2_ci",
527    "utf16_spanish_ci",
528    "utf16_swedish_ci",
529    "utf16_thai_520_w2",
530    "utf16_turkish_ci",
531    "utf16_unicode_520_ci",
532    "utf16_unicode_520_nopad_ci",
533    "utf16_unicode_ci",
534    "utf16_unicode_nopad_ci",
535    "utf16_vietnamese_ci",
536    "utf16le_bin",
537    "utf16le_general_ci",
538    "utf16le_general_nopad_ci",
539    "utf16le_nopad_bin",
540    "utf32_bin",
541    "utf32_croatian_ci",
542    "utf32_croatian_mysql561_ci",
543    "utf32_czech_ci",
544    "utf32_danish_ci",
545    "utf32_esperanto_ci",
546    "utf32_estonian_ci",
547    "utf32_general_ci",
548    "utf32_general_nopad_ci",
549    "utf32_german2_ci",
550    "utf32_hungarian_ci",
551    "utf32_icelandic_ci",
552    "utf32_latvian_ci",
553    "utf32_lithuanian_ci",
554    "utf32_myanmar_ci",
555    "utf32_nopad_bin",
556    "utf32_persian_ci",
557    "utf32_polish_ci",
558    "utf32_roman_ci",
559    "utf32_romanian_ci",
560    "utf32_sinhala_ci",
561    "utf32_slovak_ci",
562    "utf32_slovenian_ci",
563    "utf32_spanish2_ci",
564    "utf32_spanish_ci",
565    "utf32_swedish_ci",
566    "utf32_thai_520_w2",
567    "utf32_turkish_ci",
568    "utf32_unicode_520_ci",
569    "utf32_unicode_520_nopad_ci",
570    "utf32_unicode_ci",
571    "utf32_unicode_nopad_ci",
572    "utf32_vietnamese_ci",
573    "utf8mb3_bin",
574    "utf8mb3_croatian_ci",
575    "utf8mb3_croatian_mysql561_ci",
576    "utf8mb3_czech_ci",
577    "utf8mb3_danish_ci",
578    "utf8mb3_esperanto_ci",
579    "utf8mb3_estonian_ci",
580    "utf8mb3_general1400_as_ci",
581    "utf8mb3_general_ci",
582    "utf8mb3_general_mysql500_ci",
583    "utf8mb3_general_nopad_ci",
584    "utf8mb3_german2_ci",
585    "utf8mb3_hungarian_ci",
586    "utf8mb3_icelandic_ci",
587    "utf8mb3_latvian_ci",
588    "utf8mb3_lithuanian_ci",
589    "utf8mb3_myanmar_ci",
590    "utf8mb3_nopad_bin",
591    "utf8mb3_persian_ci",
592    "utf8mb3_polish_ci",
593    "utf8mb3_roman_ci",
594    "utf8mb3_romanian_ci",
595    "utf8mb3_sinhala_ci",
596    "utf8mb3_slovak_ci",
597    "utf8mb3_slovenian_ci",
598    "utf8mb3_spanish2_ci",
599    "utf8mb3_spanish_ci",
600    "utf8mb3_swedish_ci",
601    "utf8mb3_thai_520_w2",
602    "utf8mb3_tolower_ci",
603    "utf8mb3_turkish_ci",
604    "utf8mb3_unicode_520_ci",
605    "utf8mb3_unicode_520_nopad_ci",
606    "utf8mb3_unicode_ci",
607    "utf8mb3_unicode_nopad_ci",
608    "utf8mb3_vietnamese_ci",
609    "utf8mb4_0900_ai_ci",
610    "utf8mb4_0900_as_ci",
611    "utf8mb4_0900_as_cs",
612    "utf8mb4_0900_bin",
613    "utf8mb4_bg_0900_ai_ci",
614    "utf8mb4_bg_0900_as_cs",
615    "utf8mb4_bin",
616    "utf8mb4_bs_0900_ai_ci",
617    "utf8mb4_bs_0900_as_cs",
618    "utf8mb4_croatian_ci",
619    "utf8mb4_croatian_mysql561_ci",
620    "utf8mb4_cs_0900_ai_ci",
621    "utf8mb4_cs_0900_as_cs",
622    "utf8mb4_czech_ci",
623    "utf8mb4_da_0900_ai_ci",
624    "utf8mb4_da_0900_as_cs",
625    "utf8mb4_danish_ci",
626    "utf8mb4_de_pb_0900_ai_ci",
627    "utf8mb4_de_pb_0900_as_cs",
628    "utf8mb4_eo_0900_ai_ci",
629    "utf8mb4_eo_0900_as_cs",
630    "utf8mb4_es_0900_ai_ci",
631    "utf8mb4_es_0900_as_cs",
632    "utf8mb4_es_trad_0900_ai_ci",
633    "utf8mb4_es_trad_0900_as_cs",
634    "utf8mb4_esperanto_ci",
635    "utf8mb4_estonian_ci",
636    "utf8mb4_et_0900_ai_ci",
637    "utf8mb4_et_0900_as_cs",
638    "utf8mb4_general1400_as_ci",
639    "utf8mb4_general_ci",
640    "utf8mb4_general_nopad_ci",
641    "utf8mb4_german2_ci",
642    "utf8mb4_gl_0900_ai_ci",
643    "utf8mb4_gl_0900_as_cs",
644    "utf8mb4_hr_0900_ai_ci",
645    "utf8mb4_hr_0900_as_cs",
646    "utf8mb4_hu_0900_ai_ci",
647    "utf8mb4_hu_0900_as_cs",
648    "utf8mb4_hungarian_ci",
649    "utf8mb4_icelandic_ci",
650    "utf8mb4_is_0900_ai_ci",
651    "utf8mb4_is_0900_as_cs",
652    "utf8mb4_ja_0900_as_cs",
653    "utf8mb4_ja_0900_as_cs_ks",
654    "utf8mb4_la_0900_ai_ci",
655    "utf8mb4_la_0900_as_cs",
656    "utf8mb4_latvian_ci",
657    "utf8mb4_lithuanian_ci",
658    "utf8mb4_lt_0900_ai_ci",
659    "utf8mb4_lt_0900_as_cs",
660    "utf8mb4_lv_0900_ai_ci",
661    "utf8mb4_lv_0900_as_cs",
662    "utf8mb4_mn_cyrl_0900_ai_ci",
663    "utf8mb4_mn_cyrl_0900_as_cs",
664    "utf8mb4_myanmar_ci",
665    "utf8mb4_nb_0900_ai_ci",
666    "utf8mb4_nb_0900_as_cs",
667    "utf8mb4_nn_0900_ai_ci",
668    "utf8mb4_nn_0900_as_cs",
669    "utf8mb4_nopad_bin",
670    "utf8mb4_persian_ci",
671    "utf8mb4_pl_0900_ai_ci",
672    "utf8mb4_pl_0900_as_cs",
673    "utf8mb4_polish_ci",
674    "utf8mb4_ro_0900_ai_ci",
675    "utf8mb4_ro_0900_as_cs",
676    "utf8mb4_roman_ci",
677    "utf8mb4_romanian_ci",
678    "utf8mb4_ru_0900_ai_ci",
679    "utf8mb4_ru_0900_as_cs",
680    "utf8mb4_sinhala_ci",
681    "utf8mb4_sk_0900_ai_ci",
682    "utf8mb4_sk_0900_as_cs",
683    "utf8mb4_sl_0900_ai_ci",
684    "utf8mb4_sl_0900_as_cs",
685    "utf8mb4_slovak_ci",
686    "utf8mb4_slovenian_ci",
687    "utf8mb4_spanish2_ci",
688    "utf8mb4_spanish_ci",
689    "utf8mb4_sr_latn_0900_ai_ci",
690    "utf8mb4_sr_latn_0900_as_cs",
691    "utf8mb4_sv_0900_ai_ci",
692    "utf8mb4_sv_0900_as_cs",
693    "utf8mb4_swedish_ci",
694    "utf8mb4_thai_520_w2",
695    "utf8mb4_tr_0900_ai_ci",
696    "utf8mb4_tr_0900_as_cs",
697    "utf8mb4_turkish_ci",
698    "utf8mb4_unicode_520_ci",
699    "utf8mb4_unicode_520_nopad_ci",
700    "utf8mb4_unicode_ci",
701    "utf8mb4_unicode_nopad_ci",
702    "utf8mb4_vi_0900_ai_ci",
703    "utf8mb4_vi_0900_as_cs",
704    "utf8mb4_vietnamese_ci",
705    "utf8mb4_zh_0900_as_cs",
706];
707
708/// The character sets MariaDB lets the UCA-14.0.0 family be written
709/// against, measured on 12.3.3 by asking it: `utf8mb4_uca1400_ai_ci`
710/// and four more resolve, `latin1_uca1400_ai_ci` does not.
711const UCA_PREFIX_CHARSETS: &[&str] = &["ucs2", "utf16", "utf32", "utf8mb3", "utf8mb4"];
712
713/// Is this a collation name MySQL or MariaDB serves?
714///
715/// MariaDB registers the UCA-14.0.0 family WITHOUT a character set —
716/// `information_schema.collations` lists `uca1400_ai_ci`, not
717/// `utf8mb4_uca1400_ai_ci` — and accepts either spelling in SQL. The
718/// table alone therefore refused the spelling everything is written
719/// with; the prefix form is checked against the same table with the
720/// charset removed, and only for the five character sets MariaDB
721/// actually resolves it for.
722#[must_use]
723pub fn is_mysql_collation(name: &str) -> bool {
724    let lower = name.trim().to_ascii_lowercase();
725    if MYSQL_COLLATIONS.binary_search(&lower.as_str()).is_ok() {
726        return true;
727    }
728    UCA_PREFIX_CHARSETS.iter().any(|cs| {
729        lower
730            .strip_prefix(cs)
731            .and_then(|rest| rest.strip_prefix('_'))
732            .is_some_and(|rest| {
733                rest.starts_with("uca1400") && MYSQL_COLLATIONS.binary_search(&rest).is_ok()
734            })
735    })
736}
737
738#[cfg(test)]
739mod mysql_collation_tests {
740    use super::{MYSQL_COLLATIONS, is_mysql_collation};
741
742    /// `binary_search` is only an answer if the list is in byte order,
743    /// and MySQL's own `ORDER BY` is not byte order — the first version
744    /// of this table was written in the order the server returned and
745    /// would have missed names in the middle of it.
746    #[test]
747    fn the_table_is_sorted_and_lowercase() {
748        let mut sorted = MYSQL_COLLATIONS.to_vec();
749        sorted.sort_unstable();
750        assert_eq!(sorted.as_slice(), MYSQL_COLLATIONS, "not in byte order");
751        assert!(
752            MYSQL_COLLATIONS
753                .iter()
754                .all(|c| c.to_ascii_lowercase() == **c),
755            "a name is not lowercase, so the lookup would miss it"
756        );
757        // Every one of them must be findable, which a wrong order breaks
758        // silently for exactly the names a suffix rule already accepted.
759        assert!(MYSQL_COLLATIONS.iter().all(|c| is_mysql_collation(c)));
760    }
761
762    #[test]
763    fn it_answers_the_names_the_suffix_rule_could_not_tell_apart() {
764        assert!(is_mysql_collation("utf8mb4_general_ci"));
765        assert!(is_mysql_collation("utf8mb4_0900_ai_ci"));
766        assert!(is_mysql_collation("UTF8MB4_BIN"));
767        assert!(is_mysql_collation("binary"));
768        assert!(is_mysql_collation("latin1_swedish_ci"));
769        // All three suffixes the old rule waved through.
770        assert!(!is_mysql_collation("nosuch_ci"));
771        assert!(!is_mysql_collation("nosuch_cs"));
772        assert!(!is_mysql_collation("nosuch_bin"));
773        // And a real charset with a family neither engine has.
774        assert!(!is_mysql_collation("utf8mb4_madeup_ci"));
775    }
776
777    /// MariaDB's UCA-14.0.0 family, which it registers without a
778    /// character set and accepts with or without one. Every expectation
779    /// measured on MariaDB 12.3.3.
780    #[test]
781    fn the_uca1400_family_is_known_in_both_spellings() {
782        assert!(is_mysql_collation("uca1400_ai_ci"));
783        assert!(is_mysql_collation("utf8mb4_uca1400_ai_ci"));
784        assert!(is_mysql_collation("utf8mb3_uca1400_as_cs"));
785        // `latin1` has no UCA collation and MariaDB refuses this one, so
786        // a rule that just stripped any prefix would be wrong.
787        assert!(!is_mysql_collation("latin1_uca1400_ai_ci"));
788        // And the family name still has to be one that exists.
789        assert!(!is_mysql_collation("utf8mb4_nosuch1400_ai_ci"));
790        assert!(!is_mysql_collation("utf8mb4_uca1400_nosuch"));
791    }
792}