ArrayIndexOutOfBoundsException when using FactoryExpressionBase.skipNulls() #1496

Closed
pvcastro opened this Issue Aug 24, 2015 · 18 comments

Projects

None yet

5 participants

@pvcastro

I have the following code:

public class Credenciamento {

@QueryProjection
    public Credenciamento(Long id, LocalDate dataCriacao, LocalDate dataFim, InstituicaoCredora instituicaoCredora, Binario binario) {
        this.id = id;
        this.dataCriacao = dataCriacao;
        this.dataFim = dataFim;
        this.instituicaoCredora = instituicaoCredora;
        this.binario = binario;
    }

}

public Credenciamento consultar(Credenciamento credenciamentoPesquisa) {
        return newQuery()//
                .select(QCredenciamento.create(credenciamento.id, credenciamento.dataCriacao, credenciamento.dataFim, //
                        instituicaoCredora, //
                        QBinario.create(binario.id, binario.contentType, //
                                QArmazemDeBinario.create(armazemDeBinario.id, armazemDeBinario.caminho).skipNulls())//
                                .skipNulls()))//
                .from(credenciamento)//
                .join(credenciamento.instituicaoCredora, instituicaoCredora)//
                .leftJoin(credenciamento.binario, binario)//
                .leftJoin(binario.armazem, armazemDeBinario)//
                .where(credenciamento.id.eq(credenciamentoPesquisa.getId())).fetchOne();
    }

I'm trying to use the skipNulls method from the FactoryExpression class because I'm always getting empty classes when using a projected query and when leftJoin doesn't have an association in the database.

In the example above, a "Credenciamento" search performs a leftJoin with "Binario", but when there isn't any associated Binario in the database, instead of getting a null Binario passed to Credenciamento, I'm getting an instantiated Binario with empty attributes (the associated ArmazemDeBinario inside Binario is also empty instead of null).

I'm trying to use skipNulls like in my consultar method above, and I'm getting an ArrayIndexOutOfBoundsException in the following method from ArrayUtils.

I'm using QueryDSL 4.0.1.

// copied and modified from commons-lang-2.3
    // originally licensed under ASL 2.0
    public static Object[] subarray(Object[] array, int startIndexInclusive, int endIndexExclusive) {
        int newSize = endIndexExclusive - startIndexInclusive;
        Class<?> type = array.getClass().getComponentType();
        if (newSize <= 0) {
            return (Object[]) Array.newInstance(type, 0);
        }
        Object[] subarray = (Object[]) Array.newInstance(type, newSize);
        System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
        return subarray;
    }

Stacktrace:

java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at com.querydsl.core.util.ArrayUtils.subarray(ArrayUtils.java:53)
at com.querydsl.core.types.FactoryExpressionUtils.compress(FactoryExpressionUtils.java:154)
at com.querydsl.core.types.FactoryExpressionUtils.access$100(FactoryExpressionUtils.java:28)
at com.querydsl.core.types.FactoryExpressionUtils$FactoryExpressionAdapter.newInstance(FactoryExpressionUtils.java:62)
at com.querydsl.jpa.FactoryExpressionTransformer.transformTuple(FactoryExpressionTransformer.java:51)
at org.hibernate.hql.internal.HolderInstantiator.instantiate(HolderInstantiator.java:95)
at org.hibernate.loader.hql.QueryLoader.getResultList(QueryLoader.java:465)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2370)
at org.hibernate.loader.Loader.list(Loader.java:2365)
at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:497)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:387)
at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:236)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1264)
at org.hibernate.internal.QueryImpl.list(QueryImpl.java:103)
at org.hibernate.internal.AbstractQueryImpl.uniqueResult(AbstractQueryImpl.java:966)
at com.querydsl.jpa.hibernate.AbstractHibernateQuery.fetchOne(AbstractHibernateQuery.java:330)
at br.com.dataeasy.chronus.persistencia.hibernate.dao.CredenciamentoDAO.consultar(CredenciamentoDAO.java:88)
at br.com.dataeasy.chronus.service.CredenciamentoService.consultar(CredenciamentoService.java:59)

@Shredder121
Member

Could you maybe use code fences?

```java
// code here
```
@pvcastro

Is this ok?

@Shredder121
Member

Yes thank you, this is much better than manually counting parentheses.

@pedro-borges

Just for the record, I have experienced the same issue with the following code:

