Skip to content

Commit

Permalink
Fixing issues with the use of other cache types
Browse files Browse the repository at this point in the history
  • Loading branch information
rob-baillie-ortoo committed Mar 8, 2022
1 parent 58625db commit b447dad
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
<isDefaultPartition>true</isDefaultPartition>
<masterLabel>soql</masterLabel>
<platformCachePartitionTypes>
<allocatedCapacity>0</allocatedCapacity>
<allocatedPartnerCapacity>0</allocatedPartnerCapacity>
<allocatedCapacity>1</allocatedCapacity>
<allocatedPartnerCapacity>1</allocatedPartnerCapacity>
<allocatedPurchasedCapacity>0</allocatedPurchasedCapacity>
<allocatedTrialCapacity>0</allocatedTrialCapacity>
<cacheType>Session</cacheType>
</platformCachePartitionTypes>
<platformCachePartitionTypes>
<allocatedCapacity>3</allocatedCapacity>
<allocatedPartnerCapacity>3</allocatedPartnerCapacity>
<allocatedCapacity>2</allocatedCapacity>
<allocatedPartnerCapacity>2</allocatedPartnerCapacity>
<allocatedPurchasedCapacity>0</allocatedPurchasedCapacity>
<allocatedTrialCapacity>0</allocatedTrialCapacity>
<cacheType>Organization</cacheType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ public inherited sharing class CachedSoqlExecutor
{
public class CacheAccessViolationException extends Exceptions.DeveloperException {} // this looks like a config exception, but actually the system should be built
// in such a way that it's never possible to get this exception
CacheWrapper cacheWrapper = new OrgCache();

CacheWrapper cacheWrapper = new OrgCache(); // by default, configure the cache to use the org version

private final static String SOQL_PARTITION_NAME = 'soql';
private final static Integer CACHE_LIFESPAN_SECONDS = 43200; // TODO: soft setting / option
private final static Integer CACHE_LIFESPAN_SECONDS = 28800; // TODO: soft setting / option

@testVisible
private final static String CAN_ACCESS_SOQL_CACHE_PERMISSION = 'ProcessesCanAccessSOQLCache';
Expand Down Expand Up @@ -47,7 +48,7 @@ public inherited sharing class CachedSoqlExecutor
{
cacheWrapper = new SessionCache();
}
when else
when NONE
{
cacheWrapper = new NullCache();
}
Expand Down Expand Up @@ -77,24 +78,24 @@ public inherited sharing class CachedSoqlExecutor
{
if ( hasAccessToCache )
{
returnValues = (List<Sobject>)Cache.Org.get( key );
returnValues = (List<Sobject>)cacheWrapper.get( key );
}
else
{
System.debug( LoggingLevel.INFO, 'Opportunity to use Org Platform Cache skipped since user does not have required permission (custom permission: ' + CAN_ACCESS_SOQL_CACHE_PERMISSION + ')' );
System.debug( LoggingLevel.INFO, 'Opportunity to use Platform Cache skipped since user does not have required permission (custom permission: ' + CAN_ACCESS_SOQL_CACHE_PERMISSION + ')' );
}
}
catch ( Cache.Org.OrgCacheException e ) // TODO: add the other one too
catch ( Exception e )
{
System.debug( LoggingLevel.ERROR, 'Attempt to read from the Org Platform Cache failed for the SOQL: ' + soql );
System.debug( LoggingLevel.ERROR, 'Attempt to read from the Platform Cache failed for the SOQL: ' + soql );
System.debug( LoggingLevel.ERROR, e );
}

if ( returnValues == null )
{
if ( hasAccessToCache )
{
System.debug( LoggingLevel.INFO, 'Org Platform Cache miss when running the SOQL: ' + soql );
System.debug( LoggingLevel.INFO, 'Platform Cache miss when running the SOQL: ' + soql );
}

returnValues = Database.query( soql );
Expand All @@ -103,12 +104,12 @@ public inherited sharing class CachedSoqlExecutor
{
if ( hasAccessToCache )
{
Cache.Org.put( key, returnValues, CACHE_LIFESPAN_SECONDS, Cache.Visibility.NAMESPACE, false ); // immutable results
cacheWrapper.put( key, returnValues, CACHE_LIFESPAN_SECONDS );
}
}
catch ( Exception e )
{
System.debug( LoggingLevel.ERROR, 'Attempt to write into the Org Platform Cache failed for the SOQL: ' + soql );
System.debug( LoggingLevel.ERROR, 'Attempt to write into the Platform Cache failed for the SOQL: ' + soql );
System.debug( LoggingLevel.ERROR, e );
}
}
Expand All @@ -132,7 +133,7 @@ public inherited sharing class CachedSoqlExecutor
.addContext( 'method', 'clearCacheFor' )
.addContext( 'soql', soql );
}
Cache.Org.remove( generateKey( soql ) );
cacheWrapper.remove( generateKey( soql ) );
}

