mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-14 10:29:52 +08:00
🐛 fix(table-designer): 修复 DuckDB 表设计主键保存失效
- 为 DuckDB 表结构变更补充 ADD PRIMARY KEY 预览 SQL - 保存前拦截已有主键表的主键替换与删除,避免假成功 - 补充 DuckDB 主键变更判定与 schema SQL 回归测试
This commit is contained in:
@@ -11,6 +11,7 @@ import { DBGetColumns, DBGetIndexes, DBQuery, DBGetForeignKeys, DBGetTriggers, D
|
||||
import { hasIndexFormChanged, normalizeIndexFormFromRow, shouldRestoreOriginalIndex, toggleIndexSelection as getNextIndexSelection, type IndexDisplaySnapshot } from './tableDesignerIndexUtils';
|
||||
import { buildIndexCreateSqlPreview } from './tableDesignerIndexSql';
|
||||
import { buildAlterTablePreviewSql, buildCreateTablePreviewSql, hasAlterTableDraftChanges, type StarRocksCreateTableOptions, type StarRocksDistributionType, type StarRocksKeyModel, type StarRocksTableKind } from './tableDesignerSchemaSql';
|
||||
import { summarizeDuckDbPrimaryKeyChange } from './tableDesignerDuckDbPrimaryKey';
|
||||
import { normalizeSchemaStatementForExecution, parseTableCommentFromDDL, splitSchemaExecutionStatements } from './tableDesignerExecutionSql';
|
||||
import TableDesignerSqlPreview from './TableDesignerSqlPreview';
|
||||
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
|
||||
@@ -2211,6 +2212,13 @@ END;`;
|
||||
setIsPreviewOpen(true);
|
||||
} else {
|
||||
const tableInfo = resolveTableInfo();
|
||||
if (tableInfo.dbType === 'duckdb') {
|
||||
const pkChange = summarizeDuckDbPrimaryKeyChange(originalColumns, columns);
|
||||
if (pkChange.isUnsupportedChange) {
|
||||
message.warning('DuckDB 当前仅支持为无主键表新增主键;已有主键的修改或删除需要通过重建表完成。');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const sql = buildAlterTablePreviewSql({
|
||||
dbType: tableInfo.dbType,
|
||||
tableName: tableInfo.qualifiedName,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type { EditableColumnSnapshot } from './tableDesignerSchemaSql';
|
||||
import { summarizeDuckDbPrimaryKeyChange } from './tableDesignerDuckDbPrimaryKey';
|
||||
|
||||
const col = (overrides: Partial<EditableColumnSnapshot>): EditableColumnSnapshot => ({
|
||||
_key: overrides._key || 'id',
|
||||
name: overrides.name || 'id',
|
||||
type: overrides.type || 'BIGINT',
|
||||
nullable: overrides.nullable || 'NO',
|
||||
default: overrides.default || '',
|
||||
extra: overrides.extra || '',
|
||||
comment: overrides.comment || '',
|
||||
key: overrides.key || '',
|
||||
isAutoIncrement: overrides.isAutoIncrement || false,
|
||||
});
|
||||
|
||||
describe('tableDesignerDuckDbPrimaryKey', () => {
|
||||
it('treats first primary key addition as supported', () => {
|
||||
const summary = summarizeDuckDbPrimaryKeyChange(
|
||||
[col({ _key: 'id', key: '' })],
|
||||
[col({ _key: 'id', key: 'PRI' })],
|
||||
);
|
||||
|
||||
expect(summary).toEqual({
|
||||
hasChange: true,
|
||||
isAddingPrimaryKey: true,
|
||||
isUnsupportedChange: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('treats replacing an existing primary key as unsupported', () => {
|
||||
const summary = summarizeDuckDbPrimaryKeyChange(
|
||||
[col({ _key: 'id', key: 'PRI' }), col({ _key: 'name', name: 'name', key: '' })],
|
||||
[col({ _key: 'id', key: '' }), col({ _key: 'name', name: 'name', key: 'PRI' })],
|
||||
);
|
||||
|
||||
expect(summary).toEqual({
|
||||
hasChange: true,
|
||||
isAddingPrimaryKey: false,
|
||||
isUnsupportedChange: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
38
frontend/src/components/tableDesignerDuckDbPrimaryKey.ts
Normal file
38
frontend/src/components/tableDesignerDuckDbPrimaryKey.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { EditableColumnSnapshot } from './tableDesignerSchemaSql';
|
||||
|
||||
export interface DuckDbPrimaryKeyChangeSummary {
|
||||
hasChange: boolean;
|
||||
isAddingPrimaryKey: boolean;
|
||||
isUnsupportedChange: boolean;
|
||||
}
|
||||
|
||||
const collectPrimaryKeys = (columns: EditableColumnSnapshot[]): string[] => (
|
||||
columns
|
||||
.filter((column) => column.key === 'PRI')
|
||||
.map((column) => String(column._key || '').trim())
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
);
|
||||
|
||||
export const summarizeDuckDbPrimaryKeyChange = (
|
||||
originalColumns: EditableColumnSnapshot[],
|
||||
columns: EditableColumnSnapshot[],
|
||||
): DuckDbPrimaryKeyChangeSummary => {
|
||||
const originalKeys = collectPrimaryKeys(originalColumns);
|
||||
const nextKeys = collectPrimaryKeys(columns);
|
||||
const hasChange = originalKeys.length !== nextKeys.length || originalKeys.some((key, index) => key !== nextKeys[index]);
|
||||
if (!hasChange) {
|
||||
return {
|
||||
hasChange: false,
|
||||
isAddingPrimaryKey: false,
|
||||
isUnsupportedChange: false,
|
||||
};
|
||||
}
|
||||
|
||||
const isAddingPrimaryKey = originalKeys.length === 0 && nextKeys.length > 0;
|
||||
return {
|
||||
hasChange: true,
|
||||
isAddingPrimaryKey,
|
||||
isUnsupportedChange: !isAddingPrimaryKey,
|
||||
};
|
||||
};
|
||||
@@ -163,6 +163,43 @@ describe('tableDesignerSchemaSql', () => {
|
||||
expect(sql).not.toContain('MODIFY COLUMN');
|
||||
});
|
||||
|
||||
it('builds duckdb alter preview with add primary key when adding first primary key', () => {
|
||||
const sql = buildAlterTablePreviewSql(buildInput({
|
||||
dbType: 'duckdb',
|
||||
tableName: 'main.events',
|
||||
originalColumns: [
|
||||
baseColumn({ _key: 'id', name: 'id', type: 'BIGINT', nullable: 'YES', key: '' }),
|
||||
baseColumn({ _key: 'name', name: 'name', type: 'VARCHAR', nullable: 'YES', key: '' }),
|
||||
],
|
||||
columns: [
|
||||
baseColumn({ _key: 'id', name: 'id', type: 'BIGINT', nullable: 'NO', key: 'PRI' }),
|
||||
baseColumn({ _key: 'name', name: 'name', type: 'VARCHAR', nullable: 'YES', key: '' }),
|
||||
],
|
||||
}));
|
||||
|
||||
expect(sql).toContain('ALTER TABLE "main"."events"\nALTER COLUMN "id" SET NOT NULL;');
|
||||
expect(sql).toContain('ALTER TABLE "main"."events"\nADD PRIMARY KEY ("id");');
|
||||
});
|
||||
|
||||
it('marks unsupported duckdb primary key replacement with explicit warning comment', () => {
|
||||
const sql = buildAlterTablePreviewSql(buildInput({
|
||||
dbType: 'duckdb',
|
||||
tableName: 'main.events',
|
||||
originalColumns: [
|
||||
baseColumn({ _key: 'id', name: 'id', type: 'BIGINT', nullable: 'NO', key: 'PRI' }),
|
||||
baseColumn({ _key: 'name', name: 'name', type: 'VARCHAR', nullable: 'YES', key: '' }),
|
||||
],
|
||||
columns: [
|
||||
baseColumn({ _key: 'id', name: 'id', type: 'BIGINT', nullable: 'NO', key: '' }),
|
||||
baseColumn({ _key: 'name', name: 'name', type: 'VARCHAR', nullable: 'NO', key: 'PRI' }),
|
||||
],
|
||||
}));
|
||||
|
||||
expect(sql).toContain('-- DuckDB 当前仅支持为无主键表新增 PRIMARY KEY;已有主键的修改或删除需要通过重建表完成。');
|
||||
expect(sql).not.toContain('DROP CONSTRAINT');
|
||||
expect(sql).not.toContain('DROP PRIMARY KEY');
|
||||
});
|
||||
|
||||
it('builds doris alter preview without mysql-only syntax or metadata extra', () => {
|
||||
const sql = buildAlterTablePreviewSql(buildInput({
|
||||
dbType: 'doris',
|
||||
|
||||
@@ -79,6 +79,12 @@ export interface BuildStarRocksMaterializedViewPreviewInput {
|
||||
properties?: string;
|
||||
}
|
||||
|
||||
const collectPrimaryKeyColumnKeys = (columns: EditableColumnSnapshot[]): string[] => (
|
||||
columns
|
||||
.filter((col) => col.key === 'PRI')
|
||||
.map((col) => col._key)
|
||||
);
|
||||
|
||||
const escapeSqlString = (value: string) => String(value || '').replace(/'/g, "''");
|
||||
|
||||
const stripIdentifierQuotes = unquoteSqlIdentifierPart;
|
||||
@@ -607,6 +613,21 @@ const buildDuckDbAlterPreviewSql = (input: BuildAlterTablePreviewInput): string
|
||||
}
|
||||
});
|
||||
|
||||
const origPKKeys = collectPrimaryKeyColumnKeys(input.originalColumns);
|
||||
const newPKKeys = collectPrimaryKeyColumnKeys(input.columns);
|
||||
const keysChanged = origPKKeys.length !== newPKKeys.length || !origPKKeys.every((key) => newPKKeys.includes(key));
|
||||
if (keysChanged) {
|
||||
if (origPKKeys.length === 0 && newPKKeys.length > 0) {
|
||||
const pkNames = input.columns
|
||||
.filter((col) => col.key === 'PRI')
|
||||
.map((col) => quoteIdentifierPart(col.name, dbType))
|
||||
.join(', ');
|
||||
statements.push(`ALTER TABLE ${tableRef}\nADD PRIMARY KEY (${pkNames});`);
|
||||
} else {
|
||||
statements.push('-- DuckDB 当前仅支持为无主键表新增 PRIMARY KEY;已有主键的修改或删除需要通过重建表完成。');
|
||||
}
|
||||
}
|
||||
|
||||
return statements.join('\n');
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user