From daffda7f71a78b6138669ca3b31efbced9802467 Mon Sep 17 00:00:00 2001 From: denis-troller Date: Mon, 29 Sep 2025 12:59:33 +0000 Subject: [PATCH 1/3] Create rule S8040 --- rules/S8040/apex/metadata.json | 25 +++++++++++++++++++ rules/S8040/apex/rule.adoc | 44 ++++++++++++++++++++++++++++++++++ rules/S8040/metadata.json | 2 ++ 3 files changed, 71 insertions(+) create mode 100644 rules/S8040/apex/metadata.json create mode 100644 rules/S8040/apex/rule.adoc create mode 100644 rules/S8040/metadata.json diff --git a/rules/S8040/apex/metadata.json b/rules/S8040/apex/metadata.json new file mode 100644 index 00000000000..0626062ca77 --- /dev/null +++ b/rules/S8040/apex/metadata.json @@ -0,0 +1,25 @@ +{ + "title": "FIXME", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-8040", + "sqKey": "S8040", + "scope": "All", + "defaultQualityProfiles": ["Sonar way"], + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "HIGH", + "RELIABILITY": "MEDIUM", + "SECURITY": "LOW" + }, + "attribute": "CONVENTIONAL" + } +} diff --git a/rules/S8040/apex/rule.adoc b/rules/S8040/apex/rule.adoc new file mode 100644 index 00000000000..3edb7d8d0d2 --- /dev/null +++ b/rules/S8040/apex/rule.adoc @@ -0,0 +1,44 @@ +FIXME: add a description + +// If you want to factorize the description uncomment the following line and create the file. +//include::../description.adoc[] + +== Why is this an issue? + +FIXME: remove the unused optional headers (that are commented out) + +//=== What is the potential impact? + +== How to fix it +//== How to fix it in FRAMEWORK NAME + +=== Code examples + +==== Noncompliant code example + +[source,apex,diff-id=1,diff-type=noncompliant] +---- +FIXME +---- + +==== Compliant solution + +[source,apex,diff-id=1,diff-type=compliant] +---- +FIXME +---- + +//=== How does this work? + +//=== Pitfalls + +//=== Going the extra mile + + +//== Resources +//=== Documentation +//=== Articles & blog posts +//=== Conference presentations +//=== Standards +//=== External coding guidelines +//=== Benchmarks diff --git a/rules/S8040/metadata.json b/rules/S8040/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S8040/metadata.json @@ -0,0 +1,2 @@ +{ +} From b3b2dcc3b07a319b0b8c05e2d9d0783b0fa55949 Mon Sep 17 00:00:00 2001 From: denis-troller Date: Mon, 29 Sep 2025 22:20:51 +0200 Subject: [PATCH 2/3] Update rules/S8040/apex/rule.adoc in PR #5646 --- rules/S8040/apex/rule.adoc | 70 +++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/rules/S8040/apex/rule.adoc b/rules/S8040/apex/rule.adoc index 3edb7d8d0d2..a80a197604a 100644 --- a/rules/S8040/apex/rule.adoc +++ b/rules/S8040/apex/rule.adoc @@ -1,16 +1,26 @@ -FIXME: add a description - -// If you want to factorize the description uncomment the following line and create the file. -//include::../description.adoc[] +This rule raises an issue when a SOQL query is found inside any type of loop (for, while, or do-while). == Why is this an issue? -FIXME: remove the unused optional headers (that are commented out) +Salesforce enforces strict governor limits to ensure fair resource usage across all tenants on the platform. One of these limits restricts the number of SOQL queries that can be executed in a single transaction: 100 queries for synchronous operations and 200 for asynchronous operations. + +When you place a SOQL query inside a loop, the query executes once for each iteration. This means that processing just 101 records in a synchronous context would exceed the governor limit and cause a runtime exception. + +Beyond hitting limits, this pattern is also inefficient from a performance perspective. Each SOQL query requires a round trip to the database, and multiple individual queries are much slower than a single bulk query that retrieves all needed data at once. + +Salesforce's architecture is designed for bulk operations. Triggers, for example, can process up to 200 records at once, and bulk API operations can process thousands of records in a single transaction. Code that works fine when processing individual records will fail catastrophically when processing bulk data. + +=== What is the potential impact? + +Applications will throw `System.LimitException` runtime errors when the SOQL query limit is exceeded, causing transaction failures and poor user experience. + +Performance will be significantly degraded due to multiple database round trips instead of efficient bulk operations. -//=== What is the potential impact? +The application will not scale properly when processing larger datasets, making it unsuitable for enterprise use or bulk data operations. == How to fix it -//== How to fix it in FRAMEWORK NAME + +Move the SOQL query outside the loop and use bulk query patterns with the IN clause to retrieve all needed data in a single query. Then iterate over the results. === Code examples @@ -18,27 +28,49 @@ FIXME: remove the unused optional headers (that are commented out) [source,apex,diff-id=1,diff-type=noncompliant] ---- -FIXME +trigger SoqlTriggerNotBulk on Account(after update) { + for(Account a : Trigger.new) { + // Inefficient SOQL query as it runs once for each account! + Opportunity[] opps = [SELECT Id,Name,CloseDate + FROM Opportunity WHERE AccountId=:a.Id]; // Noncompliant + // Do some other processing + } +} ---- ==== Compliant solution [source,apex,diff-id=1,diff-type=compliant] ---- -FIXME +trigger SoqlTriggerBulk on Account(after update) { + // Perform SOQL query once outside the loop + List acctsWithOpps = + [SELECT Id,(SELECT Id,Name,CloseDate FROM Opportunities) + FROM Account WHERE Id IN :Trigger.new]; + // Iterate over the returned accounts + for(Account a : acctsWithOpps) { + Opportunity[] relatedOpps = a.Opportunities; + // Do some other processing + } +} ---- -//=== How does this work? +== Resources + +=== Documentation + + * Salesforce Trailhead: Bulk Apex Triggers - https://trailhead.salesforce.com/content/learn/modules/apex_triggers/apex_triggers_bulk[Official Salesforce documentation on bulk trigger design patterns and avoiding SOQL queries in loops] + + * Apex Developer Guide: Governor Limits - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm[Complete reference for Salesforce governor limits including SOQL query limits] + + * Apex Developer Guide: SOQL and SOSL Queries - https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_SOQL.htm[Comprehensive guide to writing efficient SOQL queries in Apex] + +=== Standards -//=== Pitfalls + * CWE-400: Uncontrolled Resource Consumption - https://cwe.mitre.org/data/definitions/400.html[This pattern can lead to uncontrolled consumption of database query resources] -//=== Going the extra mile +=== Related rules + * RSPEC-5380 - https://rules.sonarsource.com/apex/RSPEC-5380[Duplicate rule addressing the same SOQL queries in loops issue] -//== Resources -//=== Documentation -//=== Articles & blog posts -//=== Conference presentations -//=== Standards -//=== External coding guidelines -//=== Benchmarks + * RSPEC-5382 - https://rules.sonarsource.com/apex/RSPEC-5382[Related rule about DML operations in loops] From e97da06d75edc17fc7f5518d9ed9f570b47d55ea Mon Sep 17 00:00:00 2001 From: denis-troller Date: Mon, 29 Sep 2025 22:20:54 +0200 Subject: [PATCH 3/3] Update rules/S8040/apex/metadata.json in PR #5646 --- rules/S8040/apex/metadata.json | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/rules/S8040/apex/metadata.json b/rules/S8040/apex/metadata.json index 0626062ca77..0d25e70f033 100644 --- a/rules/S8040/apex/metadata.json +++ b/rules/S8040/apex/metadata.json @@ -1,25 +1,29 @@ { - "title": "FIXME", - "type": "CODE_SMELL", + "title": "SOQL queries should not be placed inside loops", + "type": "BUG", "status": "ready", "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" + "func": "Constant/Issue", + "constantCost": "30 min" }, "tags": [ + "performance", + "salesforce", + "governor-limits" ], - "defaultSeverity": "Major", + "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-8040", "sqKey": "S8040", - "scope": "All", - "defaultQualityProfiles": ["Sonar way"], + "scope": "Main", + "defaultQualityProfiles": [ + "Sonar way" + ], "quickfix": "unknown", "code": { "impacts": { - "MAINTAINABILITY": "HIGH", - "RELIABILITY": "MEDIUM", - "SECURITY": "LOW" + "RELIABILITY": "BLOCKER", + "MAINTAINABILITY": "BLOCKER" }, - "attribute": "CONVENTIONAL" + "attribute": "EFFICIENT" } -} +} \ No newline at end of file