Fix:支持 SQL Server 新增字段设置自增 (#2254)

Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
zipg 2026-06-30 23:30:50 +08:00 committed by GitHub
parent 16a1d4b1dd
commit ea2b5d1bfd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 170 additions and 20 deletions

View File

@ -224,6 +224,7 @@ const structureDensityMetrics: Record<
indexes: number[];
minColumnWidth: number;
minIndexColumnWidth: number;
actionButtonWidth: number;
fontSize: number;
shellPadding: number;
cellPaddingX: number;
@ -241,6 +242,7 @@ const structureDensityMetrics: Record<
indexes: [120, 180, 60, 88, 124, 144, 120, 70],
minColumnWidth: 24,
minIndexColumnWidth: 48,
actionButtonWidth: 24,
fontSize: 11,
shellPadding: 10,
cellPaddingX: 6,
@ -257,6 +259,7 @@ const structureDensityMetrics: Record<
indexes: [148, 224, 72, 108, 148, 180, 148, 84],
minColumnWidth: 28,
minIndexColumnWidth: 60,
actionButtonWidth: 28,
fontSize: 12,
shellPadding: 12,
cellPaddingX: 8,
@ -273,6 +276,7 @@ const structureDensityMetrics: Record<
indexes: [176, 260, 84, 124, 176, 216, 176, 104],
minColumnWidth: 32,
minIndexColumnWidth: 64,
actionButtonWidth: 32,
fontSize: 13,
shellPadding: 16,
cellPaddingX: 10,
@ -546,8 +550,15 @@ const showExtendedProperties = computed(() => {
return dt === "mysql" || dt === "manticoresearch" || isPostgresIdentityType(dt) || dt === "sqlserver";
});
const extendedPropertiesColumnIndex = 8;
const actionButtonGap = 2;
const columnActionButtonCount = computed(() => (canShowColumnDragControls.value ? 2 : 1));
const columnActionsWidth = computed(() => {
const metric = structureDensityMetric.value;
const count = columnActionButtonCount.value;
return metric.actionButtonWidth * count + actionButtonGap * Math.max(0, count - 1) + metric.cellPaddingX * 2;
});
const visibleColumnIndexes = computed(() => colLabels.value.map((column) => column.widthIndex));
const visibleColWidths = computed(() => visibleColumnIndexes.value.map((index) => colWidths.value[index] ?? structureDensityMetric.value.minColumnWidth));
const visibleColWidths = computed(() => colLabels.value.map((column) => (column.key === "actions" ? columnActionsWidth.value : (colWidths.value[column.widthIndex] ?? structureDensityMetric.value.minColumnWidth))));
function columnWidthIndex(visibleIndex: number) {
return visibleColumnIndexes.value[visibleIndex] ?? visibleIndex;
@ -983,7 +994,6 @@ function canDragColumn(index: number): boolean {
if (!Number.isInteger(index) || index < 0 || index >= columns.value.length) return false;
const column = columns.value[index];
if (!column || column.markedForDrop) return false;
if (!column.original) return true;
return canShowColumnDragControls.value;
}
@ -1002,6 +1012,53 @@ function canDropColumnAt(sourceIndex: number, insertionIndex: number): boolean {
const canShowColumnDragControls = computed(() => isCreateMode.value || structureCapabilities.value.reorderColumn);
function isSqlServerIdentityChecked(column: EditableStructureColumn): boolean {
return !!column.extra.autoIncrement || !!column.extra.identity;
}
function canEditSqlServerIdentity(column: EditableStructureColumn): boolean {
return !column.original && !column.markedForDrop;
}
function ensureSqlServerIdentity(column: EditableStructureColumn) {
column.extra.autoIncrement = true;
column.extra.identity = {
seed: column.extra.identity?.seed ?? 1,
increment: column.extra.identity?.increment ?? 1,
};
}
function setSqlServerIdentity(column: EditableStructureColumn, checked: boolean) {
if (!canEditSqlServerIdentity(column)) return;
if (checked) {
ensureSqlServerIdentity(column);
column.isNullable = false;
} else {
column.extra.autoIncrement = false;
column.extra.identity = undefined;
}
}
function parseOptionalNumberInput(value: string | number): number | undefined {
if (typeof value === "number") return Number.isFinite(value) ? value : undefined;
const trimmed = value.trim();
if (!trimmed) return undefined;
const numeric = Number(trimmed);
return Number.isFinite(numeric) ? numeric : undefined;
}
function updateSqlServerIdentitySeed(column: EditableStructureColumn, value: string | number) {
if (!canEditSqlServerIdentity(column)) return;
ensureSqlServerIdentity(column);
column.extra.identity!.seed = parseOptionalNumberInput(value);
}
function updateSqlServerIdentityIncrement(column: EditableStructureColumn, value: string | number) {
if (!canEditSqlServerIdentity(column)) return;
ensureSqlServerIdentity(column);
column.extra.identity!.increment = parseOptionalNumberInput(value);
}
function moveColumnTo(index: number, insertionIndex: number) {
if (!canDropColumnAt(index, insertionIndex)) return;
const nextColumns = [...columns.value];
@ -1911,42 +1968,34 @@ watch(activeTab, (tab) => {
<!-- SQL Server: IDENTITY -->
<template v-else-if="structureDialect === 'sqlserver'">
<label :class="structurePropertyLabelClass" :title="t('structureEditor.identity')">
<input v-model="column.extra.autoIncrement" type="checkbox" :class="[structureCheckboxClass, 'shrink-0']" />
<span class="min-w-0 truncate">{{ t("structureEditor.identity") }}</span>
<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>
<template v-if="column.extra.autoIncrement">
<template v-if="isSqlServerIdentityChecked(column)">
<Input
:model-value="column.extra.identity?.seed?.toString() ?? '1'"
type="number"
:class="[structureControlClass, 'w-14']"
:placeholder="t('structureEditor.identitySeed')"
@update:model-value="
(v) => {
if (!column.extra.identity) column.extra.identity = {};
column.extra.identity.seed = v ? Number(v) : undefined;
}
"
:disabled="!canEditSqlServerIdentity(column)"
@update:model-value="(v) => updateSqlServerIdentitySeed(column, v)"
/>
<Input
:model-value="column.extra.identity?.increment?.toString() ?? '1'"
type="number"
:class="[structureControlClass, 'w-14']"
:placeholder="t('structureEditor.identityIncrement')"
@update:model-value="
(v) => {
if (!column.extra.identity) column.extra.identity = {};
column.extra.identity.increment = v ? Number(v) : undefined;
}
"
:disabled="!canEditSqlServerIdentity(column)"
@update:model-value="(v) => updateSqlServerIdentityIncrement(column, v)"
/>
</template>
</template>
</div>
</td>
<td :class="structureLastCellClass">
<div class="flex min-w-0 items-center justify-start gap-0.5 overflow-hidden">
<div class="flex min-w-0 items-center justify-start gap-0.5">
<Button
v-if="canShowColumnDragControls || !column.original"
v-if="canShowColumnDragControls"
type="button"
variant="ghost"
size="icon"

View File

@ -37,4 +37,13 @@ describe("jdbc dialect inference", () => {
}),
).toBe("iris");
});
it("uses JDBC driver profiles when inferring dialect", () => {
expect(
inferJdbcDialect({
db_type: "jdbc",
driver_profile: "sqlserver",
}),
).toBe("sqlserver");
});
});

View File

@ -23,7 +23,7 @@ const JDBC_DIALECT_MATCHERS: Array<{ type: DatabaseType; patterns: RegExp[] }> =
export function inferJdbcDialect(connection?: JdbcDialectConnection): DatabaseType | undefined {
if (!connection || connection.db_type !== "jdbc") return undefined;
const haystack = [connection.connection_string, connection.jdbc_driver_class, ...(connection.jdbc_driver_paths ?? [])].filter(Boolean).join("\n");
const haystack = [connection.driver_profile, connection.connection_string, connection.jdbc_driver_class, ...(connection.jdbc_driver_paths ?? [])].filter(Boolean).join("\n");
if (!haystack) return undefined;
return JDBC_DIALECT_MATCHERS.find((matcher) => matcher.patterns.some((pattern) => pattern.test(haystack)))?.type;
}

View File

@ -92,6 +92,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
add_column: true,
drop_column: true,
rename_column: true,
reorder_column: true,
..base
},
Some(

View File

@ -439,6 +439,60 @@ fn gbase8a_uses_limited_mysql_ddl() {
);
}
#[test]
fn gbase8a_allows_mysql_style_column_reorder() {
let mut id = column("id");
id.original_position = Some(0);
id.original = Some(ColumnInfo {
name: "id".to_string(),
data_type: "varchar(255)".to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: None,
});
let mut name = column("name");
name.original_position = Some(1);
name.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,
});
let mut email = column("email");
email.original_position = Some(2);
email.original = Some(ColumnInfo {
name: "email".to_string(),
data_type: "varchar(255)".to_string(),
is_nullable: true,
column_default: None,
is_primary_key: false,
extra: None,
comment: None,
});
let result = build_table_structure_change_sql(TableStructureSqlOptions {
database_type: Some(DatabaseType::Gbase),
schema: None,
table_name: "users".to_string(),
columns: vec![id, email, name],
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 `email` varchar(255) AFTER `id`;"]);
}
#[test]
fn manticoresearch_does_not_drop_id_column() {
let mut id = column("id");
@ -1127,6 +1181,33 @@ fn sqlserver_unchanged_foreign_key_does_not_warn_when_saving_other_changes() {
assert_eq!(result.statements, vec!["ALTER TABLE [dbo].[orders] ADD [email] nvarchar(255) NOT NULL;"]);
}
#[test]
fn sqlserver_add_column_with_identity() {
let mut id = column("id");
id.data_type = "int".to_string();
id.is_nullable = false;
id.extra = Some(ColumnExtra {
auto_increment: Some(true),
identity: Some(ColumnIdentity { generation: None, seed: Some(10), increment: Some(2) }),
..Default::default()
});
let result = build_table_structure_change_sql(TableStructureSqlOptions {
database_type: Some(DatabaseType::SqlServer),
schema: Some("dbo".to_string()),
table_name: "orders".to_string(),
columns: vec![id],
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 [dbo].[orders] ADD [id] int NOT NULL IDENTITY(10, 2);"]);
}
#[test]
fn sqlserver_changed_foreign_key_still_warns_as_unsupported() {
let mut user_fk = foreign_key("fk_orders_user_id", "user_id", "accounts", "id");

View File

@ -18,3 +18,13 @@ test("infers GoldenDB for generic JDBC connections", () => {
"goldendb",
);
});
test("infers JDBC dialect from driver profile", () => {
assert.equal(
inferJdbcDialect({
db_type: "jdbc",
driver_profile: "sqlserver",
}),
"sqlserver",
);
});