fix(sqlserver): restrict identity to compatible column types
Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
1 parent
a224a6747c
commit
5d2ce9f189
13 files changed
+144
-12
No files matched your search
@@ -42,6 +42,7 @@ import {
|
||||
getDataTypeOptions,
|
||||
getDefaultLengthForType,
|
||||
isDataTypeLengthDisabled,
|
||||
isSqlServerIdentityCompatibleDataType,
|
||||
isProtectedManticoreIdColumn,
|
||||
parseExtraToColumnExtra,
|
||||
splitDataType,
|
||||
@@ -1017,7 +1018,19 @@ function isSqlServerIdentityChecked(column: EditableStructureColumn): boolean {
|
||||
}
|
||||
|
||||
function canEditSqlServerIdentity(column: EditableStructureColumn): boolean {
|
||||
return !column.original && !column.markedForDrop;
|
||||
return !column.original && !column.markedForDrop && isSqlServerIdentityCompatibleDataType(column.dataType);
|
||||
}
|
||||
|
||||
function clearSqlServerIdentity(column: EditableStructureColumn) {
|
||||
column.extra.autoIncrement = false;
|
||||
column.extra.identity = undefined;
|
||||
}
|
||||
|
||||
function syncSqlServerIdentityForDataType(column: EditableStructureColumn) {
|
||||
if (databaseType.value !== "sqlserver") return;
|
||||
if (!isSqlServerIdentityChecked(column)) return;
|
||||
if (isSqlServerIdentityCompatibleDataType(column.dataType)) return;
|
||||
clearSqlServerIdentity(column);
|
||||
}
|
||||
|
||||
function ensureSqlServerIdentity(column: EditableStructureColumn) {
|
||||
@@ -1034,8 +1047,7 @@ function setSqlServerIdentity(column: EditableStructureColumn, checked: boolean)
|
||||
ensureSqlServerIdentity(column);
|
||||
column.isNullable = false;
|
||||
} else {
|
||||
column.extra.autoIncrement = false;
|
||||
column.extra.identity = undefined;
|
||||
clearSqlServerIdentity(column);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,6 +1071,16 @@ function updateSqlServerIdentityIncrement(column: EditableStructureColumn, value
|
||||
column.extra.identity!.increment = parseOptionalNumberInput(value);
|
||||
}
|
||||
|
||||
function updateColumnDataType(column: EditableStructureColumn, baseType: string) {
|
||||
column.dataType = combineDataTypeForDatabase(databaseType.value, baseType, getDefaultLengthForType(databaseType.value, baseType));
|
||||
syncSqlServerIdentityForDataType(column);
|
||||
}
|
||||
|
||||
function updateColumnDataTypeLength(column: EditableStructureColumn, value: string | number) {
|
||||
column.dataType = combineDataTypeForDatabase(databaseType.value, splitDataType(column.dataType).baseType, String(value));
|
||||
syncSqlServerIdentityForDataType(column);
|
||||
}
|
||||
|
||||
function moveColumnTo(index: number, insertionIndex: number) {
|
||||
if (!canDropColumnAt(index, insertionIndex)) return;
|
||||
const nextColumns = [...columns.value];
|
||||
@@ -1797,17 +1819,12 @@ watch(activeTab, (tab) => {
|
||||
:loading-text="t('common.loading')"
|
||||
:allow-custom="true"
|
||||
:trigger-class="[structureMonoControlClass, 'w-full']"
|
||||
@update:model-value="(v: string) => (column.dataType = combineDataTypeForDatabase(databaseType, v, getDefaultLengthForType(databaseType, v)))"
|
||||
@update:model-value="(v: string) => updateColumnDataType(column, v)"
|
||||
/>
|
||||
<Input v-else :model-value="splitDataType(column.dataType).baseType" :class="[structureMonoControlClass, 'w-full']" disabled />
|
||||
</td>
|
||||
<td v-if="columnEditorControls.length" :class="structureCellClass">
|
||||
<Input
|
||||
:model-value="dataTypeLengthInputValue(databaseType, column.dataType)"
|
||||
:class="structureMonoControlClass"
|
||||
:disabled="isColumnLengthDisabled(column)"
|
||||
@update:model-value="column.dataType = combineDataTypeForDatabase(databaseType, splitDataType(column.dataType).baseType, String($event))"
|
||||
/>
|
||||
<Input :model-value="dataTypeLengthInputValue(databaseType, column.dataType)" :class="structureMonoControlClass" :disabled="isColumnLengthDisabled(column)" @update:model-value="updateColumnDataTypeLength(column, $event)" />
|
||||
</td>
|
||||
<td v-if="columnEditorControls.nullable" :class="structureCellClass">
|
||||
<label class="flex items-center gap-1.5">
|
||||
@@ -1967,7 +1984,7 @@ watch(activeTab, (tab) => {
|
||||
</template>
|
||||
<!-- SQL Server: IDENTITY -->
|
||||
<template v-else-if="structureDialect === 'sqlserver'">
|
||||
<label :class="structurePropertyLabelClass" :title="t('structureEditor.identity')">
|
||||
<label :class="structurePropertyLabelClass" :title="canEditSqlServerIdentity(column) || isSqlServerIdentityChecked(column) ? t('structureEditor.identity') : t('structureEditor.sqlServerIdentityTypeHint')">
|
||||
<input :checked="isSqlServerIdentityChecked(column)" type="checkbox" :class="[structureCheckboxClass, 'shrink-0']" :disabled="!canEditSqlServerIdentity(column)" @change="setSqlServerIdentity(column, ($event.target as HTMLInputElement).checked)" />
|
||||
<span class="min-w-0 truncate">{{ t("structureEditor.autoIncrement") }}</span>
|
||||
</label>
|
||||
|
||||
@@ -1591,6 +1591,7 @@ export default {
|
||||
identityGeneration: "Generation",
|
||||
identitySeed: "Seed",
|
||||
identityIncrement: "Increment",
|
||||
sqlServerIdentityTypeHint: "SQL Server identity only supports tinyint, smallint, int, bigint, and decimal/numeric with scale 0",
|
||||
},
|
||||
diagram: {
|
||||
title: "Relationship Diagram",
|
||||
|
||||
@@ -1572,6 +1572,7 @@ export default withEnglishFallback({
|
||||
identityGeneration: "Generación",
|
||||
identitySeed: "Semilla",
|
||||
identityIncrement: "Incremento",
|
||||
sqlServerIdentityTypeHint: "La identidad de SQL Server solo admite tinyint, smallint, int, bigint y decimal/numeric con escala 0",
|
||||
},
|
||||
diagram: {
|
||||
title: "Diagrama de relaciones",
|
||||
|
||||
@@ -1592,6 +1592,7 @@ export default withEnglishFallback({
|
||||
identityGeneration: "Generazione",
|
||||
identitySeed: "Seed",
|
||||
identityIncrement: "Incremento",
|
||||
sqlServerIdentityTypeHint: "Identity di SQL Server supporta solo tinyint, smallint, int, bigint e decimal/numeric con scala 0",
|
||||
},
|
||||
diagram: {
|
||||
title: "Diagramma delle Relazioni",
|
||||
|
||||
@@ -1606,6 +1606,7 @@ export default withEnglishFallback({
|
||||
identityGeneration: "生成",
|
||||
identitySeed: "開始値",
|
||||
identityIncrement: "増分",
|
||||
sqlServerIdentityTypeHint: "SQL Server の ID 列は tinyint、smallint、int、bigint、scale 0 の decimal/numeric のみ対応します",
|
||||
},
|
||||
diagram: {
|
||||
title: "リレーション図",
|
||||
|
||||
@@ -1584,6 +1584,7 @@ export default withEnglishFallback({
|
||||
identityGeneration: "Geração",
|
||||
identitySeed: "Valor inicial",
|
||||
identityIncrement: "Incremento",
|
||||
sqlServerIdentityTypeHint: "A identidade do SQL Server só aceita tinyint, smallint, int, bigint e decimal/numeric com escala 0",
|
||||
},
|
||||
diagram: {
|
||||
title: "Diagrama de relacionamentos",
|
||||
|
||||
@@ -1594,6 +1594,7 @@ export default withEnglishFallback({
|
||||
identityGeneration: "生成方式",
|
||||
identitySeed: "起始值",
|
||||
identityIncrement: "增量",
|
||||
sqlServerIdentityTypeHint: "SQL Server 自增仅支持 tinyint、smallint、int、bigint、decimal/numeric(小数位为 0)",
|
||||
},
|
||||
diagram: {
|
||||
title: "关系图",
|
||||
|
||||
@@ -1528,6 +1528,7 @@ export default withEnglishFallback({
|
||||
identityGeneration: "產生方式",
|
||||
identitySeed: "起始值",
|
||||
identityIncrement: "增量",
|
||||
sqlServerIdentityTypeHint: "SQL Server 自動遞增僅支援 tinyint、smallint、int、bigint、decimal/numeric(小數位為 0)",
|
||||
},
|
||||
diagram: {
|
||||
title: "關係圖",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { combineDataTypeForDatabase, createColumnDrafts, dataTypeLengthInputValue, isDataTypeLengthDisabled, splitDataType } from "../tableStructureEditorState";
|
||||
import { combineDataTypeForDatabase, createColumnDrafts, dataTypeLengthInputValue, isDataTypeLengthDisabled, isSqlServerIdentityCompatibleDataType, splitDataType } from "../tableStructureEditorState";
|
||||
|
||||
describe("tableStructureEditorState", () => {
|
||||
it("keeps mysql unsigned attributes in the editable base type", () => {
|
||||
@@ -70,4 +70,13 @@ describe("tableStructureEditorState", () => {
|
||||
expect(drafts.map((draft) => draft.defaultValue)).toEqual(["''", "1", "sysdatetime()", "'prefix (internal)'"]);
|
||||
expect(drafts.map((draft) => draft.original?.column_default)).toEqual(["''", "1", "sysdatetime()", "'prefix (internal)'"]);
|
||||
});
|
||||
|
||||
it("limits SQL Server identity columns to supported data types", () => {
|
||||
expect(isSqlServerIdentityCompatibleDataType("int")).toBe(true);
|
||||
expect(isSqlServerIdentityCompatibleDataType("bigint")).toBe(true);
|
||||
expect(isSqlServerIdentityCompatibleDataType("numeric(18, 0)")).toBe(true);
|
||||
expect(isSqlServerIdentityCompatibleDataType("decimal(10)")).toBe(true);
|
||||
expect(isSqlServerIdentityCompatibleDataType("varchar(255)")).toBe(false);
|
||||
expect(isSqlServerIdentityCompatibleDataType("numeric(18, 2)")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -725,6 +725,20 @@ export function splitDataType(raw: string): { baseType: string; params: string }
|
||||
return { baseType, params };
|
||||
}
|
||||
|
||||
export function isSqlServerIdentityCompatibleDataType(rawDataType: string): boolean {
|
||||
const { baseType, params } = splitDataType(rawDataType);
|
||||
const normalized = baseType.trim().replace(/\s+/g, " ").toLowerCase();
|
||||
if (["tinyint", "smallint", "int", "integer", "bigint"].includes(normalized)) return true;
|
||||
if (normalized !== "decimal" && normalized !== "numeric") return false;
|
||||
const normalizedParams = params.replace(/\s+/g, "");
|
||||
if (!normalizedParams) return true;
|
||||
const parts = normalizedParams.split(",");
|
||||
if (parts.length === 1) return /^\d+$/.test(parts[0] ?? "");
|
||||
if (parts.length !== 2) return false;
|
||||
const [precision, scale] = parts;
|
||||
return /^\d+$/.test(precision ?? "") && scale === "0";
|
||||
}
|
||||
|
||||
export function combineDataType(baseType: string, params: string): string {
|
||||
const type = baseType.trim();
|
||||
const p = params.trim();
|
||||
|
||||
Reference in new issue
Block a user