-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 
Author Message
 Post subject: DetachedCriteria and Subqueries
PostPosted: Tue Feb 22, 2005 2:01 pm 
Newbie

Joined: Tue Feb 01, 2005 6:30 am
Posts: 6
Hibernate version:3b4

I played a bit with the new Criteria API and discovered some queer behaviors, particularly when it comes to DetachedCriteria and Subqueries.

As an example, let's consider a two level folder system (yes very simple) and say we want to fetch the folders a user has access to.
First level folders are parents. Second level folders are children.
Authorizations (always positive authorizations, no negative) may be granted at the parent or child level. Authorizations are inherited from a parent to its children.

The code example (see the end of the post) creates the following objects:
- parent folder
- child folder
- authorization object referencing the parent

The Criteria fetches the folders using a disjunction. The first disjunction element fetches the folders which have been granted access explicitely. The second disjunction element fetches folders whose parents have been granted access.

When I run this simple test, I get the following NullPointerException:

Code:
java.lang.NullPointerException
   at org.hibernate.criterion.SubqueryExpression.getTypedValues(SubqueryExpression.java:73)
   at org.hibernate.loader.criteria.CriteriaQueryTranslator.getQueryParameters(CriteriaQueryTranslator.java:230)
   at org.hibernate.criterion.SubqueryExpression.toSqlString(SubqueryExpression.java:50)
   at org.hibernate.criterion.Junction.toSqlString(Junction.java:58)
   at org.hibernate.loader.criteria.CriteriaQueryTranslator.getWhereCondition(CriteriaQueryTranslator.java:312)
   at org.hibernate.loader.criteria.CriteriaLoader.<init>(CriteriaLoader.java:92)
   at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1208)
   at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:299)
   at test.subqueries.SubqueriesTest.main(SubqueriesTest.java:41)
Exception in thread "main"



To make it work, I had to do the following:

1/ update SubqueryExpression.getTypeValues(Criteria, CriteriaQuery) to initialize the params instance variable :

Code:
public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
   if (params == null) { // FIXME copy-paste from toLeftSqlString()
      final SessionFactoryImplementor factory = ( (CriteriaImpl) criteria ).getSession().getFactory();
      final OuterJoinLoadable persister = (OuterJoinLoadable) factory.getEntityPersister( criteriaImpl.getEntityOrClassName() );
      CriteriaQueryTranslator innerQuery = new CriteriaQueryTranslator(
            factory,
            criteriaImpl,
            criteriaImpl.getEntityOrClassName(), //implicit polymorphism not supported (would need a union)
            criteriaQuery.generateSQLAlias(),
            criteriaQuery);
      params = innerQuery.getQueryParameters(); //TODO: bad lifecycle....
   }
   
   Type[] types = params.getPositionalParameterTypes();
   Object[] values = params.getPositionalParameterValues();
   TypedValue[] tv = new TypedValue[types.length];
   for (int i = 0; i < types.length; i++) {
      tv[i] = new TypedValue(types[i], values[i]);
   }
   return tv;
}


2/ call getExecutableCriteria(Session) on each DetachedCriteria to have the CriteriaImpl initialized

3/ not mentionned before, I had to force Projections on every DetachedCriteria used in a SubqueryExpression, even for ExistsSubqueryExpression expressions.

Then Hibernate fetches the two folders. The generated sql is:

Code:
Hibernate: select this_.folder_id as folder1_0_, this_.label as label0_0_, this_.parent_id as parent3_0_0_ from folder this_ where (exists (select this0__.authorization_id as y0_ from authorization this0__ where this0__.user_id=? and this0__.folder_id=this_.folder_id) or this_.parent_id in (select this0__.folder_id as y0_ from folder this0__ where this0__.folder_id=this_.parent_id and exists (select this0__.authorization_id as y0_ from authorization this0__ where this0__.user_id=? and this0__.folder_id=this0__.folder_id)))




Code and mappings required to reproduce the error;
workaround comments are part of the "solution" I found to make it work:

Code:
/*
* Created on Feb 22, 2005
*/
package test.subqueries;

import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.Subqueries;

