fix(grid): 单条数据修改不再强制开启事务

Co-authored-by: zipg <4047349+zipg@users.noreply.github.com>
This commit is contained in:
zipg 2026-07-16 15:32:03 +08:00 committed by GitHub
parent fab1fa9195
commit 79c682679d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 86 additions and 3 deletions

View File

@ -1270,7 +1270,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
rollbackStatements: rollbackStmts,
});
if (useTransaction.value && hasBackendSaveTarget.value) {
if (useTransaction.value && stmts.length > 1 && hasBackendSaveTarget.value) {
try {
apiResult = await api.executeInTransaction(connectionId.value!, database.value ?? "", stmts, preparedSave?.executionSchema);
} catch (e: any) {

View File

@ -1062,7 +1062,7 @@ test("saving manually typed JSON arrays from a Postgres array column uses array
assert.deepEqual(executedSql, [`UPDATE "articles" SET "tags" = '{"draft","发布"}' WHERE "id" = 1;`]);
});
test("failed table data save records a failed history entry", async () => {
test("single-statement table data save uses auto-commit and records a failed history entry", async () => {
setActivePinia(createPinia());
installBrowserTestGlobals();
@ -1082,7 +1082,7 @@ test("failed table data save records a failed history entry", async () => {
{ status: 200, headers: { "Content-Type": "application/json" } },
);
}
if (url === "/api/query/execute-in-transaction") {
if (url === "/api/query/execute-batch") {
return new Response(permissionError, { status: 500 });
}
if (url === "/api/history/save") {
@ -1150,6 +1150,89 @@ test("failed table data save records a failed history entry", async () => {
assert.equal(historyEntry.sql, `UPDATE "pp_questions" SET "title" = 'New title' WHERE "id" = 1;`);
});
test("multi-statement table data save remains transactional", async () => {
setActivePinia(createPinia());
installBrowserTestGlobals();
const executionEndpoints: string[] = [];
globalThis.fetch = (async (input, init) => {
const url = String(input);
if (url === "/api/query/prepare-data-grid-save") {
const body = JSON.parse(String(init?.body ?? "{}"));
const options = body.options as DataGridSaveStatementOptions;
return new Response(
JSON.stringify({
statements: mockPreparedSaveStatements(options),
rollbackStatements: [],
executionSchema: options.tableMeta.schema,
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
}
if (url === "/api/query/execute-in-transaction" || url === "/api/query/execute-batch") {
executionEndpoints.push(url);
return new Response(JSON.stringify({ affected_rows: 2 }), { status: 200, headers: { "Content-Type": "application/json" } });
}
if (url === "/api/history/save") {
return new Response("null", { status: 200, headers: { "Content-Type": "application/json" } });
}
return new Response(`unexpected request: ${url}`, { status: 500 });
}) as typeof fetch;
const result = computed(() => ({
columns: ["id", "title"],
rows: [
[1, "Old title 1"],
[2, "Old title 2"],
] as CellValue[][],
}));
const rowStatusFilter = ref<"all" | "changed" | "edited" | "new" | "deleted">("all");
const editor = useDataGridEditor({
result,
editable: computed(() => true),
databaseType: computed(() => "mysql"),
connectionId: computed(() => "conn-1"),
database: computed(() => "app_db"),
tableMeta: computed(() => ({
tableName: "pp_questions",
columns: [column("id", true), column("title")],
primaryKeys: ["id"],
})),
onExecuteSql: computed(() => undefined),
customSaveHandler: computed(() => undefined),
sql: computed(() => "SELECT id, title FROM pp_questions"),
searchText: ref(""),
whereFilterInput: ref(""),
currentWhereInput: computed(() => undefined),
orderByInput: ref(""),
rowStatusFilter,
pageSize: ref(50),
currentPage: ref(1),
getRowItem: (rowId) => {
const row = result.value.rows[rowId];
if (!row) return undefined;
return {
id: rowId,
sourceIndex: rowId,
data: row,
isNew: false,
isDeleted: false,
isDirtyCol: [false, false],
status: "clean",
};
},
emit: () => {},
});
editor.applyCellValue(0, 1, "New title 1");
editor.applyCellValue(1, 1, "New title 2");
await editor.saveChanges();
assert.deepEqual(executionEndpoints, ["/api/query/execute-in-transaction"]);
assert.equal(editor.saveError.value, "");
assert.equal(editor.dirtyRows.value.size, 0);
});
test("quick entry off keeps blur edits pending without saving", async () => {
setActivePinia(createPinia());
installBrowserTestGlobals();