2

I came to the realisation that it isn't difficult to create my own String Interpolation, like this:

public class XAP_UTIL_MapInterpolation implements XAP_UTIL_InterpolationIntf {
    String template;
    Map<String, Object> valueByKeyMap;
    public XAP_UTIL_MapInterpolation(String phrase, Map<String, Object> valueByKeyMap) {
        this.template = phrase;
        this.valueByKeyMap = valueByKeyMap;
    }

    public override String toString() {
        String result = this.template;
        for (String key : this.valueByKeyMap.keySet()) {
            result = result.replace('${' + key + '}', String.valueOf(this.valueByKeyMap.get(key)));
        }
        return result;
    }
}

So I decided to get back to the problem of making a "Where Clause Interface"

But to make this work, I realised we need to move the values out of the Map in the Interpolation object and into the object which does the query, so I came up with this:

public class XAP_DAO_SOQL_Querier {
    private static Object object0;
    private static Object object1;
    private static Object object2;
    private static Object object3;
    private static Object object4;
    private static Object object5;
    private static Object object6;
    private static Object object7;
    private static Object object8;
    private static Object object9;

public List<SObject> selectFor(XAP_DAO_SOQL_QueryInterpolation queryInterpolation) {
    return Database.query(
            this.rebind(queryInterpolation)
    );
}

private String rebind(XAP_DAO_SOQL_QueryInterpolation queryInterpolation) {
    String localizedTemplate = queryInterpolation.template;
    Integer counter = 0;
    for (String key : queryInterpolation.objectsByKeyMap.keySet()) {
        localizedTemplate = this.rebind(
                localizedTemplate,
                key,
                queryInterpolation.objectsByKeyMap.get(key),
                counter++
        );
    }
    return localizedTemplate;
}

private String rebind(String localizedTemplate, String key, Object value, Integer index) {
    switch on index {
        when 0 { object0 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 1 { object1 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 2 { object2 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 3 { object3 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 4 { object4 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 5 { object5 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 6 { object6 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 7 { object7 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 8 { object8 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
        when 9 { object9 = value; return this.rebind(localizedTemplate, key, 'object' + index);}
    }

    throw new RebindObjectsLimitException(
            'XAP_DAO_SOQL_Querier currently only supports ' + LAST_OBJECT_NUMBER + ' bindings.'
                    + '  Attempted query requires ' + index + 1 + ' bindings'
    );
}

// etc.

}

This works, such that when I anonymously execute:

 Id accountId = [
        SELECT Id
        FROM Account
        LIMIT 1
].Id;

Object idSet = new Set<Id>{
                accountId
        };

XAP_DAO_SOQL_QueryInterpolation qi = new XAP_DAO_SOQL_QueryInterpolation(
        'SELECT Name FROM Account WHERE Id IN ${idSet}',
        new Map<String, Object>{
                'idSet' => idSet
        }
);

XAP_DAO_SOQL_Querier querier = new XAP_DAO_SOQL_Querier(); 
List<Account> accountList = querier.selectFor(qi);

System.debug('##' + accountList[0].Name);

I correctly get back

19:00:09.68 (101658693)|USER_DEBUG|[21]|DEBUG|##Edge Communications

But there are a few problems with this approach:

  1. It isn't scalable (enough object and inner classes need to exist before it can be consumed).

  2. The code is very WET (Write Every Time).

Any ideas?

1

I haven't found a way to avoid having many variable declarations. But this is solvable by cut and paste up to some hard-coded maximum e.g. 100 rather than needing many classes:

private void bind(Integer i, Object value) {

    Object object0;
    Object object1;
    Object object2;
    ...

    // Set bind variable
    switch on i {
        when 0 { object0 = value; }
        when 1 { object1 = value; }
        when 2 { object3 = value; }
        ...
    }

    // Set bind variable name
    switch on i {
        ...
    }

    ...
}
  • I think this is the first time in Apex where I've found switch to be a better solution than combining the Interpreter or Strategy Pattern with the Business Delegation Pattern. :-) Cheers for suggesting it, though we don't actually need two switches (e.g. we can do when 0 { object0 = value; return this.rebind(localizedTemplate, key, 'object' + index);} .... but this is still kind of WET. – Brian Kessler May 9 at 5:48
  • 1
    Yeah @BrianKessler, sometimes simple/brute force is all that is left. – Keith C May 9 at 7:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.