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 114bb7202a..42fbdb2d58 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -2175,6 +2175,7 @@ it is necessary to enable the `hibernate.query.hql.xml_functions_enabled` config | `xmlcomment()` | Constructs an XML comment from the single argument | `xmlforest()` | Constructs an XML forest from the arguments | `xmlconcat()` | Concatenates multiple XML fragments to each other +| `xmlpi()` | Constructs an XML processing instruction |=== @@ -2266,6 +2267,39 @@ include::{xml-example-dir-hql}/XmlConcatTest.java[tags=hql-xmlconcat-example] WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function. +[[hql-xmlpi-function]] +===== `xmlpi()` + +Constructs an XML processing instruction from the arguments. + +[[hql-xmlpi-bnf]] +[source, antlrv4, indent=0] +---- +include::{extrasdir}/xmlpi_bnf.txt[] +---- + +The identifier represents the XML processing instruction name and can be quoted by using backticks. + +[[hql-xmlpi-example]] +==== +[source, java, indent=0] +---- +include::{xml-example-dir-hql}/XmlPiTest.java[tags=hql-xmlpi-example] +---- +==== + +The optional second argument represents the processing instruction content. + +[[hql-xmlpi-content-example]] +==== +[source, java, indent=0] +---- +include::{xml-example-dir-hql}/XmlPiTest.java[tags=hql-xmlpi-content-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/documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/xmlpi_bnf.txt b/documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/xmlpi_bnf.txt new file mode 100644 index 0000000000..50f84ab20c --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/xmlpi_bnf.txt @@ -0,0 +1 @@ +"xmlpi(name " identifier ("," expressionOrPredicate)? ")" \ No newline at end of file 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 3f0682fe48..55a54dfeab 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 @@ -445,6 +445,7 @@ public class DB2LegacyDialect extends Dialect { functionFactory.xmlcomment(); functionFactory.xmlforest(); functionFactory.xmlconcat(); + functionFactory.xmlpi(); } @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 802c70ee4e..e00ee7e4d7 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 @@ -422,6 +422,7 @@ public class H2LegacyDialect extends Dialect { functionFactory.xmlcomment(); functionFactory.xmlforest_h2(); functionFactory.xmlconcat_h2(); + functionFactory.xmlpi_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 5548a181b1..2db39bf4e1 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 @@ -330,6 +330,7 @@ public class OracleLegacyDialect extends Dialect { functionFactory.xmlcomment(); functionFactory.xmlforest(); functionFactory.xmlconcat(); + functionFactory.xmlpi(); } @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 0ad05d3797..ac80b6e45f 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 @@ -673,6 +673,7 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.xmlcomment(); functionFactory.xmlforest(); functionFactory.xmlconcat(); + functionFactory.xmlpi(); 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 5f1860f697..52c0573f84 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 @@ -417,6 +417,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { functionFactory.xmlcomment_sqlserver(); functionFactory.xmlforest_sqlserver(); functionFactory.xmlconcat_sqlserver(); + functionFactory.xmlpi_sqlserver(); if ( getVersion().isSameOrAfter( 14 ) ) { functionFactory.listagg_stringAggWithinGroup( "varchar(max)" ); functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) ); diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index 281e1cb0e9..4f20f9bd7f 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -333,6 +333,7 @@ WRAPPER : [wW] [rR] [aA] [pP] [pP] [eE] [rR]; XMLATTRIBUTES : [xX] [mM] [lL] [aA] [tT] [tT] [rR] [iI] [bB] [uU] [tT] [eE] [sS]; XMLELEMENT : [xX] [mM] [lL] [eE] [lL] [eE] [mM] [eE] [nN] [tT]; XMLFOREST : [xX] [mM] [lL] [fF] [oO] [rR] [eE] [sS] [tT]; +XMLPI : [xX] [mM] [lL] [pP] [iI]; YEAR : [yY] [eE] [aA] [rR]; ZONED : [zZ] [oO] [nN] [eE] [dD]; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index a96b4d5500..58b91808e2 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -1720,6 +1720,7 @@ jsonUniqueKeysClause xmlFunction : xmlelementFunction | xmlforestFunction + | xmlpiFunction ; /** @@ -1743,6 +1744,13 @@ xmlforestFunction : XMLFOREST LEFT_PAREN expressionOrPredicate (AS identifier)? (COMMA expressionOrPredicate (AS identifier)?)* RIGHT_PAREN ; +/** + * The 'xmlpi()' function + */ +xmlpiFunction + : XMLPI LEFT_PAREN NAME identifier (COMMA expression)? RIGHT_PAREN + ; + /** * Support for "soft" keywords which may be used as identifiers * @@ -1952,6 +1960,7 @@ xmlforestFunction | XMLATTRIBUTES | XMLELEMENT | XMLFOREST + | XMLPI | YEAR | ZONED) { logUseOfReservedWordAsIdentifier( getCurrentToken() ); 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 edca46277d..f4c0e3913d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -430,6 +430,7 @@ public class DB2Dialect extends Dialect { functionFactory.xmlcomment(); functionFactory.xmlforest(); functionFactory.xmlconcat(); + functionFactory.xmlpi(); } @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 6d75be3004..cf7ffe2eb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -357,6 +357,7 @@ public class H2Dialect extends Dialect { functionFactory.xmlcomment(); functionFactory.xmlforest_h2(); functionFactory.xmlconcat_h2(); + functionFactory.xmlpi_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 cce90efdef..728354baf8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -420,6 +420,7 @@ public class OracleDialect extends Dialect { functionFactory.xmlcomment(); functionFactory.xmlforest(); functionFactory.xmlconcat(); + functionFactory.xmlpi(); } @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 8c05e1ef58..e06479deb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -634,6 +634,7 @@ public class PostgreSQLDialect extends Dialect { functionFactory.xmlcomment(); functionFactory.xmlforest(); functionFactory.xmlconcat(); + functionFactory.xmlpi(); 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 62b331b8d7..3736812daa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -435,6 +435,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { functionFactory.xmlcomment_sqlserver(); functionFactory.xmlforest_sqlserver(); functionFactory.xmlconcat_sqlserver(); + functionFactory.xmlpi_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 0178f5839b..0de5193454 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 @@ -156,12 +156,15 @@ 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.H2XmlPiFunction; 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.SQLServerXmlPiFunction; import org.hibernate.dialect.function.xml.XmlConcatFunction; import org.hibernate.dialect.function.xml.XmlElementFunction; import org.hibernate.dialect.function.xml.XmlForestFunction; +import org.hibernate.dialect.function.xml.XmlPiFunction; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator; import org.hibernate.query.sqm.produce.function.FunctionParameterType; @@ -4191,4 +4194,25 @@ public class CommonFunctionFactory { public void xmlconcat_sqlserver() { functionRegistry.register( "xmlconcat", new SQLServerXmlConcatFunction( typeConfiguration ) ); } + + /** + * Standard xmlpi() function + */ + public void xmlpi() { + functionRegistry.register( "xmlpi", new XmlPiFunction( typeConfiguration ) ); + } + + /** + * H2 xmlpi() function + */ + public void xmlpi_h2() { + functionRegistry.register( "xmlpi", new H2XmlPiFunction( typeConfiguration ) ); + } + + /** + * SQL Server xmlpi() function + */ + public void xmlpi_sqlserver() { + functionRegistry.register( "xmlpi", new SQLServerXmlPiFunction( typeConfiguration ) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/H2XmlPiFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/H2XmlPiFunction.java new file mode 100644 index 0000000000..34250555fc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/H2XmlPiFunction.java @@ -0,0 +1,44 @@ +/* + * 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.sql.ast.tree.expression.Literal; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * H2 xmlpi function. + */ +public class H2XmlPiFunction extends XmlPiFunction { + + public H2XmlPiFunction(TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.appendSql( "' 1 ) { + sqlAppender.appendSql( " '||" ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.appendSql( "||'?>'" ); + } + else { + sqlAppender.appendSql( "?>'" ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlPiFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlPiFunction.java new file mode 100644 index 0000000000..e56e705849 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlPiFunction.java @@ -0,0 +1,45 @@ +/* + * 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.sql.ast.tree.expression.Literal; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * SQL Server xmlpi function. + */ +public class SQLServerXmlPiFunction extends XmlPiFunction { + + public SQLServerXmlPiFunction(TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.appendSql( "cast(' 1 ) { + sqlAppender.appendSql( " '+" ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.appendSql( "+'?>'" ); + } + else { + sqlAppender.appendSql( "?>'" ); + } + 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 index 0422b9c75e..2e0ef74b83 100644 --- 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 @@ -22,7 +22,7 @@ import org.hibernate.type.spi.TypeConfiguration; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.XML; /** - * Standard XmlConcatFunction function. + * Standard xmlconcat function. */ public class XmlConcatFunction extends AbstractSqmSelfRenderingFunctionDescriptor { @@ -31,7 +31,7 @@ public class XmlConcatFunction extends AbstractSqmSelfRenderingFunctionDescripto "xmlconcat", FunctionKind.NORMAL, StandardArgumentsValidators.composite( - new ArgumentTypesValidator( StandardArgumentsValidators.min( 1 ), XML ) + new ArgumentTypesValidator( StandardArgumentsValidators.min( 2 ), XML ) ), StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML ) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlForestFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlForestFunction.java index 35dcbc38f8..7a081c5330 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlForestFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlForestFunction.java @@ -26,7 +26,7 @@ import static java.lang.Character.isLetter; import static java.lang.Character.isLetterOrDigit; /** - * Standard XmlForestFunction function. + * Standard xmlforest function. */ public class XmlForestFunction extends AbstractSqmSelfRenderingFunctionDescriptor { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlPiFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlPiFunction.java new file mode 100644 index 0000000000..813c1a8cac --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlPiFunction.java @@ -0,0 +1,60 @@ +/* + * 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.sql.ast.tree.expression.Literal; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.spi.TypeConfiguration; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.ANY; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING; + +/** + * Standard xmlpi function. + */ +public class XmlPiFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public XmlPiFunction(TypeConfiguration typeConfiguration) { + super( + "xmlpi", + FunctionKind.NORMAL, + StandardArgumentsValidators.composite( + new ArgumentTypesValidator( StandardArgumentsValidators.between( 1, 2 ), STRING, STRING ) + ), + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML ) + ), + StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, ANY, STRING ) + ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + sqlAppender.appendSql( "xmlpi(name " ); + final Literal literal = (Literal) sqlAstArguments.get( 0 ); + sqlAppender.appendDoubleQuoteEscapedString( (String) literal.getLiteralValue() ); + if ( sqlAstArguments.size() > 1 ) { + sqlAppender.appendSql( ',' ); + sqlAstArguments.get( 1 ).accept( walker ); + } + 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 c26078970f..e4746c6715 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 @@ -4097,6 +4097,22 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder { @Incubating JpaExpression xmlconcat(List> elements); + /** + * Creates an XML processing with the given name. + * + * @since 7.0 + */ + @Incubating + JpaExpression xmlpi(String elementName); + + /** + * Creates an XML processing with the given name and content. + * + * @since 7.0 + */ + @Incubating + JpaExpression xmlpi(String elementName, Expression content); + /** * 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 93ade3e839..608f1d64d2 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 @@ -3669,6 +3669,18 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde return criteriaBuilder.xmlconcat( elements ); } + @Override + @Incubating + public JpaExpression xmlpi(String elementName) { + return criteriaBuilder.xmlpi( elementName ); + } + + @Override + @Incubating + public JpaExpression xmlpi(String elementName, Expression content) { + return criteriaBuilder.xmlpi( elementName, content ); + } + @Override @Incubating public JpaExpression named(Expression expression, String name) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 35c934ff0b..51465db9cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -3032,6 +3032,17 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return creationContext.getNodeBuilder().xmlforest( elementExpressions ); } + @Override + public SqmExpression visitXmlpiFunction(HqlParser.XmlpiFunctionContext ctx) { + checkXmlFunctionsEnabled( ctx ); + final String name = visitIdentifier( ctx.identifier() ); + final HqlParser.ExpressionContext exprCtx = ctx.expression(); + //noinspection unchecked + return exprCtx == null + ? creationContext.getNodeBuilder().xmlpi( name ) + : creationContext.getNodeBuilder().xmlpi( name, (Expression) exprCtx.accept( this ) ); + } + private void checkXmlFunctionsEnabled(ParserRuleContext ctx) { if ( !creationOptions.isXmlFunctionsEnabled() ) { throw new SemanticException( 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 23ee727317..af1d057a56 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 @@ -771,6 +771,12 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext { @Override SqmExpression xmlconcat(List> elements); + @Override + SqmExpression xmlpi(String elementName); + + @Override + SqmExpression xmlpi(String elementName, Expression content); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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 eb701b2b54..1004ebc70c 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 @@ -5746,4 +5746,22 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable { queryEngine ); } + + @Override + public SqmExpression xmlpi(String elementName) { + return getFunctionDescriptor( "xmlpi" ).generateSqmExpression( + asList( literal( elementName ) ), + null, + queryEngine + ); + } + + @Override + public SqmExpression xmlpi(String elementName, Expression content) { + return getFunctionDescriptor( "xmlpi" ).generateSqmExpression( + asList( literal( elementName ), (SqmTypedNode) content ), + null, + queryEngine + ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlPiTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlPiTest.java new file mode 100644 index 0000000000..7dbb804902 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlPiTest.java @@ -0,0 +1,45 @@ +/* + * 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.SupportsXmlpi.class) +public class XmlPiTest { + + @Test + public void testSimple(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-xmlpi-example[] + em.createQuery( "select xmlpi(name php)" ).getResultList(); + //end::hql-xmlpi-example[] + } ); + } + + @Test + public void testContent(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-xmlpi-content-example[] + em.createQuery("select xmlpi(name `php`, 'echo \"test\"')" ).getResultList(); + //end::hql-xmlpi-content-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 eb92ceb5c9..4911aa3022 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 @@ -187,6 +187,20 @@ public class XmlFunctionTests { ); } + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlpi.class) + public void testXmlpi(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Tuple tuple = session.createQuery( + "select xmlpi(name test, 'abc')", + Tuple.class + ).getSingleResult(); + assertEquals( "", tuple.get( 0, String.class ).trim() ); + } + ); + } + 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 8933007388..3b9acc7bae 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 @@ -862,6 +862,12 @@ abstract public class DialectFeatureChecks { } } + public static class SupportsXmlpi implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return definesFunction( dialect, "xmlpi" ); + } + } + public static class IsJtds implements DialectFeatureCheck { public boolean apply(Dialect dialect) { return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;