/**
* @author gg
*/
public class SubqueriesTest {
    public static void main(String[] args) {
        Session session = config();
        User me = setupModel(session);
        Criteria criteria = session.createCriteria(Folder.class, "root_alias");
        String rootAlias = criteria.getAlias();
        Disjunction disjunction = Restrictions.disjunction();
        criteria.add(disjunction);
        // detached criteria to evaluate directly attached authorizations:
        DetachedCriteria localAuthCriteria = createAuthorizationCriteria(me, rootAlias);
        localAuthCriteria.getExecutableCriteria(session); // WORKAROUND
        disjunction.add(Subqueries.exists(localAuthCriteria));
        // detached criteria to evaluate parent authorization inheritance:
        DetachedCriteria authorization2 = DetachedCriteria.forClass(Folder.class, "parent_alias");
        authorization2.add(Restrictions.eqProperty("id", rootAlias + ".parent.id"));
        authorization2.getExecutableCriteria(session); // WORKAROUND
        DetachedCriteria parentAuthCriteria = createAuthorizationCriteria(me, authorization2.getAlias());
        parentAuthCriteria.getExecutableCriteria(session); // WORKAROUND
        authorization2.add(Subqueries.exists(parentAuthCriteria));
        authorization2.setProjection(Projections.property("id"));
        disjunction.add(Subqueries.propertyIn("parent.id", authorization2));
        List folders = criteria.list();
    }
    /**
     * @param me
     * @param rootAlias
     * @return
     */
    private static DetachedCriteria createAuthorizationCriteria(User me, String alias) {
        DetachedCriteria criteria = DetachedCriteria.forClass(Authorization.class, alias + "_authorization");
        criteria.add(Restrictions.eq("user", me));
        criteria.add(Restrictions.eqProperty("folder.id", alias + ".id"));
        criteria.setProjection(Projections.property("id"));
        return criteria;
    }

    private static Session config() {
        Configuration cfg = new Configuration().configure().addClass(Folder.class).addClass(User.class).addClass(Authorization.class);
        SessionFactory sessions = cfg.buildSessionFactory();
        return sessions.openSession();
    }

    private static User setupModel(Session session) {
        Folder root = new Folder("root");
        session.save(root);
        Folder child = new Folder("child");
        child.setParent(root);
        session.save(child);
        User user = new User("jerome");
        session.save(user);
        Authorization authorization = new Authorization(root, user);
        session.save(authorization);
        return user;
    }
}


The mapping documents:
Code:
<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class
        name="test.subqueries.Folder"
        table="folder"
        dynamic-update="false"
        dynamic-insert="false"
    >

        <id
            name="id"
            column="folder_id"
            type="long"
            unsaved-value="null"
        >
            <generator class="identity">
            </generator>
        </id>

        <set
            name="children"
            lazy="true"
            inverse="false"
            cascade="none"
            sort="unsorted"
        >

              <key
                  column="parent_id"
              />

              <one-to-many
                  class="test.subqueries.Folder"
              />
        </set>

        <property
            name="label"
            type="java.lang.String"
            update="true"
            insert="true"
            column="label"
        />

        <many-to-one
            name="parent"
            class="test.subqueries.Folder"
            cascade="none"
            outer-join="auto"
            update="true"
            insert="true"
            column="parent_id"
        />

        <!--
            To add non XDoclet property mappings, create a file named
                hibernate-properties-Folder.xml
            containing the additional properties and place it in your merge dir.
        -->

    </class>

</hibernate-mapping>



<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class
        name="test.subqueries.User"
        table="user"
        dynamic-update="false"
        dynamic-insert="false"
    >

        <id
            name="id"
            column="user_id"
            type="long"
            unsaved-value="null"
        >
            <generator class="identity">
            </generator>
        </id>

        <property
            name="label"
            type="java.lang.String"
            update="true"
            insert="true"
            column="label"
        />

        <!--
            To add non XDoclet property mappings, create a file named
                hibernate-properties-User.xml
            containing the additional properties and place it in your merge dir.
        -->

    </class>

</hibernate-mapping>



