Fix:支持 SQL Server 新增字段设置自增 (#2254)
Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
1 parent
16a1d4b1dd
commit
ea2b5d1bfd
6 files changed
+170
-20
No files matched your search
@@ -224,6 +224,7 @@ const structureDensityMetrics: Record<
|
|||||||
indexes: number[];
|
indexes: number[];
|
||||||
minColumnWidth: number;
|
minColumnWidth: number;
|
||||||
minIndexColumnWidth: number;
|
minIndexColumnWidth: number;
|
||||||
|
actionButtonWidth: number;
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
shellPadding: number;
|
shellPadding: number;
|
||||||
cellPaddingX: number;
|
cellPaddingX: number;
|
||||||
@@ -241,6 +242,7 @@ const structureDensityMetrics: Record<
|
|||||||
indexes: [120, 180, 60, 88, 124, 144, 120, 70],
|
indexes: [120, 180, 60, 88, 124, 144, 120, 70],
|
||||||
minColumnWidth: 24,
|
minColumnWidth: 24,
|
||||||
minIndexColumnWidth: 48,
|
minIndexColumnWidth: 48,
|
||||||
|
actionButtonWidth: 24,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
shellPadding: 10,
|
shellPadding: 10,
|
||||||
cellPaddingX: 6,
|
cellPaddingX: 6,
|
||||||
@@ -257,6 +259,7 @@ const structureDensityMetrics: Record<
|
|||||||
indexes: [148, 224, 72, 108, 148, 180, 148, 84],
|
indexes: [148, 224, 72, 108, 148, 180, 148, 84],
|
||||||
minColumnWidth: 28,
|
minColumnWidth: 28,
|
||||||
minIndexColumnWidth: 60,
|
minIndexColumnWidth: 60,
|
||||||
|
actionButtonWidth: 28,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
shellPadding: 12,
|
shellPadding: 12,
|
||||||
cellPaddingX: 8,
|
cellPaddingX: 8,
|
||||||
@@ -273,6 +276,7 @@ const structureDensityMetrics: Record<
|
|||||||
indexes: [176, 260, 84, 124, 176, 216, 176, 104],
|
indexes: [176, 260, 84, 124, 176, 216, 176, 104],
|
||||||
minColumnWidth: 32,
|
minColumnWidth: 32,
|
||||||
minIndexColumnWidth: 64,
|
minIndexColumnWidth: 64,
|
||||||
|
actionButtonWidth: 32,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
shellPadding: 16,
|
shellPadding: 16,
|
||||||
cellPaddingX: 10,
|
cellPaddingX: 10,
|
||||||
@@ -546,8 +550,15 @@ const showExtendedProperties = computed(() => {
|
|||||||
return dt === "mysql" || dt === "manticoresearch" || isPostgresIdentityType(dt) || dt === "sqlserver";
|
return dt === "mysql" || dt === "manticoresearch" || isPostgresIdentityType(dt) || dt === "sqlserver";
|
||||||
});
|
});
|
||||||
const extendedPropertiesColumnIndex = 8;
|
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 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) {
|
function columnWidthIndex(visibleIndex: number) {
|
||||||
return visibleColumnIndexes.value[visibleIndex] ?? visibleIndex;
|
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;
|
if (!Number.isInteger(index) || index < 0 || index >= columns.value.length) return false;
|
||||||
const column = columns.value[index];
|
const column = columns.value[index];
|
||||||
if (!column || column.markedForDrop) return false;
|
if (!column || column.markedForDrop) return false;
|
||||||
if (!column.original) return true;
|
|
||||||
return canShowColumnDragControls.value;
|
return canShowColumnDragControls.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1002,6 +1012,53 @@ function canDropColumnAt(sourceIndex: number, insertionIndex: number): boolean {
|
|||||||
|
|
||||||
const canShowColumnDragControls = computed(() => isCreateMode.value || structureCapabilities.value.reorderColumn);
|
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) {
|
function moveColumnTo(index: number, insertionIndex: number) {
|
||||||
if (!canDropColumnAt(index, insertionIndex)) return;
|
if (!canDropColumnAt(index, insertionIndex)) return;
|
||||||
const nextColumns = [...columns.value];
|
const nextColumns = [...columns.value];
|
||||||
@@ -1911,42 +1968,34 @@ watch(activeTab, (tab) => {
|
|||||||
<!-- SQL Server: IDENTITY -->
|
<!-- SQL Server: IDENTITY -->
|
||||||
<template v-else-if="structureDialect === 'sqlserver'">
|
<template v-else-if="structureDialect === 'sqlserver'">
|
||||||
<label :class="structurePropertyLabelClass" :title="t('structureEditor.identity')">
|
<label :class="structurePropertyLabelClass" :title="t('structureEditor.identity')">
|
||||||
<input v-model="column.extra.autoIncrement" type="checkbox" :class="[structureCheckboxClass, 'shrink-0']" />
|
<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.identity") }}</span>
|
<span class="min-w-0 truncate">{{ t("structureEditor.autoIncrement") }}</span>
|
||||||
</label>
|
</label>
|
||||||
<template v-if="column.extra.autoIncrement">
|
<template v-if="isSqlServerIdentityChecked(column)">
|
||||||
<Input
|
<Input
|
||||||
:model-value="column.extra.identity?.seed?.toString() ?? '1'"
|
:model-value="column.extra.identity?.seed?.toString() ?? '1'"
|
||||||
type="number"
|
type="number"
|
||||||
:class="[structureControlClass, 'w-14']"
|
:class="[structureControlClass, 'w-14']"
|
||||||
:placeholder="t('structureEditor.identitySeed')"
|
:placeholder="t('structureEditor.identitySeed')"
|
||||||
@update:model-value="
|
:disabled="!canEditSqlServerIdentity(column)"
|
||||||
(v) => {
|
@update:model-value="(v) => updateSqlServerIdentitySeed(column, v)"
|
||||||
if (!column.extra.identity) column.extra.identity = {};
|
|
||||||
column.extra.identity.seed = v ? Number(v) : undefined;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
:model-value="column.extra.identity?.increment?.toString() ?? '1'"
|
:model-value="column.extra.identity?.increment?.toString() ?? '1'"
|
||||||
type="number"
|
type="number"
|
||||||
:class="[structureControlClass, 'w-14']"
|
:class="[structureControlClass, 'w-14']"
|
||||||
:placeholder="t('structureEditor.identityIncrement')"
|
:placeholder="t('structureEditor.identityIncrement')"
|
||||||
@update:model-value="
|
:disabled="!canEditSqlServerIdentity(column)"
|
||||||
(v) => {
|
@update:model-value="(v) => updateSqlServerIdentityIncrement(column, v)"
|
||||||
if (!column.extra.identity) column.extra.identity = {};
|
|
||||||
column.extra.identity.increment = v ? Number(v) : undefined;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td :class="structureLastCellClass">
|
<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
|
<Button
|
||||||
v-if="canShowColumnDragControls || !column.original"
|
v-if="canShowColumnDragControls"
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
@@ -37,4 +37,13 @@ describe("jdbc dialect inference", () => {
|
|||||||
}),
|
}),
|
||||||
).toBe("iris");
|
).toBe("iris");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses JDBC driver profiles when inferring dialect", () => {
|
||||||
|
expect(
|
||||||
|
inferJdbcDialect({
|
||||||
|
db_type: "jdbc",
|
||||||
|
driver_profile: "sqlserver",
|
||||||
|
}),
|
||||||
|
).toBe("sqlserver");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -23,7 +23,7 @@ const JDBC_DIALECT_MATCHERS: Array<{ type: DatabaseType; patterns: RegExp[] }> =
|
|||||||
|
|
||||||
export function inferJdbcDialect(connection?: JdbcDialectConnection): DatabaseType | undefined {
|
export function inferJdbcDialect(connection?: JdbcDialectConnection): DatabaseType | undefined {
|
||||||
if (!connection || connection.db_type !== "jdbc") return 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;
|
if (!haystack) return undefined;
|
||||||
return JDBC_DIALECT_MATCHERS.find((matcher) => matcher.patterns.some((pattern) => pattern.test(haystack)))?.type;
|
return JDBC_DIALECT_MATCHERS.find((matcher) => matcher.patterns.some((pattern) => pattern.test(haystack)))?.type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ pub(super) fn capabilities_for(database_type: Option<DatabaseType>) -> TableStru
|
|||||||
add_column: true,
|
add_column: true,
|
||||||
drop_column: true,
|
drop_column: true,
|
||||||
rename_column: true,
|
rename_column: true,
|
||||||
|
reorder_column: true,
|
||||||
..base
|
..base
|
||||||
},
|
},
|
||||||
Some(
|
Some(
|
||||||
|
|||||||
@@ -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]
|
#[test]
|
||||||
fn manticoresearch_does_not_drop_id_column() {
|
fn manticoresearch_does_not_drop_id_column() {
|
||||||
let mut id = column("id");
|
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;"]);
|
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]
|
#[test]
|
||||||
fn sqlserver_changed_foreign_key_still_warns_as_unsupported() {
|
fn sqlserver_changed_foreign_key_still_warns_as_unsupported() {
|
||||||
let mut user_fk = foreign_key("fk_orders_user_id", "user_id", "accounts", "id");
|
let mut user_fk = foreign_key("fk_orders_user_id", "user_id", "accounts", "id");
|
||||||
|
|||||||
@@ -18,3 +18,13 @@ test("infers GoldenDB for generic JDBC connections", () => {
|
|||||||
"goldendb",
|
"goldendb",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("infers JDBC dialect from driver profile", () => {
|
||||||
|
assert.equal(
|
||||||
|
inferJdbcDialect({
|
||||||
|
db_type: "jdbc",
|
||||||
|
driver_profile: "sqlserver",
|
||||||
|
}),
|
||||||
|
"sqlserver",
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user