JPAQuery(getEM()).from(foxtrotInvoice)
                .join(foxtrotInvoice.externalCompany, foxtrotExternalCompany)
                .join(foxtrotExternalCompany.company, foxtrotCompany)
                .where(foxtrotCompany.id.in(companyIds)
                        .and(foxtrotInvoice.financingState.eq(FinancingState.APPROVED))
                        .or(foxtrotInvoice.financingState.eq(FinancingState.FINANCED)))
                .groupBy(foxtrotCompany.id, foxtrotCompany.creditLimit, foxtrotCompany.delphi, foxtrotCompany.experianDbt)
                .map(foxtrotCompany.id,
                        QBook.create(
Book.class,foxtrotInvoice.financingAmount.sum().coalesce(BigDecimal.ZERO),
foxtrotInvoice.financingAmount.multiply(foxtrotCompany.delphi).sum().coalesce(BigDecimal.ZERO),
foxtrotInvoice.financingAmount.multiply(foxtrotInvoice.financingInterestRate).sum().coalesce(BigDecimal.ZERO)
                        ));
@pedro-borges

There is something fishy going on in FactoryExpressionTransformer.transformTuple(Object[], String[])
It starts with a tuple of Long, BigDecimal, BigDecimal, BigDecimal and instead of turning it into a Tuple of Long,Book(BigDecimal, BigDecimal, BigDecimal) it is turning it into a tuple of Long,BigDecimal simply truncating the last two fields. I am running version 3.6.6, but the same behaviour happens with 4.0.3

@timowest
Member

What happens here: com.querydsl.jpa.FactoryExpressionTransformer.transformTuple(FactoryExpressionTransformer.java:51)? How big is the tuple array?

@pedro-borges

The incoming Object[] = {Long, BigDecimal}, so it's size 2.
The incoming alias[] = {"0", "1", "2", "3"}
Notice I'm trying to return a Map<Long, Book> where Book is created from 3 BigDecimals via QBook.create().

@timowest
Member

Thanks. And what about @pvcastro?

@pvcastro

My tuple array at this point is size 5, but the last element is null.

@pedro-borges

Please consider the edit in my reply above. I was answering from memory and just now reverted the code to properly debug it.

@timowest
Member

I did not manage to replicate the issue in the PR. Could you take a look? Or maybe provide a minimal example that has the same error?

@pvcastro

Hi @timowest , are you asking me or @pedro-borges ?

@timowest
Member

Both

@pvcastro

You could try a projected query left-joining another entity, with no result found for the association:

public class Entity1 {

    @ManyToOne
    private Entity2 entity2;

    @QueryProjection
    public Entity (Long id, Entity2 entity2) {
        this.id = id;
        this.entity2 = entity2;
    }

}

public class Entity2 {

    @QueryProjection
    public Entity2 (Long id) {
        this.id = id;
    }

}

public Entity1 find() {
        QEntity1 entity1 = QEntity1.entity1;
        QEntity2 entity2 = QEntity2.entity2;
        return new HibernateQuery<Entity1>(getSession())//
                .select(QEntity1.create(entity1.id, QEntity2.create(entity2.id).skipNulls()))//
                .from(entity1)//
                .leftJoin(entity1.entity2, entity2)//
                .fetchOne();
}

Try the code above, with a row contaning a null value for entity2 in the Entity1 table.
With skipNulls I get the ArrayIndexOutOfBoundsException, and without I get an empty Entity2.

@pedro-borges

Sorry for not replying on time, I believe the issue has been isolated and solved by now.
Will there be an update on this page stating the release this changes will come out in?

Thanks, keep up the great work

@johnktims
Member

This fix should be part of the release that's being made today.

@Shredder121 Shredder121 closed this in #1497 Aug 31, 2015
@timowest timowest added this to the 4.0.4 milestone Aug 31, 2015
@timowest timowest removed the progress label Aug 31, 2015
@pvcastro
pvcastro commented Sep 4, 2015

Great job! Thanks for the fix!
I have a question though. Shouldn't this "skipNulls" be the default behavior? If you don't find a particular joined association, shouldn't it be null without having to explicitly specify it?

@timowest
Member
timowest commented Sep 7, 2015

For backwards compatibility skipNulls is not the default. Also with multiple independent bindings to a projection, skipNulls might not always be the expected behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment