Releases: beyond-the-cloud-dev/soql-lib
v6.4.0
v6.3.0
v6.2.0
v6.2.0 - 18-October-2024
Scope
- Enhanced Mocking Capabilities
- Documentation Update
SOQL
- Added support for mocking query exceptions
- Improved error testing capabilities
Mocking Query Exceptions
SOQL Lib now supports mocking query exceptions, enabling you to test error handling scenarios in your Apex code. This is essential for validating that your application gracefully handles database errors, security violations, and other query-related exceptions.
Default Exception Mock
Use .throwException() to simulate a standard query exception with the default message: "List has no rows for assignment to SObject".
Controller
public with sharing class ExampleController {
public static Account getAccountById(Id accountId) {
try {
return (Account) SOQL.of(Account.SObjectType)
.with(Account.Name, Account.BillingCity)
.byId(accountId)
.mockId('ExampleController.getAccountById')
.toObject();
} catch (Exception e) {
// Logger here
throw e;
}
}
}Test Class
@IsTest
static void getAccountByIdException() {
SOQL.mock('ExampleController.getAccountById').throwException();
Test.startTest();
Exception error;
try {
Account result = ExampleController.getAccountById('001000000000000AAA');
} catch (Exception e) {
error = e;
}
Test.stopTest();
Assert.isNotNull(error, 'The query exception should be thrown.');
}Custom Exception Message Mock
Use .throwException(message) to simulate a query exception with a custom error message, such as field-level security errors or invalid field references.
Test Class
@IsTest
static void getAccountByIdException() {
String errorMessage = 'No such column \'InvalidField__c\' on entity \'Account\'.';
SOQL.mock('ExampleController.getAccountById').throwException(errorMessage);
Test.startTest();
Exception error;
try {
Account result = ExampleController.getAccountById('001000000000000AAA');
} catch (Exception e) {
error = e;
}
Test.stopTest();
Assert.isNotNull(error, 'The query exception should be thrown.');
Assert.isTrue(error.getMessage().contains('InvalidField__c'));
}Documentation Update
Added comprehensive documentation for exception mocking, including:
- Detailed examples of both default and custom exception messages
- Best practices for testing error handling scenarios
- Integration examples with controller error handling patterns
🚨 Breaking Changes 🚨
There are no breaking changes in this release. All new features are additive.
v6.1.0
v6.1.0 - September 14, 2025
Scope
- NEW: Enhanced Result Mapping with ID-based Methods
- IMPROVED: Automatic Performance Optimization with Null Checking
- IMPROVED: Documentation Website UI/UX Enhancements
- IMPROVED: Code Formatting Optimization - Converted Indentation to Tabs
Major New Features
Enhanced Result Mapping - ID-based Methods
The biggest addition to v6.1.0 is the introduction of new ID-focused result mapping methods that provide more precise control over how query results are transformed into maps using ID keys.
New ID Mapping Methods
// Map records by any ID field
Map<Id, Account> ownerIdToAccount = (Map<Id, Account>) SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.toIdMapBy(Account.OwnerId);
// Map records by relationship ID fields
Map<Id, Account> parentIdToAccount = (Map<Id, Account>) SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.toIdMapBy('Parent', Account.Id);
// Group multiple records by ID field
Map<Id, List<Account>> ownerIdToAccounts = (Map<Id, List<Account>>) SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.toAggregatedIdMapBy(Account.OwnerId);
// Group multiple records by relationship ID field
Map<Id, List<Account>> parentIdToAccounts = (Map<Id, List<Account>>) SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.toAggregatedIdMapBy('Parent', Account.Id);Automatic Performance Optimization
Smart Null Checking for Better Query Performance
All mapping methods (toMap, toAggregatedMap, toIdMapBy, toAggregatedIdMapBy) now automatically add null-checking conditions to improve query performance:
WHERE keyField != null- Automatically added for direct field mappingWHERE relationshipName.targetKeyField != null- Automatically added for relationship field mapping
This optimization reduces result set size and improves query execution time by filtering out records with null key values that would be unusable in the resulting maps.
Traditional Approach:
// Manual null checking required
Map<Id, Account> ownerIdToAccount = new Map<Id, Account>();
for (Account acc : [SELECT Id, Name, OwnerId FROM Account WHERE OwnerId != null]) {
ownerIdToAccount.put(acc.OwnerId, acc);
}SOQL Lib Approach:
// Automatic null checking built-in
Map<Id, Account> ownerIdToAccount = (Map<Id, Account>) SOQL.of(Account.SObjectType)
.with(Account.Id, Account.Name)
.toIdMapBy(Account.OwnerId);Documentation and UI Improvements
Enhanced Documentation Website
Light/Dark Mode Compatibility
- Fixed homepage text visibility issues in light mode
- Improved contrast and readability across all themes
- Enhanced user experience with responsive color schemes
Comprehensive Method Documentation
- Added complete documentation for all new ID mapping methods
- Included practical examples showing traditional vs SOQL Lib approaches
- Added performance optimization notes for all mapping methods
- Enhanced code examples with field selection for better clarity
Code Formatting Optimization
Indentation Converted to Tabs
- Converted all code indentation from spaces to tabs across the entire codebase
- Reduced character count by approximately 10% improving file size and load times
New Methods Reference
SOQL Result Methods
toIdMapBy(SObjectField field)
Creates a map where the key is the Id extracted from the specified field and the value is the SObject record.
toIdMapBy(String relationshipName, SObjectField targetKeyField)
Creates a map where the key is the Id extracted from the specified relationship field and the value is the SObject record.
toAggregatedIdMapBy(SObjectField keyField)
Creates a map where the key is the Id extracted from the specified field and the value is a list of SObject records grouped by that Id.
toAggregatedIdMapBy(String relationshipName, SObjectField targetKeyField)
Creates a map where the key is the Id extracted from the specified relationship field and the value is a list of SObject records grouped by that Id.
Contributors
Thank you @kaboomkazoom for your ideas and support!
Download: v6.1.0 Release
Documentation: https://soql.beyondthecloud.dev
v6.0.2
Release Notes - Fix SOQL Cache key
SOQLCache.cls
When a custom object with a __c or __mdt suffix was cached using the SOQLCache module, the following error would occur:
Invalid Key, Key cannot be null or empty and must be alphanumeric.
This patch resolves the issue.
v6.0.1
Release Notes - Fix toIdsOf/toValuesOf Mocking
SOQL_Test.cls
Added comprehensive test methods for mocking scenarios:
- ✅
toIdWithMocking(): Tests single Id extraction with mocked data - ✅
toIdsWithMocking(): Tests multiple Ids extraction with mocked data - ✅
toIdsOfWithMocking(): Tests field aliasing for Id extraction with AggregateResult mocking - ✅
toIdsOfRelationshipFieldWithMocking(): Tests relationship field Id extraction with mocking
Key Testing Pattern:
SOQL.mock('mockingQuery').thenReturn(
(List<AggregateResult>) JSON.deserialize(
JSON.serialize(new List<Map<String, Object>>{
new Map<String, Object>{ 'Id' => SOQL.IdGenerator.get(Account.SObjectType) }
}),
List<AggregateResult>.class
)
);What This Fixes
Before the Fix
// This would fail with mocking
Set<Id> parentIds = SOQL.of(Account.SObjectType)
.mockId('test')
.toIdsOf(Account.ParentId);After the Fix
// Setup mock with AggregateResult structure
SOQL.mock('test').thenReturn(
(List<AggregateResult>) JSON.deserialize(
JSON.serialize(new List<Map<String, Object>>{
new Map<String, Object>{ 'Id' => someAccountId }
}),
List<AggregateResult>.class
)
);
// Now works properly
Set<Id> parentIds = SOQL.of(Account.SObjectType)
.mockId('test')
.toIdsOf(Account.ParentId);v6.0.0
v6.0.0 - August 24, 2025
Scope
- NEW: SOQL Evaluator for Static SOQL Processing
- Enhanced SOQLCache with Relationship Field Support
- Comprehensive Documentation Overhaul
- Multiple Condition Support for Cached Queries
- Improved Testing and Code Coverage
SOQLEvaluator
- NEW: Introduced
SOQLEvaluatorfor processing static query results - NEW: Complete mocking framework for static data evaluation
- NEW: Field-level security support for static data
- NEW: All standard SOQL Lib result methods (
toId,toList,toMap, etc.)
SOQLCache
- NEW: Added relationship field support with
with(relationshipName, field)methods - NEW: Added
toIdOf()method for extracting IDs from specific fields - NEW: Enhanced filtering flexibility with
allowFilteringByNonUniqueFields()andallowQueryWithoutConditions() - NEW: Support for multiple conditions and additional allowed condition fields
- IMPROVED: Better error handling and query validation
Major New Features
SOQL Evaluator - Static Data Processing
The biggest addition to v6.0.0 is the new SOQLEvaluator class, which brings SOQL Lib's powerful API to static query results. This enables you to apply the same transformation and filtering patterns to in-memory data that you use for database queries.
Basic Usage
List<Account> processedAccounts = SOQLEvaluator.of([SELECT Id, Name FROM Account])
.stripInaccessible()
.toList();
// Extract data using familiar methods
Set<Id> ownerIds = SOQLEvaluator.of([SELECT OwnerId FROM Account])
.toIdsOf(Account.OwnerId);
Map<Id, Account> accountMap = SOQLEvaluator.of([SELECT Id, Name FROM Account])
.toMap();Field-Level Security Support
List<Task> safeTasks = SOQLEvaluator.of([SELECT Id, Type FROM Task])
.stripInaccessible(AccessType.READABLE)
.toList();Comprehensive Mocking for Tests
// In your controller
public static List<Account> getAccounts() {
return SOQLEvaluator.of([SELECT Id, Name FROM Account])
.mockId('ExampleController.getAccounts')
.toList();
}
// In your test
@IsTest
static void testProcessExternalAccounts() {
List<Account> mockAccounts = new List<Account>{
new Account(Name = 'Test Account 1'),
new Account(Name = 'Test Account 2')
};
SOQLEvaluator.mock('ExampleController.getAccounts')
.thenReturn(mockAccounts);
Test.startTest();
List<Account> result = ExampleController.getAccounts();
Test.stopTest();
Assert.areEqual(2, result.size());
}Enhanced SOQLCache with Relationship Field Support
SOQLCache now supports querying relationship fields directly, making it easier to work with related data in cached queries.
New Relationship Field Methods
// Single relationship field
SOQLCache.of(Account.SObjectType)
.with(Account.Name)
.with('Owner', User.Name)
.byId(accountId)
.toObject();
// Multiple relationship fields
SOQLCache.of(Account.SObjectType)
.with('Owner', User.Name, User.Email, User.Department)
.byId(accountId)
.toObject();
// Using field collections
SOQLCache.of(Account.SObjectType)
.with('Owner', new List<SObjectField>{ User.Name, User.Email })
.byId(accountId)
.toObject();New toIdOf() Method for SOQLCache
// Extract specific field IDs from cached data
Id ownerId = SOQLCache.of(Account.SObjectType)
.byId(accountId)
.toIdOf(Account.OwnerId);Enhanced Cache Configuration
Flexible Filtering Options
// Allow filtering by non-unique fields (use with caution)
SOQLCache.of(Account.SObjectType)
.allowFilteringByNonUniqueFields()
.whereEqual(Account.Industry, 'Technology')
.toList();
// Allow queries without WHERE conditions
SOQLCache.of(Account.SObjectType)
.allowQueryWithoutConditions()
.toList();Additional Allowed Condition Fields
public class SOQL_Account extends SOQLCache implements SOQLCache.Selector {
public override List<SObjectField> additionalAllowedConditionFields() {
return new List<SObjectField>{
Account.Industry,
Account.Type
};
}
}Documentation Revolution
We've completely overhauled our documentation to provide the best developer experience possible:
Comprehensive Code Examples
- Titled Code Blocks: Every code example now has descriptive titles for better context
- Real-World Scenarios: Added extensive examples covering complex business cases
- Side-by-Side Comparisons: Traditional SOQL vs SOQL Lib approaches clearly demonstrated
Breaking Changes
Encapsulation improvements
Class: SOQL.cls
.of methods will now return the SOQL.Queryable interface instead of the SOQL class itself.
If this is a breaking change for you, retain the previous approach.
Previous:
public static SOQL of(SObjectType ofObject) {
return new SOQL(ofObject);
}
public static SOQL of(String ofObject) {
return new SOQL(ofObject);
}New:
public static Queryable of(SObjectType ofObject) {
return new SOQL(ofObject);
}
public static Queryable of(String ofObject) {
return new SOQL(ofObject);
}Download: v6.0.0 Release
Documentation: https://soql.beyondthecloud.dev
v5.2.0
13-August-2025
Scope
- New Query Result Methods
- Enhanced Mocking System (
SOQL&SOQLCache) - Documentation Improvements
- Performance Optimizations
- Code Quality Improvements
SOQL
- Added new result extraction methods:
toIds(),toIdsOf(), andnullsOrder(). - Enhanced subquery support with relationship name tracking.
- Improved mocking system with sequential stack functionality.
- Performance optimizations in string building and field handling.
- Better mock field stripping for subqueries and plain fields.
SOQLCache
- Enhanced mocking system with stack functionality supporting sequential mocks.
- Improved performance with optimized object instantiation and field conversion.
- Added null safety improvements and better error handling.
- Updated to API version 64.0 across all components.
Documentation
- Added comprehensive "Minimalistic Selectors" section with design philosophy and examples.
- Enhanced "Minimal Fields" documentation with performance benefits and best practices.
- Improved mocking documentation with stack functionality explanation.
- Added practical before/after examples for new query result methods.
API Version Update
All Apex classes have been updated to API version 64.0, ensuring compatibility with the latest Salesforce features and improvements.
Enhanced Mocking System
Both SOQL and SOQLCache now feature improved mocking capabilities with better performance and cleaner code organization.
SOQL Library Improvements
The standard SOQL library has received several mocking and performance enhancements:
Legacy Mock Consolidation
// Before: Duplicate code in deprecated methods
private static void setMock(String mockId, SObject record) {
SOQL.queryIdToMock.put(mockId, new List<SoqlMock>{ new SoqlMock().useLegacyMockingBehavior() });
SOQL.queryIdToMock.get(mockId).get(SOQL.queryIdToMock.get(mockId).size() - 1).thenReturn(record);
}
// After: Clean helper method
private static void setMock(String mockId, SObject record) {
SOQL.createLegacyMock(mockId).thenReturn(record);
}
private static SoqlMock createLegacyMock(String mockId) {
SoqlMock mock = new SoqlMock().useLegacyMockingBehavior();
SOQL.queryIdToMock.put(mockId, new List<SoqlMock>{ mock });
return mock;
}Enhanced Binder with Reset Capability
public class Binder {
public void reset() {
this.bindIndex = 0;
this.binding.clear();
}
}
// Usage in toString():
public override String toString() {
binder.reset(); // Better memory management
return this.builder.toString();
}SOQLCache Enhancements
SOQLCache now supports the same sophisticated mocking capabilities as the standard SOQL library, including sequential mock execution.
Sequential Mock Stack
You can now set up multiple mocks for the same query identifier, which will be consumed in FIFO (First In, First Out) order:
Test Setup
@IsTest
static void mockStack() {
// Setup multiple sequential mocks
SOQL.mock('mockingQuery').thenReturn(new Profile(Name = 'Test 1'));
SOQL.mock('mockingQuery').thenReturn(new Profile(Name = 'Test 2'));
SOQL.mock('mockingQuery').thenReturn(new Profile(Name = 'Test 3'));
// Test
SOQLCache.Cacheable query = SOQLCache.of(Profile.SObjectType)
.mockId('mockingQuery')
.whereEqual(Profile.Name, 'System Administrator');
Profile profile1 = (Profile) query.toObject(); // Returns 'Test 1'
Profile profile2 = (Profile) query.toObject(); // Returns 'Test 2'
Profile profile3 = (Profile) query.toObject(); // Returns 'Test 3'
Profile profile4 = (Profile) query.toObject(); // Returns 'Test 3' (reuses last)
}Modernized Mock API
The mocking system has been updated to use the modern .mock().thenReturn() pattern instead of the deprecated .setMock() methods:
Before
SOQL.setMock('ProfileQuery', mockProfile);After
SOQL.mock('ProfileQuery').thenReturn(mockProfile);Performance Optimizations
SOQL Library Performance Improvements
Optimized String Building
The toString() method now uses efficient String.join() instead of string concatenation:
// Before: String concatenation in loop
public override String toString() {
String query = '';
for (QueryClause clause : this.clauses) {
if (clause != null) {
query += ' ' + clause.toString(); // Inefficient O(n²) complexity
}
}
return query.trim();
}
// After: Efficient list-based joining
public override String toString() {
List<String> queryParts = new List<String>();
for (QueryClause clause : this.clauses) {
if (clause != null) {
queryParts.add(clause.toString());
}
}
return String.join(queryParts, ' '); // O(n) complexity
}Enhanced Field Handling
- Removed unnecessary null coalescing operations in field processing
- Optimized
doExist()method withsetLimit(1)for better performance - Improved
isEmpty()checks instead ofsize() > 0comparisons
SOQLCache Performance Improvements
Reduced Object Instantiation
Optimized cache storage operations to reuse proxy objects instead of creating multiple instances:
Before
new CacheStorageProxy(ofObject).apexTransaction().removeRecordsFromCache(records);
new CacheStorageProxy(ofObject).orgCache().removeRecordsFromCache(records);
new CacheStorageProxy(ofObject).sessionCache().removeRecordsFromCache(records);After
CacheStorageProxy proxy = new CacheStorageProxy(ofObject);
proxy.apexTransaction().removeRecordsFromCache(records);
proxy.orgCache().removeRecordsFromCache(records);
proxy.sessionCache().removeRecordsFromCache(records);Improved Field Conversion
Standardized field-to-string conversion throughout the codebase using .toString() instead of string concatenation:
// Consistent field conversion
this.cachedFields.add(field.toString());
return this.whereEqual(field.toString(), value);Code Quality Improvements
Enhanced Null Safety
Added proper null safety checks to prevent NullPointerExceptions:
public Id toId() {
return this.toObject()?.Id; // Safe null handling
}Optimized Boolean Logic
Improved performance in field validation with early return patterns:
Before
public Boolean hasAllRequestedFields(SObject record) {
Boolean hasCachedFields = true;
for (String field : this.cachedFields) {
hasCachedFields &= record.isSet(field);
}
return hasCachedFields;
}After
public Boolean hasAllRequestedFields(SObject record) {
for (String field : this.cachedFields) {
if (!record.isSet(field)) {
return false; // Early exit for better performance
}
}
return true;
}Cleanup and Optimization
- Removed unused variables and redundant code
- Optimized time calculations with direct numeric constants
- Improved error handling consistency
- Enhanced executor logic for better mock management
Breaking Changes
There are no breaking changes in this release. All optimizations maintain backward compatibility while improving performance and reliability.
Contributors
Thank you to all contributors who helped make this release possible through code optimization, testing, and feedback.
v5.1.0
07-June-2025
Scope
- Documentation Update
- New SOQL Features
- Enhanced
AggregateResultMocking - Querying Improvements
SOQL
- Added support for
WITH DATA CATEGORY. - Introduced a new way to mock
AggregateResult. - Added
orderByCount()for aggregate queries. - Enhanced
toValuesOf()to support related fields. - Added
alwaysAddId()for convenience.
Documentation Update
We've invested heavily in making our documentation clearer and more comprehensive.
- New Examples: Added extensive examples for new features like
WITH DATA CATEGORYandAggregateResultmocking. - Titled Code Blocks: All code examples now have titles, making it easier to understand the context of each snippet.
- Improved Structure: The documentation sidebar has been reorganized for better navigation.
New Feature: WITH DATA CATEGORY
SOQL Lib now fully supports WITH DATA CATEGORY clauses, allowing you to filter queries based on Salesforce Knowledge data categories.
SOQL
SELECT Id, Title
FROM Knowledge__kav
WITH DATA CATEGORY Geography__c AT (Europe__c, North_America__c)SOQL Lib
SOQL.of(Knowledge__kav.SObjectType)
.with(Knowledge__kav.Id, Knowledge__kav.Title)
.withDataCategory(
SOQL.DataCategoryFilter
.with('Geography__c')
.at(new List<String>{ 'Europe__c', 'North_America__c' })
).toList();Enhanced AggregateResult Mocking
Mocking AggregateResult has always been a challenge in Apex. We've introduced SOQL.AggregateResultProxy to simplify this process. Use the new .toAggregatedProxy() method to get mockable results from your aggregate queries.
Controller
public with sharing class ExampleController {
public void getLeadAggregateResults() {
List<SOQL.AggregateResultProxy> result = SOQL.of(Lead.SObjectType)
.with(Lead.LeadSource)
.COUNT(Lead.Id, 'total')
.groupBy(Lead.LeadSource)
.mockId('ExampleController.getLeadAggregateResults')
.toAggregatedProxy(); // <== Use toAggregatedProxy()
}
}Test Class
@IsTest
static void getLeadAggregateResults() {
List<Map<String, Object>> aggregateResults = new List<Map<String, Object>>{
new Map<String, Object>{ 'LeadSource' => 'Web', 'total' => 10},
new Map<String, Object>{ 'LeadSource' => 'Phone', 'total' => 5}
};
SOQL.mock('ExampleController.getLeadAggregateResults').thenReturn(aggregateResults);
Test.startTest();
List<SOQL.AggregateResultProxy> result = ExampleController.getLeadAggregateResults();
Test.stopTest();
Assert.areEqual(2, result.size());
}Querying Improvements
ORDER BY COUNT()
You can now order grouped results by the count of a field using the orderByCount() method.
SOQL
SELECT Industry
FROM Account
GROUP BY Industry
ORDER BY COUNT(Industry) DESC NULLS LASTSOQL Lib
SOQL.of(Account.SObjectType)
.with(Account.Industry)
.groupBy(Account.Industry)
.orderByCount(Account.Industry).sortDesc().nullsLast()
.toAggregated();toValuesOf() for Related Fields
The toValuesOf() method now supports retrieving values from related parent objects, simplifying data extraction.
Apex
Set<String> parentAccountNames = new Set<String>();
for (Account acc : [SELECT Parent.Name FROM Account WHERE ParentId != NULL]) {
parentAccountNames.add(acc.Parent.Name);
}SOQL Lib
Set<String> parentAccountNames = SOQL.of(Account.SObjectType)
.toValuesOf('Parent', Account.Name);Breaking Changes
There are no breaking changes in this release.
Contributors
Thank you @packocz for your ideas and support!
v5.0.0
08-April-2025
Scope
- Documentation Update
SOQL and SOQL Cache
- Better mocking with
Mockableinterface - Code performance improvement
- Better encapsulation
- Code refactoring
Documentation Update
We significantly improved SOQL Lib documentation: https://soql.beyondthecloud.dev/
You can find many use cases at https://soql.beyondthecloud.dev/examples/showcase.
The concept is straightforward—each example is shown in SOQL and its equivalent in SOQL Lib.
Example
SOQL
SELECT Id, Name, BillingCity
FROM AccountSOQL Lib
SOQL.of(Account.SObject)
.with(Account.Id, Account.Name, Account.BillingCity)
.toList();Better Mocking
We introduced a new Mockable interface. This interface lays the foundation for future improvements in mocking within SOQL Lib.
public interface Mockable {
// SObject
Mockable thenReturn(SObject record);
Mockable thenReturn(List<SObject> records);
// Count
Mockable thenReturn(Integer count);
}List<Account> accounts = new List<Account>{
new Account(Name = 'MyAccount 1'),
new Account(Name = 'MyAccount 2')
};
SOQL.mock('ExampleController.getPartnerAccounts')
.thenReturn(accounts);Stripping Extra Fields
We also introduced logic to strip unused fields during mocking.
Problem:
Previously, mocking returned all fields in the mock object, even if those fields weren't part of the actual query. This caused unit tests to pass, but led to runtime errors like:
System.SObjectException: SObject row was retrieved via SOQL without querying the requested field.
Solution:
We now keep only the fields that are actually queried. Extra fields are stripped from mocked records. Currently, this applies only to plain fields like Id, Name, etc. Support for relationship fields and subqueries is planned for future release.
for (Account account : SOQL.of(Account.SObjectType).with(Account.Name).toList()) {
String industry = account.Industry; // <- field not queried
}List<Account> accounts = new List<Account>{
new Account(Name = 'MyAccount 1', Industry = 'IT'),
new Account(Name = 'MyAccount 2', Industry = 'Administration')
};
SOQL.mock('ExampleController.getPartnerAccounts')
.thenReturn(accounts);Better Encapsulation
Previously, the method returned an instance of SOQL instead of the Queryable interface.
New Behavior:
This ensures that callers can only access the interface’s methods - supporting loose coupling, better encapsulation, and adherence to the Dependency Inversion Principle.
// ❌ Before
public SOQL with(SObjectField field) {
// ...
}
// ✅ After
public Queryable with(SObjectField field) {
// ...
}Code Refactoring
SObjectField Optimization
Instead of using:
field.getDescribe().getName()We now use:
field.toString()This change makes operations with SObjectField approximately 9x faster.
General Refactoring
We’ve streamlined the code in the SOQL class, resulting in a shorter and more maintainable implementation.
🚨 Breaking Changes 🚨
Mocking
Old mocking methods are still supported for backward compatibility. However, we recommend using the new approach with SOQL.mock(...).thenReturn(...);.
// Backward support
@TestVisible // deprecated
private static void setMock(String mockId, SObject record) {
mock(mockId).thenReturn(record);
}
@TestVisible // deprecated
private static void setMock(String mockId, List<SObject> records) {
mock(mockId).thenReturn(records);
}
@TestVisible // deprecated
private static void setCountMock(String mockId, Integer amount) {
mock(mockId).thenReturn(amount);
}Queryable
As mentioned in the Better Encapsulation section, all SOQL methods now return the Queryable interface instead of the SOQL class directly.
// ❌ Before
public SOQL with(SObjectField field) {
// ...
}
// ✅ After
public Queryable with(SObjectField field) {
// ...
}If you have existing logic like this:
SOQL query = SOQL.of(...);Replace it with:
SOQL.Queryable query = SOQL.of(...);