0

I'm trying to figure out how to query an object within a collection of persistent objects using an RDBMS and DataNucleus. I've got a User, an ObjectAttribute, and an ObjectAttributeDefinition.

The User may have more than one ObjectAttribute, and each ObjectAttribute has a single ObjectAttributeDefinition. The idea being that a few definitions can define a whole bunch of ObjectAttributes.

I want to check for the existence of an ObjectAttribute whose value field aligns with one that is specified, and whose ObjectAttributeDefinition contains field attributeName whose value is set to an attributeName that I've specified.

In other words:

for (ObjectAttribute attribute : userAttributeCollection)
{
    if(attribute.value == myValue && attribute.definition.attributeName == myName)
        return success;
}

I have the following three classes:

User

@Component
@PersistenceCapable
public class User implements Serializable
{
    @Persistent(table="USER_ATTRIBUTES")
    @Join(column="USER_ID")
    @Element(column = "ATTRIBUTE_ID")
    private List<ObjectAttribute> userAttributeCollection;

    ///... Constructors, getters and setters, Primary Key definition below
}

ObjectAttribute

@Component
@PersistenceCapable
public class ObjectAttribute implements Serializable
{
    @Persistent(table="ATTRIBUTE_DEFINITION_JOIN")
    @Join(column="ATTRIBUTE_ID")
    @Element(column="DEFINITION_ID")
    private ObjectAttributeDefinition definition;
    private String value;

    //...

    @Override
    public boolean equals(Object obj)
    {
        ObjectAttribute testObject = (ObjectAttribute) obj;
        if (this.value.equals(testObject.getValue()) && this.definition.getAttributeName().equals(testObject.getDefinition().getAttributeName())) {
            return true;
        }
        else {
            return false;
        }
    }
}

ObjectAttributeDefinition

public class ObjectAttributeDefinition implements Serializable
{
    public enum ATTRIBUTE_NAMES
    {
        //Google Search Page Attribute (https://cloud.google.com/appengine/docs/java/search/)
        SEARCH_PAGE("SEARCH_PAGE");

        private final String text;

        ATTRIBUTE_NAMES(String type)
        {
            this.text = type;
        }

        @Override
        public String toString()
        {
            return text;
        }
    }

    private ATTRIBUTE_NAMES attributeName;

    //...

    @Override
    public boolean equals(Object obj)
    {
        ObjectAttributeDefinition definition = (ObjectAttributeDefinition) obj;
        return this.attributeName.equals(definition.attributeName);
    }

}

Further Explanation

I have a DAO class and list of methods defined for accessing these persistent objects. The overall infrastructure appears to be set up correctly, as persisting objects and retrieving them again seems to be working properly.

My way of ensuring uniqueness with an object attribute will be to query an ObjectAttribute for its value and an ObjectAttributeDefinition for its name.

What I've Tried

Basic Query

        Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att.value == :val & att.definition.attributeName == :def");
        query.declareVariables("ObjectAttribute att");
        query.declareImports("import com.us.orm.general.models.ObjectAttributeDefinition; import com.us.orm.general.models.ObjectAttribute");

        query.setResultClass(User.class);

        rval =  (List<User>) query.execute(myAtt.getValue(), myAtt.getDefinition().getAttributeName());

Basic Query Error

Query has reference to member "attributeName" of class "com.us.orm.general.models.ObjectAttribute" yet this doesnt exist!

As the JDO Specification seems to imply, everything should be very Java-like. However, for some reason I can't seem to access objects any lower than the first-tier.

So I tried to hack the comparison.

Using equals() Override within ObjectAttribute

        Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att == :val");
        query.declareVariables("ObjectAttribute att");
        query.declareImports("import com.us.orm.general.models.ObjectAttributeDefinition; import com.us.orm.general.models.ObjectAttribute");

        query.setResultClass(User.class);

        rval =  (List<User>) query.execute(myAtt);

Using equals() Override within ObjectAttribute Result

