fix(hive): gate row edits by transactional support

This commit is contained in:
t8y2
2026-05-13 20:17:15 +08:00
parent ea6b2bb36c
commit 2ba97869a1
4 changed files with 104 additions and 5 deletions
+49 -5
View File
@@ -68,7 +68,12 @@ import {
normalizeWhereInput,
quoteTableIdentifier,
} from "@/lib/tableSelectSql";
import { isHiddenGridColumn, usesSyntheticRowIdKey } from "@/lib/tableEditing";
import {
canEditExistingTableRows,
hiveTablePropertiesIndicateTransactional,
isHiddenGridColumn,
usesSyntheticRowIdKey,
} from "@/lib/tableEditing";
import { formatGridSqlLiteral } from "@/lib/dataGridSql";
import { matchesRowStatusFilter, type RowStatus, type RowStatusFilter } from "@/lib/gridRowStatus";
import { displayCellValue, type CellValue } from "@/lib/cellValue";
@@ -806,6 +811,34 @@ const canUseWhereSearch = computed(() => !!props.tableMeta && !!props.onExecuteS
const tableUsesSyntheticRowId = computed(() =>
usesSyntheticRowIdKey(props.databaseType, props.tableMeta?.primaryKeys ?? []),
);
const hiveTableTransactional = ref<boolean | undefined>(undefined);
const canEditExistingRows = computed(() => canEditExistingTableRows(props.databaseType, hiveTableTransactional.value));
watch(
() => [props.databaseType, props.connectionId, props.database, props.tableMeta?.schema, props.tableMeta?.tableName],
async () => {
if (props.databaseType !== "hive" || !props.connectionId || !props.database || !props.tableMeta) {
hiveTableTransactional.value = undefined;
return;
}
const table = qualifiedTableName({
databaseType: "hive",
schema: props.tableMeta.schema,
tableName: props.tableMeta.tableName,
});
try {
const result = await api.executeQuery(
props.connectionId,
props.database,
`SHOW TBLPROPERTIES ${table} ('transactional')`,
props.tableMeta.schema,
);
hiveTableTransactional.value = hiveTablePropertiesIndicateTransactional(result);
} catch {
hiveTableTransactional.value = false;
}
},
{ immediate: true },
);
const clientSearchText = computed(() => (searchText.value.trim() ? searchText.value : ""));
watch(clientSearchText, (value) => {
clearTimeout(_searchTimer);
@@ -899,6 +932,7 @@ const editor = useDataGridEditor({
connectionId: computed(() => props.connectionId),
database: computed(() => props.database),
tableMeta: computed(() => props.tableMeta),
canEditExistingRows,
onExecuteSql: computed(() => props.onExecuteSql),
customSave: computed(() => props.customSave),
sql: computed(() => props.sql),
@@ -952,6 +986,14 @@ const {
cleanupFrames,
} = editor;
function canEditRowItem(item: RowItem | undefined): boolean {
return !!props.editable && !!item && !item.isDeleted && (item.isNew || canEditExistingRows.value);
}
function canDeleteRowItem(item: RowItem | undefined): boolean {
return !!props.editable && !!item && !item.isDeleted && (item.isNew || canEditExistingRows.value);
}
async function onToolbarRefresh() {
if (transactionActive.value) {
discardChanges();
@@ -1186,7 +1228,7 @@ const activeCellDetail = computed(() => {
rawValue,
length: value === null ? 0 : String(value).length,
formattedJson,
isEditable: props.editable && !item.isDeleted,
isEditable: canEditRowItem(item),
};
});
@@ -1216,6 +1258,7 @@ function commitDetailEdit() {
}
if (item.sourceIndex === undefined) return;
if (!canEditExistingRows.value) return;
const oldVal = props.result.rows[item.sourceIndex]?.[detail.colIndex];
const newVal = coerceCellValue(detailEditValue.value, oldVal);
@@ -1253,6 +1296,7 @@ function setDetailNull() {
}
if (item.sourceIndex === undefined) return;
if (!canEditExistingRows.value) return;
if (!dirtyRows.value.has(item.sourceIndex)) dirtyRows.value.set(item.sourceIndex, new Map());
dirtyRows.value.get(item.sourceIndex)!.set(detail.colIndex, null);
dirtyRows.value = new Map(dirtyRows.value);
@@ -2331,12 +2375,12 @@ defineExpose({
'bg-yellow-500/10': item.isDirtyCol[actualColIdx],
'cell-selected': cellIsSelected(index, visibleColIdx),
'tabular-nums': typeof item.data[actualColIdx] === 'number',
'cursor-text hover:bg-accent/50': editable && !item.isDeleted,
'cursor-text hover:bg-accent/50': canEditRowItem(item),
'line-through': item.isDeleted,
}"
@mousedown="handleDataCellMousedown(index, visibleColIdx, item.id, $event)"
@mouseenter="extendCellSelection(index, visibleColIdx)"
@dblclick="editable && !item.isDeleted && startEdit(item.id, actualColIdx)"
@dblclick="canEditRowItem(item) && startEdit(item.id, actualColIdx)"
@contextmenu="onCellContext(item.id, index, actualColIdx, visibleColIdx)"
>
<template v-if="editingCell?.rowId === item.id && editingCell?.col === actualColIdx">
@@ -2664,7 +2708,7 @@ defineExpose({
{{ isMultiRow ? t("grid.restoreRows", { count: multiRowCount }) : t("grid.restoreRow") }}
</ContextMenuItem>
<ContextMenuItem
v-else
v-else-if="canDeleteRowItem(contextRowItem)"
class="text-destructive"
@click="isMultiRow ? requestDeleteRows(affectedRowIds()) : requestDeleteRow(contextRowItem.id)"
>
+9
View File
@@ -50,6 +50,7 @@ export interface UseDataGridEditorOptions {
}
| undefined
>;
canEditExistingRows?: ComputedRef<boolean>;
onExecuteSql: ComputedRef<((sql: string) => Promise<void>) | undefined>;
customSave?: ComputedRef<
| ((changes: {
@@ -94,6 +95,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
connectionId,
database,
tableMeta,
canEditExistingRows = computed(() => true),
onExecuteSql,
customSave,
sql,
@@ -256,6 +258,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
if (!editable.value) return;
const item = getRowItem(rowId);
if (!item || item.isDeleted) return;
if (!item.isNew && !canEditExistingRows.value) return;
isCancelling = false;
editingCell.value = { rowId, col: colIdx };
const val = item?.data[colIdx] ?? null;
@@ -291,6 +294,10 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
editingCell.value = null;
return;
}
if (!canEditExistingRows.value) {
editingCell.value = null;
return;
}
const oldVal = result.value.rows[item.sourceIndex]?.[col];
const newVal = coerceCellValue(editValue.value, oldVal);
@@ -320,6 +327,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
if (item.sourceIndex === undefined) return;
if (!canEditExistingRows.value) return;
const oldVal = result.value.rows[item.sourceIndex]?.[col];
const newVal = value === null ? null : coerceCellValue(value, oldVal);
@@ -410,6 +418,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
if (item.isNew && item.newIndex !== undefined) {
newRows.value.splice(item.newIndex, 1);
} else if (item.sourceIndex !== undefined) {
if (!canEditExistingRows.value) return;
dirtyRows.value.delete(item.sourceIndex);
deletedRows.value.add(item.sourceIndex);
}
+20
View File
@@ -19,6 +19,26 @@ export function supportsDataGridTransaction(databaseType: DatabaseType | undefin
return databaseType !== "hive";
}
export function canEditExistingTableRows(
databaseType: DatabaseType | undefined,
hiveTableTransactional?: boolean,
): boolean {
if (databaseType === "hive") return hiveTableTransactional === true;
return true;
}
export function hiveTablePropertiesIndicateTransactional(result: { rows: readonly (readonly unknown[])[] }): boolean {
return result.rows.some((row) => {
const name = String(row[0] ?? "")
.trim()
.toLowerCase();
const value = String(row[1] ?? "")
.trim()
.toLowerCase();
return name === "transactional" && value === "true";
});
}
export function usesSyntheticRowIdKey(databaseType: DatabaseType | undefined, primaryKeys: string[]): boolean {
return (
primaryKeys.length === 1 &&