HHH-18765 - additional fix needed for the generic array_to_string function
Signed-off-by: Jan Schatteman <jschatte@redhat.com>
This commit is contained in:
parent
7e1a740605
commit
b9274f5b75
|
@ -16,6 +16,9 @@ import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolv
|
|||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.type.BasicPluralType;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
|
@ -32,8 +35,7 @@ public class ArrayToStringFunction extends AbstractSqmSelfRenderingFunctionDescr
|
|||
"array_to_string",
|
||||
FunctionKind.NORMAL,
|
||||
StandardArgumentsValidators.composite(
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 3 ), ANY, STRING, ANY ),
|
||||
new ArrayAndElementArgumentValidator( 0, 2 )
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.between( 2, 3 ), ANY, STRING, ANY )
|
||||
),
|
||||
StandardFunctionReturnTypeResolvers.invariant(
|
||||
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
|
||||
|
@ -51,15 +53,42 @@ public class ArrayToStringFunction extends AbstractSqmSelfRenderingFunctionDescr
|
|||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
ReturnableType<?> returnType,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( "array_to_string(" );
|
||||
sqlAstArguments.get( 0 ).accept( walker );
|
||||
sqlAppender.appendSql( ',' );
|
||||
sqlAstArguments.get( 1 ).accept( walker );
|
||||
if ( sqlAstArguments.size() > 2 ) {
|
||||
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
|
||||
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
|
||||
final Expression defaultExpression = sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null;
|
||||
final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) arrayExpression.getExpressionType().getSingleJdbcMapping();
|
||||
final int ddlTypeCode = pluralType.getElementType().getJdbcType().getDdlTypeCode();
|
||||
if ( ddlTypeCode == SqlTypes.BOOLEAN ) {
|
||||
// For some reason, PostgreSQL turns true/false to t/f in this function, so unnest this manually
|
||||
sqlAppender.append( "case when " );
|
||||
arrayExpression.accept( walker );
|
||||
sqlAppender.append( " is not null then coalesce((select string_agg(" );
|
||||
if ( defaultExpression != null ) {
|
||||
sqlAppender.append( "coalesce(" );
|
||||
}
|
||||
sqlAppender.append( "cast(t.v as varchar)" );
|
||||
if ( defaultExpression != null ) {
|
||||
sqlAppender.append( "," );
|
||||
defaultExpression.accept( walker );
|
||||
sqlAppender.append( ")" );
|
||||
}
|
||||
sqlAppender.appendSql( ',' );
|
||||
sqlAstArguments.get( 2 ).accept( walker );
|
||||
separatorExpression.accept( walker );
|
||||
sqlAppender.append( " order by t.i) from unnest(");
|
||||
arrayExpression.accept( walker );
|
||||
sqlAppender.append(") with ordinality t(v,i)),'') end" );
|
||||
}
|
||||
else {
|
||||
sqlAppender.appendSql( "array_to_string(" );
|
||||
arrayExpression.accept( walker );
|
||||
sqlAppender.appendSql( ',' );
|
||||
separatorExpression.accept( walker );
|
||||
if ( defaultExpression != null ) {
|
||||
sqlAppender.appendSql( ',' );
|
||||
defaultExpression.accept( walker );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
sqlAppender.appendSql( ')' );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import org.hibernate.sql.ast.SqlAstTranslator;
|
|||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.type.BasicPluralType;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
|
@ -36,19 +38,35 @@ public class H2ArrayToStringFunction extends ArrayToStringFunction {
|
|||
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
|
||||
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
|
||||
final Expression defaultExpression = sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null;
|
||||
final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) arrayExpression.getExpressionType().getSingleJdbcMapping();
|
||||
final int ddlTypeCode = pluralType.getElementType().getJdbcType().getDdlTypeCode();
|
||||
final boolean needsCast = !SqlTypes.isStringType( ddlTypeCode );
|
||||
sqlAppender.append( "case when " );
|
||||
arrayExpression.accept( walker );
|
||||
sqlAppender.append( " is not null then coalesce((select listagg(" );
|
||||
if ( defaultExpression != null ) {
|
||||
sqlAppender.append( "coalesce(" );
|
||||
}
|
||||
if ( needsCast ) {
|
||||
if ( ddlTypeCode == SqlTypes.BOOLEAN ) {
|
||||
// By default, H2 uses upper case, so lower it for a consistent experience
|
||||
sqlAppender.append( "lower(" );
|
||||
}
|
||||
sqlAppender.append( "cast(" );
|
||||
}
|
||||
sqlAppender.append( "array_get(" );
|
||||
arrayExpression.accept( walker );
|
||||
sqlAppender.append(",i.idx)" );
|
||||
if ( needsCast ) {
|
||||
sqlAppender.append( " as varchar)" );
|
||||
if ( ddlTypeCode == SqlTypes.BOOLEAN ) {
|
||||
sqlAppender.append( ')' );
|
||||
}
|
||||
}
|
||||
if ( defaultExpression != null ) {
|
||||
sqlAppender.append( "," );
|
||||
sqlAppender.append( ',' );
|
||||
defaultExpression.accept( walker );
|
||||
sqlAppender.append( ")" );
|
||||
sqlAppender.append( ')' );
|
||||
}
|
||||
sqlAppender.append("," );
|
||||
separatorExpression.accept( walker );
|
||||
|
|
|
@ -12,6 +12,8 @@ import org.hibernate.sql.ast.SqlAstTranslator;
|
|||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.type.BasicPluralType;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
/**
|
||||
|
@ -32,13 +34,29 @@ public class HSQLArrayToStringFunction extends ArrayToStringFunction {
|
|||
final Expression arrayExpression = (Expression) sqlAstArguments.get( 0 );
|
||||
final Expression separatorExpression = (Expression) sqlAstArguments.get( 1 );
|
||||
final Expression defaultExpression = sqlAstArguments.size() > 2 ? (Expression) sqlAstArguments.get( 2 ) : null;
|
||||
final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) arrayExpression.getExpressionType().getSingleJdbcMapping();
|
||||
final int ddlTypeCode = pluralType.getElementType().getJdbcType().getDdlTypeCode();
|
||||
final boolean needsCast = !SqlTypes.isStringType( ddlTypeCode );
|
||||
sqlAppender.append( "case when " );
|
||||
arrayExpression.accept( walker );
|
||||
sqlAppender.append( " is not null then coalesce((select group_concat(" );
|
||||
if ( defaultExpression != null ) {
|
||||
sqlAppender.append( "coalesce(" );
|
||||
}
|
||||
if ( needsCast ) {
|
||||
if ( ddlTypeCode == SqlTypes.BOOLEAN ) {
|
||||
// By default, HSQLDB uses upper case, so lower it for a consistent experience
|
||||
sqlAppender.append( "lower(" );
|
||||
}
|
||||
sqlAppender.append( "cast(" );
|
||||
}
|
||||
sqlAppender.append( "t.val" );
|
||||
if ( needsCast ) {
|
||||
sqlAppender.append( " as longvarchar)" );
|
||||
if ( ddlTypeCode == SqlTypes.BOOLEAN ) {
|
||||
sqlAppender.append( ')' );
|
||||
}
|
||||
}
|
||||
if ( defaultExpression != null ) {
|
||||
sqlAppender.append( "," );
|
||||
defaultExpression.accept( walker );
|
||||
|
|
|
@ -4,18 +4,12 @@
|
|||
*/
|
||||
package org.hibernate.query.results.internal;
|
||||
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
|
||||
import org.hibernate.spi.EntityIdentifierNavigablePath;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.results.graph.DomainResult;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.basic.BasicFetch;
|
||||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
|
||||
|
||||
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
|
||||
|
|
|
@ -13,9 +13,7 @@ import org.hibernate.query.results.internal.DomainResultCreationStateImpl;
|
|||
import org.hibernate.query.results.FetchBuilder;
|
||||
import org.hibernate.query.results.internal.ResultsHelper;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
|
|
|
@ -13,9 +13,7 @@ import org.hibernate.query.results.internal.ResultsHelper;
|
|||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.query.results.internal.DomainResultCreationStateImpl;
|
||||
import org.hibernate.query.results.FetchBuilder;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
|
|
|
@ -10,7 +10,6 @@ import org.hibernate.query.results.internal.DomainResultCreationStateImpl;
|
|||
import org.hibernate.query.results.internal.ResultsHelper;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.basic.BasicResult;
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.hibernate.query.results.internal.complete;
|
|||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.BasicValuedMapping;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.query.results.ResultBuilder;
|
||||
import org.hibernate.query.results.internal.DomainResultCreationStateImpl;
|
||||
import org.hibernate.query.results.internal.ResultSetMappingSqlSelection;
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
package org.hibernate.query.results.internal.complete;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
|
|
|
@ -14,7 +14,6 @@ import org.hibernate.persister.entity.EntityPersister;
|
|||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
import org.hibernate.sql.results.graph.AssemblerCreationState;
|
||||
import org.hibernate.sql.results.graph.DomainResultAssembler;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.Fetchable;
|
||||
|
|
|
@ -20,7 +20,6 @@ import org.hibernate.query.results.internal.DomainResultCreationStateImpl;
|
|||
import org.hibernate.query.results.internal.ResultsHelper;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBase;
|
||||
import org.hibernate.sql.ast.spi.SqlAliasBaseConstant;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||
|
|
|
@ -9,7 +9,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
|||
import org.hibernate.metamodel.mapping.BasicValuedMapping;
|
||||
import org.hibernate.query.results.internal.ResultSetMappingSqlSelection;
|
||||
import org.hibernate.query.results.internal.ResultsHelper;
|
||||
import org.hibernate.resource.beans.spi.ManagedBean;
|
||||
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
|
||||
import org.hibernate.resource.beans.spi.ProvidedInstanceManagedBeanImpl;
|
||||
import org.hibernate.sql.ast.spi.SqlAstCreationState;
|
||||
|
@ -21,9 +20,7 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
|
|||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.descriptor.converter.internal.JpaAttributeConverterImpl;
|
||||
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import org.hibernate.query.results.internal.DomainResultCreationStateImpl;
|
|||
import org.hibernate.query.results.internal.ResultsHelper;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.results.graph.DomainResultCreationState;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
|
@ -20,7 +19,6 @@ import org.hibernate.sql.results.graph.basic.BasicFetch;
|
|||
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.hibernate.query.results.internal.ResultsHelper.impl;
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.hibernate.query.results.internal.implicit;
|
|||
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||
import org.hibernate.query.results.ResultBuilder;
|
||||
import org.hibernate.query.results.ResultBuilderBasicValued;
|
||||
import org.hibernate.query.results.internal.DomainResultCreationStateImpl;
|
||||
import org.hibernate.query.results.internal.ResultsHelper;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
|
|
|
@ -6,6 +6,7 @@ package org.hibernate.orm.test.function.array;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.dialect.HSQLDialect;
|
||||
import org.hibernate.query.criteria.JpaCriteriaQuery;
|
||||
import org.hibernate.query.criteria.JpaRoot;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
|
@ -17,6 +18,7 @@ import org.hibernate.testing.orm.junit.DomainModel;
|
|||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -126,6 +128,8 @@ public class ArrayToStringTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SkipForDialect( dialectClass = HSQLDialect.class, majorVersion = 2, minorVersion = 7, microVersion = 2,
|
||||
reason = "Needs at least 2.7.3 due to the change in HSQLArrayToStringFunction that introduced a cast")
|
||||
public void testStr(SessionFactoryScope scope) {
|
||||
scope.inSession( em -> {
|
||||
List<String> results = em.createQuery( "select str(e.theArray) from EntityWithArrays e order by e.id", String.class )
|
||||
|
|
|
@ -14,9 +14,12 @@ import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
|||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Jan Schatteman
|
||||
*/
|
||||
|
@ -24,29 +27,55 @@ import org.junit.jupiter.api.Test;
|
|||
annotatedClasses = {BooleanArrayToStringTest.TestEntity.class}
|
||||
)
|
||||
@SessionFactory
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTypedArrays.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsArrayToString.class)
|
||||
@SkipForDialect( dialectClass = OracleDialect.class, reason = "External driver fix required")
|
||||
@Jira( value = "https://hibernate.atlassian.net/browse/HHH-18765" )
|
||||
public class BooleanArrayToStringTest {
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTypedArrays.class)
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsArrayToString.class)
|
||||
@SkipForDialect( dialectClass = OracleDialect.class, reason = "External driver fix required")
|
||||
@Jira( value = "https://hibernate.atlassian.net/browse/HHH-18765" )
|
||||
public void testBooleanArrayToStringFunction(SessionFactoryScope scope) {
|
||||
@BeforeEach
|
||||
public void setup(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> session.persist( new TestEntity(1L, new Boolean[]{Boolean.FALSE, Boolean.FALSE, null, Boolean.TRUE}) )
|
||||
);
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
String s = session.createQuery( "select array_to_string(t.theBoolean, ';', 'null') "
|
||||
+ "from TestEntity t", String.class ).getSingleResult();
|
||||
Assertions.assertEquals("false;false;null;true", s);
|
||||
}
|
||||
session -> session.persist(
|
||||
new TestEntity( 1L, new Boolean[] {Boolean.FALSE, Boolean.FALSE, null, Boolean.TRUE} ) )
|
||||
);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> session.createMutationQuery( "delete from TestEntity" ).executeUpdate()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanArrayToStringWithDefault(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
final String actual = session.createQuery(
|
||||
"select array_to_string(t.theBoolean, ';', 'null') from TestEntity t",
|
||||
String.class
|
||||
)
|
||||
.getSingleResult();
|
||||
assertEquals("false;false;null;true", actual);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBooleanArrayToStringWithoutDefault(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
final String actual = session.createQuery(
|
||||
"select array_to_string(t.theBoolean, ';') from TestEntity t",
|
||||
String.class
|
||||
)
|
||||
.getSingleResult();
|
||||
assertEquals("false;false;true", actual);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Entity(name = "TestEntity")
|
||||
public static class TestEntity {
|
||||
@Id
|
||||
|
|
Loading…
Reference in New Issue