From 9aa77ab991aa7d886eaaa2ecec10749e852f5fe6 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Fri, 19 Dec 2025 18:28:28 +0100 Subject: [PATCH] Fix composite upsert (#16718) Doing an upsert on existing value, composite field not updated properly. ``` Input: { id: "08ca34fe-fc39-474f-adac-4d89f844e922", name: "tom", linkedinLink: { primaryLinkUrl: "https://www.linkedin.com/in/etienneyaouni1982", primaryLinkLabel: "etienne", secondaryLinks: null, }, } ``` Building `overwrites` for upsert forgets `linkedinLink` because column names are not flattened yet. We don't want to call formatData yet on the input, because this is heavy. Overriding `overwrites` on execute. --- .../workspace-insert-query-builder.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts index f79c4e17f6..4968334f35 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts @@ -115,6 +115,40 @@ export class WorkspaceInsertQueryBuilder< shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, }); + // Fix overwrites for composite fields - valuesSet contains formatted/flattened column names + // but overwrites was computed before formatData, missing composite field columns + if ( + isDefined(this.expressionMap.onUpdate?.overwrite) && + isDefined(this.expressionMap.valuesSet) + ) { + const valuesArray = Array.isArray(this.expressionMap.valuesSet) + ? this.expressionMap.valuesSet + : [this.expressionMap.valuesSet]; + + const allValueKeys = new Set( + valuesArray.flatMap((value) => Object.keys(value)), + ); + + const mainAliasMetadata = this.expressionMap.mainAlias?.metadata; + + if (mainAliasMetadata) { + const missingColumns = mainAliasMetadata.columns + .filter( + (col) => + allValueKeys.has(col.databaseName) && + !this.expressionMap.onUpdate.overwrite!.includes( + col.databaseName, + ), + ) + .map((col) => col.databaseName); + + this.expressionMap.onUpdate.overwrite = [ + ...this.expressionMap.onUpdate.overwrite, + ...missingColumns, + ]; + } + } + const mainAliasTarget = this.getMainAliasTarget(); const objectMetadata = getObjectMetadataFromEntityTarget(