feat(structure): add DDL capability matrix

This commit is contained in:
t8y2
2026-05-19 12:15:49 +08:00
parent f89b3a2bee
commit cf46aec158
9 changed files with 687 additions and 71 deletions
@@ -137,6 +137,11 @@ test("describes feature support through capability helpers", () => {
assert.equal(supportsTableStructureEditing("duckdb"), true);
assert.equal(supportsTableStructureEditing("oracle"), true);
assert.equal(supportsTableStructureEditing("dameng"), true);
assert.equal(supportsTableStructureEditing("gaussdb"), true);
assert.equal(supportsTableStructureEditing("opengauss"), true);
assert.equal(supportsTableStructureEditing("redshift"), true);
assert.equal(supportsTableStructureEditing("clickhouse"), true);
assert.equal(supportsTableStructureEditing("mongodb"), false);
assert.equal(supportsDatabaseCreation("clickhouse"), true);
assert.equal(supportsDatabaseCreation("sqlite"), false);
assert.equal(supportsFieldLineage("gaussdb"), true);
@@ -0,0 +1,75 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
canEditTableStructure,
getTableStructureCapabilities,
} from "../../apps/desktop/src/lib/tableStructureCapabilities.ts";
test("postgres-like databases expose safe structure editing capabilities", () => {
for (const dbType of ["postgres", "gaussdb", "opengauss", "highgo", "vastbase", "kingbase"] as const) {
const caps = getTableStructureCapabilities(dbType);
assert.equal(caps.dialect, "postgres", `${dbType} should reuse postgres DDL`);
assert.equal(caps.createTable, true, `${dbType} should create tables`);
assert.equal(caps.addColumn, true, `${dbType} should add columns`);
assert.equal(caps.dropColumn, true, `${dbType} should drop columns`);
assert.equal(caps.renameColumn, true, `${dbType} should rename columns`);
assert.equal(caps.alterExistingColumn, true, `${dbType} should edit existing columns`);
assert.equal(caps.comment, true, `${dbType} should support comments`);
assert.equal(caps.createIndex, true, `${dbType} should create indexes`);
assert.equal(caps.dropIndex, true, `${dbType} should drop indexes`);
assert.equal(caps.indexFilter, true, `${dbType} should support filtered indexes`);
assert.equal(canEditTableStructure(dbType), true);
}
});
test("redshift reuses postgres column DDL but keeps indexes disabled", () => {
const caps = getTableStructureCapabilities("redshift");
assert.equal(caps.dialect, "postgres");
assert.equal(caps.createTable, true);
assert.equal(caps.addColumn, true);
assert.equal(caps.dropColumn, true);
assert.equal(caps.renameColumn, true);
assert.equal(caps.alterExistingColumn, true);
assert.equal(caps.comment, true);
assert.equal(caps.createIndex, false);
assert.equal(caps.dropIndex, false);
assert.equal(caps.indexFilter, false);
assert.equal(canEditTableStructure("redshift"), true);
});
test("oracle-like databases expose oracle-compatible structure editing capabilities", () => {
for (const dbType of ["oracle", "dameng", "oceanbase-oracle"] as const) {
const caps = getTableStructureCapabilities(dbType);
assert.equal(caps.dialect, "oracle", `${dbType} should reuse oracle DDL`);
assert.equal(caps.createTable, true);
assert.equal(caps.addColumn, true);
assert.equal(caps.dropColumn, true);
assert.equal(caps.renameColumn, true);
assert.equal(caps.alterExistingColumn, true);
assert.equal(caps.comment, true);
assert.equal(caps.createIndex, true);
assert.equal(caps.dropIndex, true);
assert.equal(canEditTableStructure(dbType), true);
}
});
test("limited analytic engines can open the editor for supported operations only", () => {
const clickhouse = getTableStructureCapabilities("clickhouse");
assert.equal(clickhouse.dialect, "clickhouse");
assert.equal(clickhouse.createTable, true);
assert.equal(clickhouse.addColumn, true);
assert.equal(clickhouse.dropColumn, true);
assert.equal(clickhouse.renameColumn, false);
assert.equal(clickhouse.alterExistingColumn, false);
assert.equal(clickhouse.comment, false);
assert.equal(clickhouse.createIndex, false);
assert.equal(canEditTableStructure("clickhouse"), true);
});
test("unsupported non-relational databases do not open the structure editor", () => {
for (const dbType of ["redis", "mongodb", "elasticsearch", "neo4j", undefined] as const) {
const caps = getTableStructureCapabilities(dbType);
assert.equal(caps.dialect, "unsupported");
assert.equal(canEditTableStructure(dbType), false);
}
});
@@ -17,3 +17,18 @@ test("structure editor keeps columns when optional metadata fails", () => {
assert.match(source, /api\s*\n\s*\.listForeignKeys[\s\S]*\.catch\(\(\) => \[\]\)/);
assert.match(source, /api\s*\n\s*\.listTriggers[\s\S]*\.catch\(\(\) => \[\]\)/);
});
test("structure editor gates controls through table structure capabilities", () => {
assert.match(source, /getTableStructureCapabilities/);
assert.match(source, /const structureCapabilities = computed/);
assert.match(source, /function isColumnNameDisabled/);
assert.match(source, /function isColumnTypeDisabled/);
assert.match(source, /function isColumnDefaultDisabled/);
assert.match(source, /function isColumnCommentDisabled/);
assert.match(source, /function canDropColumn/);
assert.match(source, /function canEditIndexDraft/);
assert.match(source, /structureCapabilities\.value\.createIndex/);
assert.match(source, /structureCapabilities\.value\.dropIndex/);
assert.match(source, /structureCapabilities\.value\.indexInclude/);
assert.match(source, /structureCapabilities\.value\.indexFilter/);
});
@@ -592,3 +592,151 @@ test("builds Dameng existing column and create table statements", () => {
'CREATE INDEX "IDX_USERS_NAME" ON "SYSDBA"."USERS" ("NAME");',
]);
});
test("builds GaussDB statements with PostgreSQL-compatible DDL", () => {
const result = buildTableStructureChangeSql({
databaseType: "gaussdb",
schema: "public",
tableName: "accounts",
columns: [
column({
id: "status",
name: "account_status",
dataType: "text",
isNullable: false,
defaultValue: "'active'",
comment: "Current status",
original: {
name: "status",
data_type: "varchar",
is_nullable: true,
column_default: null,
is_primary_key: false,
extra: null,
comment: "",
},
}),
],
indexes: [index({ id: "idx", name: "idx_accounts_status", columns: ["account_status"] })],
});
assert.deepEqual(result.warnings, []);
assert.deepEqual(result.statements, [
'ALTER TABLE "public"."accounts" RENAME COLUMN "status" TO "account_status";',
'ALTER TABLE "public"."accounts" ALTER COLUMN "account_status" TYPE text;',
'ALTER TABLE "public"."accounts" ALTER COLUMN "account_status" SET NOT NULL;',
'ALTER TABLE "public"."accounts" ALTER COLUMN "account_status" SET DEFAULT \'active\';',
'COMMENT ON COLUMN "public"."accounts"."account_status" IS \'Current status\';',
'CREATE INDEX "idx_accounts_status" ON "public"."accounts" ("account_status");',
]);
});
test("builds openGauss statements with PostgreSQL-compatible DDL", () => {
const result = buildTableStructureChangeSql({
databaseType: "opengauss",
schema: "public",
tableName: "accounts",
columns: [column({ id: "email", name: "email", dataType: "text", isNullable: true })],
indexes: [],
});
assert.deepEqual(result.warnings, []);
assert.deepEqual(result.statements, ['ALTER TABLE "public"."accounts" ADD COLUMN "email" text;']);
});
test("Redshift skips unsupported index operations while keeping column DDL", () => {
const result = buildTableStructureChangeSql({
databaseType: "redshift",
schema: "public",
tableName: "events",
columns: [column({ id: "email", name: "email", dataType: "varchar(255)", isNullable: true })],
indexes: [index({ id: "idx", name: "idx_events_email", columns: ["email"], filter: "email IS NOT NULL" })],
});
assert.deepEqual(result.statements, ['ALTER TABLE "public"."events" ADD COLUMN "email" varchar(255);']);
assert.deepEqual(result.warnings, ['Creating indexes is not supported for redshift from this editor.']);
});
test("builds ClickHouse limited column DDL and skips indexes", () => {
const result = buildTableStructureChangeSql({
databaseType: "clickhouse",
tableName: "events",
columns: [
column({ id: "new", name: "name", dataType: "String", isNullable: true }),
column({
id: "legacy",
name: "legacy",
markedForDrop: true,
original: {
name: "legacy",
data_type: "String",
is_nullable: true,
column_default: null,
is_primary_key: false,
extra: null,
},
}),
column({
id: "kind",
name: "event_kind",
dataType: "String",
original: {
name: "kind",
data_type: "String",
is_nullable: true,
column_default: null,
is_primary_key: false,
extra: null,
},
}),
],
indexes: [index({ id: "idx", name: "idx_events_name", columns: ["name"] })],
});
assert.deepEqual(result.statements, [
'ALTER TABLE "events" ADD COLUMN "name" String;',
'ALTER TABLE "events" DROP COLUMN "legacy";',
]);
assert.deepEqual(result.warnings, [
'Renaming columns is not supported for clickhouse from this editor.',
'Creating indexes is not supported for clickhouse from this editor.',
]);
});
test("builds H2 schema-qualified existing column statements", () => {
const result = buildTableStructureChangeSql({
databaseType: "h2",
schema: "PUBLIC",
tableName: "USERS",
columns: [
column({
id: "name",
name: "DISPLAY_NAME",
dataType: "VARCHAR(120)",
isNullable: false,
defaultValue: "'guest'",
comment: "Display name",
original: {
name: "NAME",
data_type: "VARCHAR(80)",
is_nullable: true,
column_default: null,
is_primary_key: false,
extra: null,
comment: "",
},
}),
],
indexes: [index({ id: "idx", name: "IDX_USERS_DISPLAY_NAME", columns: ["DISPLAY_NAME"] })],
});
assert.deepEqual(result.warnings, []);
assert.deepEqual(result.statements, [
'ALTER TABLE "PUBLIC"."USERS" ALTER COLUMN "NAME" RENAME TO "DISPLAY_NAME";',
'ALTER TABLE "PUBLIC"."USERS" ALTER COLUMN "DISPLAY_NAME" SET DATA TYPE VARCHAR(120);',
'ALTER TABLE "PUBLIC"."USERS" ALTER COLUMN "DISPLAY_NAME" SET NOT NULL;',
'ALTER TABLE "PUBLIC"."USERS" ALTER COLUMN "DISPLAY_NAME" SET DEFAULT \'guest\';',
'COMMENT ON COLUMN "PUBLIC"."USERS"."DISPLAY_NAME" IS \'Display name\';',
'CREATE INDEX "IDX_USERS_DISPLAY_NAME" ON "PUBLIC"."USERS" ("DISPLAY_NAME");',
]);
});