mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(grid): default keyless edits for SQL tables
This commit is contained in:
@@ -30,41 +30,57 @@ const DEFAULT_TABLE_DATA_CAPABILITY: TableDataCapability = {
|
||||
transaction: true,
|
||||
};
|
||||
|
||||
const NAVICAT_STYLE_TABLE_DATA_CAPABILITY: TableDataCapability = {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
keylessRowPredicate: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
};
|
||||
|
||||
const DEFAULT_CAPABILITY: DatabaseCapability = {
|
||||
schemaAware: false,
|
||||
treeSchemaMode: false,
|
||||
tableData: DEFAULT_TABLE_DATA_CAPABILITY,
|
||||
};
|
||||
|
||||
const NAVICAT_STYLE_TABLE_DATA_TYPES = new Set<DatabaseType>([
|
||||
"mysql",
|
||||
"postgres",
|
||||
"sqlite",
|
||||
"duckdb",
|
||||
"sqlserver",
|
||||
"oracle",
|
||||
"doris",
|
||||
"starrocks",
|
||||
"redshift",
|
||||
"dameng",
|
||||
"gaussdb",
|
||||
"kingbase",
|
||||
"highgo",
|
||||
"vastbase",
|
||||
"goldendb",
|
||||
"yashandb",
|
||||
"databricks",
|
||||
"saphana",
|
||||
"teradata",
|
||||
"vertica",
|
||||
"firebird",
|
||||
"exasol",
|
||||
"opengauss",
|
||||
"oceanbase-oracle",
|
||||
"gbase",
|
||||
"access",
|
||||
"h2",
|
||||
"snowflake",
|
||||
"db2",
|
||||
"informix",
|
||||
"bigquery",
|
||||
"sundb",
|
||||
]);
|
||||
|
||||
const DATABASE_CAPABILITY_OVERRIDES: Partial<Record<DatabaseType, Partial<DatabaseCapability>>> = {
|
||||
sqlite: {
|
||||
tableData: {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: true,
|
||||
deleteRequiresPrimaryKey: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
},
|
||||
},
|
||||
duckdb: {
|
||||
tableData: {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: true,
|
||||
deleteRequiresPrimaryKey: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
},
|
||||
},
|
||||
access: {
|
||||
tableData: {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
keylessRowPredicate: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
},
|
||||
},
|
||||
hive: {
|
||||
tableData: {
|
||||
insert: true,
|
||||
@@ -75,25 +91,6 @@ const DATABASE_CAPABILITY_OVERRIDES: Partial<Record<DatabaseType, Partial<Databa
|
||||
transaction: false,
|
||||
},
|
||||
},
|
||||
dameng: {
|
||||
tableData: {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
keylessRowPredicate: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
},
|
||||
},
|
||||
informix: {
|
||||
tableData: {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: true,
|
||||
deleteRequiresPrimaryKey: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
},
|
||||
},
|
||||
jdbc: {
|
||||
tableData: {
|
||||
insert: false,
|
||||
@@ -103,15 +100,6 @@ const DATABASE_CAPABILITY_OVERRIDES: Partial<Record<DatabaseType, Partial<Databa
|
||||
transaction: false,
|
||||
},
|
||||
},
|
||||
yashandb: {
|
||||
tableData: {
|
||||
insert: false,
|
||||
updateRequiresPrimaryKey: true,
|
||||
deleteRequiresPrimaryKey: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: false,
|
||||
},
|
||||
},
|
||||
neo4j: {
|
||||
syntheticKey: "neo4j-element-id",
|
||||
},
|
||||
@@ -148,15 +136,21 @@ const DATABASE_CAPABILITY_OVERRIDES: Partial<Record<DatabaseType, Partial<Databa
|
||||
},
|
||||
};
|
||||
|
||||
function defaultTableDataCapability(dbType?: DatabaseType): TableDataCapability {
|
||||
if (dbType && NAVICAT_STYLE_TABLE_DATA_TYPES.has(dbType)) return NAVICAT_STYLE_TABLE_DATA_CAPABILITY;
|
||||
return DEFAULT_TABLE_DATA_CAPABILITY;
|
||||
}
|
||||
|
||||
export function getDatabaseCapability(dbType?: DatabaseType): DatabaseCapability {
|
||||
const override = dbType ? DATABASE_CAPABILITY_OVERRIDES[dbType] : undefined;
|
||||
const tableData = defaultTableDataCapability(dbType);
|
||||
return {
|
||||
...DEFAULT_CAPABILITY,
|
||||
...override,
|
||||
schemaAware: isSchemaAware(dbType),
|
||||
treeSchemaMode: usesTreeSchemaMode(dbType),
|
||||
tableData: {
|
||||
...DEFAULT_TABLE_DATA_CAPABILITY,
|
||||
...tableData,
|
||||
...override?.tableData,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -205,6 +205,28 @@ test("builds Hive grid save statements without primary keys using row predicates
|
||||
]);
|
||||
});
|
||||
|
||||
test("builds PostgreSQL grid save statements without primary keys using row predicates", () => {
|
||||
const statements = buildDataGridSaveStatements({
|
||||
databaseType: "postgres",
|
||||
tableMeta: {
|
||||
schema: "public",
|
||||
tableName: "visits",
|
||||
primaryKeys: [],
|
||||
},
|
||||
columns: ["id", "title", "published_at"],
|
||||
rows: [[63, "old title", null]],
|
||||
dirtyRows: [[0, [[1, "new title"]]]],
|
||||
deletedRows: [0],
|
||||
newRows: [[64, "fresh", "2026-05-20 12:00:00"]],
|
||||
});
|
||||
|
||||
assert.deepEqual(statements, [
|
||||
`UPDATE "public"."visits" SET "title" = 'new title' WHERE "id" = 63 AND "title" = 'old title' AND "published_at" IS NULL;`,
|
||||
`DELETE FROM "public"."visits" WHERE "id" = 63 AND "title" = 'old title' AND "published_at" IS NULL;`,
|
||||
`INSERT INTO "public"."visits" ("id", "title", "published_at") VALUES (64, 'fresh', '2026-05-20 12:00:00');`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("builds Dameng grid save statements without primary keys using row predicates", () => {
|
||||
const statements = buildDataGridSaveStatements({
|
||||
databaseType: "dameng",
|
||||
|
||||
@@ -108,27 +108,46 @@ test("describes table editing capabilities for special database engines", () =>
|
||||
});
|
||||
|
||||
assert.deepEqual(getDatabaseCapability("yashandb").tableData, {
|
||||
insert: false,
|
||||
updateRequiresPrimaryKey: true,
|
||||
deleteRequiresPrimaryKey: true,
|
||||
keylessRowPredicate: false,
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
keylessRowPredicate: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: false,
|
||||
transaction: true,
|
||||
});
|
||||
|
||||
assert.equal(getDatabaseCapability("oracle").syntheticKey, "oracle-rowid");
|
||||
assert.equal(getDatabaseCapability("neo4j").syntheticKey, "neo4j-element-id");
|
||||
});
|
||||
|
||||
test("uses conservative table editing defaults for unknown or keyless relational engines", () => {
|
||||
test("uses Navicat-style table editing defaults for updateable SQL table engines", () => {
|
||||
assert.deepEqual(getDatabaseCapability("postgres").tableData, {
|
||||
insert: false,
|
||||
updateRequiresPrimaryKey: true,
|
||||
deleteRequiresPrimaryKey: true,
|
||||
keylessRowPredicate: false,
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
keylessRowPredicate: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
});
|
||||
assert.deepEqual(getDatabaseCapability("mysql").tableData, {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
keylessRowPredicate: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
});
|
||||
assert.deepEqual(getDatabaseCapability("sqlite").tableData, {
|
||||
insert: true,
|
||||
updateRequiresPrimaryKey: false,
|
||||
deleteRequiresPrimaryKey: false,
|
||||
keylessRowPredicate: true,
|
||||
requiresTransactionalTableForExistingRows: false,
|
||||
transaction: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps conservative table editing defaults for unknown database types", () => {
|
||||
assert.deepEqual(getDatabaseCapability(undefined).tableData, {
|
||||
insert: false,
|
||||
updateRequiresPrimaryKey: true,
|
||||
|
||||
@@ -45,7 +45,7 @@ test("uses tbname and timestamp as TDengine editable keys", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("allows Hive table data editing even without declared primary keys", () => {
|
||||
test("allows updateable SQL table data editing even without declared primary keys", () => {
|
||||
assert.equal(isTableDataEditable("access", []), true);
|
||||
assert.equal(isTableDataEditable("sqlite", []), true);
|
||||
assert.equal(isTableDataEditable("duckdb", []), true);
|
||||
@@ -54,15 +54,16 @@ test("allows Hive table data editing even without declared primary keys", () =>
|
||||
assert.equal(isTableDataEditable("trino", []), true);
|
||||
assert.equal(isTableDataEditable("informix", []), true);
|
||||
assert.equal(isTableDataEditable("tdengine", []), true);
|
||||
assert.equal(isTableDataEditable("mysql", []), false);
|
||||
assert.equal(isTableDataEditable("mysql", []), true);
|
||||
assert.equal(isTableDataEditable("postgres", []), true);
|
||||
assert.equal(isTableDataEditable("postgres", ["id"]), true);
|
||||
});
|
||||
|
||||
test("does not use transactional grid saves for Hive", () => {
|
||||
test("does not use transactional grid saves for non-transactional engines", () => {
|
||||
assert.equal(supportsDataGridTransaction("hive"), false);
|
||||
assert.equal(supportsDataGridTransaction("trino"), false);
|
||||
assert.equal(supportsDataGridTransaction("jdbc"), false);
|
||||
assert.equal(supportsDataGridTransaction("yashandb"), false);
|
||||
assert.equal(supportsDataGridTransaction("yashandb"), true);
|
||||
assert.equal(supportsDataGridTransaction("postgres"), true);
|
||||
});
|
||||
|
||||
@@ -76,11 +77,13 @@ test("allows existing row edits according to database-specific key requirements"
|
||||
assert.equal(canEditExistingTableRows("hive", undefined), false);
|
||||
assert.equal(canEditExistingTableRows("trino", undefined, []), false);
|
||||
assert.equal(canEditExistingTableRows("trino", undefined, ["id"]), true);
|
||||
assert.equal(canEditExistingTableRows("sqlite", undefined, []), false);
|
||||
assert.equal(canEditExistingTableRows("mysql", undefined, []), true);
|
||||
assert.equal(canEditExistingTableRows("postgres", undefined, []), true);
|
||||
assert.equal(canEditExistingTableRows("sqlite", undefined, []), true);
|
||||
assert.equal(canEditExistingTableRows("sqlite", undefined, ["id"]), true);
|
||||
assert.equal(canEditExistingTableRows("duckdb", undefined, []), false);
|
||||
assert.equal(canEditExistingTableRows("duckdb", undefined, []), true);
|
||||
assert.equal(canEditExistingTableRows("duckdb", undefined, ["id"]), true);
|
||||
assert.equal(canEditExistingTableRows("informix", undefined, []), false);
|
||||
assert.equal(canEditExistingTableRows("informix", undefined, []), true);
|
||||
assert.equal(canEditExistingTableRows("informix", undefined, ["id"]), true);
|
||||
assert.equal(canEditExistingTableRows("tdengine", undefined, ["ts"]), false);
|
||||
assert.equal(canEditExistingTableRows("tdengine", undefined, [DBX_TDENGINE_TBNAME_COLUMN, "ts"]), true);
|
||||
|
||||
Reference in New Issue
Block a user