9 Replies Latest reply on Aug 21, 2012 9:48 PM by Randall Hauch

    QueryResult.getNodes() cannot be called when query contains multiple selectors

    Brian Wallis Master

      I'm running a performance test application that we have been using with Modeshape 2.8.1. I've just finished porting it to run with jboss as7/modeshape 3.0.0.Beta2. After running a little while and populating some data into my repository it attempts to execute the following query:

       

       

      {code}

      select * from [inf:episode] as e join [inf:patient] as p on isdescendantnode(e,p) where p.[inf:masterId] = '00000000'

      {code}

       

      and I get the error in the subject line. Is this something that is going to be fixed in a later beta or is it a regression from the functionality of modeshape 2.8? The query seems reasonable enough to me. Basically I'm trying to find all the episode descendents of a particular patient node (see the schema below).

       

      The error with  more detail (with stack traces):

       

       

      {code}

      16:29:31,633 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/JcrServer].[au.com.infomedix.jcrloadtest.jcrserver.restapi.JcrLoadTestApplication]] (http-localhost-127.0.0.1-8080-1) Servlet.service() for servlet au.com.infomedix.jcrloadtest.jcrserver.restapi.JcrLoadTestApplication threw exception: org.jboss.resteasy.spi.UnhandledException: java.lang.RuntimeException: Could not perform operation over the repository

                at org.jboss.resteasy.core.SynchronousDispatcher.handleApplicationException(SynchronousDispatcher.java:340) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.SynchronousDispatcher.handleException(SynchronousDispatcher.java:214) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.SynchronousDispatcher.handleInvokerException(SynchronousDispatcher.java:190) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:540) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:502) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:119) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:208) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:50) [resteasy-jaxrs-2.3.2.Final.jar:]

                at javax.servlet.http.HttpServlet.service(HttpServlet.java:847) [jboss-servlet-api_3.0_spec-1.0.0.Final.jar:1.0.0.Final]

                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:329) [jbossweb-7.0.13.Final.jar:]

                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:248) [jbossweb-7.0.13.Final.jar:]

                at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:275) [jbossweb-7.0.13.Final.jar:]

                at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:161) [jbossweb-7.0.13.Final.jar:]

                at org.jboss.as.web.security.SecurityContextAssociationValve.invoke(SecurityContextAssociationValve.java:153) [jboss-as-web-7.1.1.Final.jar:7.1.1.Final]

                at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:155) [jbossweb-7.0.13.Final.jar:]

                at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [jbossweb-7.0.13.Final.jar:]

                at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [jbossweb-7.0.13.Final.jar:]

                at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:368) [jbossweb-7.0.13.Final.jar:]

                at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877) [jbossweb-7.0.13.Final.jar:]

                at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:671) [jbossweb-7.0.13.Final.jar:]

                at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:930) [jbossweb-7.0.13.Final.jar:]

                at java.lang.Thread.run(Thread.java:680) [classes.jar:1.6.0_33]

      Caused by: java.lang.RuntimeException: Could not perform operation over the repository

                at au.com.infomedix.jcrloadtest.jcrserver.repo.JcrService.executeCommand(JcrService.java:92) [classes:]

                at au.com.infomedix.jcrloadtest.jcrserver.restapi.NodeCrudMethods.queryNodes(NodeCrudMethods.java:158) [classes:]

                at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [classes.jar:1.6.0_33]

                at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) [classes.jar:1.6.0_33]

                at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) [classes.jar:1.6.0_33]

                at java.lang.reflect.Method.invoke(Method.java:597) [classes.jar:1.6.0_33]

                at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:155) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.ResourceMethod.invokeOnTarget(ResourceMethod.java:257) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:222) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:211) [resteasy-jaxrs-2.3.2.Final.jar:]

                at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:525) [resteasy-jaxrs-2.3.2.Final.jar:]

                ... 19 more

      Caused by: javax.jcr.RepositoryException: Method cannot be called when query contains multiple selectors: select * from [inf:episode] as e join [inf:patient] as p on isdescendantnode(e,p) where p.[inf:masterId] = '00000000'

                at org.modeshape.jcr.query.JcrQueryResult.getNodes(JcrQueryResult.java:137)

                at au.com.infomedix.jcrloadtest.jcrserver.operations.QueryNodesOperation.performOperation(QueryNodesOperation.java:38) [classes:]

                at au.com.infomedix.jcrloadtest.jcrserver.operations.QueryNodesOperation.performOperation(QueryNodesOperation.java:1) [classes:]

                at au.com.infomedix.jcrloadtest.jcrserver.repo.AbstractJcrOperation.execute(AbstractJcrOperation.java:44) [classes:]

                at au.com.infomedix.jcrloadtest.jcrserver.repo.JcrService.executeCommand(JcrService.java:84) [classes:]

                ... 29 more

      {code}

       

      The relevant part of my schema is as follows:

       

      {code}

      /**

      * A patient identifier. This is an external key into another store that contains details

      * about a patient (demographics).

      */

      [inf:patientId]

        - inf:ns (string) mandatory copy

        - inf:id (string) mandatory primary copy

       

      /**

      * Root node for a particular patient record. There is only one instance of this node for a patient

      * but it can contain multiple identities for the patient (namespace:urno). One of the identities

      * is considered as the master ID within CPF for that patient. Patient demographic info is not stored

      * here in the document store and has to be separately looked up from another datastore or the PMI system.

      */

      [inf:patient] > nt:hierarchyNode,mix:versionable

        - inf:masterId (string) mandatory primary copy

        - inf:masterNs (string) mandatory copy

        + inf:urnos (inf:patientId) mandatory multiple copy

        + inf:section (inf:section) multiple copy

       

      /**

      * A section node is a named container of either episodes or documents.

      */

      [inf:section] > nt:hierarchyNode orderable

        - inf:name (string) primary mandatory copy

        + inf:items (nt:hierarchyNode) multiple version

       

      /**

      * An episode is a container of documents that pertain to a particular patient contact or incident.

      * Patient episode details are not stored here in the document store and have to be separately

      * looked up from another datastore or the PMI system.

      */

      [inf:episode] > nt:hierarchyNode

        - inf:ns (string) mandatory copy

        - inf:id (string) mandatory primary copy

        + inf:items (inf:document) multiple version

       

      /**

      * A document node represents a single document in a patient record. It has a handfull of physical types

      * (binary data (images, pdf, etc), xforms, charts, results, etc.) and a possibly large number of logical types

      * (admission form, medication order, discharge summary, referral letter, etc.) that are specified via the docCode attribute.

      *

      * This node contains the basic metadata about a document along with subnodes containing document states and a

      * history of changes including who made the change and when.

      *

      * Metadata about specific document types are mixed in when a document node is created (see the mixin nodes

      * defined elsewhere in this model)

      *

      * The actual document data is contained is type specific subnodes under content.

      */

      [inf:document] > nt:hierarchyNode,mix:versionable orderable

        - inf:docType (string) mandatory primary copy

        - inf:staging (date) copy

        - inf:location (string) copy

        + inf:history (inf:history) multiple copy

        + inf:documentState (inf:documentState) multiple copy

        + inf:content (nt:hierarchyNode) multiple copy

      {code}

        • 1. Re: Method cannot be called when query contains multiple selectors
          Horia Chiorean Master

          This seems like a bug in 3.x. Could you please open a JIRA issue for it, thanks.

          1 of 1 people found this helpful
          • 2. Re: Method cannot be called when query contains multiple selectors
            dmfay Newbie

            I'm seeing the same message in 2.8.2.Final and the 2.x snapshot when I call JcrQueryResult.getNodes() on the result of a fulltext search query. getNodes() throws if getSelectorNames().length isn't equal to 1; ModeShape 2.8.1.Final doesn't perform this check and runs successfully. getSelectorNames() is returning an empty array in both 2.8.1 and 2.8.2.

            1 of 1 people found this helpful
            • 3. Re: Method cannot be called when query contains multiple selectors
              Brian Wallis Master

              MODE-1597 created with some test case code and schema as well.

              • 4. Re: Method cannot be called when query contains multiple selectors
                Brian Wallis Master

                Found a workaround for my case. I modified the query to return only the episode nodes like so:

                 

                {code}

                select e.* from [inf:episode] as e join [inf:patient] as p on isdescendantnode(e,p) where p.[inf:masterId] = '00000000'

                {code}

                 

                now I don't get the error and I get all the episode nodes returned. I assume that the query with "select *" was wanting to return all the patient and episode nodes which is probably not what I really wanted in the first place.

                 

                The implementation of JcrQueryResult.getNodes() is only coded to handle a single set of nodes hence the check and exception if there are two (as in my original SQL statement)

                • 5. Re: Method cannot be called when query contains multiple selectors
                  Randall Hauch Master

                  Actually, the JCR API stipulates that calling javax.jcr.query.QueryResult.getNodes() on the results of a query that has more than one selector is not allowed. The JavaDoc for the method states:

                   

                  Returns an iterator over the Rows of the result table. The rows are returned according to the ordering specified in the query.
                  Returns:
                  a RowIterator
                  Throws:
                  RepositoryException - if this call is the second time either getRows or getNodes has been called on the same QueryResult object or if another error occurs.

                  Therefore, we cannot alter the behavior and be compliant with the JCR API.

                  • 6. Re: Method cannot be called when query contains multiple selectors
                    Brian Wallis Master

                    Randall Hauch wrote:

                     

                    Therefore, we cannot alter the behavior and be compliant with the JCR API.

                    Fair enough. "standard" behaviour might be inconvenient at times but standards are pretty useless if we don't stick with them.

                     

                    I wonder if the error message could be modified to reflect this? A reference to the standard or something like that?

                    • 7. Re: Method cannot be called when query contains multiple selectors
                      Randall Hauch Master

                      Fair enough. "standard" behaviour might be inconvenient at times but standards are pretty useless if we don't stick with them.

                       

                      What would the behavior of the resulting NodeIterator be when the query did involve multiple selectors? In such queries, each row in the result has multiple nodes. One could arge for some queries that simply knowing any/all of the nodes that satisfy the query is sufficient, but the same behavior has to hold true for other queries. IMO, this method is a holdover from JCR-SQL queries that cannot have joins, and it makes very little sense for many/most JCR-SQL2 and JCR-JQOM queries.

                       

                      I wonder if the error message could be modified to reflect this? A reference to the standard or something like that?

                      Yes, we can alter the message to make it more clear that the specification stipulates this behavior.

                      • 8. Re: Method cannot be called when query contains multiple selectors
                        Brian Wallis Master

                        Randall Hauch wrote:

                         

                        What would the behavior of the resulting NodeIterator be when the query did involve multiple selectors? In such queries, each row in the result has multiple nodes. One could arge for some queries that simply knowing any/all of the nodes that satisfy the query is sufficient, but the same behavior has to hold true for other queries. IMO, this method is a holdover from JCR-SQL queries that cannot have joins, and it makes very little sense for many/most JCR-SQL2 and JCR-JQOM queries.

                        What I was wrongly expecting would be to iterate over the nodes returned where the nodes would be a mix of inf:patient and inf:episode types but of course that would loose the join of which patient contained which episodes. The correct results in this case would be to return a "node" that contained the attributes of both patient and episode but that would cause problems with attribute name clashes when atempting to merge the two nodes.

                         

                        On reading some more, what I should be using is getRows() not getNodes() as this does support a query with multiple selectors and the Row class has a getNode() method that takes a selector name as a parameter allowing access to each of the nodes involved in the join.

                         

                        Learning more about JCR every day :-)

                         

                        thanks.

                        • 9. Re: Method cannot be called when query contains multiple selectors
                          Randall Hauch Master

                          what I should be using is getRows() not getNodes() as this does support a query with multiple selectors and the Row class has a getNode() method that takes a selector name as a parameter allowing access to each of the nodes involved in the join.

                          Yes, that is exactly right: iterate over the javax.jcr.query.Row instances, then for each use its "getNode(String selectorName)". There's also a "getNode()" method, but that only works if the query has one selector (that is, has no join).