fix(report): persist longform appendix on self-hosted upsert (BUG-576)
Engine calls already returned markdown; the worker failed because local postgres upsert required onConflict and the appendix write omitted it. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -39,6 +39,29 @@ function identifier(value: string): string {
|
||||
return `"${normalized}"`;
|
||||
}
|
||||
|
||||
export function upsertConflictColumns(options?: { onConflict?: string }): string[] {
|
||||
return (options?.onConflict ?? "")
|
||||
.split(",")
|
||||
.map((column) => column.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
async function primaryKeyColumns(client: PoolClient, table: string): Promise<string[]> {
|
||||
identifier(table);
|
||||
const result = await client.query<{ column_name: string }>(
|
||||
`
|
||||
select a.attname as column_name
|
||||
from pg_index i
|
||||
join pg_attribute a on a.attrelid = i.indrelid and a.attnum = any(i.indkey)
|
||||
where i.indrelid = format('%I.%I', 'public', $1::text)::regclass
|
||||
and i.indisprimary
|
||||
order by array_position(i.indkey, a.attnum)
|
||||
`,
|
||||
[table],
|
||||
);
|
||||
return result.rows.map((row) => row.column_name);
|
||||
}
|
||||
|
||||
function queryError(error: unknown): QueryError {
|
||||
const value = error as PostgresError;
|
||||
return {
|
||||
@@ -197,11 +220,9 @@ class LocalPostgresQueryBuilder implements PromiseLike<QueryResult> {
|
||||
|
||||
upsert(
|
||||
value: Record<string, unknown> | readonly Record<string, unknown>[],
|
||||
options: { onConflict: string },
|
||||
options?: { onConflict?: string },
|
||||
) {
|
||||
const conflict = options.onConflict
|
||||
.split(",")
|
||||
.map((column) => column.trim());
|
||||
const conflict = upsertConflictColumns(options);
|
||||
conflict.forEach(identifier);
|
||||
this.mutation = { kind: "upsert", rows: records(value), conflict };
|
||||
return this;
|
||||
@@ -402,13 +423,15 @@ class LocalPostgresQueryBuilder implements PromiseLike<QueryResult> {
|
||||
);
|
||||
sql = `insert into public.${identifier(this.table)} (${columns.map(identifier).join(", ")}) values ${valueGroups.join(", ")}`;
|
||||
if (this.mutation.kind === "upsert") {
|
||||
const updates = columns.filter(
|
||||
(column) =>
|
||||
!this.mutation ||
|
||||
this.mutation.kind !== "upsert" ||
|
||||
!this.mutation.conflict.includes(column),
|
||||
);
|
||||
sql += ` on conflict (${this.mutation.conflict.map(identifier).join(", ")}) do ${
|
||||
let conflict = this.mutation.conflict;
|
||||
if (conflict.length === 0) {
|
||||
conflict = await primaryKeyColumns(client, this.table);
|
||||
}
|
||||
if (conflict.length === 0) {
|
||||
throw new Error("upsert requires a conflict target");
|
||||
}
|
||||
const updates = columns.filter((column) => !conflict.includes(column));
|
||||
sql += ` on conflict (${conflict.map(identifier).join(", ")}) do ${
|
||||
updates.length === 0
|
||||
? "nothing"
|
||||
: `update set ${updates.map((column) => `${identifier(column)} = excluded.${identifier(column)}`).join(", ")}`
|
||||
|
||||
Reference in New Issue
Block a user