New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArrayIndexOutOfBoundsException when using FactoryExpressionBase.skipNulls() #1496

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

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

This comment has been minimized.

Show comment
Hide comment
@Shredder121

Shredder121 Aug 24, 2015

Member

Could you maybe use code fences?

```java
// code here
```
Member

Shredder121 commented Aug 24, 2015

Could you maybe use code fences?

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

This comment has been minimized.

Show comment
Hide comment
@pvcastro

pvcastro Aug 24, 2015

Is this ok?

Is this ok?

@Shredder121

This comment has been minimized.

Show comment
Hide comment
@Shredder121

Shredder121 Aug 24, 2015

Member

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

Member

Shredder121 commented Aug 24, 2015

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

@pedro-borges

This comment has been minimized.

Show comment
Hide comment
@pedro-borges

pedro-borges Aug 24, 2015

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)
                        ));

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

This comment has been minimized.

Show comment
Hide comment
@pedro-borges

pedro-borges Aug 24, 2015

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

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

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Aug 24, 2015

Member

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

Member

timowest commented Aug 24, 2015

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

@pedro-borges

This comment has been minimized.

Show comment
Hide comment
@pedro-borges

pedro-borges Aug 24, 2015

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().

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

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Aug 24, 2015

Member

Thanks. And what about @pvcastro?

Member

timowest commented Aug 24, 2015

Thanks. And what about @pvcastro?

@pvcastro

This comment has been minimized.

Show comment
Hide comment
@pvcastro

pvcastro Aug 25, 2015

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

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

@pedro-borges

This comment has been minimized.

Show comment
Hide comment
@pedro-borges

pedro-borges Aug 25, 2015

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

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

@timowest

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Aug 27, 2015

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?

Member

timowest commented Aug 27, 2015

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

This comment has been minimized.

Show comment
Hide comment
@pvcastro

pvcastro Aug 27, 2015

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

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

@timowest

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Aug 27, 2015

Member

Both

Member

timowest commented Aug 27, 2015

Both

@pvcastro

This comment has been minimized.

Show comment
Hide comment
@pvcastro

pvcastro Aug 27, 2015

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.

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

This comment has been minimized.

Show comment
Hide comment
@pedro-borges

pedro-borges Aug 31, 2015

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

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

This comment has been minimized.

Show comment
Hide comment
@johnktims

johnktims Aug 31, 2015

Member

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

Member

johnktims commented Aug 31, 2015

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

This comment has been minimized.

Show comment
Hide comment
@pvcastro

pvcastro 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?

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

This comment has been minimized.

Show comment
Hide comment
@timowest

timowest Sep 7, 2015

Member

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

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