Skip to content

HHH-19472: native queries can return Object[] #10178

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

Merged
merged 1 commit into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.jpa.spi;

import org.hibernate.query.TupleTransformer;

/**
* A {@link TupleTransformer} for handling {@code Object[]} results from native queries.
*
* @since 7.0
*/
public class NativeQueryArrayTransformer implements TupleTransformer<Object[]> {

public static final NativeQueryArrayTransformer INSTANCE = new NativeQueryArrayTransformer();

@Override
public Object[] transformTuple(Object[] tuple, String[] aliases) {
return tuple;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.hibernate.FlushMode;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.jpa.spi.NativeQueryArrayTransformer;
import org.hibernate.jpa.spi.NativeQueryConstructorTransformer;
import org.hibernate.jpa.spi.NativeQueryListTransformer;
import org.hibernate.jpa.spi.NativeQueryMapTransformer;
Expand Down Expand Up @@ -377,7 +378,10 @@ else if ( Map.class.equals( resultClass ) ) {
else if ( List.class.equals( resultClass ) ) {
return NativeQueryListTransformer.INSTANCE;
}
else if ( resultClass != Object.class && resultClass != Object[].class ) {
else if ( Object[].class.equals( resultClass )) {
return NativeQueryArrayTransformer.INSTANCE;
}
else if ( resultClass != Object.class ) {
// TODO: this is extremely fragile and probably a bug
if ( isClass( resultClass ) && !hasJavaTypeDescriptor( resultClass ) ) {
// not a basic type, so something we can attempt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,81 +4,144 @@
*/
package org.hibernate.orm.test.hql;

import java.util.stream.Stream;

import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.hibernate.query.SelectionQuery;

import org.hibernate.testing.orm.domain.gambit.BasicEntity;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.RequiresDialects;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;

import static org.assertj.core.api.Assertions.assertThat;

/**
* @author Marco Belladelli
*/
@DomainModel( annotatedClasses = BasicEntity.class )
@DomainModel(annotatedClasses = BasicEntity.class)
@SessionFactory
@Jira( "https://hibernate.atlassian.net/browse/HHH-18450" )
@Jira("https://hibernate.atlassian.net/browse/HHH-18450")
@Jira("https://hibernate.atlassian.net/browse/HHH-19472")
@RequiresDialects({@RequiresDialect(H2Dialect.class), @RequiresDialect(PostgreSQLDialect.class)})
public class SingleSelectionArrayResultTest {
@Test
public void testArrayResult(SessionFactoryScope scope) {

static class TestArguments implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return Stream.of(
Arguments.of( "select 1", null, null ),
Arguments.of( "select cast(1 as integer)", null, null ),
Arguments.of( "select id from BasicEntity", null, null ),
Arguments.of( "select cast(id as integer) from BasicEntity", null, null ),
Arguments.of( "select ?1", 1, 1 ),
Arguments.of( "select :p1", "p1", 1 ),
Arguments.of( "select cast(?1 as integer)", 1, 1 ),
Arguments.of( "select cast(:p1 as integer)", "p1", 1 )
);
}
}

@ParameterizedTest
@ArgumentsSource(TestArguments.class)
public void testQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) {
scope.inTransaction( session -> {
Query<Object> query = session.createQuery( ql, Object.class );
if ( arg1 instanceof Integer ) {
query.setParameter( (Integer) arg1, arg2 );
}
if ( arg1 instanceof String ) {
query.setParameter( (String) arg1, arg2 );
}
assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
} );
}

@ParameterizedTest
@ArgumentsSource(TestArguments.class)
public void testNativeQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) {
scope.inTransaction( session -> {
NativeQuery<Object> query = session.createNativeQuery( ql, Object.class );
if ( arg1 instanceof Integer ) {
query.setParameter( (Integer) arg1, arg2 );
}
if ( arg1 instanceof String ) {
query.setParameter( (String) arg1, arg2 );
}
assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
} );
}

@ParameterizedTest
@ArgumentsSource(TestArguments.class)
public void testSelectionQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) {
scope.inTransaction( session -> {
SelectionQuery<Object> query = session.createSelectionQuery( ql, Object.class );
if ( arg1 instanceof Integer ) {
query.setParameter( (Integer) arg1, arg2 );
}
if ( arg1 instanceof String ) {
query.setParameter( (String) arg1, arg2 );
}
assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
} );
}

@ParameterizedTest
@ArgumentsSource(TestArguments.class)
public void testQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) {
scope.inTransaction( session -> {
Query<Object[]> query = session.createQuery( ql, Object[].class );
if ( arg1 instanceof Integer ) {
query.setParameter( (Integer) arg1, arg2 );
}
if ( arg1 instanceof String ) {
query.setParameter( (String) arg1, arg2 );
}
assertThat( query.getSingleResult() ).containsExactly( 1 );
} );
}

@ParameterizedTest
@ArgumentsSource(TestArguments.class)
public void testNativeQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) {
scope.inTransaction( session -> {
assertThat( session.createQuery(
"select 1",
Object[].class
).getSingleResult() ).containsExactly( 1 );
assertThat( session.createQuery(
"select cast(1 as integer)",
Object[].class
).getSingleResult() ).containsExactly( 1 );
assertThat( session.createSelectionQuery(
"select id from BasicEntity",
Object[].class
).getSingleResult() ).containsExactly( 1 );
assertThat( session.createSelectionQuery(
"select cast(id as integer) from BasicEntity",
Object[].class
).getSingleResult() ).containsExactly( 1 );
assertThat( session.createSelectionQuery(
"select ?1",
Object[].class
).setParameter( 1, 1 ).getSingleResult() ).containsExactly( 1 );
assertThat( session.createQuery(
"select cast(:p1 as integer)",
Object[].class
).setParameter( "p1", 1 ).getSingleResult() ).containsExactly( 1 );
NativeQuery<Object[]> query = session.createNativeQuery( ql, Object[].class );
if ( arg1 instanceof Integer ) {
query.setParameter( (Integer) arg1, arg2 );
}
if ( arg1 instanceof String ) {
query.setParameter( (String) arg1, arg2 );
}
assertThat( query.getSingleResult() ).containsExactly( 1 );
} );
}

@Test
public void testNormalResult(SessionFactoryScope scope) {
@ParameterizedTest
@ArgumentsSource(TestArguments.class)
public void testSelectionQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) {
scope.inTransaction( session -> {
assertThat( session.createQuery(
"select 1",
Object.class
).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
assertThat( session.createQuery(
"select cast(1 as integer)",
Object.class
).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
assertThat( session.createSelectionQuery(
"select id from BasicEntity",
Object.class
).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
assertThat( session.createSelectionQuery(
"select cast(id as integer) from BasicEntity",
Object.class
).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
assertThat( session.createSelectionQuery(
"select ?1",
Object.class
).setParameter( 1, 1 ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
assertThat( session.createQuery(
"select cast(:p1 as integer)",
Object.class
).setParameter( "p1", 1 ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 );
SelectionQuery<Object[]> query = session.createSelectionQuery( ql, Object[].class );
if ( arg1 instanceof Integer ) {
query.setParameter( (Integer) arg1, arg2 );
}
if ( arg1 instanceof String ) {
query.setParameter( (String) arg1, arg2 );
}
assertThat( query.getSingleResult() ).containsExactly( 1 );
} );
}

Expand Down