fix(xugu): enable safe row editing without primary keys

This commit is contained in:
Elias authored and GitHub committed 2026-09-07 22:44:30 +08:00
1 parent 39b2d2a6ac
commit 3e75be87eb
17 files changed
+236 -50

No files matched your search

@@ -64,6 +64,21 @@ describe("editable query hidden primary keys", () => {
});
});
it("supports Xugu's unqualified ROWID expression for keyless base tables", () => {
expect(
buildQueryWithHiddenPrimaryKeys({
sql: "SELECT * FROM APP.USERS t WHERE t.ACTIVE = 1",
databaseType: "xugu",
primaryKeys: ["__DBX_ROWID"],
existingResultNames: ["ID", "NAME"],
sourceExpressions: { __DBX_ROWID: "ROWID" },
}),
).toEqual({
sql: 'SELECT *, ROWID AS "__DBX_PK_0" FROM APP.USERS t WHERE t.ACTIVE = 1',
projections: [{ sourceName: "__DBX_ROWID", alias: "__DBX_PK_0" }],
});
});
it("preserves an Oracle FOR UPDATE clause when appending a hidden row key", () => {
expect(
buildQueryWithHiddenPrimaryKeys({
@@ -3,6 +3,7 @@ import {
DBX_NEO4J_ELEMENT_ID_COLUMN,
DBX_ROWID_COLUMN,
DBX_TDENGINE_TBNAME_COLUMN,
canInsertTableRows,
canDeleteExistingTdengineRows,
canEditExistingTableRows,
canUseKeylessRowPredicate,
@@ -13,6 +14,7 @@ import {
isTdengineExistingRowReadonlyColumn,
isTableDataEditable,
supportsDataGridTransaction,
shouldIncludeSyntheticRowId,
usesSyntheticRowIdKey,
} from "@/lib/table/tableEditing";
import type { ColumnInfo, IndexInfo } from "@/types/database";
@@ -46,6 +48,29 @@ describe("tableEditing", () => {
expect(editablePrimaryKeys("oceanbase-oracle", [column("ID", true), column("NAME")], "TABLE")).toEqual(["ID"]);
});
it("uses Xugu ROWID for ordinary, partitioned, and temporary tables but not views", () => {
const columns = [column("ID"), column("VALUE")];
expect(editablePrimaryKeys("xugu", columns, "TABLE")).toEqual([DBX_ROWID_COLUMN]);
expect(editablePrimaryKeys("xugu", columns, "PARTITIONED TABLE")).toEqual([DBX_ROWID_COLUMN]);
expect(editablePrimaryKeys("xugu", columns, "TEMPORARY TABLE")).toEqual([DBX_ROWID_COLUMN]);
expect(editablePrimaryKeys("xugu", columns, "VIEW")).toEqual([]);
expect(usesSyntheticRowIdKey("xugu", [DBX_ROWID_COLUMN], "TABLE")).toBe(true);
expect(usesSyntheticRowIdKey("xugu", [DBX_ROWID_COLUMN], "PARTITIONED TABLE")).toBe(true);
expect(usesSyntheticRowIdKey("xugu", [DBX_ROWID_COLUMN], "TEMPORARY TABLE")).toBe(true);
expect(usesSyntheticRowIdKey("xugu", [DBX_ROWID_COLUMN], "VIEW")).toBe(false);
expect(isTableDataEditable("xugu", [DBX_ROWID_COLUMN], "TABLE")).toBe(true);
expect(isTableDataEditable("xugu", [DBX_ROWID_COLUMN], "VIEW")).toBe(false);
expect(canInsertTableRows("xugu")).toBe(true);
});
it("includes Xugu ROWID while cold table metadata has no declared primary keys", () => {
expect(shouldIncludeSyntheticRowId("xugu", [], "TABLE")).toBe(true);
expect(shouldIncludeSyntheticRowId("xugu", [], "PARTITIONED TABLE")).toBe(true);
expect(shouldIncludeSyntheticRowId("xugu", [], "TEMPORARY TABLE")).toBe(true);
expect(shouldIncludeSyntheticRowId("xugu", [], "VIEW")).toBe(false);
expect(shouldIncludeSyntheticRowId("oracle", [], "TABLE")).toBe(false);
});
it("treats view data tabs as readonly", () => {
expect(isTableDataEditable("oracle", [DBX_ROWID_COLUMN], "VIEW")).toBe(false);
});
@@ -1,7 +1,7 @@
import type { DatabaseType } from "@/types/database";
import { isSchemaAware, usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport";
export type SyntheticEditKey = "oracle-rowid" | "neo4j-element-id";
export type SyntheticEditKey = "oracle-rowid" | "xugu-rowid" | "neo4j-element-id";
export interface TableDataCapability {
insert: boolean;
@@ -158,6 +158,20 @@ const DATABASE_CAPABILITY_OVERRIDES: Partial<Record<DatabaseType, Partial<Databa
oracle: {
syntheticKey: "oracle-rowid",
},
xugu: {
// Xugu exposes a stable ROWID pseudo-column for base, partitioned and
// temporary tables. Keep this capability scoped to Xugu instead of
// broadening Oracle-compatible behavior for other drivers.
syntheticKey: "xugu-rowid",
tableData: {
insert: true,
updateRequiresPrimaryKey: false,
deleteRequiresPrimaryKey: false,
keylessRowPredicate: true,
requiresTransactionalTableForExistingRows: false,
transaction: true,
},
},
"oceanbase-oracle": {
syntheticKey: "oracle-rowid",
},
+14 -3
View File
@@ -19,7 +19,7 @@ export function editablePrimaryKeys(databaseType: DatabaseType | undefined, colu
if (isViewTableType(tableType)) return primaryKeys;
if (databaseType === "tdengine" && primaryKeys.length > 0 && isTdengineStableTableType(tableType)) return [DBX_TDENGINE_TBNAME_COLUMN, ...primaryKeys];
const syntheticKey = getDatabaseCapability(databaseType).syntheticKey;
if (syntheticKey === "oracle-rowid" && primaryKeys.length === 0) return [DBX_ROWID_COLUMN];
if ((syntheticKey === "oracle-rowid" || syntheticKey === "xugu-rowid") && primaryKeys.length === 0) return [DBX_ROWID_COLUMN];
if (syntheticKey === "neo4j-element-id" && primaryKeys.length === 0) return [DBX_NEO4J_ELEMENT_ID_COLUMN];
return primaryKeys;
}
@@ -93,12 +93,23 @@ export function hiveTablePropertiesIndicateTransactional(result: { rows: readonl
export function usesSyntheticRowIdKey(databaseType: DatabaseType | undefined, primaryKeys: string[], tableType?: string): boolean {
if (isViewTableType(tableType)) return false;
const syntheticKey = getDatabaseCapability(databaseType).syntheticKey;
return primaryKeys.length === 1 && ((syntheticKey === "oracle-rowid" && primaryKeys[0].toUpperCase() === DBX_ROWID_COLUMN) || (syntheticKey === "neo4j-element-id" && primaryKeys[0] === DBX_NEO4J_ELEMENT_ID_COLUMN));
return primaryKeys.length === 1 && (((syntheticKey === "oracle-rowid" || syntheticKey === "xugu-rowid") && primaryKeys[0].toUpperCase() === DBX_ROWID_COLUMN) || (syntheticKey === "neo4j-element-id" && primaryKeys[0] === DBX_NEO4J_ELEMENT_ID_COLUMN));
}
/**
* Table-data tabs may start before metadata has populated declared primary keys.
* Xugu base/partitioned/temp tables can still be addressed safely with ROWID,
* so request the hidden projection during that cold-cache window as well.
*/
export function shouldIncludeSyntheticRowId(databaseType: DatabaseType | undefined, primaryKeys: string[], tableType?: string): boolean {
if (isViewTableType(tableType)) return false;
if (usesSyntheticRowIdKey(databaseType, primaryKeys, tableType)) return true;
return databaseType === "xugu" && primaryKeys.length === 0;
}
export function isHiddenGridColumn(databaseType: DatabaseType | undefined, column: string, primaryKeys: string[], tableType?: string): boolean {
if (databaseType === "neo4j" && column === DBX_NEO4J_ELEMENT_ID_COLUMN) return true;
return usesSyntheticRowIdKey(databaseType, primaryKeys, tableType) && column.toUpperCase() === DBX_ROWID_COLUMN;
return shouldIncludeSyntheticRowId(databaseType, primaryKeys, tableType) && column.toUpperCase() === DBX_ROWID_COLUMN;
}
export function isTdengineExistingRowReadonlyColumn(databaseType: DatabaseType | undefined, column: string, columns: ColumnInfo[]): boolean {