<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class
        name="test.subqueries.Authorization"
        table="authorization"
        dynamic-update="false"
        dynamic-insert="false"
    >
        <id
            name="id"
            column="authorization_id"
            type="long"
            unsaved-value="null"
        >
            <generator class="identity">
            </generator>
        </id>
       
        <many-to-one
            name="folder"
            class="test.subqueries.Folder"
            cascade="none"
            outer-join="auto"
            update="true"
            insert="true"
            column="folder_id"
        />
       
        <many-to-one
            name="user"
            class="test.subqueries.User"
            cascade="none"
            outer-join="auto"
            update="true"
            insert="true"
            column="user_id"
        />
   </class>
</hibernate-mapping>


The java classes:
Code:
/*
* Created on Feb 22, 2005
*/
package test.subqueries;

import java.util.Set;

/**
* @author gg
* @hibernate.class table = "folder"
*/
public class Folder {
    private long id;
    private String label;
    private Folder parent;
    private Set children;

    public Folder() {
    }
   
    public Folder(String label) {
        this.label = label;
    }
   
    /**
     * @hibernate.set lazy = "true"
     * @hibernate.collection-key column = "parent_id"
     * @hibernate.collection-one-to-many class = "test.subqueries.Folder"
     * @return
     */
    public Set getChildren() {
        return this.children;
    }

    public void setChildren(Set children) {
        this.children = children;
    }

    /**
     * @hibernate.id    column = "folder_id"
     *                generator-class = "identity"
     *                unsaved-value = "null"
     * @return
     */
    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    /**
     * @hibernate.property column = "label"
     * @return
     */
    public String getLabel() {
        return this.label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    /**
     * @hibernate.many-to-one column = "parent_id"
     * @return
     */
    public Folder getParent() {
        return this.parent;
    }

    public void setParent(Folder parent) {
        this.parent = parent;
    }
}

/*
* Created on Feb 22, 2005
*/
package test.subqueries;

/**
* @author gg
* @hibernate.class table = "user"
*/
public class User {
    private long id;
    private String label;

    public User() {
    }

    public User(String label) {
        this.label = label;
    }

    /**
     * @hibernate.id    column = "user_id"
     *                generator-class = "identity"
     *                unsaved-value = "null"
     * @return
     */
    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    /**
     * @hibernate.property column = "label"
     * @return
     */
    public String getLabel() {
        return this.label;
    }

    public void setLabel(String label) {
        this.label = label;
    }
}



/*
* Created on Feb 22, 2005
*/
package test.subqueries;

/**
* @author gg
* @hibernate.class table = "authorization"
*/
public class Authorization {
    private long id;
    private Folder folder;
    private User user;

    public Authorization() {
    }
   
    /**
     * @hibernate.id    column = "authorization_id"
     *                generator-class = "identity"
     *                unsaved-value = "null"
     * @return
     */
    public long getId() {
        return this.id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Authorization(Folder folder, User user) {
        this.folder = folder;
        this.user = user;
    }

    /**
     * @hibernate.many-to-one column = "folder_id"
     * @return
     */
    public Folder getFolder() {
        return this.folder;
    }

    public void setFolder(Folder folder) {
        this.folder = folder;
    }

    /**
     * @hibernate.many-to-one column = "user_id"
     * @return
     */
    public User getUser() {
        return this.user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}


Has anybody encountered similar problems with DetachedCriteria and subqueries ?

Cheers,
Jerome

PS: apart from these issues, the new Criteria API rocks ;)


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 25, 2005 4:51 am 
Newbie

Joined: Tue Feb 01, 2005 6:30 am
Posts: 6
Has anybody an idea about this issue ?
Is there a bug to be reported ?

Cheers,

_________________
Jerome


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 25, 2005 5:28 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
Looks like a bug. Theoretically, toSqlString() is supposed to be called before getTypedValues() and init the params instvar.

Please submit a simple, runnable testcase to JIRA, thanks.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Feb 28, 2005 2:36 pm 
Newbie

Joined: Tue Feb 01, 2005 6:30 am
Posts: 6
Just posted a bug to Jira: HHH-158

Hope it helps!

_________________
Jerome


Top
 Profile  
 
 Post subject:
PostPosted: Wed Mar 23, 2005 4:26 pm 
Expert
Expert

Joined: Sat Jan 17, 2004 2:57 pm
Posts: 329
Location: In the basement in my underwear
I am happy to know that I am not alone, I ran across this myself today with RC1.

Time to find out of creating aliases (joins) in subqueries are still hokey :D


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.