fix: prevent source deletion failures caused by UUID and text type mismatch

This commit is contained in:
FFXN 2026-08-06 14:40:30 +08:00
parent 18ae46f7a3
commit b68289f24a
3 changed files with 38 additions and 3 deletions

View File

@ -1,6 +1,6 @@
{
"schemaVersion": 5,
"subtreeTree": "42fa6cb607aff009cc84df50b2f92e90b05dcc4c",
"subtreeTree": "ffffd2c76ac649416f17a1340f73c2da042a99f4",
"openapiSha256": "2dbc5f58e4300f2dd9885a4867f02d0f3b2a65cd7b2b067a1ee6ba3cbd5210d3",
"capabilityV2AuthManifestSha256": "fc0a47e23cce12544882f0298522b4933002e892b84ce1815df7e81d36a7a0c7",
"capabilityV2AuthTestVectorSha256": "ae0de37b1ff05c40f905cf17a7b410d8971acacf64db07d5ee3d6fecfa559ce3",

View File

@ -1892,6 +1892,37 @@ describe("database durable deletion target capabilities", () => {
).toBe(true);
});
it(`uses a UUID source predicate while deleting source Overview residue (${dialect})`, async () => {
const calls: DatabaseExecuteInput[] = [];
const execute = async (input: DatabaseExecuteInput): Promise<DatabaseExecuteResult> => {
calls.push(input);
return result([]);
};
await capabilitiesFor(dialect, execute).deleteDerivedDataPage({
job: job({ targetType: "source" }),
limit: 7,
signal: new AbortController().signal,
});
const overviewQueries = calls.filter(
(call) =>
call.operation === "select" &&
(call.tableName === "knowledge_space_attention_states" ||
call.tableName === "knowledge_space_activity_events") &&
call.sql.includes("logical_documents"),
);
expect(overviewQueries.length).toBeGreaterThan(0);
for (const query of overviewQueries) {
const targetPosition = query.params.length === 4 ? 4 : 3;
expect(query.sql).toContain(
dialect === "postgres"
? `"source_id" = CAST($${targetPosition} AS UUID)`
: "`source_id` = ?",
);
}
});
it(`removes only an unpublished aggregate exactly owned by the deleted asset (${dialect})`, async () => {
const prefix = `tenant-a/spaces/${spaceId}`;
const rawObjectKey = `${prefix}/documents/${targetDocumentId}/raw.md`;

View File

@ -833,7 +833,9 @@ async function deleteOverviewResiduePage(
database.dialect === "postgres"
? `CAST(${q("id")} AS TEXT)`
: `CAST(${q("id")} AS CHAR(36))`
} FROM ${q("logical_documents")} WHERE ${q("tenant_id")} = ${p(1)} AND ${q("knowledge_space_id")} = ${p(2)} AND ${q("source_id")} = ${p(4)})))`;
} FROM ${q("logical_documents")} WHERE ${q("tenant_id")} = ${p(1)} AND ${q("knowledge_space_id")} = ${p(2)} AND ${q("source_id")} = ${
database.dialect === "postgres" ? `CAST(${p(4)} AS UUID)` : p(4)
})))`;
} else if (job.targetType === "logical_document") {
params.push(job.targetId);
targetPredicate = ` AND ${q("resource_type")} = 'document' AND ${q("resource_id")} = ${p(4)}`;
@ -885,7 +887,9 @@ async function hasOverviewResidue(
database.dialect === "postgres"
? `CAST(${q("id")} AS TEXT)`
: `CAST(${q("id")} AS CHAR(36))`
} FROM ${q("logical_documents")} WHERE ${q("tenant_id")} = ${p(1)} AND ${q("knowledge_space_id")} = ${p(2)} AND ${q("source_id")} = ${p(3)})))`;
} FROM ${q("logical_documents")} WHERE ${q("tenant_id")} = ${p(1)} AND ${q("knowledge_space_id")} = ${p(2)} AND ${q("source_id")} = ${
database.dialect === "postgres" ? `CAST(${p(3)} AS UUID)` : p(3)
})))`;
} else if (job.targetType === "logical_document") {
params.push(job.targetId);
targetPredicate = ` AND ${q("resource_type")} = 'document' AND ${q("resource_id")} = ${p(3)}`;