117 lines
5.5 KiB
TypeScript
117 lines
5.5 KiB
TypeScript
type QueryLikeResult = {
|
|
columns: string[];
|
|
rows: (string | number | boolean | null)[][];
|
|
};
|
|
|
|
export interface CreateDatabaseCharsetMetadata {
|
|
charsets: string[];
|
|
collationsByCharset: Record<string, string[]>;
|
|
}
|
|
|
|
export const CREATE_DATABASE_CHARSET_OPTIONS = ["utf8mb4", "utf8", "gbk", "latin1", "ascii", "big5", "gb2312", "utf16", "utf32"] as const;
|
|
|
|
const FALLBACK_COLLATION_OPTIONS: Record<string, string[]> = {
|
|
ascii: ["ascii_general_ci", "ascii_bin"],
|
|
big5: ["big5_chinese_ci", "big5_bin"],
|
|
gb2312: ["gb2312_chinese_ci", "gb2312_bin"],
|
|
gbk: ["gbk_chinese_ci", "gbk_bin"],
|
|
latin1: ["latin1_swedish_ci", "latin1_general_ci", "latin1_bin"],
|
|
utf16: ["utf16_general_ci", "utf16_unicode_ci", "utf16_bin"],
|
|
utf32: ["utf32_general_ci", "utf32_unicode_ci", "utf32_bin"],
|
|
utf8: ["utf8mb3_general_ci", "utf8mb3_unicode_ci", "utf8mb3_bin"],
|
|
utf8mb3: ["utf8mb3_general_ci", "utf8mb3_unicode_ci", "utf8mb3_bin"],
|
|
utf8mb4: ["utf8mb4_unicode_ci", "utf8mb4_general_ci", "utf8mb4_bin"],
|
|
};
|
|
|
|
export function normalizeCreateDatabaseCharset(value: string): string {
|
|
return value.trim();
|
|
}
|
|
|
|
export function normalizeCreateDatabaseCharsetKey(value: string): string {
|
|
const normalized = normalizeCreateDatabaseCharset(value).toLowerCase();
|
|
return normalized === "utf8" ? "utf8mb3" : normalized;
|
|
}
|
|
|
|
export function createDatabaseCollationOptionsForCharset(charset: string, collationsByCharset: Record<string, string[]> = FALLBACK_COLLATION_OPTIONS): string[] {
|
|
return collationsByCharset[normalizeCreateDatabaseCharsetKey(charset)] ?? [];
|
|
}
|
|
|
|
export function defaultCreateDatabaseCollationForCharset(charset: string, collationsByCharset: Record<string, string[]> = FALLBACK_COLLATION_OPTIONS): string {
|
|
return createDatabaseCollationOptionsForCharset(charset, collationsByCharset)[0] ?? "";
|
|
}
|
|
|
|
export function nextCreateDatabaseCollation(nextCharset: string, previousCharset: string, currentCollation: string, collationsByCharset: Record<string, string[]> = FALLBACK_COLLATION_OPTIONS): string {
|
|
const previousDefault = defaultCreateDatabaseCollationForCharset(previousCharset, collationsByCharset);
|
|
if (currentCollation && currentCollation !== previousDefault) return currentCollation;
|
|
return defaultCreateDatabaseCollationForCharset(nextCharset, collationsByCharset) || currentCollation;
|
|
}
|
|
|
|
export function fallbackCreateDatabaseCharsetMetadata(): CreateDatabaseCharsetMetadata {
|
|
return {
|
|
charsets: [...CREATE_DATABASE_CHARSET_OPTIONS],
|
|
collationsByCharset: { ...FALLBACK_COLLATION_OPTIONS },
|
|
};
|
|
}
|
|
|
|
export function parseCreateDatabaseCharsetMetadata(charsetResult: QueryLikeResult, collationResult: QueryLikeResult): CreateDatabaseCharsetMetadata {
|
|
const charsetNameIndex = columnIndex(charsetResult.columns, ["charset", "character set", "character_set_name"]);
|
|
const charsetDefaultCollationIndex = columnIndex(charsetResult.columns, ["default collation", "default_collate_name", "default_collation_name"]);
|
|
const collationNameIndex = columnIndex(collationResult.columns, ["collation", "collation_name"]);
|
|
const collationCharsetIndex = columnIndex(collationResult.columns, ["charset", "character_set_name"]);
|
|
const collationDefaultIndex = columnIndex(collationResult.columns, ["default", "is_default"]);
|
|
|
|
const charsets = uniqueStrings(charsetResult.rows.map((row) => cellString(row[charsetNameIndex])));
|
|
const collationsByCharset: Record<string, string[]> = {};
|
|
|
|
for (const row of collationResult.rows) {
|
|
const collation = cellString(row[collationNameIndex]);
|
|
const charset = normalizeCreateDatabaseCharsetKey(cellString(row[collationCharsetIndex]));
|
|
if (!collation || !charset || isHiddenCreateDatabaseCollation(collation)) continue;
|
|
collationsByCharset[charset] ??= [];
|
|
if (isDefaultFlag(row[collationDefaultIndex])) {
|
|
collationsByCharset[charset].unshift(collation);
|
|
} else if (!collationsByCharset[charset].includes(collation)) {
|
|
collationsByCharset[charset].push(collation);
|
|
}
|
|
}
|
|
|
|
for (const row of charsetResult.rows) {
|
|
const charset = normalizeCreateDatabaseCharsetKey(cellString(row[charsetNameIndex]));
|
|
const defaultCollation = cellString(row[charsetDefaultCollationIndex]);
|
|
if (!charset || !defaultCollation || isHiddenCreateDatabaseCollation(defaultCollation)) continue;
|
|
collationsByCharset[charset] ??= [];
|
|
const existingIndex = collationsByCharset[charset].indexOf(defaultCollation);
|
|
if (existingIndex > 0) collationsByCharset[charset].splice(existingIndex, 1);
|
|
if (existingIndex !== 0) collationsByCharset[charset].unshift(defaultCollation);
|
|
}
|
|
|
|
return {
|
|
charsets: charsets.length ? charsets : [...CREATE_DATABASE_CHARSET_OPTIONS],
|
|
collationsByCharset: Object.keys(collationsByCharset).length ? collationsByCharset : { ...FALLBACK_COLLATION_OPTIONS },
|
|
};
|
|
}
|
|
|
|
function columnIndex(columns: string[], names: string[]): number {
|
|
const lowerNames = names.map((name) => name.toLowerCase());
|
|
const index = columns.findIndex((column) => lowerNames.includes(column.trim().toLowerCase()));
|
|
return index >= 0 ? index : 0;
|
|
}
|
|
|
|
function cellString(value: unknown): string {
|
|
return typeof value === "string" ? value.trim() : value == null ? "" : String(value).trim();
|
|
}
|
|
|
|
function uniqueStrings(values: string[]): string[] {
|
|
return [...new Set(values.filter(Boolean))];
|
|
}
|
|
|
|
function isHiddenCreateDatabaseCollation(value: string): boolean {
|
|
return value.toLowerCase().includes("_0900_");
|
|
}
|
|
|
|
function isDefaultFlag(value: unknown): boolean {
|
|
if (value === true) return true;
|
|
if (typeof value !== "string") return false;
|
|
return ["yes", "y", "1", "true"].includes(value.trim().toLowerCase());
|
|
}
|