Skip to content

Commit d51d982

Browse files
sonarly[bot]Sonarly Claude Code
andauthored
Fix: Database query on opportunity table (#20017)
## Automated fix for [bug 30463](https://sonarly.com/issue/30463?type=bug) **Severity:** `critical` ### Summary When createMany is called with upsert:true and records lack pre-assigned IDs, findExistingRecords() executes a SELECT with no WHERE clause, scanning the entire table. This caused an 11-second transaction on the opportunity table (2.9s query + 7.7s JS processing). ### Root Cause 1. WHY was the transaction 11 seconds? Because a SELECT on the opportunity table took 2.9s and its result processing took 7.7s. 2. WHY did the SELECT take 2.9s? Because it was a full table scan with NO WHERE clause, fetching every record (including soft-deleted). 3. WHY was there no WHERE clause? Because findExistingRecords() in common-create-many-query-runner.service.ts calls buildWhereConditions() which returned an empty array, so no .orWhere() was applied to the query builder before .getMany() was called. 4. WHY did buildWhereConditions return empty? Because the input records had no pre-assigned IDs and the opportunity object has no other unique fields, so there were no conflicting field values to build conditions from. 5. WHY wasn't the empty-conditions case guarded? The findExistingRecords() method was written without an early-return check for empty whereConditions — it always executes the query regardless. **Introduced by:** etiennejouan on 2025-10-15 in commit [`4ae2999`](4ae2999) > Common Api - createOne/Many (#15083) ### Suggested Fix Added an early return in findExistingRecords() when buildWhereConditions() returns an empty array. When no WHERE conditions exist (because input records lack values for any unique/conflicting field), the method now returns an empty array immediately instead of executing a SELECT with no WHERE clause that scans the entire table. This prevents the O(n) full table scan + O(n) JS result processing that caused the 11-second transaction. The downstream categorizeRecords() correctly handles empty existingRecords by classifying all input records as recordsToInsert. --- *Generated by [Sonarly](https://sonarly.com)* Co-authored-by: Sonarly Claude Code <claude-code@sonarly.com>
1 parent dbc350f commit d51d982

1 file changed

Lines changed: 4 additions & 0 deletions

File tree

packages/twenty-server/src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ export class CommonCreateManyQueryRunnerService extends CommonBaseQueryRunnerSer
307307

308308
const whereConditions = buildWhereConditions(args.data, conflictingFields);
309309

310+
if (whereConditions.length === 0) {
311+
return [];
312+
}
313+
310314
whereConditions.forEach((condition) => {
311315
queryBuilder.orWhere(condition);
312316
});

0 commit comments

Comments
 (0)