@@ -54,6 +54,7 @@ public inherited sharing class SOQL implements Queryable {
5454 Queryable with (SObjectField field1 , SObjectField field2 , SObjectField field3 , SObjectField field4 , SObjectField field5 );
5555 Queryable with (List <SObjectField > fields ); // For more than 5 fields
5656 Queryable with (String fields ); // Dynamic SOQL
57+ Queryable with (SObjectField field , String alias ); // Only aggregate expressions use field aliasing
5758 Queryable with (String relationshipName , SObjectField field );
5859 Queryable with (String relationshipName , SObjectField field1 , SObjectField field2 );
5960 Queryable with (String relationshipName , SObjectField field1 , SObjectField field2 , SObjectField field3 );
@@ -114,8 +115,9 @@ public inherited sharing class SOQL implements Queryable {
114115 Queryable byIds (List <SObject > records );
115116
116117 String toString ();
117- Object toField (SObjectField fieldToExtract );
118- Integer toInteger ();
118+ Object toValueOf (SObjectField fieldToExtract );
119+ Set <String > toValuesOf (SObjectField fieldToExtract );
120+ Integer toInteger (); // For COUNT query
119121 SObject toObject ();
120122 List <SObject > toList ();
121123 List <AggregateResult > toAggregated ();
@@ -277,6 +279,11 @@ public inherited sharing class SOQL implements Queryable {
277279 return this ;
278280 }
279281
282+ public SOQL with (SObjectField field , String alias ) {
283+ builder .fields .with (field , alias );
284+ return this ;
285+ }
286+
280287 public SOQL with (String relationshipName , SObjectField field ) {
281288 return with (relationshipName , new List <SObjectField >{ field });
282289 }
@@ -480,10 +487,16 @@ public inherited sharing class SOQL implements Queryable {
480487 return builder .toString ();
481488 }
482489
483- public Object toField (SObjectField fieldToExtract ) {
490+ public Object toValueOf (SObjectField fieldToExtract ) {
491+ builder .fields .clearAllFields (); // other fields not needed
484492 return with (fieldToExtract ).toObject ()?. get (fieldToExtract );
485493 }
486494
495+ public Set <String > toValuesOf (SObjectField fieldToExtract ) {
496+ // https://salesforce.stackexchange.com/questions/393308/get-a-list-of-one-column-from-a-soql-result
497+ return new Map <String , SObject >(with (fieldToExtract , ' Id' ).groupBy (fieldToExtract ).toAggregated ()).keySet ();
498+ }
499+
487500 public Integer toInteger () {
488501 return executor .toInteger (builder .toString (), binder .getBindingMap ());
489502 }
@@ -652,12 +665,19 @@ public inherited sharing class SOQL implements Queryable {
652665 }
653666
654667 public void count (String countSoql ) {
655- // clear all default fields to avoid "Field must be grouped or aggregated"
656- fields . clear ();
668+ // Clear all default fields to avoid "Field must be grouped or aggregated"
669+ clearAllFields ();
657670 counts .add (countSoql );
658671 }
659672
673+ public void with (SObjectField field , String alias ) {
674+ // Only aggregate expressions use field aliasing. Clear all default fields to avoid "Field must be grouped or aggregated"
675+ clearAllFields ();
676+ fields .add (field + ' ' + alias );
677+ }
678+
660679 public void with (String stringFields ) {
680+ // To avoid field duplicates in query
661681 fields .addAll (stringFields .deleteWhitespace ().split (' ,' ));
662682 }
663683
@@ -681,6 +701,10 @@ public inherited sharing class SOQL implements Queryable {
681701 fields .add (relationshipPath + ' .' + field );
682702 }
683703
704+ public void clearAllFields () {
705+ fields .clear ();
706+ }
707+
684708 public override String toString () {
685709 if (fields .isEmpty () && counts .isEmpty ()) {
686710 return ' SELECT Id' ;
@@ -1119,25 +1143,25 @@ public inherited sharing class SOQL implements Queryable {
11191143 }
11201144
11211145 public Filter includesAll (Iterable <String > iterable ) {
1122- // Bind expressions can't be used with other clauses, such as INCLUDES.
1146+ // Bind expressions can't be used with other clauses, such as INCLUDES.
11231147 skipBinding = true ;
11241148 return set (' INCLUDES' , ' (\' ' + String .join (iterable , ' ;' ) + ' \' )' );
11251149 }
11261150
11271151 public Filter includesSome (Iterable <String > iterable ) {
1128- // Bind expressions can't be used with other clauses, such as INCLUDES.
1152+ // Bind expressions can't be used with other clauses, such as INCLUDES.
11291153 skipBinding = true ;
11301154 return set (' INCLUDES' , ' (\' ' + String .join (iterable , ' \' , \' ' ) + ' \' )' );
11311155 }
11321156
11331157 public Filter excludesAll (Iterable <String > iterable ) {
1134- // Bind expressions can't be used with other clauses, such as EXCLUDES.
1158+ // Bind expressions can't be used with other clauses, such as EXCLUDES.
11351159 skipBinding = true ;
11361160 return set (' EXCLUDES' , ' (\' ' + String .join (iterable , ' \' , \' ' ) + ' \' )' );
11371161 }
11381162
11391163 public Filter excludesSome (Iterable <String > iterable ) {
1140- // Bind expressions can't be used with other clauses, such as EXCLUDES.
1164+ // Bind expressions can't be used with other clauses, such as EXCLUDES.
11411165 skipBinding = true ;
11421166 return set (' EXCLUDES' , ' (\' ' + String .join (iterable , ' ;' ) + ' \' )' );
11431167 }
@@ -1341,27 +1365,27 @@ public inherited sharing class SOQL implements Queryable {
13411365 }
13421366
13431367 private class Mock {
1344- private final Map <String , List <SObject >> mocks = new Map <String , List <SObject >>();
1368+ private final Map <String , List <SObject >> sObjectsMocks = new Map <String , List <SObject >>();
13451369 private final Map <String , Integer > countMocks = new Map <String , Integer >();
13461370
13471371 public void setMock (String mockId , List <SObject > records ) {
1348- mocks .put (mockId , records );
1372+ sObjectsMocks .put (mockId , records );
13491373 }
13501374
13511375 public void setCountMock (String mockId , Integer amount ) {
13521376 countMocks .put (mockId , amount );
13531377 }
13541378
13551379 public Boolean hasMock (String mockId ) {
1356- return mocks .containsKey (mockId );
1380+ return sObjectsMocks .containsKey (mockId );
13571381 }
13581382
13591383 public Boolean hasCountMock (String mockId ) {
13601384 return countMocks .containsKey (mockId );
13611385 }
13621386
1363- public List <SObject > getMocks (String mockId ) {
1364- return mocks .get (mockId );
1387+ public List <SObject > getSObjectsMock (String mockId ) {
1388+ return sObjectsMocks .get (mockId );
13651389 }
13661390
13671391 public Integer getCountMock (String mockId ) {
@@ -1396,72 +1420,80 @@ public inherited sharing class SOQL implements Queryable {
13961420 }
13971421
13981422 public SObject toObject (String query , Map <String , Object > binding ) {
1399- try {
1400- return toList (query , binding )[0 ];
1401- } catch (ListException e ) {
1402- return null ; // List index out of bounds: 0
1423+ List <SObject > records = toList (query , binding );
1424+
1425+ if (records .size () > 1 ) {
1426+ QueryException e = new QueryException ();
1427+ e .setMessage (' List has more than 1 row for assignment to SObject' );
1428+ throw e ;
14031429 }
1404- }
14051430
1406- public Integer toInteger (String query , Map <String , Object > binding ) {
1407- if (mock .hasCountMock (mockId )) {
1408- return mock .getCountMock (mockId );
1431+ if (records .size () == 0 ) {
1432+ return null ; // handle: List has no rows for assignment to SObject
14091433 }
14101434
1411- return sharingExecutor . executeCount ( query , binding , accessMode ) ;
1435+ return records [ 0 ] ;
14121436 }
14131437
14141438 public List <SObject > toList (String query , Map <String , Object > binding ) {
14151439 if (mock .hasMock (mockId )) {
1416- return mock .getMocks (mockId );
1440+ return mock .getSObjectsMock (mockId );
14171441 }
14181442
14191443 if (accessType == null ) {
1420- return sharingExecutor .execute (query , binding , accessMode );
1444+ return sharingExecutor .toSObjects (query , binding , accessMode );
14211445 }
14221446
14231447 return Security .stripInaccessible (
14241448 accessType ,
1425- sharingExecutor .execute (query , binding , accessMode )
1449+ sharingExecutor .toSObjects (query , binding , accessMode )
14261450 ).getRecords ();
14271451 }
14281452
1453+ public Integer toInteger (String query , Map <String , Object > binding ) {
1454+ if (mock .hasCountMock (mockId )) {
1455+ return mock .getCountMock (mockId );
1456+ }
1457+
1458+ return sharingExecutor .toInteger (query , binding , accessMode );
1459+ }
1460+
14291461 public Database.QueryLocator toQueryLocator (String query , Map <String , Object > binding ) {
14301462 return Database .getQueryLocatorWithBinds (query , binding , accessMode );
14311463 }
14321464 }
14331465
14341466 private interface DatabaseQuery {
1435- List <SObject > execute (String query , Map <String , Object > binding , AccessLevel accessLevel );
1436- Integer executeCount (String query , Map <String , Object > binding , AccessLevel accessLevel );
1467+ List <SObject > toSObjects (String query , Map <String , Object > binding , AccessLevel accessLevel );
1468+ Integer toInteger (String query , Map <String , Object > binding , AccessLevel accessLevel );
14371469 }
14381470
14391471 private inherited sharing class InheritedSharing implements DatabaseQuery {
1440- public List <SObject > execute (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
1472+ public List <SObject > toSObjects (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
14411473 return Database .queryWithBinds (query , binding , accessLevel );
14421474 }
14431475
1444- public Integer executeCount (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
1476+ public Integer toInteger (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
14451477 return Database .countQueryWithBinds (query , binding , accessLevel );
14461478 }
14471479 }
14481480
14491481 private without sharing class WithoutSharing implements DatabaseQuery {
1450- public List <SObject > execute (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
1482+ public List <SObject > toSObjects (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
14511483 return Database .queryWithBinds (query , binding , accessLevel );
14521484 }
14531485
1454- public Integer executeCount (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
1486+ public Integer toInteger (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
14551487 return Database .countQueryWithBinds (query , binding , accessLevel );
14561488 }
14571489 }
14581490
14591491 private with sharing class WithSharing implements DatabaseQuery {
1460- public List <SObject > execute (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
1492+ public List <SObject > toSObjects (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
14611493 return Database .queryWithBinds (query , binding , accessLevel );
14621494 }
14631495
1464- public Integer executeCount (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
1496+ public Integer toInteger (String query , Map <String , Object > binding , AccessLevel accessLevel ) {
14651497 return Database .countQueryWithBinds (query , binding , accessLevel );
14661498 }
14671499 }
0 commit comments