I receive zero results back. Looking at the actual SQL generated for this object, I get the following:

SELECT DISTINCT 'com.us.orm.user.models.User' AS NUCLEUS_TYPE,`A0`.`CREATED`,`A0`.`EMAIL`,`A0`.`FIRSTNAME`,`A0`.`ISACTIVE`,`A0`.`LASTMODIFIED`,`A0`.`LASTNAME`,`A0`.`PASSWORD`,`A0`.`PROFILEBANNERBUCKET`,`A0`.`PROFILEBANNERFILENAME`,`A0`.`PROFILEIMAGEBUCKET`,`A0`.`PROFILEIMAGEFILENAME`,`A0`.`USERID`,`A0`.`USERNAME` FROM `USER` `A0` INNER JOIN `USER_ATTRIBUTES` `B0` ON `A0`.`USERID` = `B0`.`USER_ID` INNER JOIN `OBJECTATTRIBUTE` `C0` ON `B0`.`ATTRIBUTE_ID` = `C0`.`ATTRIBUTEID` WHERE `C0`.`ATTRIBUTEID` = null

The ATTRIBUTEID checking for a null reference will never return results. So this is on the side of my code's interface with DataNucleus, but I'm again at a loss.

Maybe I can override equals() on both the ObjectAttribute and ObjectAttributeDefinition..

Equals Override on both the ObjectAttribute and ObjectAttributeDefinition

    Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att.value == :val & att.definition == :def");
    query.declareVariables("ObjectAttribute att");
    query.declareImports("import com.up.orm.general.models.ObjectAttributeDefinition; import com.up.orm.general.models.ObjectAttribute");

    query.setResultClass(User.class);

    rval =  (List<User>) query.execute(myAtt.getValue(), myAtt.getDefinition());

Equals Override on both the ObjectAttribute and ObjectAttributeDefinition Result

rval returns null, no generated database query is made, and no exception is thrown.

Only Using Value Comparison

        Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att.value == :val");
        query.declareVariables("ObjectAttribute att");
        query.declareImports("import com.up.orm.general.models.ObjectAttributeDefinition; import com.up.orm.general.models.ObjectAttribute");

        query.setResultClass(User.class);

        rval =  (List<User>) query.execute(myAtt.getValue());

Only Using Value Comparison Result

This returns the expected values, but without using the definition comparison, which is only half of the goal.

Is there any way to query the definition and its fields at the second level of depth?

moscro
  • 486
  • 5
  • 13
  • Query itself looks fine. What does the log say about its "generic compilation" ? And what version are you using ? latest release ? nightly builds ? I usually try something later if hitting a problem – Neil Stockton Apr 08 '15 at 14:06

1 Answers1

0

I've figured out the solution. I may have missed this in the documentation, but I wasn't aware that declared variables actually perform a "cross join" in SQL. This is a really important aspect to the solving the problem.

The query is as follows:

    Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att.value == :val & definition.attributeName == :name");
    query.declareVariables("ObjectAttribute att; ObjectAttributeDefinition definition");
    query.declareImports("import com.us.orm.general.models.ObjectAttributeDefinition; import com.us.orm.general.models.ObjectAttribute");

    query.setResultClass(User.class);

    rval =  (List<User>) query.execute(myAtt.getValue(), name.toString());

I was missing the "cross joined" variable piece to the filter (in the form of ObjectAttributeDefinition). Now that it's there, the query is pulling the expected results.

moscro
  • 486
  • 5
  • 13
  • but that's a different query to your original one. Your not linking "att" to the "definition". – Neil Stockton Apr 09 '15 at 08:33
  • That's what I was thinking initially as well, but looking at the raw SQL query, the cross join operation of the ObjectAttributeDefinition table onto the ObjectAttribute table where the ObjectAttributeDefinition.attributeName == :name seems to return the correct results set because the 'att' in the filter is part of the 'WHERE' clause, with an AND in between them. This effectively accomplishes the same thing as a second level dot operater access. – moscro Apr 09 '15 at 16:11