diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc index 77b4a9fe94..114bb7202a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -2173,7 +2173,8 @@ it is necessary to enable the `hibernate.query.hql.xml_functions_enabled` config | `xmlelement()` | Constructs an XML element from arguments | `xmlcomment()` | Constructs an XML comment from the single argument -| `xmlforest()` | Constructs an XML forest from thearguments +| `xmlforest()` | Constructs an XML forest from the arguments +| `xmlconcat()` | Concatenates multiple XML fragments to each other |=== @@ -2250,6 +2251,21 @@ include::{xml-example-dir-hql}/XmlForestTest.java[tags=hql-xmlforest-example] WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function. +[[hql-xmlconcat-function]] +===== `xmlconcat()` + +Concatenates multiple XML fragments to each other. + +[[hql-xmlconcat-example]] +==== +[source, java, indent=0] +---- +include::{xml-example-dir-hql}/XmlConcatTest.java[tags=hql-xmlconcat-example] +---- +==== + +WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function. + [[hql-user-defined-functions]] ==== Native and user-defined functions diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java index 5ae1aa9be7..3f0682fe48 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java @@ -444,6 +444,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement(); functionFactory.xmlcomment(); functionFactory.xmlforest(); + functionFactory.xmlconcat(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java index 6bf19eccfe..802c70ee4e 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java @@ -421,6 +421,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement_h2(); functionFactory.xmlcomment(); functionFactory.xmlforest_h2(); + functionFactory.xmlconcat_h2(); } else { functionFactory.listagg_groupConcat(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index b3dfabf6b6..5548a181b1 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -329,6 +329,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement(); functionFactory.xmlcomment(); functionFactory.xmlforest(); + functionFactory.xmlconcat(); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java index 23528fb9a0..0ad05d3797 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -672,6 +672,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement(); functionFactory.xmlcomment(); functionFactory.xmlforest(); + functionFactory.xmlconcat(); if ( getVersion().isSameOrAfter( 9, 4 ) ) { functionFactory.makeDateTimeTimestamp(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java index f2743c65d7..5f1860f697 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java @@ -416,6 +416,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement_sqlserver(); functionFactory.xmlcomment_sqlserver(); functionFactory.xmlforest_sqlserver(); + functionFactory.xmlconcat_sqlserver(); if ( getVersion().isSameOrAfter( 14 ) ) { functionFactory.listagg_stringAggWithinGroup( "varchar(max)" ); functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 4d574c3884..edca46277d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -429,6 +429,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement(); functionFactory.xmlcomment(); functionFactory.xmlforest(); + functionFactory.xmlconcat(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 618490e21b..6d75be3004 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -356,6 +356,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement_h2(); functionFactory.xmlcomment(); functionFactory.xmlforest_h2(); + functionFactory.xmlconcat_h2(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 09af5eac87..cce90efdef 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -419,6 +419,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement(); functionFactory.xmlcomment(); functionFactory.xmlforest(); + functionFactory.xmlconcat(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 1295a8dede..8c05e1ef58 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -633,6 +633,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement(); functionFactory.xmlcomment(); functionFactory.xmlforest(); + functionFactory.xmlconcat(); functionFactory.makeDateTimeTimestamp(); // Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 6553340317..62b331b8d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -434,6 +434,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.xmlelement_sqlserver(); functionFactory.xmlcomment_sqlserver(); functionFactory.xmlforest_sqlserver(); + functionFactory.xmlconcat_sqlserver(); if ( getVersion().isSameOrAfter( 14 ) ) { functionFactory.listagg_stringAggWithinGroup( "varchar(max)" ); functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java index 1e9f382735..0178f5839b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java @@ -153,10 +153,13 @@ import org.hibernate.dialect.function.json.SQLServerJsonReplaceFunction; import org.hibernate.dialect.function.json.SQLServerJsonSetFunction; import org.hibernate.dialect.function.json.SQLServerJsonValueFunction; +import org.hibernate.dialect.function.xml.H2XmlConcatFunction; import org.hibernate.dialect.function.xml.H2XmlElementFunction; import org.hibernate.dialect.function.xml.H2XmlForestFunction; +import org.hibernate.dialect.function.xml.SQLServerXmlConcatFunction; import org.hibernate.dialect.function.xml.SQLServerXmlElementFunction; import org.hibernate.dialect.function.xml.SQLServerXmlForestFunction; +import org.hibernate.dialect.function.xml.XmlConcatFunction; import org.hibernate.dialect.function.xml.XmlElementFunction; import org.hibernate.dialect.function.xml.XmlForestFunction; import org.hibernate.query.sqm.function.SqmFunctionRegistry; @@ -4167,4 +4170,25 @@ public void xmlforest_h2() { public void xmlforest_sqlserver() { functionRegistry.register( "xmlforest", new SQLServerXmlForestFunction( typeConfiguration ) ); } + + /** + * Standard xmlconcat() function + */ + public void xmlconcat() { + functionRegistry.register( "xmlconcat", new XmlConcatFunction( typeConfiguration ) ); + } + + /** + * H2 xmlconcat() function + */ + public void xmlconcat_h2() { + functionRegistry.register( "xmlconcat", new H2XmlConcatFunction( typeConfiguration ) ); + } + + /** + * SQL Server xmlconcat() function + */ + public void xmlconcat_sqlserver() { + functionRegistry.register( "xmlconcat", new SQLServerXmlConcatFunction( typeConfiguration ) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/H2XmlConcatFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/H2XmlConcatFunction.java new file mode 100644 index 0000000000..e742830936 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/H2XmlConcatFunction.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.xml; + +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * H2 xmlconcat function. + */ +public class H2XmlConcatFunction extends XmlConcatFunction { + + public H2XmlConcatFunction(TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + String separator = ""; + sqlAppender.appendSql( '(' ); + for ( SqlAstNode sqlAstArgument : sqlAstArguments ) { + sqlAppender.appendSql( separator ); + sqlAstArgument.accept( walker ); + separator = "||"; + } + sqlAppender.appendSql( ')' ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlConcatFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlConcatFunction.java new file mode 100644 index 0000000000..150a7aaa59 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlConcatFunction.java @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.xml; + +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * SQL Server xmlconcat function. + */ +public class SQLServerXmlConcatFunction extends XmlConcatFunction { + + public SQLServerXmlConcatFunction(TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.appendSql( "cast" ); + char separator = '('; + for ( SqlAstNode sqlAstArgument : sqlAstArguments ) { + sqlAppender.appendSql( separator ); + sqlAppender.appendSql( "cast(" ); + sqlAstArgument.accept( walker ); + sqlAppender.appendSql( " as nvarchar(max))" ); + separator = '+'; + } + sqlAppender.appendSql( " as xml)" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlConcatFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlConcatFunction.java new file mode 100644 index 0000000000..0422b9c75e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlConcatFunction.java @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect.function.xml; + +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.function.FunctionKind; +import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.spi.TypeConfiguration; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.XML; + +/** + * Standard XmlConcatFunction function. + */ +public class XmlConcatFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public XmlConcatFunction(TypeConfiguration typeConfiguration) { + super( + "xmlconcat", + FunctionKind.NORMAL, + StandardArgumentsValidators.composite( + new ArgumentTypesValidator( StandardArgumentsValidators.min( 1 ), XML ) + ), + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML ) + ), + StandardFunctionArgumentTypeResolvers.impliedOrInvariant( typeConfiguration, XML ) + ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.appendSql( "xmlconcat" ); + char separator = '('; + for ( SqlAstNode sqlAstArgument : sqlAstArguments ) { + sqlAppender.appendSql( separator ); + sqlAstArgument.accept( walker ); + separator = ','; + } + sqlAppender.appendSql( ')' ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java index 153cb0cf0a..c26078970f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java @@ -4067,6 +4067,7 @@ default JpaPredicate collectionOverlapsNullable(Collection collection1, E * Creates an XML forest from the given XML element expressions. * * @since 7.0 + * @see #named(Expression, String) */ @Incubating JpaExpression xmlforest(Expression... elements); @@ -4075,10 +4076,27 @@ default JpaPredicate collectionOverlapsNullable(Collection collection1, E * Creates an XML forest from the given XML element expressions. * * @since 7.0 + * @see #named(Expression, String) */ @Incubating JpaExpression xmlforest(List> elements); + /** + * Concatenates the given XML element expressions. + * + * @since 7.0 + */ + @Incubating + JpaExpression xmlconcat(Expression... elements); + + /** + * Concatenates the given XML element expressions. + * + * @since 7.0 + */ + @Incubating + JpaExpression xmlconcat(List> elements); + /** * Creates a named expression. The name is important for the result of the expression, * e.g. when building an {@code xmlforest}, the name acts as the XML element name. diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java index 04b7b96967..93ade3e839 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java @@ -3657,6 +3657,18 @@ public JpaExpression xmlforest(List> elements) { return criteriaBuilder.xmlforest( elements ); } + @Override + @Incubating + public JpaExpression xmlconcat(Expression... elements) { + return criteriaBuilder.xmlconcat( elements ); + } + + @Override + @Incubating + public JpaExpression xmlconcat(List> elements) { + return criteriaBuilder.xmlconcat( elements ); + } + @Override @Incubating public JpaExpression named(Expression expression, String name) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java index f924ed5cef..23ee727317 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java @@ -765,6 +765,12 @@ SqmJsonValueExpression jsonValue( @Override SqmExpression xmlforest(Expression... elements); + @Override + SqmExpression xmlconcat(Expression... elements); + + @Override + SqmExpression xmlconcat(List> elements); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Covariant overrides diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index 48e39163ca..eb701b2b54 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -5732,4 +5732,18 @@ public SqmExpression xmlforest(List> elements) { queryEngine ); } + + @Override + public SqmExpression xmlconcat(Expression... elements) { + return xmlconcat( Arrays.asList( elements ) ); + } + + @Override + public SqmExpression xmlconcat(List> elements) { + return getFunctionDescriptor( "xmlforest" ).generateSqmExpression( + (List>) elements, + null, + queryEngine + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java index ecf85aaf50..05cb05fdb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java @@ -251,6 +251,8 @@ private static boolean isCompatible(FunctionParameterType type, JdbcType jdbcTyp case SPATIAL -> jdbcType.isSpatial(); case JSON -> jdbcType.isJson(); case IMPLICIT_JSON -> jdbcType.isImplicitJson(); + case XML -> jdbcType.isXml(); + case IMPLICIT_XML -> jdbcType.isImplicitXml(); default -> true; // TODO: should we throw here? }; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/FunctionParameterType.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/FunctionParameterType.java index 750e719343..d2ba1aa06c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/FunctionParameterType.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/FunctionParameterType.java @@ -98,6 +98,18 @@ public enum FunctionParameterType { * @since 7.0 */ IMPLICIT_JSON, + /** + * Indicates that the argument should be a XML type + * @see org.hibernate.type.SqlTypes#isXmlType(int) + * @since 7.0 + */ + XML, + /** + * Indicates that the argument should be a XML or String type + * @see org.hibernate.type.SqlTypes#isImplicitXmlType(int) + * @since 7.0 + */ + IMPLICIT_XML, /** * Indicates a parameter that accepts any type, except untyped expressions like {@code null} literals */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java b/hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java index d3b3ab441e..d033705c9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java @@ -1021,4 +1021,36 @@ public static boolean isImplicitJsonType(int typeCode) { return isCharacterOrClobType( typeCode ); } } + + /** + * Does the typecode represent a XML type. + * + * @param typeCode - a JDBC type code + * @since 7.0 + */ + public static boolean isXmlType(int typeCode) { + switch ( typeCode ) { + case SQLXML: + case XML_ARRAY: + return true; + default: + return false; + } + } + + /** + * Does the typecode represent an XML type or a type that can be implicitly cast to XML. + * + * @param typeCode - a JDBC type code + * @since 7.0 + */ + public static boolean isImplicitXmlType(int typeCode) { + switch ( typeCode ) { + case SQLXML: + case XML_ARRAY: + return true; + default: + return isCharacterOrClobType( typeCode ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java index 2206c40406..bff937c059 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java @@ -435,6 +435,16 @@ default boolean isImplicitJson() { return isImplicitJsonType( getDefaultSqlTypeCode() ); } + @Incubating + default boolean isXml() { + return isXmlType( getDefaultSqlTypeCode() ); + } + + @Incubating + default boolean isImplicitXml() { + return isImplicitXmlType( getDefaultSqlTypeCode() ); + } + @Incubating default boolean isBoolean() { return getDefaultSqlTypeCode() == BOOLEAN; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlConcatTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlConcatTest.java new file mode 100644 index 0000000000..c0d0d9790c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlConcatTest.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.function.xml; + +import org.hibernate.cfg.QuerySettings; + +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +/** + * @author Christian Beikov + */ +@DomainModel +@SessionFactory +@ServiceRegistry(settings = @Setting(name = QuerySettings.XML_FUNCTIONS_ENABLED, value = "true")) +@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsXmlconcat.class) +public class XmlConcatTest { + + @Test + public void testSimple(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-xmlconcat-example[] + em.createQuery( "select xmlconcat(xmlelement(name e1, 123), xmlelement(name e2, 'text'))" ).getResultList(); + //end::hql-xmlconcat-example[] + } ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/XmlFunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/XmlFunctionTests.java index 9925e1578f..eb92ceb5c9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/XmlFunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/XmlFunctionTests.java @@ -170,6 +170,23 @@ public void testXmlforest(SessionFactoryScope scope) { ); } + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlconcat.class) + public void testXmlconcat(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Tuple tuple = session.createQuery( + "select xmlconcat(xmlelement(name e1, 123), xmlelement(name e2, 'text'))," + + "xmlconcat(xmlelement(name id, e.id), xmlelement(name theString, e.theString)) " + + "from EntityOfBasics e where e.id = 1", + Tuple.class + ).getSingleResult(); + assertXmlEquals( "123text", "" + tuple.get( 0, String.class ) + "" ); + assertXmlEquals( "1Dog", "" + tuple.get( 1, String.class ) + "" ); + } + ); + } + private void assertXmlEquals(String expected, String actual) { final Document expectedDoc = parseXml( xmlNormalize( expected ) ); final Document actualDoc = parseXml( xmlNormalize( actual ) ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java index c0ece52c26..8933007388 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java @@ -856,6 +856,12 @@ public boolean apply(Dialect dialect) { } } + public static class SupportsXmlconcat implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return definesFunction( dialect, "xmlconcat" ); + } + } + public static class IsJtds implements DialectFeatureCheck { public boolean apply(Dialect dialect) { return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;