diff --git a/force-app/main/default/classes/SOQL.cls b/force-app/main/default/classes/SOQL.cls index 30b42b5..df6cf06 100644 --- a/force-app/main/default/classes/SOQL.cls +++ b/force-app/main/default/classes/SOQL.cls @@ -275,7 +275,6 @@ public virtual inherited sharing class SOQL implements Queryable { } public SOQL(String ofObject) { - binder = new Binder(); builder = new QueryBuilder(ofObject); executor = new Executor(ofObject, builder); } @@ -577,11 +576,13 @@ public virtual inherited sharing class SOQL implements Queryable { } public SOQL preview() { - executor.withPreview(); + System.debug(LoggingLevel.ERROR, '\n\n============ SOQL Preview ============\n' + toString() + '\n=======================================\n'); + System.debug(LoggingLevel.ERROR, '\n\n============ SOQL Binding ============\n' + JSON.serializePretty(binding()) + '\n=======================================\n'); return this; } - public Map binding() { + @TestVisible + private Map binding() { return binder.getBindingMap(); } @@ -768,6 +769,8 @@ public virtual inherited sharing class SOQL implements Queryable { } public override String toString() { + binder = new Binder(); + List soqlParts = new List(); for (QueryClause clause : clauses) { @@ -1079,7 +1082,7 @@ public virtual inherited sharing class SOQL implements Queryable { private virtual class SoqlFilterGroup implements FilterGroup { private List queryConditions = new List(); - private String order; + private String customOrder; private String connector = 'AND'; public FilterGroup add(FilterGroup filterGroup) { @@ -1108,7 +1111,7 @@ public virtual inherited sharing class SOQL implements Queryable { } public FilterGroup conditionLogic(String order) { - this.order = order; + customOrder = order; return this; } @@ -1127,31 +1130,32 @@ public virtual inherited sharing class SOQL implements Queryable { return '(' + buildNested() + ')'; } - private void setDefaultOrderWhenNotSpecified() { - if (String.isNotEmpty(order)) { - return; - } + public String buildNested() { + return String.format(getOrderWithSpecialCharacters(), queryConditions); + } - List defaultOrder = new List(); + private String getOrderWithSpecialCharacters() { + String orderWithSpecialCharacters = getConditionsLogic(); for (Integer i = 0; i < queryConditions.size(); i++) { - defaultOrder.add(String.valueOf(i + 1)); + orderWithSpecialCharacters = orderWithSpecialCharacters.replace(String.valueOf(i + 1), '{' + i + '}'); } - order = String.join(defaultOrder, ' ' + connector + ' '); + return orderWithSpecialCharacters; // e.g ({0} AND ({1} AND {2})) } - public String buildNested() { - setDefaultOrderWhenNotSpecified(); // e.g (0 AND 1 AND 2) - addSpecialCharactersToOrder(); // e.g ({0} AND ({1} AND {2})) + private String getConditionsLogic() { + if (String.isNotEmpty(customOrder)) { + return customOrder; + } - return String.format(order, queryConditions); - } + List defaultOrder = new List(); - private void addSpecialCharactersToOrder() { for (Integer i = 0; i < queryConditions.size(); i++) { - order = order.replace(String.valueOf(i + 1), '{' + i + '}'); + defaultOrder.add(String.valueOf(i + 1)); } + + return String.join(defaultOrder, ' ' + connector + ' '); // e.g (0 AND 1 AND 2) } } @@ -1622,7 +1626,6 @@ public virtual inherited sharing class SOQL implements Queryable { private AccessType accessType; private String mockId; private String ofObject; - private Boolean preview = false; private QueryBuilder builder; public Executor(String ofObject, QueryBuilder builder) { @@ -1650,27 +1653,6 @@ public virtual inherited sharing class SOQL implements Queryable { mockId = id; } - public void withPreview() { - preview = true; - } - - private String buildSOQL() { - if (preview) { - String soql = builder.toString(); - System.debug(LoggingLevel.ERROR, '\n\n============ SOQL Preview ============\n' + soql + '\n=======================================\n'); - return soql; - } - - return builder.toString(); - } - - private Map buildBinding() { - if (preview) { - System.debug(LoggingLevel.ERROR, '\n\n============ SOQL Binding ============\n' + JSON.serializePretty(binder.getBindingMap()) + '\n=======================================\n'); - } - return binder.getBindingMap(); - } - public SObject toObject() { List records = toList(); @@ -1693,12 +1675,12 @@ public virtual inherited sharing class SOQL implements Queryable { } if (accessType == null) { - return sharingExecutor.toSObjects(buildSOQL(), buildBinding(), accessMode); + return sharingExecutor.toSObjects(builder.toString(), binder.getBindingMap(), accessMode); } return Security.stripInaccessible( accessType, - sharingExecutor.toSObjects(buildSOQL(), buildBinding(), accessMode) + sharingExecutor.toSObjects(builder.toString(), binder.getBindingMap(), accessMode) ).getRecords(); } @@ -1765,11 +1747,11 @@ public virtual inherited sharing class SOQL implements Queryable { return mock.getCountMock(mockId); } - return sharingExecutor.toInteger(buildSOQL(), buildBinding(), accessMode); + return sharingExecutor.toInteger(builder.toString(), binder.getBindingMap(), accessMode); } public Database.QueryLocator toQueryLocator() { - return Database.getQueryLocatorWithBinds(buildSOQL(), buildBinding(), accessMode); + return Database.getQueryLocatorWithBinds(builder.toString(), binder.getBindingMap(), accessMode); } } diff --git a/force-app/main/default/classes/SOQL_Test.cls b/force-app/main/default/classes/SOQL_Test.cls index 760185b..b4fa629 100644 --- a/force-app/main/default/classes/SOQL_Test.cls +++ b/force-app/main/default/classes/SOQL_Test.cls @@ -1241,6 +1241,35 @@ private class SOQL_Test { Assert.areEqual('Krakow', binding.get('v2')); } + @IsTest + static void dynamicFiltersGroupOnSoqlInstance() { + // Setup + SOQL.FilterGroup filterGroup = SOQL.FilterGroup; + + filterGroup.add(SOQL.Filter.with(Account.Name).equal('Test')); + filterGroup.add(SOQL.Filter.with(Account.BillingCity).equal('Krakow')); + + // Test + SOQL builder = SOQL.of(Account.SObjectType) + .whereAre(SOQL.Filter.with(Account.Industry).equal('IT')); + + Assert.areEqual('SELECT Id FROM Account WHERE Industry = :v1', builder.toString()); + + builder.whereAre( + SOQL.FilterGroup + .add(SOQL.Filter.with(Account.Name).equal('Test')) + .add(SOQL.Filter.with(Account.BillingCity).equal('Krakow')) + ); + + // Verify + Assert.areEqual('SELECT Id FROM Account WHERE Industry = :v1 AND (Name = :v2 AND BillingCity = :v3)', builder.toString()); + + Map binding = builder.binding(); + Assert.areEqual('IT', binding.get('v1')); + Assert.areEqual('Test', binding.get('v2')); + Assert.areEqual('Krakow', binding.get('v3')); + } + @IsTest static void anyConditionMatchingForInnerGroup() { // Test @@ -1928,6 +1957,37 @@ private class SOQL_Test { Assert.isTrue(isRecordExist); } + @IsTest + static void multipleToStringExecutions() { + // Setup + Exception soqlException = null; + + SOQL builder = SOQL.of(Account.SObjectType) + .whereAre(SOQL.FilterGroup + .add(SOQL.Filter.with(Account.Name).equal('Test')) + .add(SOQL.Filter.with(Account.Industry).equal('IT')) + ); + + // Test + try { + builder.preview(); + builder.toString(); + builder.toString(); + builder.toList(); + } catch (Exception e) { + soqlException = e; + } + + // Verify + Assert.isNull(soqlException); + + Assert.areEqual('SELECT Id FROM Account WHERE (Name = :v1 AND Industry = :v2)', builder.toString()); + + Map binding = builder.binding(); + Assert.areEqual('Test', binding.get('v1')); + Assert.areEqual('IT', binding.get('v2')); + } + @IsTest static void toValueOf() { // Setup