fields icon indicating copy to clipboard operation
fields copied to clipboard

Column sort for index crashes on fields containing multiple values

Open monetschemist opened this issue 5 years ago • 2 comments

I'm having a hard time describing this, but basically in the index view on any table in my project, if I click on the column heading to sort fields, things work fine, except when I click on a column whose elements are many-to-many; that generates a crash.

I'm at the very earliest stages in the application, just prototyping the data model. The views, controllers and services are the stock ones generated by "grails generate-all". The image attached shows the index page; clicking on either of the two rightmost columns causes the code to crash; all other columns sort just fine.

Screenshot from 2020-09-14 16-04-44

The crash looks like this:

ArrayIndexOutOfBoundsException occurred when processing request: [GET] /ocupante/index - parameters:
sort: actividades
max: 10
order: asc
0. Stacktrace follows:

java.lang.reflect.InvocationTargetException: null
        at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
        at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
        at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
        at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
        at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
        at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ArrayIndexOutOfBoundsException: 0
        at org.hibernate.criterion.Order.toSqlString(Order.java:119)
        at org.hibernate.loader.criteria.CriteriaQueryTranslator.getOrderBy(CriteriaQueryTranslator.java:414)
        at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:106)
        at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:75)
        at org.hibernate.loader.criteria.CriteriaLoader.<init>(CriteriaLoader.java:80)
        at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1773)
        at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:363)
        at org.grails.orm.hibernate.query.AbstractHibernateQuery.listForCriteria(AbstractHibernateQuery.java:719)
        at org.grails.orm.hibernate.query.AbstractHibernateQuery.list(AbstractHibernateQuery.java:709)
        at grails.gorm.PagedResultList.<init>(PagedResultList.java:43)
        at grails.gorm.DetachedCriteria$_list_closure2.doCall(DetachedCriteria.groovy:137)
        at grails.gorm.DetachedCriteria$_withPopulatedQuery_closure8.doCall(DetachedCriteria.groovy:769)
        at org.grails.datastore.gorm.GormStaticApi$_withDatastoreSession_closure24.doCall(GormStaticApi.groovy:862)
        at org.grails.datastore.mapping.core.DatastoreUtils.execute(DatastoreUtils.java:319)
        at org.grails.datastore.gorm.AbstractDatastoreApi.execute(AbstractDatastoreApi.groovy:40)
        at org.grails.datastore.gorm.GormStaticApi.withDatastoreSession(GormStaticApi.groovy:861)
        at grails.gorm.DetachedCriteria.withPopulatedQuery(DetachedCriteria.groovy:740)
        at grails.gorm.DetachedCriteria.list(DetachedCriteria.groovy:135)
        at grails.gorm.transactions.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:94)
        at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
        at grails.gorm.transactions.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:91)
        at sgs.OcupanteController.index(OcupanteController.groovy:14)
        ... 14 common frames omitted

The code in the controller that provokes this is the "respond" below at line 14:

package sgs

import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*

class OcupanteController {

    OcupanteService ocupanteService

    static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond ocupanteService.list(params), model:[ocupanteCount: ocupanteService.count()]
    }


I'm at the very earliest stages in the application, just prototyping the data model. The views, controllers and services are the stock ones generated by "grails generate-all". The image attached shows the index page; clicking on either of the two rightmost columns causes the code to crash; all other columns sort just fine.

Screenshot from 2020-09-14 16-04-44

gradle.properties: grailsVersion=3.3.9 gormVersion=6.1.11.RELEASE gradleWrapperVersion=3.5

monetschemist avatar Sep 14 '20 23:09 monetschemist

If you can do a small demo project and push it to github that would be very helpful. Judging from the stacktrace however, this does not look like it happens in the Fields Plugin, but I might be wrong

sbglasius avatar Sep 15 '20 06:09 sbglasius

Sorry I have taken so long to get back to this. I have a workaround that I don't particularly like because I feel it's a bit of a hack; but it both explains the problem and (kind of) solves it.

If you look at the f:table template here: https://github.com/grails-fields-plugin/grails-fields/blob/master/grails-app/views/templates/_fields/_table.gsp

you will see the following code snippet:

    <thead>
         <tr>
            <g:each in="${domainProperties}" var="p" status="i">
                <g:sortableColumn property="${p.property}" title="${p.label}" />
            </g:each>
        </tr>
    </thead>

The problem is that columns that are one-to-many associations cause the g:sortableColumn tag to crash when the column is selected. I guess that g:sortableColumn should be more robust, but in any case, I don't believe it makes sense to sort on such a column; and if I recall correctly, the old Grails scaffolding (pre fields plugin) did not render such columns as sortable.

My workaround therefore looks at the type of each columnProperty to be rendered to decide whether to render the column as sortable or not. I was originally trying to detect p.type instanceof ManyToOne, following the suggestions at: https://docs.grails.org/3.3.x/guide/upgrading.html

but after some testing, discovered that according to p.type.toString these columns are interface java.util.Set which I suppose makes sense given the way Gorm creates them.

So my working code replacing the above is:

    <thead>
        <tr>
            <g:each in="${columnProperties}" var="p" status="i">
                <g:if test="${p.type.toString() == 'interface java.util.Set'}">
                    <th>${p.label}</th>
                </g:if>
                <g:else>
                    <g:sortableColumn property="${p.property}" title="${p.label}" />
                </g:else>
            </g:each>
        </tr>
    </thead>

I'm not sure of "the correct solution". We can say that g:sortableColumn should fail more gracefully on this problem, which of course would be nice; or we can accept that it would be a better idea to not suggest that hasMany columns should be sortable in the first place and fix the f:table template.

monetschemist avatar Oct 14 '21 18:10 monetschemist