Is there a typesafe way to get a Database.QueryLocator?

Provide hardcoded fields in query locator? I believe you can do this, this gives a compile-time check

Database.getQueryLocator([SELECT Id FROM Account]);

Src:https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_database_batch.htm#apex_Database_QueryLocator_getQuery


Partially. You can always establish static references to the involved fields and sObjects, and build the query dynamically with templating. I never like seeing queries built by string concatenation anyway.

List<String> fieldList = new List<String>{
    String.valueOf(Account.Id),
    String.valueOf(Account.Name)
};

String query = String.format(
    'SELECT {0} FROM {1}',
    new List<String> {
        String.join(fieldList, ', '),
        String.valueOf(Account.sobjectType)
    }
);
System.debug(query);
Database.query(query);

11:31:26:004 USER_DEBUG [13]|DEBUG|SELECT Id, Name FROM Account

That at least gets the system's metadata dependency checking into the mix. Gets a lot messier if you want to traverse relationships, of course.

You're not going to be able to validate name references in Apex bindings, though, because you cannot introspect the local namespace in Apex. At most, you could factor it into a builder method, but the vulnerability to changing, say, parameter names to a builder method is still there.


@PranayJaiswal has the best answer but you can also consider the Force.com Enterprise Patterns - Selector layer that uses a queryFactory and type safe fields as in:

Database.QueryLocator ql = CasesSelector.newInstance().selectByCloseDateAsQueryLocator(someDateFilter);


public virtual class CasesSelector extends fflib_SObjectSelector implements ICasesSelector {
  public List<Schema.SObjectField> getSobjectFieldList() {
    return new List<Schema.SObjectField> {
      Case.Id,
      Case.CloseDate,
      Case.Subject,
      ...
    };
  }
 public virtual Database.QueryLocator selectByCloseDateAsQueryLocator(String dateFilter) {
  fflib_QueryFactory qF = new QueryFactory(false) // false=enumerate fields you want 
                           .selectFields(new Set<SObjectField> {
                              Case.Id, Case.CloseDate, ...}  
                           .setCondition('CloseDate = :dateFilter');
  return Database.getQueryLocator(qf.toSOQL());

}

public virtual Database.QueryLocator selectByCloseDateAsQueryLocator(String dateFilter) {
  fflib_QueryFactory qF = new QueryFactory(true) // use fields defined by class method getSobjectFieldList  
                           .setCondition('CloseDate = :dateFilter');
  return Database.getQueryLocator(qf.toSOQL());

}

That said, fflib Selector pattern doesn't support setCondition expressions using Schema.SObjectField (the arg to setCondition is a string) but you could do:

.setCondition(Case.CloseDate.getDescribe.getName() + ' = :dateFilter)

if you were super worried about typesafe here.

The nice thing about the pattern is that the queryfactory allows composing queries from multiple factories (so you can build lookups and children) easily, you get all the describe bits for free, and my favorite, the selectors are easily mockable (ApexMocks or other DI approaches) making unit tests super fast.

I'm not showing the whole shebang as that is a larger topic - see Trailhead and Andrew Fawcett's blog/book