Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ada6a4a
Type definitions for TypeOf class
stackasaur Feb 3, 2026
80d2373
fix spacing
stackasaur Feb 3, 2026
077daed
Add `TypeOfFields` to `SoqlFields` and create builder methods to cons…
stackasaur Feb 4, 2026
880ad57
ignore empty fields collection inside with method
konwron Feb 3, 2026
ff52e45
add unit test
konwron Feb 3, 2026
a34b748
change isEmpty call to iterator.hasNext
konwron Feb 3, 2026
ab7048f
Merge branch 'beyond-the-cloud-dev:main' into feature/typeof
stackasaur Feb 4, 2026
3907851
fix formatting issues
stackasaur Feb 4, 2026
abb0c11
split TypeOfFields into new inner class TypeOfWhenThen
stackasaur Feb 4, 2026
d1492ec
remove relationship fields from whenElse
stackasaur Feb 6, 2026
6f17813
test coverage
stackasaur Feb 6, 2026
9cfcc2d
remove unused typeofs class
stackasaur Feb 6, 2026
4b0a43a
test coverage
stackasaur Feb 6, 2026
6584bdd
documentation
stackasaur Feb 18, 2026
2168dc3
remove inner scope in tests for formatting consistency
stackasaur Feb 19, 2026
7ea8af8
Feature/cursor skills (#280)
pgajek2 Mar 13, 2026
8caece0
agentforce rules
pgajek2 Mar 14, 2026
fcf0708
.claude rules
pgajek2 Mar 14, 2026
9eb4196
Rules Improvement
pgajek2 Mar 14, 2026
5cf1b27
feat: add support for toLabel with List<String> and test (#279)
MichalKormanik Mar 16, 2026
948adaa
Merge branch 'main' into release/v6.9.0
pgajek2 Mar 16, 2026
7c5be24
toLabel unit tests
pgajek2 Mar 16, 2026
dbbce91
SObject Schema
pgajek2 Mar 16, 2026
b1c794c
feat: enhance SOQL Lib documentation with new filter method patterns …
pgajek2 Mar 20, 2026
4076316
SOQL package directory
pgajek2 Mar 27, 2026
ca2d338
add example page
stackasaur May 19, 2026
d885490
Merge branch 'release/v6.9.0' into feature/typeof
stackasaur May 19, 2026
af986b6
Merge remote-tracking branch 'upstream/release/v6.11.0' into feature/…
stackasaur May 20, 2026
c0e1219
run prettier
stackasaur May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 230 additions & 1 deletion force-app/main/default/classes/standard-soql/SOQL.cls
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public virtual inherited sharing class SOQL implements Queryable {
Queryable with(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5);
Queryable with(String relationshipName, Iterable<SObjectField> fields);
Queryable with(SubQuery subQuery);
Queryable with(TypeOf typeOf);
Queryable withFieldSet(String fieldSetName);
// SELECT - AGGREGATE FUNCTIONS
Queryable count();
Expand Down Expand Up @@ -248,6 +249,33 @@ public virtual inherited sharing class SOQL implements Queryable {
String getChildRelationshipName();
}

@NamespaceAccessible
public interface TypeOf {
// TYPEOF
TypeOf of(String ofField);
TypeOf of(SObjectField ofField);
// WHEN
Typeof when(SObjectType whenObject);
// THEN
Typeof then(SObjectField field);
Typeof then(SObjectField field1, SObjectField field2);
Typeof then(SObjectField field1, SObjectField field2, SObjectField field3);
Typeof then(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4);
Typeof then(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5);
Typeof then(List<SObjectField> fields);
Typeof then(Iterable<String> fields);
Typeof then(String fields);
Typeof then(String relationshipName, SObjectField field);
Typeof then(String relationshipName, SObjectField field1, SObjectField field2);
Typeof then(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3);
Typeof then(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4);
Typeof then(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5);
Typeof then(String relationshipName, Iterable<SObjectField> fields);
// ELSE
Typeof whenElse(Iterable<String> fields);
Typeof whenElse(String fields);
}

@NamespaceAccessible
public interface FilterGroup {
// ADD CONDITION
Expand Down Expand Up @@ -419,6 +447,13 @@ public virtual inherited sharing class SOQL implements Queryable {
}
}

@NamespaceAccessible
public static Typeof Type {
get {
return new SoqlTypeof();
}
}

@NamespaceAccessible
public static FilterGroup FilterGroup {
get {
Expand Down Expand Up @@ -658,6 +693,12 @@ public virtual inherited sharing class SOQL implements Queryable {
return this;
}

@NamespaceAccessible
public Queryable with(TypeOf typeof) {
this.builder.fields.typeofFields.add(typeof);
return this;
}

@NamespaceAccessible
public Queryable count() {
this.builder.fields.count();
Expand Down Expand Up @@ -1488,6 +1529,7 @@ public virtual inherited sharing class SOQL implements Queryable {
public RelationshipFields relationshipFields = new RelationshipFields();
public FunctionsFields functionsFields = new FunctionsFields(); // toLabel, FORMAT
public AggregateFunctionsFields aggregateFields = new AggregateFunctionsFields();
public TypeOfFields typeofFields = new TypeOfFields();

private String ofObject;
private Set<String> groupedFields = new Set<String>();
Expand Down Expand Up @@ -1558,7 +1600,13 @@ public virtual inherited sharing class SOQL implements Queryable {
}

public override String toString() {
if (this.plainFields.isEmpty() && this.relationshipFields.isEmpty() && this.aggregateFields.isEmpty() && this.functionsFields.isEmpty()) {
if (
this.plainFields.isEmpty() &&
this.relationshipFields.isEmpty() &&
this.aggregateFields.isEmpty() &&
this.functionsFields.isEmpty() &&
this.typeofFields.isEmpty()
) {
this.plainFields.add('Id');
}

Expand All @@ -1575,6 +1623,7 @@ public virtual inherited sharing class SOQL implements Queryable {
selectFields.addAll(this.relationshipFields.get());
selectFields.addAll(this.functionsFields.get());
selectFields.addAll(this.aggregateFields.get());
selectFields.addAll(this.typeofFields.get());

return 'SELECT ' + String.join(selectFields, ', ');
}
Expand Down Expand Up @@ -1688,6 +1737,63 @@ public virtual inherited sharing class SOQL implements Queryable {
}
}

private class TypeOfFields extends SelectFields {
public void add(TypeOf typeof) {
this.add(typeof.toString());
}
}

private class TypeOfWhenThen {
// WHEN
private SObjectType whenObject;
// THEN
public PlainFields plainFields = new PlainFields();
public RelationshipFields relationshipFields = new RelationshipFields();

public TypeOfWhenThen(SObjectType whenObject) {
this.whenObject = whenObject;
}

public TypeOfWhenThen() {
this(null);
}

public Boolean isEmpty() {
return this.plainFields.isEmpty() && this.relationshipFields.isEmpty();
}

public void with(String commaSeparatedFields) {
// Add to Set to avoid "duplicate field selected" error
for (String splitField : commaSeparatedFields.split(',')) {
this.classifyField(splitField);
}
}

private void classifyField(String field) {
String trimmedField = field.trim();

if (this.relationshipFields.isQualified(trimmedField)) {
this.relationshipFields.add(trimmedField);
} else {
this.plainFields.add(trimmedField);
}
}

public override String toString() {
if (this.isEmpty()) {
return '';
}

String prefix = this.whenObject != null ? 'WHEN ' + this.whenObject.toString() + ' THEN ' : 'ELSE ';

Set<String> fields = new Set<String>();
fields.addAll(plainFields.get());
fields.addAll(relationshipFields.get());

return prefix + String.join(fields, ',');
}
}

private class SoqlSubQuery implements SubQuery {
private SoqlBuilder builder;
private String childRelationshipName;
Expand Down Expand Up @@ -1841,6 +1947,129 @@ public virtual inherited sharing class SOQL implements Queryable {
}
}

private class SoqlTypeOf implements Typeof {
private String ofField;
private Map<SObjectType, TypeOfWhenThen> fieldsByType = new Map<SObjectType, TypeOfWhenThen>();
private List<SObjectType> whenTypes = new List<SObjectType>();
private TypeOfWhenThen elseFields = new TypeOfWhenThen();

public SoqlTypeOf of(String ofField) {
if (this.ofField != null) {
throw new QueryException('TYPEOF has already been initialized for: ' + this.ofField);
}
this.ofField = ofField;

return this;
}
public SoqlTypeOf of(SObjectField ofField) {
return this.of(ofField.getDescribe().getRelationshipName());
}

public SoqlTypeOf when(SObjectType whenObject) {
if (fieldsByType.containsKey(whenObject)) {
throw new QueryException('TYPEOF already contains configuration for: ' + whenObject.toString());
}

this.fieldsByType.put(whenObject, new TypeOfWhenThen(whenObject));
this.whenTypes.add(whenObject);

return this;
}

public Typeof then(SObjectField field) {
this.fieldsByType.get(this.latestWhen()).plainFields.add(field.toString());
return this;
}

public Typeof then(SObjectField field1, SObjectField field2) {
return this.then(field1).then(field2);
}

public Typeof then(SObjectField field1, SObjectField field2, SObjectField field3) {
return this.then(field1, field2).then(field3);
}

public Typeof then(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4) {
return this.then(field1, field2, field3).then(field4);
}

public Typeof then(SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5) {
return this.then(field1, field2, field3, field4).then(field5);
}

public Typeof then(List<SObjectField> fields) {
this.fieldsByType.get(this.latestWhen()).plainFields.add(fields);
return this;
}

public Typeof then(Iterable<String> fields) {
return this.then(String.join(fields, ','));
}

public Typeof then(String fields) {
this.fieldsByType.get(this.latestWhen()).with(fields);
return this;
}

public Typeof then(String relationshipName, SObjectField field) {
return then(relationshipName, new List<SObjectField>{ field });
}

public Typeof then(String relationshipName, SObjectField field1, SObjectField field2) {
return then(relationshipName, new List<SObjectField>{ field1, field2 });
}

public Typeof then(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3) {
return then(relationshipName, new List<SObjectField>{ field1, field2, field3 });
}

public Typeof then(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4) {
return then(relationshipName, new List<SObjectField>{ field1, field2, field3, field4 });
}

public Typeof then(String relationshipName, SObjectField field1, SObjectField field2, SObjectField field3, SObjectField field4, SObjectField field5) {
return then(relationshipName, new List<SObjectField>{ field1, field2, field3, field4, field5 });
}

public Typeof then(String relationshipName, Iterable<SObjectField> fields) {
this.fieldsByType.get(this.latestWhen()).relationshipFields.add(relationshipName, fields);
return this;
}

public Typeof whenElse(Iterable<String> fields) {
return this.whenElse(String.join(fields, ','));
}

public Typeof whenElse(String fields) {
this.elseFields.with(fields);
return this;
}

public override String toString() {
String prefix = 'TYPEOF ' + this.ofField + ' ';
List<String> clauses = new List<String>();
String suffix = ' END';

for (SObjectType t : this.whenTypes) {
TypeOfWhenThen fields = this.fieldsByType.get(t);

if (!fields.isEmpty()) {
clauses.add(fields.toString());
}
}

if (!clauses.isEmpty() && !this.elseFields.isEmpty()) {
clauses.add(this.elseFields.toString());
}

return !clauses.isEmpty() ? prefix + String.join(clauses, ' ') + suffix : '';
}

private SObjectType latestWhen() {
return this.whenTypes.get(this.whenTypes.size() - 1);
}
}

private class SoqlSubQueries implements QueryClause {
private List<SubQuery> subQueries = new List<SubQuery>();

Expand Down
Loading