fix(hive): allow non-transactional grid edits
This commit is contained in:
parent
3eb00b7fe2
commit
ea6b2bb36c
|
|
@ -17,6 +17,7 @@ const QueryChart = defineAsyncComponent(() => import("@/components/chart/QueryCh
|
|||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { canCancelQueryExecution, queryExecutionLabelKey } from "@/lib/queryExecutionState";
|
||||
import { databaseDisplayNameForTab } from "@/lib/tabPresentation";
|
||||
import { isTableDataEditable } from "@/lib/tableEditing";
|
||||
import type { QueryTab, ConnectionConfig } from "@/types/database";
|
||||
import type { SqlFormatDialect } from "@/lib/sqlFormatter";
|
||||
|
||||
|
|
@ -390,7 +391,7 @@ defineExpose({ focusSearch });
|
|||
:result="activeTab.result"
|
||||
:sql="activeTab.sql"
|
||||
:loading="activeTab.isExecuting"
|
||||
:editable="!!activeTab.tableMeta?.primaryKeys?.length"
|
||||
:editable="isTableDataEditable(activeConnection?.db_type, activeTab.tableMeta?.primaryKeys ?? [])"
|
||||
context="table-data"
|
||||
:initial-where-input="activeTab.whereInput"
|
||||
:database-type="activeConnection?.db_type"
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import {
|
|||
buildDataGridRollbackStatements,
|
||||
buildDataGridSaveStatements,
|
||||
dataGridSaveExecutionSchema,
|
||||
normalizeDataGridSaveError,
|
||||
validateDataGridSave,
|
||||
} from "@/lib/dataGridSql";
|
||||
import { rowStatusFilterAfterAddingRow, type RowStatusFilter } from "@/lib/gridRowStatus";
|
||||
import { supportsDataGridTransaction } from "@/lib/tableEditing";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
import type { ColumnInfo, DatabaseType } from "@/types/database";
|
||||
|
|
@ -125,7 +127,10 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
const saveError = ref("");
|
||||
|
||||
const useTransaction = computed(
|
||||
() => editable.value && (!!customSave?.value || (!!connectionId.value && !!database.value && !!tableMeta.value)),
|
||||
() =>
|
||||
editable.value &&
|
||||
supportsDataGridTransaction(databaseType.value) &&
|
||||
(!!customSave?.value || (!!connectionId.value && !!database.value && !!tableMeta.value)),
|
||||
);
|
||||
|
||||
function enterTransaction() {
|
||||
|
|
@ -536,7 +541,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
rows: result.value.rows,
|
||||
});
|
||||
} catch (e: any) {
|
||||
saveError.value = String(e.message || e);
|
||||
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
|
||||
isSaving.value = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -599,7 +604,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
dataGridSaveExecutionSchema(databaseType.value, tableMeta.value),
|
||||
);
|
||||
} catch (e: any) {
|
||||
saveError.value = String(e.message || e);
|
||||
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
|
||||
isSaving.value = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -607,7 +612,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
try {
|
||||
apiResult = await api.executeBatch(connectionId.value, database.value, stmts);
|
||||
} catch (e: any) {
|
||||
saveError.value = String(e.message || e);
|
||||
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
|
||||
isSaving.value = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -617,7 +622,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
await onExecuteSql.value(sqlStmt);
|
||||
}
|
||||
} catch (e: any) {
|
||||
saveError.value = String(e.message || e);
|
||||
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
|
||||
isSaving.value = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,6 +181,14 @@ export function dataGridSaveExecutionSchema(
|
|||
return tableMeta?.schema;
|
||||
}
|
||||
|
||||
export function normalizeDataGridSaveError(databaseType: DatabaseType | undefined, error: unknown): string {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (databaseType === "hive" && /Attempt to do update or delete|Error 10294/i.test(message)) {
|
||||
return "Hive UPDATE/DELETE are not enabled for this table or server. Add rows with INSERT, or enable ACID transactional tables in Hive before editing/deleting existing rows.";
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
export function formatGridSqlLiteral(value: GridCellValue, databaseType?: DatabaseType): string {
|
||||
if (value === null || value === undefined) return "NULL";
|
||||
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
||||
|
|
@ -197,6 +205,7 @@ function buildPrimaryKeyWhere(
|
|||
columns: string[],
|
||||
row: GridCellValue[],
|
||||
): string {
|
||||
if (databaseType === "hive" && primaryKeys.length === 0) return buildRowWhere(databaseType, columns, row);
|
||||
return primaryKeys
|
||||
.map((primaryKey) => {
|
||||
const value = row[columns.indexOf(primaryKey)];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,15 @@ export function editablePrimaryKeys(databaseType: DatabaseType | undefined, colu
|
|||
return primaryKeys;
|
||||
}
|
||||
|
||||
export function isTableDataEditable(databaseType: DatabaseType | undefined, primaryKeys: string[]): boolean {
|
||||
if (databaseType === "hive") return true;
|
||||
return primaryKeys.length > 0;
|
||||
}
|
||||
|
||||
export function supportsDataGridTransaction(databaseType: DatabaseType | undefined): boolean {
|
||||
return databaseType !== "hive";
|
||||
}
|
||||
|
||||
export function usesSyntheticRowIdKey(databaseType: DatabaseType | undefined, primaryKeys: string[]): boolean {
|
||||
return (
|
||||
primaryKeys.length === 1 &&
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
buildDataGridRollbackStatements,
|
||||
buildDataGridSaveStatements,
|
||||
dataGridSaveExecutionSchema,
|
||||
normalizeDataGridSaveError,
|
||||
validateDataGridSave,
|
||||
} from "../src/lib/dataGridSql.ts";
|
||||
import { DBX_NEO4J_ELEMENT_ID_COLUMN } from "../src/lib/tableEditing.ts";
|
||||
|
|
@ -59,6 +60,26 @@ test("builds Hive grid save statements with backtick identifiers", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test("builds Hive grid save statements without primary keys using row predicates", () => {
|
||||
const statements = buildDataGridSaveStatements({
|
||||
databaseType: "hive",
|
||||
tableMeta: {
|
||||
tableName: "departments",
|
||||
primaryKeys: [],
|
||||
},
|
||||
columns: ["id", "name", "location"],
|
||||
rows: [[10, "Sales", null]],
|
||||
dirtyRows: [[0, [[1, "Marketing"]]]],
|
||||
deletedRows: [0],
|
||||
newRows: [],
|
||||
});
|
||||
|
||||
assert.deepEqual(statements, [
|
||||
"UPDATE `departments` SET `name` = 'Marketing' WHERE `id` = 10 AND `name` = 'Sales' AND `location` IS NULL;",
|
||||
"DELETE FROM `departments` WHERE `id` = 10 AND `name` = 'Sales' AND `location` IS NULL;",
|
||||
]);
|
||||
});
|
||||
|
||||
test("uses Oracle ROWID as a synthetic key without writing it as a normal column", () => {
|
||||
const statements = buildDataGridSaveStatements({
|
||||
databaseType: "oracle",
|
||||
|
|
@ -141,6 +162,18 @@ test("skips current_schema setup for Oracle data grid saves", () => {
|
|||
);
|
||||
});
|
||||
|
||||
test("normalizes Hive ACID update and delete errors", () => {
|
||||
const error = normalizeDataGridSaveError(
|
||||
"hive",
|
||||
"Statement 1 failed: Agent RPC error (-1): Error while compiling statement: FAILED: SemanticException [Error 10294]: Attempt to do update or delete using transaction manager that does not support these operations.. Previous 0 statement(s) may have been committed.",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
error,
|
||||
"Hive UPDATE/DELETE are not enabled for this table or server. Add rows with INSERT, or enable ACID transactional tables in Hive before editing/deleting existing rows.",
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects NULL writes to non-null table columns", () => {
|
||||
const error = validateDataGridSave({
|
||||
columns: ["ID", "CREATED_AT", "CITY"],
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import {
|
|||
DBX_ROWID_COLUMN,
|
||||
editablePrimaryKeys,
|
||||
isHiddenGridColumn,
|
||||
isTableDataEditable,
|
||||
supportsDataGridTransaction,
|
||||
usesSyntheticRowIdKey,
|
||||
} from "../src/lib/tableEditing.ts";
|
||||
import type { ColumnInfo } from "../src/types/database.ts";
|
||||
|
|
@ -32,6 +34,17 @@ test("does not synthesize ROWID for non-Oracle keyless tables", () => {
|
|||
assert.deepEqual(editablePrimaryKeys("mysql", [column("ID"), column("CITY")]), []);
|
||||
});
|
||||
|
||||
test("allows Hive table data editing even without declared primary keys", () => {
|
||||
assert.equal(isTableDataEditable("hive", []), true);
|
||||
assert.equal(isTableDataEditable("mysql", []), false);
|
||||
assert.equal(isTableDataEditable("postgres", ["id"]), true);
|
||||
});
|
||||
|
||||
test("does not use transactional grid saves for Hive", () => {
|
||||
assert.equal(supportsDataGridTransaction("hive"), false);
|
||||
assert.equal(supportsDataGridTransaction("postgres"), true);
|
||||
});
|
||||
|
||||
test("uses elementId as Neo4j editable key when labels have no primary key", () => {
|
||||
assert.deepEqual(editablePrimaryKeys("neo4j", [column("name"), column("role")]), [DBX_NEO4J_ELEMENT_ID_COLUMN]);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue