fix(editor): route grid edits through manual txn session
This commit is contained in:
6 files changed
+95
-4
No files matched your search
@@ -10,6 +10,7 @@ const mocks = vi.hoisted(() => ({
|
||||
executeConditionalUpdate: vi.fn(),
|
||||
cancelConditionalUpdate: vi.fn(),
|
||||
executeInTransaction: vi.fn(),
|
||||
executeInManualTransaction: vi.fn(),
|
||||
addHistory: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -19,6 +20,7 @@ vi.mock("@/lib/backend/api", () => ({
|
||||
executeConditionalUpdate: mocks.executeConditionalUpdate,
|
||||
cancelConditionalUpdate: mocks.cancelConditionalUpdate,
|
||||
executeInTransaction: mocks.executeInTransaction,
|
||||
executeInManualTransaction: mocks.executeInManualTransaction,
|
||||
unlockConnectionWrites: vi.fn(),
|
||||
lockConnectionWrites: vi.fn(),
|
||||
connectionWriteUnlockState: vi.fn().mockResolvedValue(0),
|
||||
@@ -586,11 +588,21 @@ describe("useDataGridEditor saveChanges reload", () => {
|
||||
mocks.executeConditionalUpdate.mockReset();
|
||||
mocks.cancelConditionalUpdate.mockReset();
|
||||
mocks.executeInTransaction.mockReset();
|
||||
mocks.executeInManualTransaction.mockReset();
|
||||
mocks.addHistory.mockReset();
|
||||
mocks.getConfig.mockReset();
|
||||
});
|
||||
|
||||
function createSaveTestEditor(options: { currentPage?: Ref<number>; prepareFullReload?: () => void; customSaveHandler?: { save: ReturnType<typeof vi.fn> } } = {}) {
|
||||
function createSaveTestEditor(
|
||||
options: {
|
||||
currentPage?: Ref<number>;
|
||||
prepareFullReload?: () => void;
|
||||
customSaveHandler?: { save: ReturnType<typeof vi.fn> };
|
||||
manualTransactionSessionId?: string;
|
||||
refreshSavedRows?: ReturnType<typeof vi.fn>;
|
||||
onManualTransactionMutation?: ReturnType<typeof vi.fn>;
|
||||
} = {},
|
||||
) {
|
||||
const emit = vi.fn();
|
||||
const currentPage = options.currentPage ?? ref(1);
|
||||
const result = ref<{ columns: string[]; rows: CellValue[][] }>({
|
||||
@@ -617,6 +629,8 @@ describe("useDataGridEditor saveChanges reload", () => {
|
||||
sourceColumns: computed(() => undefined),
|
||||
onExecuteSql: computed(() => undefined),
|
||||
customSaveHandler: computed(() => options.customSaveHandler),
|
||||
manualTransactionSessionId: computed(() => options.manualTransactionSessionId),
|
||||
onManualTransactionMutation: options.onManualTransactionMutation,
|
||||
sql: computed(() => undefined),
|
||||
searchText: ref(""),
|
||||
whereFilterInput: ref(""),
|
||||
@@ -629,6 +643,7 @@ describe("useDataGridEditor saveChanges reload", () => {
|
||||
cacheKey: computed(() => undefined),
|
||||
getRowItem: () => undefined,
|
||||
prepareFullReload: options.prepareFullReload,
|
||||
refreshSavedRows: options.refreshSavedRows,
|
||||
emit,
|
||||
});
|
||||
return { editor, emit, currentPage };
|
||||
@@ -647,6 +662,39 @@ describe("useDataGridEditor saveChanges reload", () => {
|
||||
expect(emit).toHaveBeenCalledWith("reload", undefined, "", undefined, undefined, 100, 0);
|
||||
});
|
||||
|
||||
it("saves query-result edits through the active manual transaction session", async () => {
|
||||
const statement = "UPDATE orders_test SET status='shipped' WHERE id=1";
|
||||
const refreshSavedRows = vi.fn().mockResolvedValue(true);
|
||||
const onManualTransactionMutation = vi.fn();
|
||||
mocks.prepareDataGridSave.mockResolvedValue({ statements: [statement], rollbackStatements: [] });
|
||||
mocks.executeInManualTransaction.mockResolvedValue([{ affected_rows: 1 }]);
|
||||
|
||||
const { editor, emit } = createSaveTestEditor({ manualTransactionSessionId: "txn-session-1", refreshSavedRows, onManualTransactionMutation });
|
||||
editor.dirtyRows.value.set(0, new Map([[1, "shipped"]]));
|
||||
|
||||
await editor.saveChanges();
|
||||
|
||||
expect(mocks.executeInManualTransaction).toHaveBeenCalledWith("txn-session-1", statement, "app", undefined);
|
||||
expect(onManualTransactionMutation).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.executeBatch).not.toHaveBeenCalled();
|
||||
expect(refreshSavedRows).not.toHaveBeenCalled();
|
||||
expect(emit).toHaveBeenCalledWith("reload", undefined, "", undefined, undefined, 100, 0);
|
||||
});
|
||||
|
||||
it("marks the manual transaction dirty before a result-grid mutation can fail", async () => {
|
||||
const onManualTransactionMutation = vi.fn();
|
||||
mocks.prepareDataGridSave.mockResolvedValue({ statements: ["UPDATE orders_test SET status='shipped' WHERE id=1"], rollbackStatements: [] });
|
||||
mocks.executeInManualTransaction.mockRejectedValue(new Error("Query timed out"));
|
||||
|
||||
const { editor } = createSaveTestEditor({ manualTransactionSessionId: "txn-session-1", onManualTransactionMutation });
|
||||
editor.dirtyRows.value.set(0, new Map([[1, "shipped"]]));
|
||||
|
||||
await editor.saveChanges();
|
||||
|
||||
expect(onManualTransactionMutation).toHaveBeenCalledTimes(1);
|
||||
expect(editor.saveError.value).toContain("Query timed out");
|
||||
});
|
||||
|
||||
it("executes a conditional update immediately, records affected rows, and reloads", async () => {
|
||||
mocks.getConfig.mockReturnValue({ id: "connection-1", name: "Local MySQL", db_type: "mysql" });
|
||||
mocks.executeConditionalUpdate.mockResolvedValue({ affected_rows: 7 });
|
||||
|
||||
@@ -103,6 +103,8 @@ export interface UseDataGridEditorOptions {
|
||||
canEditExistingRows?: ComputedRef<boolean>;
|
||||
onExecuteSql: ComputedRef<((sql: string) => Promise<void>) | undefined>;
|
||||
customSaveHandler?: ComputedRef<CustomSaveHandler | undefined>;
|
||||
manualTransactionSessionId?: ComputedRef<string | undefined>;
|
||||
onManualTransactionMutation?: () => void;
|
||||
sql: ComputedRef<string | undefined>;
|
||||
searchText: Ref<string>;
|
||||
whereFilterInput: Ref<string>;
|
||||
@@ -219,6 +221,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
canEditExistingRows = computed(() => true),
|
||||
onExecuteSql,
|
||||
customSaveHandler,
|
||||
manualTransactionSessionId = computed(() => undefined),
|
||||
sql,
|
||||
searchText,
|
||||
orderByInput,
|
||||
@@ -1818,8 +1821,19 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
statements: stmts,
|
||||
rollbackStatements: rollbackStmts,
|
||||
});
|
||||
|
||||
if (useTransaction.value && stmts.length > 1 && hasBackendSaveTarget.value) {
|
||||
if (manualTransactionSessionId.value && hasBackendSaveTarget.value) {
|
||||
options.onManualTransactionMutation?.();
|
||||
try {
|
||||
const results = await api.executeInManualTransaction(manualTransactionSessionId.value, stmts.join(";\n"), database.value ?? "", preparedSave?.executionSchema);
|
||||
apiResult = {
|
||||
affected_rows: results.reduce((total, result) => total + (result.affected_rows ?? 0), 0),
|
||||
};
|
||||
} catch (e: any) {
|
||||
saveError.value = await recordFailedDataGridHistory(stmts, rollbackStmts, start, snapshot, e);
|
||||
await finishInterruptedSaveChanges(snapshot);
|
||||
return;
|
||||
}
|
||||
} else if (useTransaction.value && stmts.length > 1 && hasBackendSaveTarget.value) {
|
||||
try {
|
||||
apiResult = await api.executeInTransaction(connectionId.value!, database.value ?? "", stmts, preparedSave?.executionSchema);
|
||||
} catch (e: any) {
|
||||
@@ -1854,7 +1868,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
||||
applyDirtyRowsToResult(snapshot);
|
||||
options.onResultPayloadMutated?.();
|
||||
let savedRowsRefreshed = false;
|
||||
if (!shouldReloadAfterSave && snapshot.dirtyRows.size > 0 && options.refreshSavedRows) {
|
||||
if (!manualTransactionSessionId.value && !shouldReloadAfterSave && snapshot.dirtyRows.size > 0 && options.refreshSavedRows) {
|
||||
try {
|
||||
savedRowsRefreshed = await options.refreshSavedRows({
|
||||
dirtyRows: snapshot.dirtyRows,
|
||||
|
||||
Reference in new issue
Block a user