From f791252b2fe58bb282e0f2f004724a169a9a847a Mon Sep 17 00:00:00 2001 From: "Visal .In" Date: Sun, 15 Dec 2024 17:32:36 +0700 Subject: [PATCH] support change column collation for mysql (#206) --- .../gui/schema-editor/column-collation.tsx | 21 ++ .../schema-editor/column-type-selector.tsx | 1 - src/components/gui/schema-editor/index.tsx | 7 + .../schema-editor-column-list.tsx | 107 +++++-- src/drivers/base-driver.ts | 1 + src/drivers/common-sql-imp.ts | 4 + src/drivers/mysql/generate-schema.ts | 7 +- src/drivers/mysql/mysql-data-type.ts | 289 ++++++++++++++++++ src/drivers/mysql/mysql-driver.ts | 19 +- 9 files changed, 421 insertions(+), 35 deletions(-) create mode 100644 src/components/gui/schema-editor/column-collation.tsx diff --git a/src/components/gui/schema-editor/column-collation.tsx b/src/components/gui/schema-editor/column-collation.tsx new file mode 100644 index 00000000..04265653 --- /dev/null +++ b/src/components/gui/schema-editor/column-collation.tsx @@ -0,0 +1,21 @@ +export default function ColumnCollation({ + value, + onChange, + disabled, +}: { + value?: string; + onChange: (value: string) => void; + disabled?: boolean; +}) { + return ( + onChange(e.currentTarget.value)} + /> + ); +} diff --git a/src/components/gui/schema-editor/column-type-selector.tsx b/src/components/gui/schema-editor/column-type-selector.tsx index de2514fd..588d2048 100644 --- a/src/components/gui/schema-editor/column-type-selector.tsx +++ b/src/components/gui/schema-editor/column-type-selector.tsx @@ -170,7 +170,6 @@ export default function ColumnTypeSelector({ onFocus={() => setShowSuggestion(true)} onBlur={() => { setShowSuggestion(false); - console.log("blur"); }} value={value} onChange={(e) => { diff --git a/src/components/gui/schema-editor/index.tsx b/src/components/gui/schema-editor/index.tsx index 8d4d370a..521e76ff 100644 --- a/src/components/gui/schema-editor/index.tsx +++ b/src/components/gui/schema-editor/index.tsx @@ -65,6 +65,12 @@ export default function SchemaEditor({ return databaseDriver.createUpdateTableSchema(value).join(";\n"); }, [value, databaseDriver]); + const editorOptions = useMemo(() => { + return { + collations: databaseDriver.getCollationList(), + }; + }, [databaseDriver]); + return (
@@ -188,6 +194,7 @@ export default function SchemaEditor({ onChange={onChange} onAddColumn={onAddColumn} schemaName={value.schemaName} + options={editorOptions} disabledEditExistingColumn={ !databaseDriver.getFlags().supportModifyColumn } diff --git a/src/components/gui/schema-editor/schema-editor-column-list.tsx b/src/components/gui/schema-editor/schema-editor-column-list.tsx index 8cc88506..681739fd 100644 --- a/src/components/gui/schema-editor/schema-editor-column-list.tsx +++ b/src/components/gui/schema-editor/schema-editor-column-list.tsx @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction, useCallback } from "react"; +import { Dispatch, SetStateAction, useCallback, useMemo } from "react"; import { DropdownMenu, DropdownMenuContent, @@ -42,11 +42,16 @@ import { import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; import { useDatabaseDriver } from "@/context/driver-provider"; import ColumnTypeSelector from "./column-type-selector"; +import ColumnCollation from "./column-collation"; export type ColumnChangeEvent = ( newValue: Partial | null ) => void; +export interface SchemaEditorOptions { + collations: string[]; +} + function changeColumnOnIndex( idx: number, value: Partial | null, @@ -135,6 +140,7 @@ function ColumnItem({ idx, schemaName, onChange, + options, disabledEditExistingColumn, }: { value: DatabaseTableColumnChange; @@ -142,6 +148,7 @@ function ColumnItem({ schemaName?: string; onChange: Dispatch>; disabledEditExistingColumn?: boolean; + options: SchemaEditorOptions; }) { const { setNodeRef, @@ -344,6 +351,21 @@ function ColumnItem({
+ {options.collations.length > 0 && ( + + { + change({ + constraint: { + ...column.constraint, + collate: value || undefined, + }, + }); + }} + /> + + )} + + + + +
); } diff --git a/src/drivers/base-driver.ts b/src/drivers/base-driver.ts index 32c5d21d..0184684f 100644 --- a/src/drivers/base-driver.ts +++ b/src/drivers/base-driver.ts @@ -270,6 +270,7 @@ export abstract class BaseDriver { abstract getFlags(): DriverFlags; abstract getCurrentSchema(): Promise; abstract columnTypeSelector: ColumnTypeSelector; + abstract getCollationList(): string[]; // Helper class abstract escapeId(id: string): string; diff --git a/src/drivers/common-sql-imp.ts b/src/drivers/common-sql-imp.ts index a9222da9..a5502fd1 100644 --- a/src/drivers/common-sql-imp.ts +++ b/src/drivers/common-sql-imp.ts @@ -28,6 +28,10 @@ export default abstract class CommonSQLImplement extends BaseDriver { return null; } + getCollationList(): string[] { + return []; + } + async updateTableData( schemaName: string, tableName: string, diff --git a/src/drivers/mysql/generate-schema.ts b/src/drivers/mysql/generate-schema.ts index 60c8cc9e..492fc1f6 100644 --- a/src/drivers/mysql/generate-schema.ts +++ b/src/drivers/mysql/generate-schema.ts @@ -8,6 +8,7 @@ import { import { omit, isEqual } from "lodash"; function wrapParen(str: string) { + if (str.toUpperCase() === "NULL") return str; if (str.length >= 2 && str.startsWith("(") && str.endsWith(")")) return str; return "(" + str + ")"; } @@ -81,6 +82,10 @@ function generateCreateColumn( ); } + if (col.constraint?.collate) { + tokens.push("COLLATE " + driver.escapeValue(col.constraint.collate)); + } + if (col.constraint?.checkExpression) { tokens.push("CHECK " + wrapParen(col.constraint.checkExpression)); } @@ -149,8 +154,6 @@ export function generateMySqlSchemaChange( ); } - console.log(col.old, col.new); - // check if there is any changed except name if (!isEqual(omit(col.old, ["name"]), omit(col.new, ["name"]))) { lines.push(`MODIFY COLUMN ${generateCreateColumn(driver, col.new)}`); diff --git a/src/drivers/mysql/mysql-data-type.ts b/src/drivers/mysql/mysql-data-type.ts index 3f58553c..78510a39 100644 --- a/src/drivers/mysql/mysql-data-type.ts +++ b/src/drivers/mysql/mysql-data-type.ts @@ -195,3 +195,292 @@ function decimalDescription(_: string, parameters?: string[]) { return `
Fixed-point number
`; } + +export const MYSQL_COLLATION_LIST = [ + "armscii8_bin", + "armscii8_general_ci", + "ascii_bin", + "ascii_general_ci", + "big5_bin", + "big5_chinese_ci", + "binary", + "cp1250_bin", + "cp1250_croatian_ci", + "cp1250_czech_cs", + "cp1250_general_ci", + "cp1250_polish_ci", + "cp1251_bin", + "cp1251_bulgarian_ci", + "cp1251_general_ci", + "cp1251_general_cs", + "cp1251_ukrainian_ci", + "cp1256_bin", + "cp1256_general_ci", + "cp1257_bin", + "cp1257_general_ci", + "cp1257_lithuanian_ci", + "cp850_bin", + "cp850_general_ci", + "cp852_bin", + "cp852_general_ci", + "cp866_bin", + "cp866_general_ci", + "cp932_bin", + "cp932_japanese_ci", + "dec8_bin", + "dec8_swedish_ci", + "eucjpms_bin", + "eucjpms_japanese_ci", + "euckr_bin", + "euckr_korean_ci", + "gb18030_bin", + "gb18030_chinese_ci", + "gb18030_unicode_520_ci", + "gb2312_bin", + "gb2312_chinese_ci", + "gbk_bin", + "gbk_chinese_ci", + "geostd8_bin", + "geostd8_general_ci", + "greek_bin", + "greek_general_ci", + "hebrew_bin", + "hebrew_general_ci", + "hp8_bin", + "hp8_english_ci", + "keybcs2_bin", + "keybcs2_general_ci", + "koi8r_bin", + "koi8r_general_ci", + "koi8u_bin", + "koi8u_general_ci", + "latin1_bin", + "latin1_danish_ci", + "latin1_general_ci", + "latin1_general_cs", + "latin1_german1_ci", + "latin1_german2_ci", + "latin1_spanish_ci", + "latin1_swedish_ci", + "latin2_bin", + "latin2_croatian_ci", + "latin2_czech_cs", + "latin2_general_ci", + "latin2_hungarian_ci", + "latin5_bin", + "latin5_turkish_ci", + "latin7_bin", + "latin7_estonian_cs", + "latin7_general_ci", + "latin7_general_cs", + "macce_bin", + "macce_general_ci", + "macroman_bin", + "macroman_general_ci", + "sjis_bin", + "sjis_japanese_ci", + "swe7_bin", + "swe7_swedish_ci", + "tis620_bin", + "tis620_thai_ci", + "ucs2_bin", + "ucs2_croatian_ci", + "ucs2_czech_ci", + "ucs2_danish_ci", + "ucs2_esperanto_ci", + "ucs2_estonian_ci", + "ucs2_general_ci", + "ucs2_general_mysql500_ci", + "ucs2_german2_ci", + "ucs2_hungarian_ci", + "ucs2_icelandic_ci", + "ucs2_latvian_ci", + "ucs2_lithuanian_ci", + "ucs2_persian_ci", + "ucs2_polish_ci", + "ucs2_romanian_ci", + "ucs2_roman_ci", + "ucs2_sinhala_ci", + "ucs2_slovak_ci", + "ucs2_slovenian_ci", + "ucs2_spanish2_ci", + "ucs2_spanish_ci", + "ucs2_swedish_ci", + "ucs2_turkish_ci", + "ucs2_unicode_520_ci", + "ucs2_unicode_ci", + "ucs2_vietnamese_ci", + "ujis_bin", + "ujis_japanese_ci", + "utf16le_bin", + "utf16le_general_ci", + "utf16_bin", + "utf16_croatian_ci", + "utf16_czech_ci", + "utf16_danish_ci", + "utf16_esperanto_ci", + "utf16_estonian_ci", + "utf16_general_ci", + "utf16_german2_ci", + "utf16_hungarian_ci", + "utf16_icelandic_ci", + "utf16_latvian_ci", + "utf16_lithuanian_ci", + "utf16_persian_ci", + "utf16_polish_ci", + "utf16_romanian_ci", + "utf16_roman_ci", + "utf16_sinhala_ci", + "utf16_slovak_ci", + "utf16_slovenian_ci", + "utf16_spanish2_ci", + "utf16_spanish_ci", + "utf16_swedish_ci", + "utf16_turkish_ci", + "utf16_unicode_520_ci", + "utf16_unicode_ci", + "utf16_vietnamese_ci", + "utf32_bin", + "utf32_croatian_ci", + "utf32_czech_ci", + "utf32_danish_ci", + "utf32_esperanto_ci", + "utf32_estonian_ci", + "utf32_general_ci", + "utf32_german2_ci", + "utf32_hungarian_ci", + "utf32_icelandic_ci", + "utf32_latvian_ci", + "utf32_lithuanian_ci", + "utf32_persian_ci", + "utf32_polish_ci", + "utf32_romanian_ci", + "utf32_roman_ci", + "utf32_sinhala_ci", + "utf32_slovak_ci", + "utf32_slovenian_ci", + "utf32_spanish2_ci", + "utf32_spanish_ci", + "utf32_swedish_ci", + "utf32_turkish_ci", + "utf32_unicode_520_ci", + "utf32_unicode_ci", + "utf32_vietnamese_ci", + "utf8mb3_bin", + "utf8mb3_croatian_ci", + "utf8mb3_czech_ci", + "utf8mb3_danish_ci", + "utf8mb3_esperanto_ci", + "utf8mb3_estonian_ci", + "utf8mb3_general_ci", + "utf8mb3_general_mysql500_ci", + "utf8mb3_german2_ci", + "utf8mb3_hungarian_ci", + "utf8mb3_icelandic_ci", + "utf8mb3_latvian_ci", + "utf8mb3_lithuanian_ci", + "utf8mb3_persian_ci", + "utf8mb3_polish_ci", + "utf8mb3_romanian_ci", + "utf8mb3_roman_ci", + "utf8mb3_sinhala_ci", + "utf8mb3_slovak_ci", + "utf8mb3_slovenian_ci", + "utf8mb3_spanish2_ci", + "utf8mb3_spanish_ci", + "utf8mb3_swedish_ci", + "utf8mb3_tolower_ci", + "utf8mb3_turkish_ci", + "utf8mb3_unicode_520_ci", + "utf8mb3_unicode_ci", + "utf8mb3_vietnamese_ci", + "utf8mb4_0900_ai_ci", + "utf8mb4_0900_as_ci", + "utf8mb4_0900_as_cs", + "utf8mb4_0900_bin", + "utf8mb4_bg_0900_ai_ci", + "utf8mb4_bg_0900_as_cs", + "utf8mb4_bin", + "utf8mb4_bs_0900_ai_ci", + "utf8mb4_bs_0900_as_cs", + "utf8mb4_croatian_ci", + "utf8mb4_cs_0900_ai_ci", + "utf8mb4_cs_0900_as_cs", + "utf8mb4_czech_ci", + "utf8mb4_danish_ci", + "utf8mb4_da_0900_ai_ci", + "utf8mb4_da_0900_as_cs", + "utf8mb4_de_pb_0900_ai_ci", + "utf8mb4_de_pb_0900_as_cs", + "utf8mb4_eo_0900_ai_ci", + "utf8mb4_eo_0900_as_cs", + "utf8mb4_esperanto_ci", + "utf8mb4_estonian_ci", + "utf8mb4_es_0900_ai_ci", + "utf8mb4_es_0900_as_cs", + "utf8mb4_es_trad_0900_ai_ci", + "utf8mb4_es_trad_0900_as_cs", + "utf8mb4_et_0900_ai_ci", + "utf8mb4_et_0900_as_cs", + "utf8mb4_general_ci", + "utf8mb4_german2_ci", + "utf8mb4_gl_0900_ai_ci", + "utf8mb4_gl_0900_as_cs", + "utf8mb4_hr_0900_ai_ci", + "utf8mb4_hr_0900_as_cs", + "utf8mb4_hungarian_ci", + "utf8mb4_hu_0900_ai_ci", + "utf8mb4_hu_0900_as_cs", + "utf8mb4_icelandic_ci", + "utf8mb4_is_0900_ai_ci", + "utf8mb4_is_0900_as_cs", + "utf8mb4_ja_0900_as_cs", + "utf8mb4_ja_0900_as_cs_ks", + "utf8mb4_latvian_ci", + "utf8mb4_la_0900_ai_ci", + "utf8mb4_la_0900_as_cs", + "utf8mb4_lithuanian_ci", + "utf8mb4_lt_0900_ai_ci", + "utf8mb4_lt_0900_as_cs", + "utf8mb4_lv_0900_ai_ci", + "utf8mb4_lv_0900_as_cs", + "utf8mb4_mn_cyrl_0900_ai_ci", + "utf8mb4_mn_cyrl_0900_as_cs", + "utf8mb4_nb_0900_ai_ci", + "utf8mb4_nb_0900_as_cs", + "utf8mb4_nn_0900_ai_ci", + "utf8mb4_nn_0900_as_cs", + "utf8mb4_persian_ci", + "utf8mb4_pl_0900_ai_ci", + "utf8mb4_pl_0900_as_cs", + "utf8mb4_polish_ci", + "utf8mb4_romanian_ci", + "utf8mb4_roman_ci", + "utf8mb4_ro_0900_ai_ci", + "utf8mb4_ro_0900_as_cs", + "utf8mb4_ru_0900_ai_ci", + "utf8mb4_ru_0900_as_cs", + "utf8mb4_sinhala_ci", + "utf8mb4_sk_0900_ai_ci", + "utf8mb4_sk_0900_as_cs", + "utf8mb4_slovak_ci", + "utf8mb4_slovenian_ci", + "utf8mb4_sl_0900_ai_ci", + "utf8mb4_sl_0900_as_cs", + "utf8mb4_spanish2_ci", + "utf8mb4_spanish_ci", + "utf8mb4_sr_latn_0900_ai_ci", + "utf8mb4_sr_latn_0900_as_cs", + "utf8mb4_sv_0900_ai_ci", + "utf8mb4_sv_0900_as_cs", + "utf8mb4_swedish_ci", + "utf8mb4_tr_0900_ai_ci", + "utf8mb4_tr_0900_as_cs", + "utf8mb4_turkish_ci", + "utf8mb4_unicode_520_ci", + "utf8mb4_unicode_ci", + "utf8mb4_vietnamese_ci", + "utf8mb4_vi_0900_ai_ci", + "utf8mb4_vi_0900_as_cs", + "utf8mb4_zh_0900_as_cs", +]; diff --git a/src/drivers/mysql/mysql-driver.ts b/src/drivers/mysql/mysql-driver.ts index 9225c1b5..92f5ba26 100644 --- a/src/drivers/mysql/mysql-driver.ts +++ b/src/drivers/mysql/mysql-driver.ts @@ -13,7 +13,10 @@ import { import CommonSQLImplement from "../common-sql-imp"; import { escapeSqlValue } from "../sqlite/sql-helper"; import { generateMySqlSchemaChange } from "./generate-schema"; -import { MYSQL_DATA_TYPE_SUGGESTION } from "./mysql-data-type"; +import { + MYSQL_COLLATION_LIST, + MYSQL_DATA_TYPE_SUGGESTION, +} from "./mysql-data-type"; interface MySqlDatabase { SCHEMA_NAME: string; @@ -33,6 +36,7 @@ interface MySqlColumn { NUMERIC_SCALE: number; COLUMN_DEFAULT: string | null; COLUMN_TYPE: string; + COLLATION_NAME: string | null; } interface MySqlTable { @@ -91,6 +95,13 @@ function mapColumn(column: MySqlColumn): DatabaseTableColumn { }; } + if (column.COLLATION_NAME) { + result.constraint = { + ...result.constraint, + collate: column.COLLATION_NAME, + }; + } + return result; } @@ -122,6 +133,10 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { }; } + getCollationList(): string[] { + return MYSQL_COLLATION_LIST; + } + async getCurrentSchema(): Promise { const result = (await this.query("SELECT DATABASE() AS db")) as unknown as { rows: { db?: string | null }[]; @@ -276,7 +291,7 @@ export default abstract class MySQLLikeDriver extends CommonSQLImplement { schemaName: string, tableName: string ): Promise { - const columnSql = `SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, DATA_TYPE, EXTRA, COLUMN_KEY, IS_NULLABLE, COLUMN_DEFAULT FROM information_schema.columns WHERE TABLE_NAME=${escapeSqlValue(tableName)} AND TABLE_SCHEMA=${escapeSqlValue(schemaName)} ORDER BY ORDINAL_POSITION`; + const columnSql = `SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, DATA_TYPE, EXTRA, COLUMN_KEY, IS_NULLABLE, COLUMN_DEFAULT, COLLATION_NAME FROM information_schema.columns WHERE TABLE_NAME=${escapeSqlValue(tableName)} AND TABLE_SCHEMA=${escapeSqlValue(schemaName)} ORDER BY ORDINAL_POSITION`; const columnResult = (await this.query(columnSql)) .rows as unknown as MySqlColumn[];