scc-universal 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +44 -0
- package/.cursor/agents/deep-researcher.md +142 -0
- package/.cursor/agents/doc-updater.md +219 -0
- package/.cursor/agents/eval-runner.md +335 -0
- package/.cursor/agents/learning-engine.md +210 -0
- package/.cursor/agents/loop-operator.md +245 -0
- package/.cursor/agents/refactor-cleaner.md +119 -0
- package/.cursor/agents/sf-admin-agent.md +127 -0
- package/.cursor/agents/sf-agentforce-agent.md +126 -0
- package/.cursor/agents/sf-apex-agent.md +117 -0
- package/.cursor/agents/sf-architect.md +426 -0
- package/.cursor/agents/sf-aura-reviewer.md +369 -0
- package/.cursor/agents/sf-bugfix-agent.md +101 -0
- package/.cursor/agents/sf-flow-agent.md +155 -0
- package/.cursor/agents/sf-integration-agent.md +141 -0
- package/.cursor/agents/sf-lwc-agent.md +123 -0
- package/.cursor/agents/sf-review-agent.md +357 -0
- package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
- package/.cursor/hooks/adapter.js +81 -0
- package/.cursor/hooks/after-file-edit.js +26 -0
- package/.cursor/hooks/after-mcp-execution.js +12 -0
- package/.cursor/hooks/after-shell-execution.js +30 -0
- package/.cursor/hooks/after-tab-file-edit.js +12 -0
- package/.cursor/hooks/before-mcp-execution.js +11 -0
- package/.cursor/hooks/before-read-file.js +13 -0
- package/.cursor/hooks/before-shell-execution.js +29 -0
- package/.cursor/hooks/before-submit-prompt.js +23 -0
- package/.cursor/hooks/pre-compact.js +7 -0
- package/.cursor/hooks/session-end.js +10 -0
- package/.cursor/hooks/session-start.js +10 -0
- package/.cursor/hooks/stop.js +18 -0
- package/.cursor/hooks/subagent-start.js +10 -0
- package/.cursor/hooks/subagent-stop.js +10 -0
- package/.cursor/hooks.json +107 -0
- package/.cursor/skills/aside/SKILL.md +115 -0
- package/.cursor/skills/checkpoint/SKILL.md +50 -0
- package/.cursor/skills/configure-scc/SKILL.md +160 -0
- package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
- package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
- package/.cursor/skills/model-route/SKILL.md +81 -0
- package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
- package/.cursor/skills/refactor-clean/SKILL.md +133 -0
- package/.cursor/skills/resume-session/SKILL.md +111 -0
- package/.cursor/skills/save-session/SKILL.md +183 -0
- package/.cursor/skills/search-first/SKILL.md +140 -0
- package/.cursor/skills/security-scan/SKILL.md +142 -0
- package/.cursor/skills/sessions/SKILL.md +124 -0
- package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
- package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
- package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
- package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
- package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
- package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
- package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
- package/.cursor/skills/sf-api-design/SKILL.md +237 -0
- package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
- package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
- package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
- package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
- package/.cursor/skills/sf-debugging/SKILL.md +362 -0
- package/.cursor/skills/sf-deployment/SKILL.md +291 -0
- package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
- package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
- package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
- package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
- package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
- package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
- package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
- package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
- package/.cursor/skills/sf-help/SKILL.md +156 -0
- package/.cursor/skills/sf-integration/SKILL.md +479 -0
- package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
- package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
- package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
- package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
- package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
- package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
- package/.cursor/skills/sf-security/SKILL.md +330 -0
- package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
- package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
- package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
- package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
- package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
- package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
- package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
- package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
- package/.cursor/skills/strategic-compact/SKILL.md +205 -0
- package/.cursor/skills/update-docs/SKILL.md +162 -0
- package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
- package/.cursor-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/agents/deep-researcher.md +145 -0
- package/agents/doc-updater.md +222 -0
- package/agents/eval-runner.md +340 -0
- package/agents/learning-engine.md +211 -0
- package/agents/loop-operator.md +247 -0
- package/agents/refactor-cleaner.md +122 -0
- package/agents/sf-admin-agent.md +131 -0
- package/agents/sf-agentforce-agent.md +132 -0
- package/agents/sf-apex-agent.md +124 -0
- package/agents/sf-architect.md +435 -0
- package/agents/sf-aura-reviewer.md +372 -0
- package/agents/sf-bugfix-agent.md +105 -0
- package/agents/sf-flow-agent.md +159 -0
- package/agents/sf-integration-agent.md +146 -0
- package/agents/sf-lwc-agent.md +127 -0
- package/agents/sf-review-agent.md +366 -0
- package/agents/sf-visualforce-reviewer.md +468 -0
- package/assets/logo.svg +18 -0
- package/docs/ARCHITECTURE.md +133 -0
- package/docs/authoring-guide.md +373 -0
- package/docs/hook-development.md +578 -0
- package/docs/token-optimization.md +139 -0
- package/docs/workflow-examples.md +645 -0
- package/examples/agentforce-action/README.md +227 -0
- package/examples/apex-trigger-handler/README.md +114 -0
- package/examples/devops-pipeline/README.md +325 -0
- package/examples/flow-automation/README.md +188 -0
- package/examples/integration-pattern/README.md +416 -0
- package/examples/lwc-component/README.md +180 -0
- package/examples/platform-events/README.md +492 -0
- package/examples/scratch-org-setup/README.md +138 -0
- package/examples/security-audit/README.md +244 -0
- package/examples/visualforce-migration/README.md +314 -0
- package/hooks/hooks.json +338 -0
- package/hooks/memory-persistence/README.md +73 -0
- package/manifests/install-modules.json +217 -0
- package/manifests/install-profiles.json +17 -0
- package/mcp-configs/mcp-servers.json +19 -0
- package/package.json +89 -0
- package/schemas/hooks.schema.json +123 -0
- package/schemas/install-modules.schema.json +76 -0
- package/schemas/install-profiles.schema.json +28 -0
- package/schemas/install-state.schema.json +73 -0
- package/schemas/package-manager.schema.json +18 -0
- package/schemas/plugin.schema.json +112 -0
- package/schemas/scc-install-config.schema.json +29 -0
- package/schemas/state-store.schema.json +111 -0
- package/scripts/cli/install-apply.js +170 -0
- package/scripts/cli/uninstall.js +193 -0
- package/scripts/hooks/check-console-log.js +101 -0
- package/scripts/hooks/check-hook-enabled.js +17 -0
- package/scripts/hooks/check-platform-docs-age.js +48 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +98 -0
- package/scripts/hooks/governor-check.js +220 -0
- package/scripts/hooks/learning-observe.sh +206 -0
- package/scripts/hooks/mcp-health-check.js +588 -0
- package/scripts/hooks/post-bash-build-complete.js +34 -0
- package/scripts/hooks/post-bash-pr-created.js +43 -0
- package/scripts/hooks/post-edit-console-warn.js +61 -0
- package/scripts/hooks/post-edit-format.js +79 -0
- package/scripts/hooks/post-edit-typecheck.js +98 -0
- package/scripts/hooks/post-write.js +168 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
- package/scripts/hooks/pre-compact.js +51 -0
- package/scripts/hooks/pre-tool-use.js +163 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +251 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +135 -0
- package/scripts/hooks/session-end-marker.js +29 -0
- package/scripts/hooks/session-end.js +311 -0
- package/scripts/hooks/session-start.js +202 -0
- package/scripts/hooks/sfdx-scanner-check.js +142 -0
- package/scripts/hooks/sfdx-validate.js +119 -0
- package/scripts/hooks/stop-hook.js +170 -0
- package/scripts/hooks/suggest-compact.js +67 -0
- package/scripts/lib/agent-adapter.js +82 -0
- package/scripts/lib/apex-analysis.js +194 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install-config.js +73 -0
- package/scripts/lib/install-executor.js +363 -0
- package/scripts/lib/install-state.js +121 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.js +124 -0
- package/scripts/lib/project-detect.js +228 -0
- package/scripts/lib/schema-validator.js +190 -0
- package/scripts/lib/skill-adapter.js +100 -0
- package/scripts/lib/state-store.js +376 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
- package/scripts/lib/utils.js +313 -0
- package/scripts/scc.js +164 -0
- package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
- package/skills/_reference/APEX_CURSOR.md +159 -0
- package/skills/_reference/API_VERSIONS.md +78 -0
- package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
- package/skills/_reference/ASYNC_PATTERNS.md +163 -0
- package/skills/_reference/AURA_COMPONENTS.md +146 -0
- package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
- package/skills/_reference/DATA_MODELING.md +124 -0
- package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
- package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
- package/skills/_reference/DEPRECATIONS.md +79 -0
- package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
- package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
- package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
- package/skills/_reference/FLOW_PATTERNS.md +113 -0
- package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
- package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
- package/skills/_reference/LWC_PATTERNS.md +79 -0
- package/skills/_reference/METADATA_TYPES.md +115 -0
- package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
- package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
- package/skills/_reference/PLATFORM_EVENTS.md +121 -0
- package/skills/_reference/REPORTING_API.md +143 -0
- package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
- package/skills/_reference/SECURITY_PATTERNS.md +127 -0
- package/skills/_reference/SHARING_MODEL.md +120 -0
- package/skills/_reference/SOQL_PATTERNS.md +119 -0
- package/skills/_reference/TESTING_STANDARDS.md +96 -0
- package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
- package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
- package/skills/aside/SKILL.md +118 -0
- package/skills/checkpoint/SKILL.md +53 -0
- package/skills/configure-scc/SKILL.md +163 -0
- package/skills/continuous-agent-loop/SKILL.md +264 -0
- package/skills/mcp-server-patterns/SKILL.md +146 -0
- package/skills/model-route/SKILL.md +84 -0
- package/skills/prompt-optimizer/SKILL.md +369 -0
- package/skills/refactor-clean/SKILL.md +136 -0
- package/skills/resume-session/SKILL.md +114 -0
- package/skills/save-session/SKILL.md +186 -0
- package/skills/search-first/SKILL.md +144 -0
- package/skills/security-scan/SKILL.md +146 -0
- package/skills/sessions/SKILL.md +127 -0
- package/skills/sf-agentforce-development/SKILL.md +450 -0
- package/skills/sf-apex-async-patterns/SKILL.md +326 -0
- package/skills/sf-apex-best-practices/SKILL.md +425 -0
- package/skills/sf-apex-constraints/SKILL.md +81 -0
- package/skills/sf-apex-cursor/SKILL.md +338 -0
- package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
- package/skills/sf-apex-testing/SKILL.md +409 -0
- package/skills/sf-api-design/SKILL.md +238 -0
- package/skills/sf-approval-processes/SKILL.md +315 -0
- package/skills/sf-aura-development/SKILL.md +263 -0
- package/skills/sf-build-fix/SKILL.md +121 -0
- package/skills/sf-data-modeling/SKILL.md +278 -0
- package/skills/sf-debugging/SKILL.md +363 -0
- package/skills/sf-deployment/SKILL.md +295 -0
- package/skills/sf-deployment-constraints/SKILL.md +155 -0
- package/skills/sf-devops-ci-cd/SKILL.md +325 -0
- package/skills/sf-docs-lookup/SKILL.md +103 -0
- package/skills/sf-e2e-testing/SKILL.md +324 -0
- package/skills/sf-experience-cloud/SKILL.md +249 -0
- package/skills/sf-flow-development/SKILL.md +377 -0
- package/skills/sf-governor-limits/SKILL.md +323 -0
- package/skills/sf-harness-audit/SKILL.md +142 -0
- package/skills/sf-help/SKILL.md +159 -0
- package/skills/sf-integration/SKILL.md +483 -0
- package/skills/sf-lwc-constraints/SKILL.md +130 -0
- package/skills/sf-lwc-development/SKILL.md +303 -0
- package/skills/sf-lwc-testing/SKILL.md +388 -0
- package/skills/sf-metadata-management/SKILL.md +288 -0
- package/skills/sf-platform-events-cdc/SKILL.md +375 -0
- package/skills/sf-quickstart/SKILL.md +173 -0
- package/skills/sf-security/SKILL.md +334 -0
- package/skills/sf-security-constraints/SKILL.md +127 -0
- package/skills/sf-soql-constraints/SKILL.md +131 -0
- package/skills/sf-soql-optimization/SKILL.md +354 -0
- package/skills/sf-tdd-workflow/SKILL.md +336 -0
- package/skills/sf-testing-constraints/SKILL.md +200 -0
- package/skills/sf-trigger-constraints/SKILL.md +90 -0
- package/skills/sf-trigger-frameworks/SKILL.md +347 -0
- package/skills/sf-visualforce-development/SKILL.md +260 -0
- package/skills/strategic-compact/SKILL.md +208 -0
- package/skills/update-docs/SKILL.md +165 -0
- package/skills/update-platform-docs/SKILL.md +90 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-soql-constraints
|
|
3
|
+
description: "Enforce SOQL/SOSL safety rules, selectivity, and governor limit compliance. Use when writing or reviewing ANY SOQL query, SOSL search, or database operations. Do NOT use for Apex structure, LWC, or Flow."
|
|
4
|
+
origin: SCC
|
|
5
|
+
user-invocable: false
|
|
6
|
+
allowed-tools: Read, Grep, Glob
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# SOQL Constraints
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
This skill auto-activates when writing, reviewing, or optimizing any SOQL query, SOSL search, or Apex database operation. It enforces query safety rules, selectivity requirements, and governor limit compliance for all database operations.
|
|
14
|
+
|
|
15
|
+
Hard rules for every SOQL query, SOSL search, and Apex database operation.
|
|
16
|
+
Violations cause governor limit failures, security vulnerabilities, or
|
|
17
|
+
production outages. See @../_reference/SOQL_PATTERNS.md for selectivity thresholds and
|
|
18
|
+
@../_reference/GOVERNOR_LIMITS.md for per-transaction budgets.
|
|
19
|
+
|
|
20
|
+
## Never Rules
|
|
21
|
+
|
|
22
|
+
1. **Never place SOQL or SOSL inside a loop.** Every iteration consumes one
|
|
23
|
+
of the per-transaction SOQL query budget (see @../_reference/GOVERNOR_LIMITS.md). Query
|
|
24
|
+
once before the loop, store results in a `Map<Id, SObject>`, then iterate.
|
|
25
|
+
|
|
26
|
+
2. **Never write a non-selective query on objects with >200,000 rows.** The
|
|
27
|
+
query optimizer will full-table-scan and the query fails in trigger context.
|
|
28
|
+
Every WHERE clause must target an indexed field below the selectivity
|
|
29
|
+
threshold (see @../_reference/SOQL_PATTERNS.md, Selectivity Thresholds table).
|
|
30
|
+
|
|
31
|
+
3. **Never use `FIELDS(ALL)` or `FIELDS(CUSTOM)` in triggers, service classes,
|
|
32
|
+
or production paths.** Select only the fields the calling code actually
|
|
33
|
+
reads. `FIELDS()` directives are for exploration and debugging only.
|
|
34
|
+
|
|
35
|
+
4. **Never hardcode Salesforce record IDs.** IDs differ between orgs and
|
|
36
|
+
sandboxes. Use `Schema.SObjectType`, Custom Metadata, or Custom Labels to
|
|
37
|
+
resolve IDs at runtime.
|
|
38
|
+
|
|
39
|
+
5. **Never omit `LIMIT` on unbounded queries.** Any query that could return an
|
|
40
|
+
unknown number of rows must include a `LIMIT` clause to stay within the
|
|
41
|
+
per-transaction row limit (see @../_reference/GOVERNOR_LIMITS.md).
|
|
42
|
+
|
|
43
|
+
6. **Never concatenate user input into dynamic SOQL strings.** This creates
|
|
44
|
+
SOQL injection vulnerabilities. Use bind variables (`:variable`) for inline
|
|
45
|
+
SOQL or `Database.queryWithBinds()` for dynamic SOQL.
|
|
46
|
+
|
|
47
|
+
7. **Never use a leading wildcard in `LIKE` filters** (`LIKE '%term%'`).
|
|
48
|
+
Leading wildcards prevent index use and cause full table scans. Use SOSL
|
|
49
|
+
(`FIND`) for full-text search instead.
|
|
50
|
+
|
|
51
|
+
8. **Never use `!=` or `NOT IN` as the sole WHERE filter.** These operators
|
|
52
|
+
are non-optimizable and always produce table scans (see @../_reference/SOQL_PATTERNS.md,
|
|
53
|
+
Optimizable vs Non-Optimizable Operators).
|
|
54
|
+
|
|
55
|
+
9. **Never omit security enforcement on user-facing queries.** Queries
|
|
56
|
+
triggered by user actions (LWC, Aura, VF, REST endpoints) must include
|
|
57
|
+
`WITH USER_MODE` or equivalent FLS/CRUD enforcement.
|
|
58
|
+
|
|
59
|
+
10. **Never load all records just to count them.** Use `SELECT COUNT() FROM`
|
|
60
|
+
or aggregate queries instead of querying records and calling `.size()`.
|
|
61
|
+
|
|
62
|
+
## Always Rules
|
|
63
|
+
|
|
64
|
+
1. **Always bulkify database operations.** Collect IDs in a `Set<Id>`, query
|
|
65
|
+
once with `WHERE Id IN :idSet`, and store results in a `Map<Id, SObject>`.
|
|
66
|
+
|
|
67
|
+
2. **Always use bind variables** (`:variable`) in inline SOQL. For dynamic
|
|
68
|
+
SOQL, always use `Database.queryWithBinds()` with a bind map.
|
|
69
|
+
|
|
70
|
+
3. **Always use `WITH USER_MODE`** (see @../_reference/API_VERSIONS.md for minimum version) on queries executed in
|
|
71
|
+
user-facing contexts (LWC controllers, Aura controllers, VF controllers,
|
|
72
|
+
REST resources). Use `WITH SYSTEM_MODE` only for documented system
|
|
73
|
+
processes (batch jobs, integrations) with explicit justification.
|
|
74
|
+
|
|
75
|
+
4. **Always filter on indexed fields.** Prefer `Id`, `Name`, `OwnerId`,
|
|
76
|
+
`RecordTypeId`, `CreatedDate`, `SystemModstamp`, lookup/master-detail
|
|
77
|
+
fields, or External ID fields. See @../_reference/SOQL_PATTERNS.md, Standard Indexed
|
|
78
|
+
Fields table for the full list.
|
|
79
|
+
|
|
80
|
+
5. **Always add `LIMIT` when only one record is expected** (`LIMIT 1`) or
|
|
81
|
+
when displaying a bounded list.
|
|
82
|
+
|
|
83
|
+
6. **Always use relationship queries** (parent-to-child subqueries or
|
|
84
|
+
child-to-parent dot notation) instead of separate queries when fetching
|
|
85
|
+
related data. Subqueries do not count as separate SOQL queries.
|
|
86
|
+
|
|
87
|
+
7. **Always use SOSL instead of `LIKE` for text search across objects.**
|
|
88
|
+
SOSL uses the search index and is far more efficient than `LIKE` on
|
|
89
|
+
large-volume objects.
|
|
90
|
+
|
|
91
|
+
8. **Always validate object and field names** via `Schema.getGlobalDescribe()`
|
|
92
|
+
before building dynamic SOQL. Never trust external input for object or
|
|
93
|
+
field names.
|
|
94
|
+
|
|
95
|
+
9. **Always test triggers and services with 200 records** (the standard
|
|
96
|
+
trigger batch size) to validate bulk safety against governor limits.
|
|
97
|
+
|
|
98
|
+
## Anti-Pattern Table
|
|
99
|
+
|
|
100
|
+
| Problem | Correct Pattern |
|
|
101
|
+
|---|---|
|
|
102
|
+
| SOQL inside `for` loop | Query before loop, store in `Map<Id, SObject>` |
|
|
103
|
+
| `SELECT FIELDS(ALL) FROM Account` in service class | `SELECT Id, Name FROM Account` -- explicit fields only |
|
|
104
|
+
| `WHERE Description LIKE '%keyword%'` | `FIND 'keyword' IN ALL FIELDS RETURNING Account(Id, Name)` (SOSL) |
|
|
105
|
+
| `WHERE Custom_Field__c = 'value'` on non-indexed field (LDV) | Add indexed field to WHERE, or request custom index |
|
|
106
|
+
| `String query = '...WHERE Name = \'' + input + '\''` | `[SELECT Id FROM Account WHERE Name = :input]` or `Database.queryWithBinds()` |
|
|
107
|
+
| `List<Account> all = [SELECT Id FROM Account]; Integer c = all.size();` | `Integer c = [SELECT COUNT() FROM Account];` |
|
|
108
|
+
| Hardcoded `WHERE Id = '001xx000003DGXXX'` | `WHERE Id = :accountId` with runtime-resolved variable |
|
|
109
|
+
| No `WITH USER_MODE` on LWC controller query | Add `WITH USER_MODE` to enforce FLS + sharing |
|
|
110
|
+
| Separate queries for parent and child records | Use subquery: `SELECT Id, (SELECT Id FROM Contacts) FROM Account` |
|
|
111
|
+
| `WHERE Status__c != 'Closed'` as only filter | Add a selective indexed filter: `WHERE RecordTypeId = :rtId AND Status__c != 'Closed'` |
|
|
112
|
+
|
|
113
|
+
## Limit Budgets (Quick Reference)
|
|
114
|
+
|
|
115
|
+
Do not memorize raw numbers -- always check @../_reference/GOVERNOR_LIMITS.md for the
|
|
116
|
+
authoritative table. Key constraint categories that shape every query decision:
|
|
117
|
+
|
|
118
|
+
- **SOQL query limit** per synchronous/asynchronous transaction (see @../_reference/GOVERNOR_LIMITS.md)
|
|
119
|
+
- **Total rows returned** per transaction (see @../_reference/GOVERNOR_LIMITS.md)
|
|
120
|
+
- **Heap size** synchronous/asynchronous (see @../_reference/GOVERNOR_LIMITS.md)
|
|
121
|
+
- **CPU time** synchronous/asynchronous (see @../_reference/GOVERNOR_LIMITS.md)
|
|
122
|
+
|
|
123
|
+
Use `Limits.getQueries()` / `Limits.getLimitQueries()` to check remaining
|
|
124
|
+
budget at runtime before issuing additional queries.
|
|
125
|
+
|
|
126
|
+
## Related
|
|
127
|
+
|
|
128
|
+
- **sf-soql-optimization** -- Action skill for interactive query optimization,
|
|
129
|
+
index strategy guidance, and Query Plan Tool usage.
|
|
130
|
+
- **sf-apex-constraints** -- Apex-level constraint rules (bulkification, DML
|
|
131
|
+
patterns, CPU/heap management).
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-soql-optimization
|
|
3
|
+
description: "SOQL optimization — selective queries, index strategy, query plans, relationship efficiency, large data volumes. Use when queries hit limits or designing for 100K+ records. Do NOT use for general Apex, LWC, or Flow."
|
|
4
|
+
origin: SCC
|
|
5
|
+
user-invocable: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# SOQL Optimization
|
|
9
|
+
|
|
10
|
+
Poorly written SOQL is the most common cause of governor limit exceptions and slow page loads in Salesforce. This skill covers optimization procedures, query plan analysis, and index strategy.
|
|
11
|
+
|
|
12
|
+
@../_reference/SOQL_PATTERNS.md
|
|
13
|
+
@../_reference/GOVERNOR_LIMITS.md
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- SOQL queries hitting governor limits (100-query limit, CPU time, or heap size)
|
|
18
|
+
- Investigating slow page loads, timeout errors, or CPU violations in Apex code
|
|
19
|
+
- Adding new SOQL queries to triggers, batch jobs, or service classes
|
|
20
|
+
- Auditing code for SOQL-in-loops anti-patterns during code review
|
|
21
|
+
- Designing queries for large data sets (100K+ records) requiring selective filters
|
|
22
|
+
- Troubleshooting `System.LimitException: Too many SOQL queries` errors
|
|
23
|
+
|
|
24
|
+
## Selectivity Fundamentals
|
|
25
|
+
|
|
26
|
+
The query optimizer decides whether to use an index or perform a full table scan based on **selectivity** — the percentage of records a query expects to return. See @../_reference/SOQL_PATTERNS.md for threshold tables.
|
|
27
|
+
|
|
28
|
+
```apex
|
|
29
|
+
// Potentially NOT selective — if more than 10% of Contacts have Status = 'Active'
|
|
30
|
+
List<Contact> contacts = [SELECT Id FROM Contact WHERE Status__c = 'Active'];
|
|
31
|
+
|
|
32
|
+
// Selective — CreatedDate is indexed, LAST_N_DAYS:30 likely returns < 10%
|
|
33
|
+
List<Contact> recentContacts = [
|
|
34
|
+
SELECT Id FROM Contact
|
|
35
|
+
WHERE CreatedDate = LAST_N_DAYS:30
|
|
36
|
+
];
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> Use the Query Plan Tool in Developer Console to verify actual query plans. These thresholds are guidelines; the optimizer considers data distribution, available indexes, and org-specific factors.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## SOQL in Loops — The Fix
|
|
44
|
+
|
|
45
|
+
Every iteration of a loop containing a SOQL query multiplies the query count toward the 100/200 limit.
|
|
46
|
+
|
|
47
|
+
### The Fix — Query Once, Store in Map
|
|
48
|
+
|
|
49
|
+
```apex
|
|
50
|
+
// Collect all IDs first
|
|
51
|
+
List<Account> accounts = [SELECT Id, OwnerId FROM Account WHERE Type = 'Customer'];
|
|
52
|
+
|
|
53
|
+
Set<Id> ownerIds = new Set<Id>();
|
|
54
|
+
for (Account acc : accounts) {
|
|
55
|
+
ownerIds.add(acc.OwnerId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Single query for all related records
|
|
59
|
+
Map<Id, User> ownerMap = new Map<Id, User>(
|
|
60
|
+
[SELECT Id, Name, Email FROM User WHERE Id IN :ownerIds]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Now iterate — zero SOQL queries in this loop
|
|
64
|
+
for (Account acc : accounts) {
|
|
65
|
+
User owner = ownerMap.get(acc.OwnerId);
|
|
66
|
+
if (owner != null) {
|
|
67
|
+
sendWelcomeEmail(acc, owner);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Trigger Context Pattern
|
|
73
|
+
|
|
74
|
+
```apex
|
|
75
|
+
trigger OpportunityTrigger on Opportunity (after update) {
|
|
76
|
+
Set<Id> oppIds = Trigger.newMap.keySet();
|
|
77
|
+
|
|
78
|
+
Map<Id, List<OpportunityLineItem>> itemsByOppId = new Map<Id, List<OpportunityLineItem>>();
|
|
79
|
+
for (OpportunityLineItem item : [
|
|
80
|
+
SELECT Id, OpportunityId, Quantity, UnitPrice
|
|
81
|
+
FROM OpportunityLineItem
|
|
82
|
+
WHERE OpportunityId IN :oppIds
|
|
83
|
+
]) {
|
|
84
|
+
if (!itemsByOppId.containsKey(item.OpportunityId)) {
|
|
85
|
+
itemsByOppId.put(item.OpportunityId, new List<OpportunityLineItem>());
|
|
86
|
+
}
|
|
87
|
+
itemsByOppId.get(item.OpportunityId).add(item);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (Opportunity opp : Trigger.new) {
|
|
91
|
+
List<OpportunityLineItem> items = itemsByOppId.get(opp.Id);
|
|
92
|
+
if (items == null) items = new List<OpportunityLineItem>();
|
|
93
|
+
// Process items...
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Query Optimization Checklist
|
|
101
|
+
|
|
102
|
+
### Use Indexed Fields in WHERE Clause
|
|
103
|
+
|
|
104
|
+
```apex
|
|
105
|
+
// Filter on RecordTypeId (indexed) rather than custom non-indexed fields
|
|
106
|
+
[SELECT Id FROM Account WHERE RecordTypeId = :RETAIL_RECORD_TYPE_ID]
|
|
107
|
+
|
|
108
|
+
// Combine indexed + non-indexed for compound selectivity
|
|
109
|
+
[SELECT Id FROM Account
|
|
110
|
+
WHERE Type = 'Customer'
|
|
111
|
+
AND Custom_Category__c = 'Retail']
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Select Only Fields You Need
|
|
115
|
+
|
|
116
|
+
```apex
|
|
117
|
+
// Select only what the calling code actually uses
|
|
118
|
+
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Use LIMIT When Possible
|
|
122
|
+
|
|
123
|
+
```apex
|
|
124
|
+
Account acc = [SELECT Id, Name FROM Account WHERE Name = 'Acme' LIMIT 1];
|
|
125
|
+
|
|
126
|
+
List<Account> recentAccounts = [
|
|
127
|
+
SELECT Id, Name, CreatedDate
|
|
128
|
+
FROM Account
|
|
129
|
+
ORDER BY CreatedDate DESC
|
|
130
|
+
LIMIT 50
|
|
131
|
+
];
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Avoid LIKE with % Prefix
|
|
135
|
+
|
|
136
|
+
```apex
|
|
137
|
+
// Trailing wildcard CAN use the index
|
|
138
|
+
[SELECT Id FROM Account WHERE Name LIKE 'Acme%']
|
|
139
|
+
|
|
140
|
+
// For full-text search — use SOSL instead
|
|
141
|
+
List<List<SObject>> results = [FIND 'Corp' IN NAME FIELDS RETURNING Account(Id, Name)];
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Use Bind Variables
|
|
145
|
+
|
|
146
|
+
```apex
|
|
147
|
+
// Bind variables are safe and performant
|
|
148
|
+
List<Account> accounts = [SELECT Id FROM Account WHERE Name = :accountName];
|
|
149
|
+
|
|
150
|
+
// For dynamic SOQL — use Database.queryWithBinds
|
|
151
|
+
Map<String, Object> bindVars = new Map<String, Object>{
|
|
152
|
+
'accountName' => accountName,
|
|
153
|
+
'minRevenue' => minimumRevenue
|
|
154
|
+
};
|
|
155
|
+
String safeQuery = 'SELECT Id, Name FROM Account WHERE Name = :accountName AND AnnualRevenue >= :minRevenue';
|
|
156
|
+
List<Account> accounts2 = Database.queryWithBinds(safeQuery, bindVars, AccessLevel.USER_MODE);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Relationship Queries
|
|
162
|
+
|
|
163
|
+
### Parent-to-Child (Subquery)
|
|
164
|
+
|
|
165
|
+
```apex
|
|
166
|
+
List<Account> accounts = [
|
|
167
|
+
SELECT Id, Name,
|
|
168
|
+
(SELECT Id, FirstName, LastName, Email
|
|
169
|
+
FROM Contacts
|
|
170
|
+
WHERE Email != null
|
|
171
|
+
ORDER BY LastName)
|
|
172
|
+
FROM Account
|
|
173
|
+
WHERE Type = 'Customer'
|
|
174
|
+
WITH USER_MODE
|
|
175
|
+
];
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Limits:** Max 1 level of subquery depth. Child subqueries do NOT count as separate SOQL queries. Max 20 subqueries per query.
|
|
179
|
+
|
|
180
|
+
### Child-to-Parent (Dot Notation)
|
|
181
|
+
|
|
182
|
+
```apex
|
|
183
|
+
List<Contact> contacts = [
|
|
184
|
+
SELECT Id, FirstName, LastName,
|
|
185
|
+
Account.Name,
|
|
186
|
+
Account.Owner.Name,
|
|
187
|
+
Account.Owner.Email
|
|
188
|
+
FROM Contact
|
|
189
|
+
WHERE AccountId != null
|
|
190
|
+
WITH USER_MODE
|
|
191
|
+
];
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Limits:** Max 5 levels of parent traversal. Max 35 relationship traversals total per query.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Aggregate Queries
|
|
199
|
+
|
|
200
|
+
```apex
|
|
201
|
+
// Use COUNT() instead of loading records to count
|
|
202
|
+
Integer customerCount = [SELECT COUNT() FROM Account WHERE Type = 'Customer'];
|
|
203
|
+
|
|
204
|
+
// AggregateResult for grouped/summed data
|
|
205
|
+
List<AggregateResult> results = [
|
|
206
|
+
SELECT Type, COUNT(Id) recordCount, SUM(AnnualRevenue) totalRevenue
|
|
207
|
+
FROM Account
|
|
208
|
+
WHERE Type != null
|
|
209
|
+
GROUP BY Type
|
|
210
|
+
HAVING COUNT(Id) > 5
|
|
211
|
+
ORDER BY COUNT(Id) DESC
|
|
212
|
+
];
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Dynamic SOQL — Safe Pattern
|
|
218
|
+
|
|
219
|
+
```apex
|
|
220
|
+
public List<SObject> buildDynamicQuery(
|
|
221
|
+
String objectName, List<String> fields,
|
|
222
|
+
String whereClause, Integer maxRecords
|
|
223
|
+
) {
|
|
224
|
+
Schema.DescribeSObjectResult describe =
|
|
225
|
+
Schema.getGlobalDescribe().get(objectName)?.getDescribe();
|
|
226
|
+
if (describe == null) {
|
|
227
|
+
throw new InvalidQueryException('Unknown object: ' + objectName);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
Map<String, Schema.SObjectField> fieldMap = describe.fields.getMap();
|
|
231
|
+
List<String> validatedFields = new List<String>();
|
|
232
|
+
for (String field : fields) {
|
|
233
|
+
if (fieldMap.containsKey(field.toLowerCase())) {
|
|
234
|
+
validatedFields.add(field);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (validatedFields.isEmpty()) validatedFields.add('Id');
|
|
238
|
+
|
|
239
|
+
String soql = 'SELECT ' + String.join(validatedFields, ', ') +
|
|
240
|
+
' FROM ' + objectName;
|
|
241
|
+
if (String.isNotBlank(whereClause)) {
|
|
242
|
+
soql += ' WHERE ' + whereClause;
|
|
243
|
+
}
|
|
244
|
+
soql += ' LIMIT ' + Math.min(maxRecords, 2000);
|
|
245
|
+
|
|
246
|
+
return Database.queryWithBinds(soql, new Map<String, Object>(), AccessLevel.USER_MODE);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Large Data Volume Patterns
|
|
253
|
+
|
|
254
|
+
### Custom Indexes
|
|
255
|
+
|
|
256
|
+
For heavily-queried fields not indexed by default, request a custom index from Salesforce Support. Provide object name, field API name, and typical query pattern.
|
|
257
|
+
|
|
258
|
+
### Skinny Tables
|
|
259
|
+
|
|
260
|
+
For objects with >10 million records, Salesforce Support can create a skinny table — a narrow copy with only the most-queried fields.
|
|
261
|
+
|
|
262
|
+
### Batch Apex for LDV Processing
|
|
263
|
+
|
|
264
|
+
```apex
|
|
265
|
+
public class LargeDataProcessingBatch implements Database.Batchable<SObject> {
|
|
266
|
+
public Database.QueryLocator start(Database.BatchableContext bc) {
|
|
267
|
+
return Database.getQueryLocator([
|
|
268
|
+
SELECT Id, Status__c FROM Account
|
|
269
|
+
WHERE CreatedDate < :Date.today().addYears(-5)
|
|
270
|
+
AND Status__c = 'Active'
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
273
|
+
public void execute(Database.BatchableContext bc, List<Account> scope) { /* ... */ }
|
|
274
|
+
public void finish(Database.BatchableContext bc) {}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### SOSL for Full-Text Search
|
|
279
|
+
|
|
280
|
+
```apex
|
|
281
|
+
String searchTerm = 'Acme Holdings';
|
|
282
|
+
List<List<SObject>> searchResults = [
|
|
283
|
+
FIND :searchTerm
|
|
284
|
+
IN ALL FIELDS
|
|
285
|
+
RETURNING
|
|
286
|
+
Account(Id, Name, Type WHERE Type = 'Customer'),
|
|
287
|
+
Contact(Id, FirstName, LastName, AccountId)
|
|
288
|
+
LIMIT 50
|
|
289
|
+
];
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Modern SOQL Features
|
|
295
|
+
|
|
296
|
+
### TYPEOF for Polymorphic Lookups
|
|
297
|
+
|
|
298
|
+
```apex
|
|
299
|
+
List<Task> tasks = [
|
|
300
|
+
SELECT Id, Subject,
|
|
301
|
+
TYPEOF What
|
|
302
|
+
WHEN Account THEN Name, Phone
|
|
303
|
+
WHEN Opportunity THEN Amount, StageName
|
|
304
|
+
END
|
|
305
|
+
FROM Task
|
|
306
|
+
WHERE OwnerId = :userId
|
|
307
|
+
WITH USER_MODE
|
|
308
|
+
];
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Use when querying objects with polymorphic lookups (Task.WhoId, Task.WhatId, Event.WhoId, Event.WhatId) to avoid multiple queries or instanceof checks.
|
|
312
|
+
|
|
313
|
+
### Semi-Join and Anti-Join Subqueries
|
|
314
|
+
|
|
315
|
+
```soql
|
|
316
|
+
-- Semi-join: accounts that have contacts with emails
|
|
317
|
+
SELECT Id, Name FROM Account
|
|
318
|
+
WHERE Id IN (SELECT AccountId FROM Contact WHERE Email != null)
|
|
319
|
+
|
|
320
|
+
-- Anti-join: accounts with no closed opportunities
|
|
321
|
+
SELECT Id, Name FROM Account
|
|
322
|
+
WHERE Id NOT IN (SELECT AccountId FROM Opportunity WHERE IsClosed = true)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Inner subquery can only return one field. Max one level of nesting. More efficient than querying both objects and filtering in Apex.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Query Plan Tool
|
|
330
|
+
|
|
331
|
+
Use the Developer Console Query Plan to understand query execution before deploying.
|
|
332
|
+
|
|
333
|
+
1. Open Developer Console > Query Editor
|
|
334
|
+
2. Enable Query Plan: Help menu > Preferences > Enable Query Plan
|
|
335
|
+
3. Write query and click "Query Plan" instead of "Execute"
|
|
336
|
+
|
|
337
|
+
| Result | Meaning |
|
|
338
|
+
|---|---|
|
|
339
|
+
| `TableScan` | Full table scan — potentially slow on large objects |
|
|
340
|
+
| `Index` | Index used — fast and selective |
|
|
341
|
+
| `Cost` | Estimated relative cost — lower is better |
|
|
342
|
+
|
|
343
|
+
If you see `TableScan` on a large object:
|
|
344
|
+
|
|
345
|
+
1. Add a more selective condition using an indexed field
|
|
346
|
+
2. Request a custom index from Salesforce Support
|
|
347
|
+
3. Restructure the query to use SOSL
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Related
|
|
352
|
+
|
|
353
|
+
- **Agent**: `sf-apex-agent` — For interactive, in-depth guidance
|
|
354
|
+
- **Constraints**: `sf-soql-constraints` — Hard rules for SOQL safety and compliance
|