feat(mysql): edit column charset and collation
This commit is contained in:
parent
0674433eea
commit
e429c8945b
|
|
@ -30,7 +30,9 @@ import { canAddTableStructureColumn, getTableStructureCapabilities } from "@/lib
|
|||
import { connectionObjectTreeQuerySchema, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import type { TableInfoTab, TableStructureEditorDraft, TableStructureEditorTarget, TableStructureEditorViewport } from "@/types/database";
|
||||
import {
|
||||
applyManticoreDdlColumnExtras,
|
||||
buildStructureTargetLabel,
|
||||
canEditManticoreColumnProperties,
|
||||
combineDataTypeForDatabase,
|
||||
createColumnDrafts,
|
||||
createForeignKeyDrafts,
|
||||
|
|
@ -43,15 +45,16 @@ import {
|
|||
getDataTypeOptions,
|
||||
getDefaultLengthForType,
|
||||
isDataTypeLengthDisabled,
|
||||
isSqlServerIdentityCompatibleDataType,
|
||||
isMysqlCharacterDataType,
|
||||
isProtectedManticoreIdColumn,
|
||||
isSqlServerIdentityCompatibleDataType,
|
||||
parseExtraToColumnExtra,
|
||||
rehydrateColumnDraftsFromMetadata,
|
||||
splitDataType,
|
||||
toColumnNames,
|
||||
applyManticoreDdlColumnExtras,
|
||||
canEditManticoreColumnProperties,
|
||||
} from "@/lib/table/tableStructureEditorState";
|
||||
import { CREATE_DATABASE_CHARSET_OPTIONS, createDatabaseCollationOptionsForCharset, fallbackCreateDatabaseCharsetMetadata, normalizeCreateDatabaseCharsetKey, parseCreateDatabaseCharsetMetadata } from "@/lib/database/createDatabaseCharsetOptions";
|
||||
import type { CreateDatabaseCharsetMetadata } from "@/lib/database/createDatabaseCharsetOptions";
|
||||
import * as api from "@/lib/backend/api";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
|
@ -174,6 +177,8 @@ function columnChanged(column: EditableStructureColumn, index: number): boolean
|
|||
!sameText(column.defaultValue, original.column_default) ||
|
||||
!sameText(column.comment, original.comment) ||
|
||||
column.isPrimaryKey !== original.is_primary_key ||
|
||||
!sameText(column.characterSet, original.character_set) ||
|
||||
!sameText(column.collation, original.collation) ||
|
||||
JSON.stringify(column.extra) !== JSON.stringify(parseExtraToColumnExtra(original.extra, databaseType.value))
|
||||
);
|
||||
}
|
||||
|
|
@ -232,7 +237,7 @@ const structureDensityValues: StructureEditorDensity[] = ["compact", "standard",
|
|||
const STRUCTURE_COLUMNS_WIDTHS_STORAGE_KEY = "dbx-structure-editor-column-widths";
|
||||
const STRUCTURE_INDEX_COLUMNS_WIDTHS_STORAGE_KEY = "dbx-structure-editor-index-column-widths";
|
||||
const STRUCTURE_SQL_PREVIEW_COLLAPSED_STORAGE_KEY = "dbx-structure-editor-sql-preview-collapsed";
|
||||
const STRUCTURE_COLUMN_WIDTH_COUNT = 10;
|
||||
const STRUCTURE_COLUMN_WIDTH_COUNT = 12;
|
||||
const STRUCTURE_INDEX_COLUMN_WIDTH_COUNT = 8;
|
||||
const PERSISTED_STRUCTURE_INDEX_COLUMN_WIDTHS = new Set([0, 1, 6]);
|
||||
const structureDensityMetrics: Record<
|
||||
|
|
@ -256,7 +261,7 @@ const structureDensityMetrics: Record<
|
|||
}
|
||||
> = {
|
||||
compact: {
|
||||
columns: [28, 168, 136, 82, 60, 52, 108, 220, 144, 108],
|
||||
columns: [28, 168, 136, 82, 60, 52, 108, 220, 80, 120, 144, 108],
|
||||
indexes: [120, 180, 60, 88, 124, 144, 120, 70],
|
||||
minColumnWidth: 24,
|
||||
minIndexColumnWidth: 48,
|
||||
|
|
@ -273,7 +278,7 @@ const structureDensityMetrics: Record<
|
|||
lineHeight: 1.35,
|
||||
},
|
||||
standard: {
|
||||
columns: [32, 200, 160, 104, 72, 64, 128, 260, 160, 136],
|
||||
columns: [32, 200, 160, 104, 72, 64, 128, 260, 90, 140, 160, 136],
|
||||
indexes: [148, 224, 72, 108, 148, 180, 148, 84],
|
||||
minColumnWidth: 28,
|
||||
minIndexColumnWidth: 60,
|
||||
|
|
@ -290,7 +295,7 @@ const structureDensityMetrics: Record<
|
|||
lineHeight: 1.4,
|
||||
},
|
||||
comfortable: {
|
||||
columns: [36, 232, 188, 116, 84, 76, 152, 300, 188, 148],
|
||||
columns: [36, 232, 188, 116, 84, 76, 152, 300, 100, 160, 188, 148],
|
||||
indexes: [176, 260, 84, 124, 176, 216, 176, 104],
|
||||
minColumnWidth: 32,
|
||||
minIndexColumnWidth: 64,
|
||||
|
|
@ -317,10 +322,17 @@ function metricsForDensity(density: StructureEditorDensity) {
|
|||
}
|
||||
|
||||
function normalizeStructureColumnWidths(value: unknown, density: StructureEditorDensity): number[] | null {
|
||||
if (!Array.isArray(value) || value.length !== STRUCTURE_COLUMN_WIDTH_COUNT) return null;
|
||||
const minWidth = metricsForDensity(density).minColumnWidth;
|
||||
const widths = value.map((item) => Number(item));
|
||||
if (!Array.isArray(value)) return null;
|
||||
let widths = value.map((item) => Number(item));
|
||||
if (widths.some((item) => !Number.isFinite(item))) return null;
|
||||
// Backward compatibility: pad old 11-column persisted layout to 12 by inserting
|
||||
// a default collation width at index 9.
|
||||
if (widths.length === STRUCTURE_COLUMN_WIDTH_COUNT - 1) {
|
||||
const defaultWidths = metricsForDensity(density).columns;
|
||||
widths = [...widths.slice(0, 9), defaultWidths[9], ...widths.slice(9)];
|
||||
}
|
||||
if (widths.length !== STRUCTURE_COLUMN_WIDTH_COUNT) return null;
|
||||
const minWidth = metricsForDensity(density).minColumnWidth;
|
||||
return widths.map((item) => Math.max(minWidth, item));
|
||||
}
|
||||
|
||||
|
|
@ -651,7 +663,56 @@ const showExtendedProperties = computed(() => {
|
|||
const dt = databaseType.value;
|
||||
return dt === "mysql" || dt === "manticoresearch" || isPostgresIdentityType(dt) || dt === "sqlserver";
|
||||
});
|
||||
const extendedPropertiesColumnIndex = 8;
|
||||
const showCharacterSet = computed(() => structureDialect.value === "mysql");
|
||||
|
||||
const serverCharsetMetadata = ref<CreateDatabaseCharsetMetadata>();
|
||||
const charsetMetadataLoading = ref(false);
|
||||
|
||||
const mysqlCharsetOptions = computed<string[]>(() => {
|
||||
const meta = serverCharsetMetadata.value;
|
||||
return meta ? meta.charsets : ([...CREATE_DATABASE_CHARSET_OPTIONS] as string[]);
|
||||
});
|
||||
|
||||
function collationOptionsForCharset(charset: string): string[] {
|
||||
const meta = serverCharsetMetadata.value;
|
||||
if (meta) {
|
||||
return meta.collationsByCharset[normalizeCreateDatabaseCharsetKey(charset)] ?? [];
|
||||
}
|
||||
return createDatabaseCollationOptionsForCharset(charset);
|
||||
}
|
||||
|
||||
async function loadCharsetMetadata() {
|
||||
if (charsetMetadataLoading.value || !showCharacterSet.value) return;
|
||||
charsetMetadataLoading.value = true;
|
||||
try {
|
||||
await store.ensureConnected(props.connectionId);
|
||||
const [charsetResult, collationResult] = await Promise.all([api.executeQuery(props.connectionId, props.database, "SHOW CHARACTER SET"), api.executeQuery(props.connectionId, props.database, "SHOW COLLATION")]);
|
||||
serverCharsetMetadata.value = parseCreateDatabaseCharsetMetadata(charsetResult, collationResult);
|
||||
} catch {
|
||||
serverCharsetMetadata.value = fallbackCreateDatabaseCharsetMetadata();
|
||||
} finally {
|
||||
charsetMetadataLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onCharsetChange(column: EditableStructureColumn, charset: string) {
|
||||
column.characterSet = charset;
|
||||
// If the collation is no longer valid for the new charset, clear it so the
|
||||
// server picks its default (COLLATE is only emitted when explicitly chosen).
|
||||
if (column.collation && !collationOptionsForCharset(charset).includes(column.collation)) {
|
||||
column.collation = "";
|
||||
}
|
||||
}
|
||||
|
||||
function columnCharset(column: EditableStructureColumn): string {
|
||||
return column.characterSet ?? "";
|
||||
}
|
||||
|
||||
function columnCollation(column: EditableStructureColumn): string {
|
||||
return column.collation ?? "";
|
||||
}
|
||||
|
||||
const extendedPropertiesColumnIndex = 10;
|
||||
const actionButtonGap = 2;
|
||||
const columnActionButtonCount = computed(() => (canShowColumnDragControls.value ? 2 : 1));
|
||||
const columnActionsWidth = computed(() => {
|
||||
|
|
@ -677,10 +738,12 @@ const colLabels = computed(() => {
|
|||
if (columnEditorControls.value.primaryKey) labels.push({ key: "primaryKey", label: t("structureEditor.primaryKey"), widthIndex: 5 });
|
||||
if (columnEditorControls.value.defaultValue) labels.push({ key: "defaultValue", label: t("structureEditor.defaultValue"), widthIndex: 6 });
|
||||
if (columnEditorControls.value.comment) labels.push({ key: "comment", label: t("structureEditor.comment"), widthIndex: 7 });
|
||||
if (showCharacterSet.value) labels.push({ key: "characterSet", label: t("structureEditor.characterSet"), widthIndex: 8 });
|
||||
if (showCharacterSet.value) labels.push({ key: "collation", label: t("structureEditor.collation"), widthIndex: 9 });
|
||||
if (showExtendedProperties.value) {
|
||||
labels.push({ key: "extendedProperties", label: t("structureEditor.extendedProperties"), widthIndex: extendedPropertiesColumnIndex });
|
||||
}
|
||||
labels.push({ key: "actions", label: t("structureEditor.actions"), widthIndex: 9 });
|
||||
labels.push({ key: "actions", label: t("structureEditor.actions"), widthIndex: 11 });
|
||||
return labels;
|
||||
});
|
||||
const indexColLabels = computed(() => [t("structureEditor.indexName"), t("structureEditor.indexColumns"), t("structureEditor.unique"), t("structureEditor.indexType"), t("structureEditor.includedColumns"), t("structureEditor.filter"), t("structureEditor.comment"), t("structureEditor.actions")]);
|
||||
|
|
@ -1117,6 +1180,9 @@ async function loadStructure(silent = false, scope: StructureRefreshScope = FULL
|
|||
/* ignore — Manticore column properties can still come from SHOW COLUMNS when available */
|
||||
}
|
||||
}
|
||||
// Load live charset/collation metadata from the MySQL server so the column
|
||||
// editor shows the correct options for the server version.
|
||||
void loadCharsetMetadata();
|
||||
columns.value = createColumnDrafts(nextColumns, databaseType.value);
|
||||
}
|
||||
|
||||
|
|
@ -1185,6 +1251,8 @@ async function addColumn() {
|
|||
defaultValue: "",
|
||||
comment: "",
|
||||
isPrimaryKey: false,
|
||||
characterSet: "",
|
||||
collation: "",
|
||||
extra: {},
|
||||
markedForDrop: false,
|
||||
};
|
||||
|
|
@ -1311,6 +1379,11 @@ function updateSqlServerIdentityIncrement(column: EditableStructureColumn, value
|
|||
function updateColumnDataType(column: EditableStructureColumn, baseType: string) {
|
||||
column.dataType = combineDataTypeForDatabase(databaseType.value, baseType, getDefaultLengthForType(databaseType.value, baseType));
|
||||
syncSqlServerIdentityForDataType(column);
|
||||
// Clear charset/collation when switching to a non-character MySQL type
|
||||
if (showCharacterSet.value && !isMysqlCharacterDataType(column.dataType)) {
|
||||
column.characterSet = "";
|
||||
column.collation = "";
|
||||
}
|
||||
}
|
||||
|
||||
function updateColumnDataTypeLength(column: EditableStructureColumn, value: string | number) {
|
||||
|
|
@ -1630,6 +1703,12 @@ function isColumnCommentDisabled(column: EditableStructureColumn): boolean {
|
|||
return column.markedForDrop || !structureCapabilities.value.comment;
|
||||
}
|
||||
|
||||
function isColumnCharsetDisabled(column: EditableStructureColumn): boolean {
|
||||
if (column.markedForDrop) return true;
|
||||
if (!showCharacterSet.value) return true;
|
||||
return !isMysqlCharacterDataType(column.dataType);
|
||||
}
|
||||
|
||||
function isPrimaryKeyDisabled(column: EditableStructureColumn): boolean {
|
||||
if (column.markedForDrop) return true;
|
||||
if (!column.original) return false;
|
||||
|
|
@ -2372,6 +2451,32 @@ watch(activeTab, (tab) => {
|
|||
</Popover>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="showCharacterSet" :class="structureCellClass">
|
||||
<SearchableSelect
|
||||
:model-value="columnCharset(column)"
|
||||
:options="mysqlCharsetOptions"
|
||||
:placeholder="t('structureEditor.charsetPlaceholder')"
|
||||
:search-placeholder="t('structureEditor.charsetPlaceholder')"
|
||||
:empty-text="t('structureEditor.noMatchingType')"
|
||||
:allow-custom="true"
|
||||
:disabled="isColumnCharsetDisabled(column)"
|
||||
:trigger-class="[structureMonoControlClass, 'w-20']"
|
||||
@update:model-value="(v: string) => onCharsetChange(column, v)"
|
||||
/>
|
||||
</td>
|
||||
<td v-if="showCharacterSet" :class="structureCellClass">
|
||||
<SearchableSelect
|
||||
:model-value="columnCollation(column)"
|
||||
:options="collationOptionsForCharset(columnCharset(column))"
|
||||
:placeholder="t('structureEditor.collationPlaceholder')"
|
||||
:search-placeholder="t('structureEditor.collationPlaceholder')"
|
||||
:empty-text="t('structureEditor.noMatchingType')"
|
||||
:allow-custom="true"
|
||||
:disabled="isColumnCharsetDisabled(column)"
|
||||
:trigger-class="[structureMonoControlClass, 'w-28']"
|
||||
@update:model-value="(v: string) => (column.collation = v)"
|
||||
/>
|
||||
</td>
|
||||
<td v-if="showExtendedProperties" :class="structureCellClass">
|
||||
<div :class="structurePropertyListClass">
|
||||
<!-- Manticore Search: character data type properties -->
|
||||
|
|
|
|||
|
|
@ -1840,6 +1840,10 @@ export default {
|
|||
defaultValue: "Default",
|
||||
defaultValuePresets: "Default value presets",
|
||||
comment: "Comment",
|
||||
characterSet: "Charset",
|
||||
charsetPlaceholder: "Charset",
|
||||
collation: "Collation",
|
||||
collationPlaceholder: "Collation",
|
||||
editComment: "Edit comment",
|
||||
commentPlaceholder: "Enter column comment...",
|
||||
tableCommentPlaceholder: "Enter table comment...",
|
||||
|
|
|
|||
|
|
@ -1772,6 +1772,10 @@ export default withEnglishFallback({
|
|||
defaultValue: "Valor por defecto",
|
||||
defaultValuePresets: "Valores predeterminados preestablecidos",
|
||||
comment: "Comentario",
|
||||
characterSet: "Conjunto de caracteres",
|
||||
charsetPlaceholder: "Charset",
|
||||
collation: "Cotejamiento",
|
||||
collationPlaceholder: "Cotejamiento",
|
||||
editComment: "Editar comentario",
|
||||
commentPlaceholder: "Ingresar comentario de columna...",
|
||||
tableCommentPlaceholder: "Ingresar comentario de tabla...",
|
||||
|
|
|
|||
|
|
@ -1770,6 +1770,10 @@ export default withEnglishFallback({
|
|||
defaultValue: "Valore Predefinito",
|
||||
defaultValuePresets: "Preimpostazioni valore predefinito",
|
||||
comment: "Commento",
|
||||
characterSet: "Set di caratteri",
|
||||
charsetPlaceholder: "Charset",
|
||||
collation: "Regole di confronto",
|
||||
collationPlaceholder: "Regole di confronto",
|
||||
editComment: "Modifica commento",
|
||||
commentPlaceholder: "Inserisci commento colonna...",
|
||||
tableCommentPlaceholder: "Inserisci commento tabella...",
|
||||
|
|
|
|||
|
|
@ -1804,6 +1804,10 @@ export default withEnglishFallback({
|
|||
defaultValue: "デフォルト",
|
||||
defaultValuePresets: "デフォルト値プリセット",
|
||||
comment: "コメント",
|
||||
characterSet: "文字セット",
|
||||
charsetPlaceholder: "文字セット",
|
||||
collation: "照合順序",
|
||||
collationPlaceholder: "照合順序",
|
||||
editComment: "コメントを編集",
|
||||
commentPlaceholder: "列コメントを入力...",
|
||||
tableCommentPlaceholder: "テーブルコメントを入力...",
|
||||
|
|
|
|||
|
|
@ -1771,6 +1771,10 @@ export default withEnglishFallback({
|
|||
defaultValue: "Padrão",
|
||||
defaultValuePresets: "Predefinições de valor padrão",
|
||||
comment: "Comentário",
|
||||
characterSet: "Conjunto de caracteres",
|
||||
charsetPlaceholder: "Charset",
|
||||
collation: "Collation",
|
||||
collationPlaceholder: "Collation",
|
||||
editComment: "Editar comentário",
|
||||
commentPlaceholder: "Insira o comentário da coluna...",
|
||||
tableCommentPlaceholder: "Insira o comentário da tabela...",
|
||||
|
|
|
|||
|
|
@ -1839,6 +1839,10 @@ export default withEnglishFallback({
|
|||
defaultValue: "默认值",
|
||||
defaultValuePresets: "默认值预设",
|
||||
comment: "注释",
|
||||
characterSet: "字符集",
|
||||
charsetPlaceholder: "字符集",
|
||||
collation: "排序规则",
|
||||
collationPlaceholder: "排序规则",
|
||||
editComment: "编辑注释",
|
||||
commentPlaceholder: "输入字段注释...",
|
||||
tableCommentPlaceholder: "输入表注释...",
|
||||
|
|
|
|||
|
|
@ -1727,6 +1727,10 @@ export default withEnglishFallback({
|
|||
identitySeed: "起始值",
|
||||
identityIncrement: "增量",
|
||||
sqlServerIdentityTypeHint: "SQL Server 自動遞增僅支援 tinyint、smallint、int、bigint、decimal/numeric(小數位為 0)",
|
||||
characterSet: "字元集",
|
||||
charsetPlaceholder: "字元集",
|
||||
collationPlaceholder: "排序規則",
|
||||
collation: "排序規則",
|
||||
},
|
||||
diagram: {
|
||||
title: "關係圖",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { combineDataTypeForDatabase, createColumnDrafts, dataTypeLengthInputValue, isDataTypeLengthDisabled, isSqlServerIdentityCompatibleDataType, splitDataType } from "@/lib/table/tableStructureEditorState";
|
||||
import { combineDataTypeForDatabase, createColumnDrafts, dataTypeLengthInputValue, isDataTypeLengthDisabled, isMysqlCharacterDataType, isSqlServerIdentityCompatibleDataType, splitDataType } from "@/lib/table/tableStructureEditorState";
|
||||
|
||||
describe("tableStructureEditorState", () => {
|
||||
it("keeps mysql unsigned attributes in the editable base type", () => {
|
||||
|
|
@ -87,4 +87,28 @@ describe("tableStructureEditorState", () => {
|
|||
expect(isSqlServerIdentityCompatibleDataType("varchar(255)")).toBe(false);
|
||||
expect(isSqlServerIdentityCompatibleDataType("numeric(18, 2)")).toBe(false);
|
||||
});
|
||||
|
||||
it("identifies MySQL character data types that accept charset/collation", () => {
|
||||
expect(isMysqlCharacterDataType("char(1)")).toBe(true);
|
||||
expect(isMysqlCharacterDataType("varchar(255)")).toBe(true);
|
||||
expect(isMysqlCharacterDataType("tinytext")).toBe(true);
|
||||
expect(isMysqlCharacterDataType("text")).toBe(true);
|
||||
expect(isMysqlCharacterDataType("mediumtext")).toBe(true);
|
||||
expect(isMysqlCharacterDataType("longtext")).toBe(true);
|
||||
expect(isMysqlCharacterDataType("enum('a','b')")).toBe(true);
|
||||
expect(isMysqlCharacterDataType("set('x','y')")).toBe(true);
|
||||
expect(isMysqlCharacterDataType("int")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("bigint(20) unsigned")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("decimal(10,2)")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("float")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("double")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("date")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("datetime")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("timestamp")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("json")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("binary(16)")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("varbinary(255)")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("blob")).toBe(false);
|
||||
expect(isMysqlCharacterDataType("geometry")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ function templateColumn(createId: () => string, name: string, dataType: string,
|
|||
defaultValue,
|
||||
comment,
|
||||
isPrimaryKey: false,
|
||||
characterSet: "",
|
||||
collation: "",
|
||||
extra: {},
|
||||
markedForDrop: false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ export interface EditableStructureColumn {
|
|||
comment: string;
|
||||
isPrimaryKey: boolean;
|
||||
extra: ColumnExtra;
|
||||
characterSet?: string;
|
||||
collation?: string;
|
||||
original?: ColumnInfo;
|
||||
originalPosition?: number;
|
||||
markedForDrop: boolean;
|
||||
|
|
|
|||
|
|
@ -614,6 +614,8 @@ export function createColumnDrafts(columns: ColumnInfo[], databaseType?: Databas
|
|||
defaultValue,
|
||||
comment: column.comment ?? "",
|
||||
isPrimaryKey: column.is_primary_key,
|
||||
characterSet: column.character_set ?? "",
|
||||
collation: column.collation ?? "",
|
||||
extra: parseExtraToColumnExtra(column.extra, databaseType),
|
||||
original: { ...column, column_default: column.column_default === null ? null : defaultValue },
|
||||
originalPosition: index,
|
||||
|
|
@ -809,6 +811,14 @@ export function splitDataType(raw: string): { baseType: string; params: string }
|
|||
return { baseType, params };
|
||||
}
|
||||
|
||||
/** MySQL character/text types that accept `CHARACTER SET` and `COLLATE`. */
|
||||
const MYSQL_CHARACTER_DATA_TYPES = new Set(["char", "varchar", "tinytext", "text", "mediumtext", "longtext", "enum", "set"]);
|
||||
|
||||
export function isMysqlCharacterDataType(dataType: string): boolean {
|
||||
const { baseType } = splitDataType(dataType);
|
||||
return MYSQL_CHARACTER_DATA_TYPES.has(baseType.trim().replace(/\s+/g, " ").toLowerCase());
|
||||
}
|
||||
|
||||
export function isSqlServerIdentityCompatibleDataType(rawDataType: string): boolean {
|
||||
const { baseType, params } = splitDataType(rawDataType);
|
||||
const normalized = baseType.trim().replace(/\s+/g, " ").toLowerCase();
|
||||
|
|
|
|||
|
|
@ -358,6 +358,8 @@ export interface ColumnInfo {
|
|||
numeric_scale?: number | null;
|
||||
character_maximum_length?: number | null;
|
||||
enum_values?: string[] | null;
|
||||
character_set?: string | null;
|
||||
collation?: string | null;
|
||||
}
|
||||
|
||||
export interface IndexInfo {
|
||||
|
|
|
|||
|
|
@ -539,6 +539,7 @@ pub async fn get_columns(client: &ChClient, database: &str, table: &str) -> Resu
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
|
|
|||
|
|
@ -334,6 +334,7 @@ fn push_mapping_column(
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -424,6 +424,7 @@ pub async fn get_columns(client: &InfluxdbClient, database: &str, table: &str) -
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let cols: Vec<ColumnInfo> = std::iter::once(time_col)
|
||||
|
|
@ -439,6 +440,7 @@ pub async fn get_columns(client: &InfluxdbClient, database: &str, table: &str) -
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}))
|
||||
.chain(field_series.first().into_iter().flat_map(|s| s.values.iter()).map(|row| {
|
||||
let data_type = row.get(1).and_then(|v| v.as_str()).unwrap_or("unknown").to_string();
|
||||
|
|
@ -454,6 +456,7 @@ pub async fn get_columns(client: &InfluxdbClient, database: &str, table: &str) -
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
}))
|
||||
.collect();
|
||||
|
|
@ -519,6 +522,7 @@ async fn get_columns_v2(client: &InfluxdbClient, bucket: &str, measurement: &str
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
};
|
||||
let tag_cols = flux_column_values(&tag_result, "_value")
|
||||
.into_iter()
|
||||
|
|
@ -535,6 +539,7 @@ async fn get_columns_v2(client: &InfluxdbClient, bucket: &str, measurement: &str
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
});
|
||||
let field_cols = flux_column_values(&field_result, "_value").into_iter().map(|name| ColumnInfo {
|
||||
name,
|
||||
|
|
@ -548,6 +553,7 @@ async fn get_columns_v2(client: &InfluxdbClient, bucket: &str, measurement: &str
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
});
|
||||
Ok(std::iter::once(time_col).chain(tag_cols).chain(field_cols).collect())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2150,6 +2150,7 @@ fn columns_sql(database: &str, table: &str) -> String {
|
|||
format!(
|
||||
"SELECT c.COLUMN_NAME, c.DATA_TYPE, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.EXTRA, \
|
||||
c.COLUMN_COMMENT, c.COLUMN_KEY, c.NUMERIC_PRECISION, c.NUMERIC_SCALE, c.CHARACTER_MAXIMUM_LENGTH, \
|
||||
c.CHARACTER_SET_NAME, c.COLLATION_NAME \
|
||||
FROM information_schema.COLUMNS c \
|
||||
WHERE c.TABLE_SCHEMA = {} AND c.TABLE_NAME = {} \
|
||||
ORDER BY c.ORDINAL_POSITION",
|
||||
|
|
@ -2326,6 +2327,8 @@ pub async fn get_columns(pool: &MySqlPool, database: &str, table: &str) -> Resul
|
|||
numeric_scale: get_opt_i32(row, "NUMERIC_SCALE"),
|
||||
character_maximum_length: get_opt_i32(row, "CHARACTER_MAXIMUM_LENGTH"),
|
||||
enum_values,
|
||||
character_set: get_opt_str(row, "CHARACTER_SET_NAME").filter(|s| !s.is_empty()),
|
||||
collation: get_opt_str(row, "COLLATION_NAME").filter(|s| !s.is_empty()),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -2359,6 +2362,7 @@ pub async fn get_columns_show(pool: &MySqlPool, database: &str, table: &str) ->
|
|||
return None;
|
||||
}
|
||||
let key = get_str_by_name(row, "Key");
|
||||
let collation = get_opt_str(row, "Collation").filter(|s| !s.is_empty());
|
||||
Some(ColumnInfo {
|
||||
name,
|
||||
data_type: get_str_by_name(row, "Type"),
|
||||
|
|
@ -2373,6 +2377,11 @@ pub async fn get_columns_show(pool: &MySqlPool, database: &str, table: &str) ->
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
character_set: collation
|
||||
.as_deref()
|
||||
.and_then(|c| c.split_once('_').map(|(charset, _)| charset.to_string()))
|
||||
.filter(|s| !s.is_empty()),
|
||||
collation,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
|
|
@ -3314,6 +3323,7 @@ pub async fn get_columns_show_from(
|
|||
return None;
|
||||
}
|
||||
let key = get_str_by_name(row, "Key");
|
||||
let collation = get_opt_str(row, "Collation").filter(|s| !s.is_empty());
|
||||
Some(ColumnInfo {
|
||||
name,
|
||||
data_type: get_str_by_name(row, "Type"),
|
||||
|
|
@ -3328,6 +3338,11 @@ pub async fn get_columns_show_from(
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
character_set: collation
|
||||
.as_deref()
|
||||
.and_then(|c| c.split_once('_').map(|(charset, _)| charset.to_string()))
|
||||
.filter(|s| !s.is_empty()),
|
||||
collation,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ pub async fn get_columns(pool: &mysql_async::Pool, schema: &str, table: &str) ->
|
|||
numeric_scale: scale,
|
||||
character_maximum_length: length,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
|
|
|||
|
|
@ -2216,6 +2216,7 @@ fn column_info_from_row(row: &Row) -> ColumnInfo {
|
|||
numeric_scale: row.try_get::<_, Option<i32>>(8).ok().flatten(),
|
||||
character_maximum_length: row.try_get::<_, Option<i32>>(9).ok().flatten(),
|
||||
enum_values: parse_enum_values_from_row(row, 10),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ pub async fn get_columns(pool: &Pool, _schema: &str, table: &str) -> Result<Vec<
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ pub async fn get_columns(client: &RqliteClient, _schema: &str, table: &str) -> R
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1142,6 +1142,7 @@ pub async fn get_columns(pool: &SqliteHandle, _schema: &str, table: &str) -> Res
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
|
|
|||
|
|
@ -874,6 +874,7 @@ pub async fn get_linked_server_columns(
|
|||
numeric_scale,
|
||||
character_maximum_length: linked_i32(row, 15),
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
|
|
@ -1435,6 +1436,7 @@ pub async fn get_columns(client: &mut SqlServerClient, schema: &str, table: &str
|
|||
numeric_scale: num_scale,
|
||||
character_maximum_length: max_len,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ pub async fn get_columns(client: &TursoClient, _schema: &str, table: &str) -> Re
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ pub fn duckdb_query_columns_in_database_with_attached(
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
|
@ -1239,6 +1240,7 @@ fn oracle_columns_from_query_result(result: db::QueryResult) -> Vec<db::ColumnIn
|
|||
numeric_scale: scale,
|
||||
character_maximum_length: length,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -2173,6 +2175,7 @@ fn presto_like_columns_from_query_result(result: &db::QueryResult) -> Vec<db::Co
|
|||
numeric_scale: presto_like_numeric_scale(&data_type),
|
||||
character_maximum_length: presto_like_character_maximum_length(&data_type),
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -2274,6 +2277,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5498,6 +5502,7 @@ mod object_source_tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
};
|
||||
let mut ignored = column.clone();
|
||||
ignored.name = "EMPTY_COMMENT".to_string();
|
||||
|
|
@ -5531,6 +5536,7 @@ mod object_source_tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ddl = append_oracle_comments_to_ddl(
|
||||
|
|
@ -5565,6 +5571,7 @@ mod ddl_tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ pub fn duckdb_query_columns_in_database_with_attached(
|
|||
numeric_precision: None,
|
||||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ mod tests {
|
|||
numeric_precision: None,
|
||||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1509,6 +1509,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1963,6 +1964,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}),
|
||||
target: None,
|
||||
changes: Vec::new(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use super::column_format::{clickhouse_column_type, column_data_type, column_definition};
|
||||
use super::column_format::{clickhouse_column_type, column_data_type, column_definition, is_mysql_character_data_type};
|
||||
use super::columns::build_drop_column_sql;
|
||||
use super::comments::build_sqlserver_column_comment_sql;
|
||||
use super::dialect::{capabilities_for, database_label, StructureDialect};
|
||||
|
|
@ -54,7 +54,10 @@ pub fn build_single_column_alter_sql(options: SingleColumnAlterSqlOptions) -> Ta
|
|||
let has_attribute_change = options.column.data_type.trim() != original.data_type.trim()
|
||||
|| options.column.is_nullable != original.is_nullable
|
||||
|| normalize_default(Some(&options.column.default_value)) != original_default(&options.column)
|
||||
|| clean(&options.column.comment) != original_comment(&options.column);
|
||||
|| clean(&options.column.comment) != original_comment(&options.column)
|
||||
|| (is_mysql_character_data_type(&options.column.data_type)
|
||||
&& (options.column.character_set.trim() != original.character_set.as_deref().unwrap_or("")
|
||||
|| options.column.collation.trim() != original.collation.as_deref().unwrap_or("")));
|
||||
|
||||
if has_rename && !capabilities.rename_column {
|
||||
warnings.push(format!("Renaming columns is not supported for {database_label} from this editor."));
|
||||
|
|
@ -727,4 +730,7 @@ pub(super) fn has_existing_column_attribute_change(column: &EditableStructureCol
|
|||
|| column.is_nullable != original.is_nullable
|
||||
|| normalize_default(Some(&column.default_value)) != original_default(column)
|
||||
|| clean(&column.comment) != original_comment(column)
|
||||
|| (is_mysql_character_data_type(&column.data_type)
|
||||
&& (column.character_set.trim() != original.character_set.as_deref().unwrap_or("")
|
||||
|| column.collation.trim() != original.collation.as_deref().unwrap_or("")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,15 @@ pub(super) fn column_definition(dialect: StructureDialect, column: &EditableStru
|
|||
parts.insert(0, "IF NOT EXISTS".to_string());
|
||||
return parts.join(" ");
|
||||
}
|
||||
// CHARACTER SET / COLLATE — MySQL character data types only
|
||||
if dialect == StructureDialect::Mysql && is_mysql_character_data_type(&column.data_type) {
|
||||
if !column.character_set.trim().is_empty() {
|
||||
parts.push(format!("CHARACTER SET {}", quote_ident(dialect, &column.character_set)));
|
||||
}
|
||||
if !column.collation.trim().is_empty() {
|
||||
parts.push(format!("COLLATE {}", quote_ident(dialect, &column.collation)));
|
||||
}
|
||||
}
|
||||
if !column.is_nullable && !is_oracle_like(dialect) && dialect != StructureDialect::ClickHouse {
|
||||
parts.push("NOT NULL".to_string());
|
||||
}
|
||||
|
|
@ -329,3 +338,20 @@ pub(super) fn questdb_column_type(column: &EditableStructureColumn) -> String {
|
|||
None => data_type,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` when the data type is a MySQL character/string type that accepts
|
||||
/// `CHARACTER SET` and `COLLATE` clauses in column definitions.
|
||||
///
|
||||
/// Character types that support per-column charset/collation:
|
||||
/// `char`, `varchar`, `tinytext`, `text`, `mediumtext`, `longtext`, `enum`, `set`
|
||||
///
|
||||
/// Numeric, temporal, spatial, binary/blob, and JSON types do NOT support these clauses.
|
||||
pub(super) fn is_mysql_character_data_type(data_type: &str) -> bool {
|
||||
let trimmed = data_type.trim();
|
||||
let base_type = match trimmed.find('(') {
|
||||
Some(open_index) => trimmed[..open_index].trim(),
|
||||
None => trimmed,
|
||||
};
|
||||
let normalized = base_type.split_whitespace().collect::<Vec<_>>().join(" ").to_ascii_lowercase();
|
||||
matches!(normalized.as_str(), "char" | "varchar" | "tinytext" | "text" | "mediumtext" | "longtext" | "enum" | "set")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use super::column_alter::{
|
|||
build_postgres_existing_column_sql, build_questdb_existing_column_sql, build_sqlite_existing_column_sql,
|
||||
build_sqlserver_existing_column_sql, has_column_extra_change, has_existing_column_attribute_change,
|
||||
};
|
||||
use super::column_format::column_definition;
|
||||
use super::column_format::{column_definition, is_mysql_character_data_type};
|
||||
use super::comments::build_sqlserver_column_comment_sql;
|
||||
use super::dialect::{capabilities_for, database_label, StructureDialect};
|
||||
use super::types::{EditableStructureColumn, TableStructureSqlOptions};
|
||||
|
|
@ -119,6 +119,9 @@ pub(super) fn build_column_sql(options: &TableStructureSqlOptions, warnings: &mu
|
|||
|| column.is_nullable != original.is_nullable
|
||||
|| normalize_default(Some(&column.default_value)) != original_default(column)
|
||||
|| clean(&column.comment) != original_comment(column)
|
||||
|| (is_mysql_character_data_type(&column.data_type)
|
||||
&& (column.character_set.trim() != original.character_set.as_deref().unwrap_or("")
|
||||
|| column.collation.trim() != original.collation.as_deref().unwrap_or("")))
|
||||
|| has_column_extra_change(column);
|
||||
if has_position_change && !capabilities.reorder_column {
|
||||
warnings.push(format!("Reordering columns is not supported for {database_label} from this editor."));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ fn column(name: &str) -> EditableStructureColumn {
|
|||
original: None,
|
||||
original_position: None,
|
||||
marked_for_drop: false,
|
||||
character_set: String::new(),
|
||||
collation: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +77,7 @@ fn builds_mysql_column_and_index_changes() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
let mut email = column("email");
|
||||
email.is_nullable = false;
|
||||
|
|
@ -151,6 +154,7 @@ fn doris_table_editor_renames_column_without_mysql_change_syntax() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some("Group DTP".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -182,6 +186,7 @@ fn doris_single_column_alter_renames_then_modifies_column_definition() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some("Division DTP".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -283,6 +288,7 @@ fn builds_informix_column_and_index_changes() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
let mut email = column("email");
|
||||
email.is_nullable = false;
|
||||
|
|
@ -296,6 +302,7 @@ fn builds_informix_column_and_index_changes() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
let mut old_index = index("idx_old", &["name"]);
|
||||
old_index.marked_for_drop = true;
|
||||
|
|
@ -350,6 +357,7 @@ fn oracle_does_not_generate_drop_sql_for_all_columns() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
let mut name = column("name");
|
||||
name.marked_for_drop = true;
|
||||
|
|
@ -361,6 +369,7 @@ fn oracle_does_not_generate_drop_sql_for_all_columns() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -397,6 +406,7 @@ fn oracle_timestamp_default_precedes_nullability_in_modify_sql() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -425,6 +435,7 @@ fn oracle_timestamp_precision_change_does_not_repeat_unchanged_nullability() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -535,6 +546,7 @@ fn manticoresearch_builds_add_and_drop_column_sql() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut name = column("name");
|
||||
|
|
@ -580,6 +592,7 @@ fn gbase8a_uses_limited_mysql_ddl() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
let new_col = column("nickname");
|
||||
let mut old_col = column("old_col");
|
||||
|
|
@ -592,6 +605,7 @@ fn gbase8a_uses_limited_mysql_ddl() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
let mut index = index("idx_users_email", &["display_email"]);
|
||||
index.original = Some(IndexInfo {
|
||||
|
|
@ -643,6 +657,7 @@ fn gbase8a_allows_mysql_style_column_reorder() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut name = column("name");
|
||||
|
|
@ -655,6 +670,7 @@ fn gbase8a_allows_mysql_style_column_reorder() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut email = column("email");
|
||||
|
|
@ -667,6 +683,7 @@ fn gbase8a_allows_mysql_style_column_reorder() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -698,6 +715,7 @@ fn manticoresearch_does_not_drop_id_column() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -734,6 +752,7 @@ fn manticoresearch_warns_when_existing_column_properties_change() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut resource = column("resource");
|
||||
|
|
@ -747,6 +766,7 @@ fn manticoresearch_warns_when_existing_column_properties_change() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut old_resource = column("old_resource");
|
||||
|
|
@ -760,6 +780,7 @@ fn manticoresearch_warns_when_existing_column_properties_change() {
|
|||
is_primary_key: false,
|
||||
extra: Some("secondary_index='1'".to_string()),
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -997,6 +1018,7 @@ fn warns_for_sqlite_unsafe_column_changes() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1084,6 +1106,7 @@ fn builds_mysql_column_reorder_statements() {
|
|||
is_primary_key: true,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut email = column("email");
|
||||
|
|
@ -1096,6 +1119,7 @@ fn builds_mysql_column_reorder_statements() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut name = column("display_name");
|
||||
|
|
@ -1110,6 +1134,7 @@ fn builds_mysql_column_reorder_statements() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1143,6 +1168,7 @@ fn mysql_add_column_before_existing_column_does_not_reorder_shifted_column() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let new_column = column("sss");
|
||||
|
|
@ -1161,6 +1187,7 @@ fn mysql_add_column_before_existing_column_does_not_reorder_shifted_column() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some("tenant id".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1194,6 +1221,7 @@ fn mysql_existing_column_reorder_does_not_reorder_columns_shifted_by_prior_move(
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut name = column("name");
|
||||
|
|
@ -1206,6 +1234,7 @@ fn mysql_existing_column_reorder_does_not_reorder_columns_shifted_by_prior_move(
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut email = column("email");
|
||||
|
|
@ -1218,6 +1247,7 @@ fn mysql_existing_column_reorder_does_not_reorder_columns_shifted_by_prior_move(
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1250,6 +1280,7 @@ fn mysql_moving_first_column_to_end_uses_single_reorder_statement() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut col_1 = column("col_1");
|
||||
|
|
@ -1262,6 +1293,7 @@ fn mysql_moving_first_column_to_end_uses_single_reorder_statement() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut col_2 = column("col_2");
|
||||
|
|
@ -1274,6 +1306,7 @@ fn mysql_moving_first_column_to_end_uses_single_reorder_statement() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut col_3 = column("col_3");
|
||||
|
|
@ -1286,6 +1319,7 @@ fn mysql_moving_first_column_to_end_uses_single_reorder_statement() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1411,6 +1445,7 @@ fn sqlserver_default_changes_drop_old_constraints_with_isolated_batches() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut active = column("active");
|
||||
|
|
@ -1425,6 +1460,7 @@ fn sqlserver_default_changes_drop_old_constraints_with_isolated_batches() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1610,6 +1646,7 @@ fn sqlserver_unchanged_identity_extra_does_not_mark_existing_column_changed() {
|
|||
is_primary_key: true,
|
||||
extra: Some("IDENTITY(1,1)".to_string()),
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1647,6 +1684,7 @@ fn sqlserver_existing_column_identity_change_warns_without_unchanged_foreign_key
|
|||
is_primary_key: true,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut user_fk = foreign_key("fk_orders_user_id", "user_id", "users", "id");
|
||||
|
|
@ -1730,6 +1768,7 @@ fn builds_clickhouse_nullable_comment_and_reorder_statements() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some("old status".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1773,6 +1812,7 @@ fn builds_h2_schema_qualified_existing_column_statements() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1815,6 +1855,7 @@ fn builds_postgres_alter_table_add_primary_key() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1847,6 +1888,7 @@ fn builds_postgres_alter_table_drop_primary_key() {
|
|||
is_primary_key: true,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1880,6 +1922,7 @@ fn builds_mysql_alter_table_change_primary_key() {
|
|||
is_primary_key: true,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut new_pk = column("uuid");
|
||||
|
|
@ -1895,6 +1938,7 @@ fn builds_mysql_alter_table_change_primary_key() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1930,6 +1974,7 @@ fn builds_no_statements_when_primary_key_unchanged() {
|
|||
is_primary_key: true,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -1962,6 +2007,7 @@ fn warns_sqlite_cannot_alter_primary_key() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
|
|
@ -2230,6 +2276,7 @@ fn postgres_timestamp_literal_is_quoted() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -2255,6 +2302,7 @@ fn mysql_single_column_alter_quotes_datetime_literal() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -2498,6 +2546,7 @@ fn postgres_varchar_default_is_quoted() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -2523,6 +2572,7 @@ fn postgres_empty_string_default_is_not_quoted_again() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -2548,6 +2598,7 @@ fn postgres_string_default_cast_matches_plain_literal() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -2573,6 +2624,7 @@ fn postgres_integer_default_is_not_quoted() {
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: Some(String::new()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_single_column_alter_sql(SingleColumnAlterSqlOptions {
|
||||
|
|
@ -2584,3 +2636,174 @@ fn postgres_integer_default_is_not_quoted() {
|
|||
|
||||
assert_eq!(result.statements, vec!["ALTER TABLE \"core\".\"products\" ALTER COLUMN \"stock\" SET DEFAULT 0;"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_character_column_add_with_charset_collation() {
|
||||
let mut col = column("name");
|
||||
col.data_type = "varchar(255)".to_string();
|
||||
col.character_set = "utf8mb4".to_string();
|
||||
col.collation = "utf8mb4_unicode_ci".to_string();
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![col],
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: Vec::new(),
|
||||
triggers: Vec::new(),
|
||||
table_comment: None,
|
||||
original_table_comment: None,
|
||||
});
|
||||
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec![
|
||||
"ALTER TABLE `users` ADD COLUMN `name` varchar(255) CHARACTER SET `utf8mb4` COLLATE `utf8mb4_unicode_ci`;"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_numeric_column_omits_charset_collation_in_column_definition() {
|
||||
let mut col = column("score");
|
||||
col.data_type = "int".to_string();
|
||||
// Even if charset/collation are set on the editable column, they must NOT
|
||||
// appear in the DDL because int does not accept CHARACTER SET or COLLATE.
|
||||
col.character_set = "utf8mb4".to_string();
|
||||
col.collation = "utf8mb4_unicode_ci".to_string();
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "games".to_string(),
|
||||
columns: vec![col],
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: Vec::new(),
|
||||
triggers: Vec::new(),
|
||||
table_comment: None,
|
||||
original_table_comment: None,
|
||||
});
|
||||
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert!(result.statements.len() == 1);
|
||||
let sql = &result.statements[0];
|
||||
assert!(!sql.contains("CHARACTER SET"));
|
||||
assert!(!sql.contains("COLLATE"));
|
||||
assert!(sql.contains("int"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_numeric_column_ignores_charset_collation_in_change_detection() {
|
||||
// When an existing INT column has no original character_set / collation but
|
||||
// the editable draft carries stale values, the column should NOT be flagged
|
||||
// as having an attribute change.
|
||||
let mut col = column("score");
|
||||
col.data_type = "int".to_string();
|
||||
col.character_set = "utf8mb4".to_string();
|
||||
col.collation = "utf8mb4_unicode_ci".to_string();
|
||||
col.original = Some(ColumnInfo {
|
||||
name: "score".to_string(),
|
||||
data_type: "int".to_string(),
|
||||
is_nullable: true,
|
||||
column_default: None,
|
||||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "games".to_string(),
|
||||
columns: vec![col],
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: Vec::new(),
|
||||
triggers: Vec::new(),
|
||||
table_comment: None,
|
||||
original_table_comment: None,
|
||||
});
|
||||
|
||||
// No ALTER should be emitted — charset/collation changes on
|
||||
// non-character columns are no-ops.
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert_eq!(result.statements, Vec::<String>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_character_column_detects_charset_collation_change() {
|
||||
let mut col = column("name");
|
||||
col.data_type = "varchar(255)".to_string();
|
||||
col.character_set = "utf8mb4".to_string();
|
||||
col.collation = "utf8mb4_unicode_ci".to_string();
|
||||
col.original = Some(ColumnInfo {
|
||||
name: "name".to_string(),
|
||||
data_type: "varchar(255)".to_string(),
|
||||
is_nullable: true,
|
||||
column_default: None,
|
||||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![col],
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: Vec::new(),
|
||||
triggers: Vec::new(),
|
||||
table_comment: None,
|
||||
original_table_comment: None,
|
||||
});
|
||||
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec!["ALTER TABLE `users` MODIFY COLUMN `name` varchar(255) CHARACTER SET `utf8mb4` COLLATE `utf8mb4_unicode_ci`;"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mysql_character_column_preserves_charset_collation_on_other_change() {
|
||||
// Changing the default value on a character column should still
|
||||
// re-emit the charset/collation clauses so they are not lost.
|
||||
let mut col = column("name");
|
||||
col.data_type = "varchar(255)".to_string();
|
||||
col.character_set = "utf8mb4".to_string();
|
||||
col.collation = "utf8mb4_unicode_ci".to_string();
|
||||
col.default_value = "guest".to_string();
|
||||
col.original = Some(ColumnInfo {
|
||||
name: "name".to_string(),
|
||||
data_type: "varchar(255)".to_string(),
|
||||
is_nullable: true,
|
||||
column_default: None,
|
||||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
character_set: Some("utf8mb4".to_string()),
|
||||
collation: Some("utf8mb4_unicode_ci".to_string()),
|
||||
});
|
||||
|
||||
let result = build_table_structure_change_sql(TableStructureSqlOptions {
|
||||
database_type: Some(DatabaseType::Mysql),
|
||||
schema: None,
|
||||
table_name: "users".to_string(),
|
||||
columns: vec![col],
|
||||
indexes: Vec::new(),
|
||||
foreign_keys: Vec::new(),
|
||||
triggers: Vec::new(),
|
||||
table_comment: None,
|
||||
original_table_comment: None,
|
||||
});
|
||||
|
||||
assert_eq!(result.warnings, Vec::<String>::new());
|
||||
assert_eq!(
|
||||
result.statements,
|
||||
vec!["ALTER TABLE `users` MODIFY COLUMN `name` varchar(255) CHARACTER SET `utf8mb4` COLLATE `utf8mb4_unicode_ci` DEFAULT 'guest';"]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ pub struct EditableStructureColumn {
|
|||
pub original_position: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub marked_for_drop: bool,
|
||||
#[serde(default)]
|
||||
pub character_set: String,
|
||||
#[serde(default)]
|
||||
pub collation: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
|
|
@ -55,7 +59,7 @@ pub struct ColumnIdentity {
|
|||
pub increment: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct ColumnInfo {
|
||||
pub name: String,
|
||||
pub data_type: String,
|
||||
|
|
@ -67,6 +71,10 @@ pub struct ColumnInfo {
|
|||
pub extra: Option<String>,
|
||||
#[serde(default)]
|
||||
pub comment: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub character_set: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub collation: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -2489,6 +2489,7 @@ fn mongo_columns_from_documents(documents: &[serde_json::Value]) -> Vec<db::Colu
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -3734,6 +3735,7 @@ where
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
sql_target_column_names = sql_target_columns.iter().map(|column| column.name.clone()).collect();
|
||||
|
|
@ -4671,6 +4673,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5524,6 +5527,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
},
|
||||
db::ColumnInfo {
|
||||
name: "identity_id".to_string(),
|
||||
|
|
@ -5537,6 +5541,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
},
|
||||
db::ColumnInfo {
|
||||
name: "computed_id".to_string(),
|
||||
|
|
@ -5550,6 +5555,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
"users",
|
||||
|
|
@ -5624,6 +5630,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
},
|
||||
db::ColumnInfo {
|
||||
name: "name".to_string(),
|
||||
|
|
@ -5637,6 +5644,7 @@ mod tests {
|
|||
numeric_scale: None,
|
||||
character_maximum_length: None,
|
||||
enum_values: None,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
let source_ddl = crate::schema::render_postgres_table_ddl("public", "it_quick_entry", &columns, &[], &[]);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ pub struct ObjectSource {
|
|||
pub editable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ColumnInfo {
|
||||
pub name: String,
|
||||
pub data_type: String,
|
||||
|
|
@ -122,6 +122,10 @@ pub struct ColumnInfo {
|
|||
pub character_maximum_length: Option<i32>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub enum_values: Option<Vec<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub character_set: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub collation: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
|
|
|
|||
|
|
@ -187,9 +187,12 @@ fn structure_column(
|
|||
is_primary_key: false,
|
||||
extra: None,
|
||||
comment: None,
|
||||
..Default::default()
|
||||
}),
|
||||
original_position: None,
|
||||
marked_for_drop: false,
|
||||
character_set: String::new(),
|
||||
collation: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ test("normalizes table font size", () => {
|
|||
assert.equal(normalizeEditorSettings({}).tableFontSize, 13);
|
||||
assert.equal(normalizeEditorSettings({ tableFontSize: 12 }).tableFontSize, 12);
|
||||
assert.equal(normalizeEditorSettings({ tableFontSize: 14.6 }).tableFontSize, 15);
|
||||
assert.equal(normalizeEditorSettings({ tableFontSize: 8 }).tableFontSize, 12);
|
||||
assert.equal(normalizeEditorSettings({ tableFontSize: 8 }).tableFontSize, 8);
|
||||
assert.equal(normalizeEditorSettings({ tableFontSize: 20 }).tableFontSize, 16);
|
||||
assert.equal(normalizeEditorSettings({ tableFontSize: "large" as any }).tableFontSize, 13);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ export interface ColumnInfo {
|
|||
numeric_scale?: number | null;
|
||||
character_maximum_length?: number | null;
|
||||
enum_values?: string[] | null;
|
||||
character_set?: string | null;
|
||||
collation?: string | null;
|
||||
}
|
||||
|
||||
export interface QueryResult {
|
||||
|
|
@ -564,11 +566,13 @@ interface BridgeColumnInfo {
|
|||
numeric_scale?: number | null;
|
||||
character_maximum_length?: number | null;
|
||||
enum_values?: string[] | null;
|
||||
character_set?: string | null;
|
||||
collation?: string | null;
|
||||
}
|
||||
|
||||
const POSTGRES_DESCRIBE_TABLE_SQL = `SELECT c.column_name AS name, CASE WHEN c.data_type = 'USER-DEFINED' THEN c.udt_name ELSE c.data_type END AS data_type, c.is_nullable = 'YES' AS is_nullable, c.column_default, CASE WHEN tc.constraint_type = 'PRIMARY KEY' THEN true ELSE false END AS is_primary_key, col_description(cls.oid, c.ordinal_position) AS comment, CASE WHEN enum_t.oid IS NULL THEN NULL ELSE COALESCE((SELECT array_to_json(array_agg(e.enumlabel ORDER BY e.enumsortorder)) FROM pg_enum e WHERE e.enumtypid = enum_t.oid), '[]'::json) END AS enum_values FROM information_schema.columns c LEFT JOIN information_schema.key_column_usage kcu ON kcu.table_schema = c.table_schema AND kcu.table_name = c.table_name AND kcu.column_name = c.column_name LEFT JOIN information_schema.table_constraints tc ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.constraint_type = 'PRIMARY KEY' LEFT JOIN pg_class cls ON cls.relname = c.table_name AND cls.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema) LEFT JOIN pg_namespace type_ns ON type_ns.nspname = c.udt_schema LEFT JOIN pg_type t ON t.typnamespace = type_ns.oid AND t.typname = c.udt_name LEFT JOIN pg_type enum_t ON enum_t.oid = CASE WHEN t.typtype = 'd' THEN t.typbasetype WHEN t.typtype = 'e' THEN t.oid ELSE NULL END AND enum_t.typtype = 'e' WHERE c.table_schema = $1 AND c.table_name = $2 ORDER BY c.ordinal_position`;
|
||||
const POSTGRES_DESCRIBE_TABLE_COMPAT_SQL = `SELECT c.column_name AS name, CASE WHEN c.data_type = 'USER-DEFINED' THEN c.udt_name ELSE c.data_type END AS data_type, c.is_nullable = 'YES' AS is_nullable, c.column_default, CASE WHEN tc.constraint_type = 'PRIMARY KEY' THEN true ELSE false END AS is_primary_key, col_description(cls.oid, c.ordinal_position) AS comment, NULL AS enum_values FROM information_schema.columns c LEFT JOIN information_schema.key_column_usage kcu ON kcu.table_schema = c.table_schema AND kcu.table_name = c.table_name AND kcu.column_name = c.column_name LEFT JOIN information_schema.table_constraints tc ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.constraint_type = 'PRIMARY KEY' LEFT JOIN pg_class cls ON cls.relname = c.table_name AND cls.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema) WHERE c.table_schema = $1 AND c.table_name = $2 ORDER BY c.ordinal_position`;
|
||||
const MYSQL_DESCRIBE_TABLE_SQL = `SELECT c.COLUMN_NAME AS name, c.DATA_TYPE AS data_type, c.COLUMN_TYPE AS column_type, c.IS_NULLABLE = 'YES' AS is_nullable, c.COLUMN_DEFAULT AS column_default, c.COLUMN_KEY = 'PRI' AS is_primary_key, c.COLUMN_COMMENT AS comment FROM information_schema.COLUMNS c WHERE c.TABLE_SCHEMA = DATABASE() AND c.TABLE_NAME = ? ORDER BY c.ORDINAL_POSITION`;
|
||||
const MYSQL_DESCRIBE_TABLE_SQL = `SELECT c.COLUMN_NAME AS name, c.DATA_TYPE AS data_type, c.COLUMN_TYPE AS column_type, c.IS_NULLABLE = 'YES' AS is_nullable, c.COLUMN_DEFAULT AS column_default, c.COLUMN_KEY = 'PRI' AS is_primary_key, c.COLUMN_COMMENT AS comment, c.CHARACTER_SET_NAME AS character_set, c.COLLATION_NAME AS collation FROM information_schema.COLUMNS c WHERE c.TABLE_SCHEMA = DATABASE() AND c.TABLE_NAME = ? ORDER BY c.ORDINAL_POSITION`;
|
||||
|
||||
function normalizeEnumValues(value: unknown): string[] | null {
|
||||
if (value == null) return null;
|
||||
|
|
@ -649,6 +653,8 @@ function mapDescribeTableColumn(
|
|||
numeric_precision?: number | null;
|
||||
numeric_scale?: number | null;
|
||||
character_maximum_length?: number | null;
|
||||
character_set?: string | null;
|
||||
collation?: string | null;
|
||||
},
|
||||
enumValues: string[] | null,
|
||||
): ColumnInfo {
|
||||
|
|
@ -664,6 +670,8 @@ function mapDescribeTableColumn(
|
|||
if ("numeric_precision" in row) column.numeric_precision = row.numeric_precision;
|
||||
if ("numeric_scale" in row) column.numeric_scale = row.numeric_scale;
|
||||
if ("character_maximum_length" in row) column.character_maximum_length = row.character_maximum_length;
|
||||
if ("character_set" in row) column.character_set = row.character_set ?? null;
|
||||
if ("collation" in row) column.collation = row.collation ?? null;
|
||||
return column;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue