fix(housekeeping): lock poll questions by primary key
This commit is contained in:
1 parent
6e16b948e6
commit
e9680775f9
2 files changed
+70
-23
No files matched your search
+61
-21
@@ -15,9 +15,15 @@ const sqlMocks = vi.hoisted(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const database = vi.hoisted(() => {
|
const database = vi.hoisted(() => {
|
||||||
|
type SelectedResult =
|
||||||
|
| Record<string, unknown>
|
||||||
|
| readonly Record<string, unknown>[];
|
||||||
let selected: Record<string, unknown> = { id: 7, title: "Existing" };
|
let selected: Record<string, unknown> = { id: 7, title: "Existing" };
|
||||||
let selectedQueue: Record<string, unknown>[] = [];
|
let selectedQueue: SelectedResult[] = [];
|
||||||
let selectedTable: string | undefined;
|
let selectedTable: string | undefined;
|
||||||
|
let selectedCondition:
|
||||||
|
| { operator: string; column: unknown; value: unknown }
|
||||||
|
| undefined;
|
||||||
let lockedTables: Array<readonly [string | undefined, string]> = [];
|
let lockedTables: Array<readonly [string | undefined, string]> = [];
|
||||||
let mutationSteps: string[] = [];
|
let mutationSteps: string[] = [];
|
||||||
const insertedValues = vi.fn(async (_values: Record<string, unknown>) => [
|
const insertedValues = vi.fn(async (_values: Record<string, unknown>) => [
|
||||||
@@ -29,16 +35,33 @@ const database = vi.hoisted(() => {
|
|||||||
values,
|
values,
|
||||||
}));
|
}));
|
||||||
const update = vi.fn(() => ({ set }));
|
const update = vi.fn(() => ({ set }));
|
||||||
const nextSelected = () => [selectedQueue.shift() ?? selected];
|
const nextSelected = () => {
|
||||||
|
const result = selectedQueue.shift() ?? selected;
|
||||||
|
return Array.isArray(result) ? [...result] : [result];
|
||||||
|
};
|
||||||
const limit = vi.fn(async () => nextSelected());
|
const limit = vi.fn(async () => nextSelected());
|
||||||
const lock = vi.fn(async (strength: string) => {
|
const lock = vi.fn(async (strength: string) => {
|
||||||
lockedTables.push([selectedTable, strength]);
|
lockedTables.push([selectedTable, strength]);
|
||||||
mutationSteps.push(`lock:${selectedTable ?? "unknown"}`);
|
mutationSteps.push(
|
||||||
|
`lock:${selectedTable ?? "unknown"}:${selectedCondition?.operator ?? "none"}:${String(selectedCondition?.column ?? "none")}:${String(selectedCondition?.value ?? "none")}`,
|
||||||
|
);
|
||||||
return nextSelected();
|
return nextSelected();
|
||||||
});
|
});
|
||||||
const where = vi.fn(() => ({ for: lock, limit }));
|
const orderBy = vi.fn(async (column: unknown) => {
|
||||||
|
mutationSteps.push(
|
||||||
|
`read:${selectedTable ?? "unknown"}:${selectedCondition?.operator ?? "none"}:${String(selectedCondition?.column ?? "none")}:orderBy:${String(column)}`,
|
||||||
|
);
|
||||||
|
return nextSelected();
|
||||||
|
});
|
||||||
|
const where = vi.fn(
|
||||||
|
(condition: { operator: string; column: unknown; value: unknown }) => {
|
||||||
|
selectedCondition = condition;
|
||||||
|
return { for: lock, limit, orderBy };
|
||||||
|
},
|
||||||
|
);
|
||||||
const from = vi.fn((table: unknown) => {
|
const from = vi.fn((table: unknown) => {
|
||||||
selectedTable = (table as { tableName?: string }).tableName;
|
selectedTable = (table as { tableName?: string }).tableName;
|
||||||
|
selectedCondition = undefined;
|
||||||
return { where };
|
return { where };
|
||||||
});
|
});
|
||||||
const select = vi.fn(() => ({ from }));
|
const select = vi.fn(() => ({ from }));
|
||||||
@@ -75,10 +98,11 @@ const database = vi.hoisted(() => {
|
|||||||
selected = value;
|
selected = value;
|
||||||
selectedQueue = [];
|
selectedQueue = [];
|
||||||
selectedTable = undefined;
|
selectedTable = undefined;
|
||||||
|
selectedCondition = undefined;
|
||||||
lockedTables = [];
|
lockedTables = [];
|
||||||
mutationSteps = [];
|
mutationSteps = [];
|
||||||
},
|
},
|
||||||
queueSelected(...values: Record<string, unknown>[]) {
|
queueSelected(...values: SelectedResult[]) {
|
||||||
selectedQueue = [...values];
|
selectedQueue = [...values];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -86,8 +110,16 @@ const database = vi.hoisted(() => {
|
|||||||
|
|
||||||
vi.mock("drizzle-orm", () => ({
|
vi.mock("drizzle-orm", () => ({
|
||||||
count: vi.fn(() => "count"),
|
count: vi.fn(() => "count"),
|
||||||
eq: vi.fn(),
|
eq: vi.fn((column: unknown, value: unknown) => ({
|
||||||
inArray: vi.fn(),
|
operator: "eq",
|
||||||
|
column,
|
||||||
|
value,
|
||||||
|
})),
|
||||||
|
inArray: vi.fn((column: unknown, value: unknown) => ({
|
||||||
|
operator: "inArray",
|
||||||
|
column,
|
||||||
|
value,
|
||||||
|
})),
|
||||||
sql: Object.assign(sqlMocks.tagged, {
|
sql: Object.assign(sqlMocks.tagged, {
|
||||||
join: sqlMocks.join,
|
join: sqlMocks.join,
|
||||||
raw: sqlMocks.raw,
|
raw: sqlMocks.raw,
|
||||||
@@ -173,17 +205,22 @@ describe("Content database mutation runtime partial updates", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deletes poll votes, questions, and the poll in child-first order", async () => {
|
it("locks poll questions by ascending primary key before child-first deletion", async () => {
|
||||||
database.selected({
|
database.queueSelected(
|
||||||
id: 7,
|
{
|
||||||
title: "Existing",
|
id: 7,
|
||||||
description: null,
|
title: "Existing",
|
||||||
status: "draft",
|
description: null,
|
||||||
showResults: 1,
|
status: "draft",
|
||||||
multipleChoice: 0,
|
showResults: 1,
|
||||||
startsAt: null,
|
multipleChoice: 0,
|
||||||
endsAt: null,
|
startsAt: null,
|
||||||
});
|
endsAt: null,
|
||||||
|
},
|
||||||
|
[{ id: 4 }, { id: 9 }],
|
||||||
|
{ id: 4 },
|
||||||
|
{ id: 9 },
|
||||||
|
);
|
||||||
|
|
||||||
await executeContentDatabaseMutation(
|
await executeContentDatabaseMutation(
|
||||||
"poll.change",
|
"poll.change",
|
||||||
@@ -200,10 +237,13 @@ describe("Content database mutation runtime partial updates", () => {
|
|||||||
expect(database.lockedTables()).toEqual([
|
expect(database.lockedTables()).toEqual([
|
||||||
["WebsitePoll", "update"],
|
["WebsitePoll", "update"],
|
||||||
["WebsitePollQuestion", "update"],
|
["WebsitePollQuestion", "update"],
|
||||||
|
["WebsitePollQuestion", "update"],
|
||||||
]);
|
]);
|
||||||
expect(database.mutationSteps()).toEqual([
|
expect(database.mutationSteps()).toEqual([
|
||||||
"lock:WebsitePoll",
|
"lock:WebsitePoll:eq:id:7",
|
||||||
"lock:WebsitePollQuestion",
|
"read:WebsitePollQuestion:eq:pollId:orderBy:id",
|
||||||
|
"lock:WebsitePollQuestion:eq:id:4",
|
||||||
|
"lock:WebsitePollQuestion:eq:id:9",
|
||||||
"delete:WebsitePollVote",
|
"delete:WebsitePollVote",
|
||||||
"delete:WebsitePollQuestion",
|
"delete:WebsitePollQuestion",
|
||||||
"delete:WebsitePoll",
|
"delete:WebsitePoll",
|
||||||
@@ -241,7 +281,7 @@ describe("Content database mutation runtime partial updates", () => {
|
|||||||
["WebsitePollQuestion", "update"],
|
["WebsitePollQuestion", "update"],
|
||||||
]);
|
]);
|
||||||
expect(database.mutationSteps()).toEqual([
|
expect(database.mutationSteps()).toEqual([
|
||||||
"lock:WebsitePollQuestion",
|
"lock:WebsitePollQuestion:eq:id:7",
|
||||||
"delete:WebsitePollVote",
|
"delete:WebsitePollVote",
|
||||||
"delete:WebsitePollQuestion",
|
"delete:WebsitePollQuestion",
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -635,11 +635,18 @@ async function pollChange(
|
|||||||
.for("update");
|
.for("update");
|
||||||
if (!existing) throw notFound();
|
if (!existing) throw notFound();
|
||||||
if (action === "delete") {
|
if (action === "delete") {
|
||||||
await connection
|
const questionIds = await connection
|
||||||
.select({ id: WebsitePollQuestion.id })
|
.select({ id: WebsitePollQuestion.id })
|
||||||
.from(WebsitePollQuestion)
|
.from(WebsitePollQuestion)
|
||||||
.where(eq(WebsitePollQuestion.pollId, id))
|
.where(eq(WebsitePollQuestion.pollId, id))
|
||||||
.for("update");
|
.orderBy(WebsitePollQuestion.id);
|
||||||
|
for (const question of questionIds) {
|
||||||
|
await connection
|
||||||
|
.select({ id: WebsitePollQuestion.id })
|
||||||
|
.from(WebsitePollQuestion)
|
||||||
|
.where(eq(WebsitePollQuestion.id, question.id))
|
||||||
|
.for("update");
|
||||||
|
}
|
||||||
await connection
|
await connection
|
||||||
.delete(WebsitePollVote)
|
.delete(WebsitePollVote)
|
||||||
.where(
|
.where(
|
||||||
|
|||||||
Reference in new issue
Block a user