/**
Expand All @@ -147,13 +148,13 @@ public inherited sharing class CachedSoqlExecutor
.addContext( 'method', 'clearAllCache' );
}

String fullSoqlPartitionName = Cache.OrgPartition.createFullyQualifiedPartition( PackageUtils.NAMESPACE_PREFIX, SOQL_PARTITION_NAME );
for ( String thisKey : Cache.Org.getKeys() )
String fullSoqlPartitionName = cacheWrapper.createFullyQualifiedPartitionName( PackageUtils.NAMESPACE_PREFIX, SOQL_PARTITION_NAME );
for ( String thisKey : cacheWrapper.getKeys() )
{
String qualifiedKey = qualifiedKey( thisKey );
if ( Cache.Org.contains( qualifiedKey ) )
if ( cacheWrapper.contains( qualifiedKey ) )
{
Cache.Org.remove( qualifiedKey );
cacheWrapper.remove( qualifiedKey );
}
}
}
Expand All @@ -166,7 +167,7 @@ public inherited sharing class CachedSoqlExecutor

private String qualifiedKey( String subkey )
{
return Cache.OrgPartition.createFullyQualifiedKey( PackageUtils.NAMESPACE_PREFIX, SOQL_PARTITION_NAME, subkey );
return cacheWrapper.createFullyQualifiedKey( PackageUtils.NAMESPACE_PREFIX, SOQL_PARTITION_NAME, subkey );
}

private interface CacheWrapper
Expand All @@ -189,7 +190,7 @@ public inherited sharing class CachedSoqlExecutor

public void put( String key, Object value, Integer lifespan )
{
Cache.Org.put( key, value, lifespan, Cache.Visibility.NAMESPACE, false ); // immutable results
Cache.Org.put( key, value, lifespan, Cache.Visibility.NAMESPACE, true ); // immutable results
}

public Set<String> getKeys()
Expand Down Expand Up @@ -227,7 +228,7 @@ public inherited sharing class CachedSoqlExecutor

public void put( String key, Object value, Integer lifespan )
{
Cache.Session.put( key, value, lifespan, Cache.Visibility.NAMESPACE, false ); // immutable results
Cache.Session.put( key, value, lifespan, Cache.Visibility.NAMESPACE, true ); // immutable results
}

public Set<String> getKeys()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@isTest
private without sharing class CachedSoqlExecutorTest
{
// TODO: tests for the NONE scope
@isTest
private static void query_whenCalledTwiceByAUserWithAccessToTheCache_onlyIssuesOneSoqlStatement() // NOPMD: Test method name format
{
Expand Down Expand Up @@ -238,8 +237,6 @@ private without sharing class CachedSoqlExecutorTest
System.assert( true, 'query, when run multiple times for 100 distinct queries, will not throw an exception' );
}

///

@isTest
private static void query_session_whenCalledTwiceByAUserWithAccessToTheCache_onlyIssuesOneSoqlStatement() // NOPMD: Test method name format
{
Expand Down Expand Up @@ -296,7 +293,6 @@ private without sharing class CachedSoqlExecutorTest
executor.query( soqlStatement );
executor.query( soqlStatement );


Integer soqlCalls = Limits.getQueries();
Test.stopTest();

Expand Down Expand Up @@ -457,6 +453,222 @@ private without sharing class CachedSoqlExecutorTest
System.assert( true, 'query, when run multiple times for 100 distinct queries, will not throw an exception' );
}

@isTest
private static void query_none_whenCalledTwiceByAUserWithAccessToTheCache_issuesTwoSoqlStatements() // NOPMD: Test method name format
{
String soqlStatement = 'SELECT Id FROM Account';

setupAccessToSoqlCache( true );

CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.NONE );

Test.startTest();
List<Sobject> originalResults = executor.query( soqlStatement );
List<Sobject> secondResults = executor.query( soqlStatement );
Integer soqlCalls = Limits.getQueries();
Test.stopTest();

System.assertEquals( 2, soqlCalls, 'query against a NONE cache, when called twice by a user with access to the cache, will issue two SOQL statements' );
}
/*
@isTest
private static void query_session_whenCalledTwiceByAUserWithoutAccessToTheCache_issuesTwoSoqlStatements() // NOPMD: Test method name format
{
String soqlStatement = 'SELECT Id FROM Account';
setupAccessToSoqlCache( false );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
Test.startTest();
List<Sobject> originalResults = executor.query( soqlStatement );
List<Sobject> secondResults = executor.query( soqlStatement );
Integer soqlCalls = Limits.getQueries();
Test.stopTest();
System.assertEquals( 2, soqlCalls, 'query, when called twice by a user with no access to the cache, will issue two SOQL statements' );
System.assertEquals( originalResults, secondResults, 'query, when called twice by a user with not access to the cache, returns the same results in both calls' );
}
@isTest
private static void clearAllCache_session_willClearAllStatementsAndResultsFromTheCache() // NOPMD: Test method name format
{
String soqlStatement = 'SELECT Id FROM Account';
setupAccessToSoqlCache( true );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
Test.startTest();
executor.query( soqlStatement ); // executes SOQL
executor.clearAllCache();
executor.query( soqlStatement ); // executes another SOQL
executor.query( soqlStatement );
executor.query( soqlStatement );
Integer soqlCalls = Limits.getQueries();
Test.stopTest();
System.assertEquals( 2, soqlCalls, 'clearAllCache, when called, will clear statements and results from the cache meaning that SOQL executions will need to be repeated when query is called' );
}
@isTest
private static void clearAllCache_session_whenThereIsNothingInTheCache_willNotThrowAnException() // NOPMD: Test method name format
{
setupAccessToSoqlCache( true );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
Test.startTest();
executor.clearAllCache();
Test.stopTest();
System.assert( true, 'clearAllCache, when there is nothing in the cache, will not throw an exception' );
}
@isTest
private static void clearAllCache_session_whenTheUserDoesNotHaveAccessToTheCache_throwsAnException() // NOPMD: Test method name format
{
setupAccessToSoqlCache( false );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
Test.startTest();
Exception exceptionThrown;
try
{
executor.clearAllCache();
}
catch ( CachedSoqlExecutor.CacheAccessViolationException e )
{
exceptionThrown = e;
}
Test.stopTest();
ortoo_Asserts.assertContains( Label.ortoo_core_soql_cache_access_violation, exceptionThrown?.getMessage(), 'clearAllCache, when the user does not have access to the cache, will throw an exception' );
}
@isTest
private static void clearCacheFor_session_whenGivenASoqlStatementThatHasBeenExecuted_willClearTheCacheForThatStatement() // NOPMD: Test method name format
{
String soqlStatement = 'SELECT Id FROM Account';
setupAccessToSoqlCache( true );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
executor.query( soqlStatement );
Test.startTest();
executor.clearCacheFor( soqlStatement );
executor.query( soqlStatement ); // should execute another soql
Integer soqlCalls = Limits.getQueries();
Test.stopTest();
System.assertEquals( 1, soqlCalls, 'clearCacheFor, when given a SOQL statement that is already in the cache, will clear that soql from the cache' );
}
@isTest
private static void clearCacheFor_session_whenGivenASoqlStatementThatHasBeenExecuted_willNotClearTheCacheForOtherStatements() // NOPMD: Test method name format
{
String soqlStatement1 = 'SELECT Id FROM Account';
String soqlStatement2 = 'SELECT Id FROM Account LIMIT 1';
setupAccessToSoqlCache( true );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
executor.query( soqlStatement1 );
executor.query( soqlStatement2 );
Test.startTest();
executor.clearCacheFor( soqlStatement1 );
executor.query( soqlStatement2 ); // should not execute another soql
Integer soqlCalls = Limits.getQueries();
Test.stopTest();
System.assertEquals( 0, soqlCalls, 'clearCacheFor, when given a SOQL statement that is already in the cache, will not clear other soql from the cache' );
}
@isTest
private static void clearCacheFor_session_whenGivenASoqlStatementThatHasNotBeenExecuted_willNotThrowAnException() // NOPMD: Test method name format
{
String soqlStatement = 'SELECT Id FROM Account';
setupAccessToSoqlCache( true );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
Test.startTest();
executor.clearCacheFor( soqlStatement );
Test.stopTest();
System.assert( true, 'clearCacheFor, when given a SOQL statement that has not been executed, will not throw an exception' );
}
@isTest
private static void clearCacheFor_session_whenTheUserDoesNotHaveAccessToTheCache_throwsAnException() // NOPMD: Test method name format
{
String soqlStatement = 'SELECT Id FROM Account';
setupAccessToSoqlCache( false );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
Test.startTest();
Exception exceptionThrown;
try
{
executor.clearCacheFor( soqlStatement );
}
catch ( CachedSoqlExecutor.CacheAccessViolationException e )
{
exceptionThrown = e;
}
Test.stopTest();
ortoo_Asserts.assertContains( Label.ortoo_core_soql_cache_access_violation, exceptionThrown?.getMessage(), 'clearCacheFor, when the user does not have access to the cache, will throw an exception' );
}
@isTest
private static void query_session_whenRanFor100Queries_willNotThrowAnException()
{
List<String> soqlStatements = new List<String>();
for ( Integer i=1; i<=100; i++ )
{
soqlStatements.add( 'SELECT Id FROM Account LIMIT ' + i );
}
setupAccessToSoqlCache( true );
CachedSoqlExecutor executor = new CachedSoqlExecutor().setScope( CachedSoqlExecutor.CacheScope.SESSION );
Test.startTest();
// Run each statement multiple times, one by one
for ( String thisSoqlStatement : soqlStatements )
{
executor.query( thisSoqlStatement );
executor.query( thisSoqlStatement );
executor.query( thisSoqlStatement );
executor.query( thisSoqlStatement );
executor.query( thisSoqlStatement );
}
// Then run each statement again
for ( String thisSoqlStatement : soqlStatements )
{
executor.query( thisSoqlStatement );
}
Test.stopTest();
System.assert( true, 'query, when run multiple times for 100 distinct queries, will not throw an exception' );
}
*/
private static void setupAccessToSoqlCache( Boolean accessToCache )
{
ApplicationMockRegistrar.registerMockService( IPermissionsService.class )
Expand Down

0 comments on commit b447dad

Please sign in to comment.