Skip to content

Commit

Permalink
SOQL Cache (#140)
Browse files Browse the repository at this point in the history
* SOQL Cache [Draft] (#137)

* Cannot modify a collection while it is being iterated Fix (#133)

* Cannot modify a collection while it is being iterated Fix

Signed-off-by: Piotr PG Gajek <[email protected]>

* Code refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* groupByWithDefaultFieldsAndAggregateFunction unit test

Signed-off-by: Piotr PG Gajek <[email protected]>

---------

Signed-off-by: Piotr PG Gajek <[email protected]>

* Cache Init

Signed-off-by: Piotr PG Gajek <[email protected]>

* draft

Signed-off-by: Piotr PG Gajek <[email protected]>

* draft

Signed-off-by: Piotr PG Gajek <[email protected]>

* Cache Concept

Signed-off-by: Piotr PG Gajek <[email protected]>

* Cache SOQL Examples

Signed-off-by: Piotr PG Gajek <[email protected]>

* Cache refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* caching draft

Signed-off-by: Piotr PG Gajek <[email protected]>

* final draft

Signed-off-by: Piotr PG Gajek <[email protected]>

* SQOL Cache

Signed-off-by: Piotr PG Gajek <[email protected]>

* refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQLCache

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQLCache_Test

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQLCache_Test

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQLCache_Test

Signed-off-by: Piotr PG Gajek <[email protected]>

* docs placeholder

Signed-off-by: Piotr PG Gajek <[email protected]>

* Documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* handlefiltering

Signed-off-by: Piotr PG Gajek <[email protected]>

* refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* Additional methods

Signed-off-by: Piotr PG Gajek <[email protected]>

* cache mocking

Signed-off-by: Piotr PG Gajek <[email protected]>

* Examples

Signed-off-by: Piotr PG Gajek <[email protected]>

* stucture changes

Signed-off-by: Piotr PG Gajek <[email protected]>

* Fix unit tests

Signed-off-by: Piotr PG Gajek <[email protected]>

* Unit Test Fix

Signed-off-by: Piotr PG Gajek <[email protected]>

* Cache Fixes

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQLCache_Test

Signed-off-by: Piotr PG Gajek <[email protected]>

* toObjectWithMultipleRows

Signed-off-by: Piotr PG Gajek <[email protected]>

* Default Sharing for cached selector

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQLCache_Test

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* website update

Signed-off-by: Piotr PG Gajek <[email protected]>

* mermaid

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* toId() (#139)

Signed-off-by: Piotr PG Gajek <[email protected]>

* soql cache

Signed-off-by: Piotr PG Gajek <[email protected]>

* Documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* Fix

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQL Cache Test

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQLCacheTest

Signed-off-by: Piotr PG Gajek <[email protected]>

* PMD

Signed-off-by: Piotr PG Gajek <[email protected]>

* Refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* License

Signed-off-by: Piotr PG Gajek <[email protected]>

* Fix Unit Tests

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation update

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* refactoring

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation 3.7.0

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* mermaid

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

* documentation

Signed-off-by: Piotr PG Gajek <[email protected]>

---------

Signed-off-by: Piotr PG Gajek <[email protected]>

* SOQLCache_Test Fix

Signed-off-by: Piotr PG Gajek <[email protected]>

* fixed failing Platform Cache

Signed-off-by: Maciej Ptak <[email protected]>

---------

Signed-off-by: Piotr PG Gajek <[email protected]>
Signed-off-by: Maciej Ptak <[email protected]>
Co-authored-by: Maciej Ptak <[email protected]>
  • Loading branch information
pgajek2 and 0ptaq0 authored Jan 7, 2025
1 parent 1c59f1e commit e0f4531
Show file tree
Hide file tree
Showing 81 changed files with 17,731 additions and 17,258 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 BeyondTheCloud.Dev
Copyright (c) 2025 BeyondTheCloud.Dev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ Read about the features in the [documentation](https://soql-lib.vercel.app/docs/
6. **Avoid query duplicates**
7. **The default configuration for all queries**
8. **Dynamic conditions**
9. **Cache records**

----

Expand All @@ -107,4 +108,4 @@ Read about the features in the [documentation](https://soql-lib.vercel.app/docs/
## License notes

- For proper license management each repository should contain LICENSE file similar to this one.
- Each original class should contain copyright mark: Copyright (c) 2023 BeyondTheCloud.Dev
- Each original class should contain copyright mark: Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
3 changes: 2 additions & 1 deletion config/project-scratch-def.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"edition": "Developer",
"features": [
"EnableSetPasswordInApi",
"PersonAccounts"
"PersonAccounts",
"PlatformCache"
],
"settings": {
"lightningExperienceSettings": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<PlatformCachePartition xmlns="http://soap.sforce.com/2006/04/metadata">
<isDefaultPartition>false</isDefaultPartition>
<masterLabel>SOQL</masterLabel>
<platformCachePartitionTypes>
<allocatedCapacity>1</allocatedCapacity>
<allocatedPartnerCapacity>0</allocatedPartnerCapacity>
<allocatedPurchasedCapacity>0</allocatedPurchasedCapacity>
<allocatedTrialCapacity>0</allocatedTrialCapacity>
<cacheType>Organization</cacheType>
</platformCachePartitionTypes>
<platformCachePartitionTypes>
<allocatedCapacity>1</allocatedCapacity>
<allocatedPartnerCapacity>0</allocatedPartnerCapacity>
<allocatedPurchasedCapacity>0</allocatedPurchasedCapacity>
<allocatedTrialCapacity>0</allocatedTrialCapacity>
<cacheType>Session</cacheType>
</platformCachePartitionTypes>
</PlatformCachePartition>
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
public with sharing class ExampleController {

@AuraEnabled
public static List<Contact> getContactsByRecordType(String recordType) {
return SOQL_Contact.query()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
public with sharing class SOQL_BusinessHoursCache extends SOQLCache implements SOQLCache.Selector {
public static SOQL_BusinessHoursCache query() {
return new SOQL_BusinessHoursCache();
}

private SOQL_BusinessHoursCache() {
super(BusinessHours.SObjectType);
cacheInOrgCache();
with(BusinessHours.Id, BusinessHours.Name);
}

public override SOQL.Queryable initialQuery() {
return SOQL.of(BusinessHours.SObjectType).whereAre(SOQL.Filter.with(BusinessHours.IsActive).isTrue());
}

public SOQL_BusinessHoursCache byName(String name) {
whereEqual(BusinessHours.Name, name);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public with sharing class SOQL_OrgWideEmailAddressCache extends SOQLCache implements SOQLCache.Selector {
public static SOQL_OrgWideEmailAddressCache query() {
return new SOQL_OrgWideEmailAddressCache();
}

private SOQL_OrgWideEmailAddressCache() {
super(OrgWideEmailAddress.SObjectType);
with(OrgWideEmailAddress.Id, OrgWideEmailAddress.DisplayName, OrgWideEmailAddress.Address);
}

public override SOQL.Queryable initialQuery() {
return SOQL.of(OrgWideEmailAddress.SObjectType);
}

public SOQL_OrgWideEmailAddressCache byAddress(String address) {
whereEqual(OrgWideEmailAddress.Address, address);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
public with sharing class SOQL_ProfileCache extends SOQLCache implements SOQLCache.Selector {
public static SOQL_ProfileCache query() {
return new SOQL_ProfileCache();
}

private SOQL_ProfileCache() {
super(Profile.SObjectType);
cacheInOrgCache();
with(Profile.Id, Profile.Name, Profile.UserType);
}

public override SOQL.Queryable initialQuery() {
return SOQL.of(Profile.SObjectType);
}

public SOQL_ProfileCache byName(String name) {
whereEqual(Profile.Name, name);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
public with sharing class SOQL_UserCache extends SOQLCache implements SOQLCache.Selector {
public static SOQL_UserCache query() {
return new SOQL_UserCache();
}

private SOQL_UserCache() {
super(User.SObjectType);
cacheInSessionCache();
with(User.Id, User.Username, User.Name, User.Country);
}

public override SOQL.Queryable initialQuery() {
return SOQL.of(User.SObjectType).whereAre(SOQL.Filter.id().equal(UserInfo.getUserId()));
}

public SOQL_UserCache byUsername(String username) {
whereEqual(User.Username, username);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<status>Active</status>
</ApexClass>
134 changes: 134 additions & 0 deletions force-app/main/default/classes/main/cached-soql/CacheManager.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)
* Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/cache-manager/blob/main/LICENSE)
*
* PMD False Positives:
* - CognitiveComplexity: It is a library and we tried to put everything into ONE class
* - PropertyNamingConventions: It was intentional to make the lib more fluent and readable
* - FieldNamingConventions: It was intentional to make the lib more fluent and readable
**/
@SuppressWarnings('PMD.CognitiveComplexity, PMD.PropertyNamingConventions, PMD.FieldNamingConventions')
public with sharing class CacheManager {
public interface Cacheable {
Boolean contains(String key);
Set<String> getKeys();
Object get(String key);
void put(String key, Object value);
void remove(String key);
}

public final static Cacheable ApexTransaction = new ApexTransactionCache();

public final static Cacheable SOQLOrgCache {
get { return getOrgCache('SOQL'); }
}

public final static Cacheable SOQLSessionCache {
get { return getSessionCache('SOQL'); }
}

// Implementation

private enum CacheType { ORG, SESSION }

private final static Map<CacheType, Map<String, Cacheable>> CACHE_MAP = new Map<CacheType, Map<String, Cacheable>>{
CacheType.ORG => new Map<String, Cacheable>(),
CacheType.SESSION => new Map<String, Cacheable>()
};

private static Cacheable getOrgCache(String partitionName) {
if (!CACHE_MAP.get(CacheType.ORG).containsKey(partitionName)) {
CACHE_MAP.get(CacheType.ORG).put(partitionName, new OrgPlatformCache(partitionName));
}
return CACHE_MAP.get(CacheType.ORG).get(partitionName);
}

private static Cacheable getSessionCache(String partitionName) {
if (!CACHE_MAP.get(CacheType.SESSION).containsKey(partitionName)) {
CACHE_MAP.get(CacheType.SESSION).put(partitionName, new SessionPlatformCache(partitionName));
}
return CACHE_MAP.get(CacheType.SESSION).get(partitionName);
}

private static void validateKey(String key) {
if (!Pattern.compile('^[a-zA-Z0-9]+$').matcher(key).matches()) {
throw new IllegalArgumentException('Key must be alphanumeric, received key: ' + key);
}
}

private class ApexTransactionCache implements Cacheable {
private final Map<String, Object> TRANSACTION_CACHE = new Map<String, Object>();

public Boolean contains(String key) {
return this.TRANSACTION_CACHE.containsKey(key);
}

public Set<String> getKeys() {
return TRANSACTION_CACHE.keySet();
}

public Object get(String key) {
return this.TRANSACTION_CACHE.get(key);
}

public void put(String key, Object value) {
validateKey(key);
this.TRANSACTION_CACHE.put(key, value);
}

public void remove(String key) {
this.TRANSACTION_CACHE.remove(key);
}
}

private abstract class PlatformCache implements Cacheable {
private Cache.Partition platformCachePartition;

public PlatformCache(String partitionName) {
this.platformCachePartition = getPartition(partitionName);
}

protected abstract Cache.Partition getPartition(String partitionName);

public Boolean contains(String key) {
return this.platformCachePartition.contains(key);
}

public Set<String> getKeys() {
return this.platformCachePartition.getKeys();
}

public Object get(String key) {
return this.platformCachePartition.get(key);
}

public void remove(String key) {
this.platformCachePartition.remove(key);
}

public void put(String key, Object value) {
validateKey(key);
this.platformCachePartition.put(key, value);
}
}

private class OrgPlatformCache extends PlatformCache {
public OrgPlatformCache(String partitionName) {
super(partitionName);
}

public override Cache.Partition getPartition(String partitionName) {
return Cache.Org.getPartition(partitionName);
}
}

private class SessionPlatformCache extends PlatformCache {
public SessionPlatformCache(String partitionName) {
super(partitionName);
}

public override Cache.Partition getPartition(String partitionName) {
return Cache.Session.getPartition(partitionName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading

0 comments on commit e0f4531

Please sign in to comment.