SEL* query template / generator assumes that all SObjects have some standard fields

Issue #556 resolved
Michal Zaganczyk created an issue

Some standard SObjects do not have the full set of standard fields like Name, IsDeleted etc. But the "SEL*" query template generates the query assuming that all standard fields are always available for any SObject, which is not true. The result - for some standard SObjects the generated query will raise INVALID_FIELD: XXX exceptions.

To reproduce: Type "SEL*", press Ctrl+Space, type/select "User" into the FROM clause, press enter, run the query.

You will receive "INVALID_FIELD: IsDeleted,^ ERROR at Row:59:Column:3 No such column 'IsDeleted' on entity 'User'."

Some other examples of such standard SObjects: Network, NetworkMember, OrgWideEmailAddress.

Comments (6)

  1. Scott Wells repo owner

    Yeah, this is actually more of a side-effect of how I've tried to add true inheritance and polymorphism to SObjects in the offline symbol table. If you look all of the stub Apex classes in the OST for SObjects extend SObject, and I've added what should be the common lifecycle fields to that base class so that you can address instances through the base interface. However, there are a few SObjects that don't actually some have some of these fields. The problem is that if you then move the fields that are common to 95+% of all SObjects back down to the concrete SObject classes, e.g., Account, Opportunity, etc., you can no longer use expressions like:

    SObject sobj = account;
    if (sobj.IsDeleted) {
        ...
    }
    

    For the most part you don't run into this when using features like completion, but as you've pointed out, the SEL* live template just emits all of the scalar value fields of the selected SObject type which includes these inherited fields. For a small handful of SObject types, some of them don't apply.

    So that's the why...let me chew a bit on what might be the best way to fix this...

  2. Michal Zaganczyk reporter

    Hi Scott, thanks for the response.

    I agree that it is actually the most elegant and intuitive solution to use the polymorphism here. However, given the odd approach that is actually implemented by SF for Apex to access fields by SObject class - I can see some design flaw in your solution here. Let me explain.

    In reality, the only field that can be referenced statically in the SObject is the Id, the other fields can only be referenced dynamically by using SObject.get(fieldName). Thus, your assumption that you should be able to reference IsDeleted (and other fields) from the SObject level is wrong. Try this:

    Datetime now = Datetime.now();
    List<User> usersList = [SELECT Id, CreatedDate FROM User ORDER BY CreatedDate ASC LIMIT 1];
    for (Integer i = 0, j = usersList.size(); i < j; i++) {
        SObject u = usersList[i];
        if (u.CreatedDate > now) {
            System.debug('WEIRD');
        }
    }
    

    and you will receive "Compile failure on line 5, column 6: Field expression not allowed for generic SObject", because we are trying to reference the CreatedDate. It would be the same for the IsDeleted field from your example (but since I have used the User entity here, the SOQL query would not compile here).

    So, actually, in my humble opinion, your OST Apex stubs do not exactly represent the true way that the fields at SObject class level are implemented in Apex. Don't get me wrong, I'm not saying it has to be a one-to-one mapping between the real Apex implementation and the OST Apex stubs for SObject. I assume that probably you have some Java implementation internally, and probably (given the similarities between Apex and Java) it tries to reflect the Apex code as close as possible. Maybe it is the source of the problem?

    If you would ask me how to resolve that - I would add some additional layers into the polymorphism / inheritance tree and make SObject class for OST stub to have only the Id field (as in reality). Then, I would add some intermediary class (direct descendant of SObject stub) to hold the standard fields that are always accessible in all concrete SObjects. Then I would add next layer for deletable SObjects, then for these with Name field accessible.

    global class SObject {
        global Id Id; // THE ONLY FIELD ACCESSIBLE STATICALLY (FIELD NAME AFTER THE DOT)
        global void addError(Exception msg, Boolean escape) {
        }
        //... OTHER METHODS
    }
    global class SObjectIntermediary extends SObject {
        global Datetime CreatedDate;
        global User CreatedBy;
        global Datetime LastModifiedDate;
        global User LastModifiedBy;
        // ... OTHER FIELDS THAT ACCESSIBLE STATICALLY (FIELD NAME AFTER THE DOT) FOR ALL SF SOBJECT TYPES
    }
    global class DeletableSObject extends SObjectIntermediary {
        global Boolean IsDeleted;
    }
    

    I am aware that this approach would produce some artificial layers and they are maybe not neccessarily needed for end-users to be seen in the generated stubs code. But maybe it can be used for the internal plugin implementation?

  3. Scott Wells repo owner

    Michal, thanks for the thoughts. That's pretty much what I was thinking. As you stated, on the positive side you do get the benefits of a polymorphic inheritance hierarchy; on the negative side you introduce types into the inheritance hierarchy that can't actually be referenced in Apex. I'll need to keep chewing on this a bit to see where I want to land.

    Oh, and there's actually no Apex=>Java type of thing happening here. IC includes its own parser for Apex/SOQL/SOSL that builds up the symbol tables, identifier cross-referencing, etc., all based on the parsed ASTs. Some of the odd/inconsistent behaviors of Apex as a formal language definitely make this awkward at times.

    Like I said...more to chew on here...

  4. Scott Wells repo owner

    Fix delivered in 1.7.9.1. After you regenerate your OST with that version or higher (SObjects-only is sufficient), only the Id field will be common among SObjects. All other SObject-specific fields will be on the appropriate derived stub Apex class in the OST.

  5. Michal Zaganczyk reporter

    Hi Scott, thanks for the fix. I must admit that I am very positively surprised by the quality of your support for the product. Keep up the good work!

  6. Log in to comment