From af09813ced5d6ab4ad170c19098b4eaf392aaca8 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Sat, 21 Sep 2024 02:36:22 +0200 Subject: [PATCH] HHH-18497 Add xmlexists function --- .../chapters/query/hql/QueryLanguage.adoc | 25 +++++++ .../query/hql/extras/xmlexists_bnf.txt | 1 + .../community/dialect/DB2LegacyDialect.java | 1 + .../dialect/OracleLegacyDialect.java | 1 + .../dialect/PostgreSQLLegacyDialect.java | 1 + .../dialect/SQLServerLegacyDialect.java | 1 + .../org/hibernate/grammars/hql/HqlLexer.g4 | 1 + .../org/hibernate/grammars/hql/HqlParser.g4 | 9 +++ .../org/hibernate/dialect/DB2Dialect.java | 1 + .../org/hibernate/dialect/OracleDialect.java | 1 + .../hibernate/dialect/PostgreSQLDialect.java | 1 + .../hibernate/dialect/SQLServerDialect.java | 1 + .../xml/SQLServerXmlExistsFunction.java | 46 ++++++++++++ .../function/xml/XmlExistsFunction.java | 71 +++++++++++++++++++ .../criteria/HibernateCriteriaBuilder.java | 16 +++++ .../spi/HibernateCriteriaBuilderDelegate.java | 12 ++++ .../hql/internal/SemanticQueryBuilder.java | 8 +++ .../org/hibernate/query/sqm/NodeBuilder.java | 6 ++ .../sqm/internal/SqmCriteriaNodeBuilder.java | 14 ++++ .../orm/test/function/xml/XmlExistsTest.java | 36 ++++++++++ .../orm/test/query/hql/XmlFunctionTests.java | 15 ++++ .../orm/junit/DialectFeatureChecks.java | 6 ++ 22 files changed, 274 insertions(+) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/xmlexists_bnf.txt create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlExistsFunction.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlExistsFunction.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlExistsTest.java 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 0a512e73cc..49ec94ee97 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -2177,6 +2177,7 @@ it is necessary to enable the `hibernate.query.hql.xml_functions_enabled` config | `xmlconcat()` | Concatenates multiple XML fragments to each other | `xmlpi()` | Constructs an XML processing instruction | `xmlquery()` | Extracts content from XML document using XQuery or XPath +| `xmlexists()` | Checks if an XQuery or XPath expression exists in an XML document |=== @@ -2325,6 +2326,30 @@ include::{xml-example-dir-hql}/XmlQueryTest.java[tags=hql-xmlquery-example] WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function. +[[hql-xmlexists-function]] +===== `xmlexists()` + +Checks if an XQuery or XPath expression exists in an XML document. + +[[hql-xmlexists-bnf]] +[source, antlrv4, indent=0] +---- +include::{extrasdir}/xmlexists_bnf.txt[] +---- + +The first argument represents the XQuery or XPath expression. +The second argument after the `passing` keyword represents the XML document. + +[[hql-xmlexists-example]] +==== +[source, java, indent=0] +---- +include::{xml-example-dir-hql}/XmlExistsTest.java[tags=hql-xmlexists-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/xmlexists_bnf.txt b/documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/xmlexists_bnf.txt new file mode 100644 index 0000000000..e7af7019da --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/extras/xmlexists_bnf.txt @@ -0,0 +1 @@ +"xmlexists(" expression "passing" expression ")" \ 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 48e7b73704..7ce0eb6cc1 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 @@ -447,6 +447,7 @@ public class DB2LegacyDialect extends Dialect { functionFactory.xmlconcat(); functionFactory.xmlpi(); functionFactory.xmlquery_db2(); + functionFactory.xmlexists(); } @Override 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 a35356d7e8..4f60897297 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 @@ -332,6 +332,7 @@ public class OracleLegacyDialect extends Dialect { functionFactory.xmlconcat(); functionFactory.xmlpi(); functionFactory.xmlquery_oracle(); + functionFactory.xmlexists(); } @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 c31b8a05f9..890f7a45f0 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 @@ -675,6 +675,7 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.xmlconcat(); functionFactory.xmlpi(); functionFactory.xmlquery_postgresql(); + functionFactory.xmlexists(); 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 7cc9f1156e..c2533daee5 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 @@ -419,6 +419,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { functionFactory.xmlconcat_sqlserver(); functionFactory.xmlpi_sqlserver(); functionFactory.xmlquery_sqlserver(); + functionFactory.xmlexists_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 14fdbeba82..a7f9f16b26 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 @@ -332,6 +332,7 @@ WITHOUT : [wW] [iI] [tT] [hH] [oO] [uU] [tT]; 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]; +XMLEXISTS : [xX] [mM] [lL] [eE] [xX] [iI] [sS] [tT] [sS]; XMLFOREST : [xX] [mM] [lL] [fF] [oO] [rR] [eE] [sS] [tT]; XMLPI : [xX] [mM] [lL] [pP] [iI]; XMLQUERY : [xX] [mM] [lL] [qQ] [uU] [eE] [rR] [yY]; 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 9c62f12d2e..0ace1b3587 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 @@ -1722,6 +1722,7 @@ xmlFunction | xmlforestFunction | xmlpiFunction | xmlqueryFunction + | xmlexistsFunction ; /** @@ -1759,6 +1760,13 @@ xmlqueryFunction : XMLQUERY LEFT_PAREN expression PASSING expression RIGHT_PAREN ; +/** + * The 'xmlexists()' function + */ +xmlexistsFunction + : XMLEXISTS LEFT_PAREN expression PASSING expression RIGHT_PAREN + ; + /** * Support for "soft" keywords which may be used as identifiers * @@ -1967,6 +1975,7 @@ xmlqueryFunction | WRAPPER | XMLATTRIBUTES | XMLELEMENT + | XMLEXISTS | XMLFOREST | XMLPI | XMLQUERY 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 b27fbb92cf..5cb6fe471b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -432,6 +432,7 @@ public class DB2Dialect extends Dialect { functionFactory.xmlconcat(); functionFactory.xmlpi(); functionFactory.xmlquery_db2(); + functionFactory.xmlexists(); } @Override 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 07947028bc..0d0c522888 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -422,6 +422,7 @@ public class OracleDialect extends Dialect { functionFactory.xmlconcat(); functionFactory.xmlpi(); functionFactory.xmlquery_oracle(); + functionFactory.xmlexists(); } @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 71236953cc..ef3660c8fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -636,6 +636,7 @@ public class PostgreSQLDialect extends Dialect { functionFactory.xmlconcat(); functionFactory.xmlpi(); functionFactory.xmlquery_postgresql(); + functionFactory.xmlexists(); 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 a12432b957..6b932817e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -437,6 +437,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { functionFactory.xmlconcat_sqlserver(); functionFactory.xmlpi_sqlserver(); functionFactory.xmlquery_sqlserver(); + functionFactory.xmlexists_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/xml/SQLServerXmlExistsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlExistsFunction.java new file mode 100644 index 0000000000..2cbbecf8f8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/SQLServerXmlExistsFunction.java @@ -0,0 +1,46 @@ +/* + * 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.dialect.function.json.ExpressionTypeHelper; +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.Expression; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * SQL Server xmlexists function. + */ +public class SQLServerXmlExistsFunction extends XmlExistsFunction { + + public SQLServerXmlExistsFunction(TypeConfiguration typeConfiguration) { + super( typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression xmlDocument = (Expression) sqlAstArguments.get( 1 ); + final boolean needsCast = !ExpressionTypeHelper.isXml( xmlDocument ); + sqlAppender.appendSql( '(' ); + if ( needsCast ) { + sqlAppender.appendSql( "cast(" ); + } + sqlAstArguments.get( 1 ).accept( walker ); + if ( needsCast ) { + sqlAppender.appendSql( " as xml)" ); + } + sqlAppender.appendSql( ".exist(" ); + sqlAstArguments.get( 0 ).accept( walker ); + sqlAppender.appendSql( ")=1)" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlExistsFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlExistsFunction.java new file mode 100644 index 0000000000..080c34e991 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/xml/XmlExistsFunction.java @@ -0,0 +1,71 @@ +/* + * 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.dialect.function.json.ExpressionTypeHelper; +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.Expression; +import org.hibernate.type.spi.TypeConfiguration; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.IMPLICIT_XML; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.XML; + +/** + * Standard xmlexists function. + */ +public class XmlExistsFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + public XmlExistsFunction(TypeConfiguration typeConfiguration) { + super( + "xmlexists", + FunctionKind.NORMAL, + StandardArgumentsValidators.composite( + new ArgumentTypesValidator( null, STRING, IMPLICIT_XML ) + ), + StandardFunctionReturnTypeResolvers.invariant( + typeConfiguration.getBasicTypeRegistry().getRegisteredType( Boolean.class ) + ), + StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, STRING, XML ) + ); + } + + @Override + public boolean isPredicate() { + return true; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + ReturnableType returnType, + SqlAstTranslator walker) { + final Expression xmlDocument = (Expression) sqlAstArguments.get( 1 ); + final boolean needsCast = !ExpressionTypeHelper.isXml( xmlDocument ); + sqlAppender.appendSql( "xmlexists(" ); + sqlAstArguments.get( 0 ).accept( walker ); + sqlAppender.appendSql( " passing " ); + if ( needsCast ) { + sqlAppender.appendSql( "xmlparse(document " ); + } + sqlAstArguments.get( 1 ).accept( walker ); + if ( needsCast ) { + sqlAppender.appendSql( ')' ); + } + 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 ab9a5248ce..edeee1c9a9 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 @@ -4129,6 +4129,22 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder { @Incubating JpaExpression xmlquery(Expression query, Expression xmlDocument); + /** + * Checks if the given XPath or XQuery query exists in the given XML document. + * + * @since 7.0 + */ + @Incubating + JpaExpression xmlexists(String query, Expression xmlDocument); + + /** + * Checks if the given XPath or XQuery query exists in the given XML document. + * + * @since 7.0 + */ + @Incubating + JpaExpression xmlexists(Expression query, Expression xmlDocument); + /** * 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 e8b1b2ddbb..a39425b37a 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 @@ -3693,6 +3693,18 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde return criteriaBuilder.xmlquery( query, xmlDocument ); } + @Override + @Incubating + public JpaExpression xmlexists(String query, Expression xmlDocument) { + return criteriaBuilder.xmlexists( query, xmlDocument ); + } + + @Override + @Incubating + public JpaExpression xmlexists(Expression query, Expression xmlDocument) { + return criteriaBuilder.xmlexists( query, xmlDocument ); + } + @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 55a7cbccfb..ea2644047c 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 @@ -3051,6 +3051,14 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem return creationContext.getNodeBuilder().xmlquery( query, xmlDocument ); } + @Override + public SqmExpression visitXmlexistsFunction(HqlParser.XmlexistsFunctionContext ctx) { + checkXmlFunctionsEnabled( ctx ); + final SqmExpression query = (SqmExpression) ctx.expression( 0 ).accept( this ); + final SqmExpression xmlDocument = (SqmExpression) ctx.expression( 1 ).accept( this ); + return creationContext.getNodeBuilder().xmlexists( query, xmlDocument ); + } + 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 1dcd11547b..3dda15c549 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 @@ -783,6 +783,12 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext { @Override SqmExpression xmlquery(Expression query, Expression xmlDocument); + @Override + SqmExpression xmlexists(String query, Expression xmlDocument); + + @Override + SqmExpression xmlexists(Expression query, Expression xmlDocument); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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 7f1067b298..1a9b9e9c68 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 @@ -5778,4 +5778,18 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable { queryEngine ); } + + @Override + public SqmExpression xmlexists(String query, Expression xmlDocument) { + return xmlexists( value( query ), xmlDocument ); + } + + @Override + public SqmExpression xmlexists(Expression query, Expression xmlDocument) { + return getFunctionDescriptor( "xmlexists" ).generateSqmExpression( + asList( (SqmTypedNode) query, (SqmTypedNode) xmlDocument ), + null, + queryEngine + ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlExistsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlExistsTest.java new file mode 100644 index 0000000000..e3cc31fca0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlExistsTest.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.SupportsXmlexists.class) +public class XmlExistsTest { + + @Test + public void testSimple(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-xmlexists-example[] + em.createQuery( "select xmlexists('/a/val' passing 'asd')" ).getResultList(); + //end::hql-xmlexists-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 4989e1b97b..cdf352123d 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 @@ -49,6 +49,7 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; @DomainModel( annotatedClasses = { XmlFunctionTests.XmlHolder.class, @@ -215,6 +216,20 @@ public class XmlFunctionTests { ); } + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlexists.class) + public void testXmlexists(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Tuple tuple = session.createQuery( + "select xmlexists('/a/val' passing 'asd')", + Tuple.class + ).getSingleResult(); + assertTrue( tuple.get( 0, Boolean.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 ae32650957..456f93b0e3 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 @@ -874,6 +874,12 @@ abstract public class DialectFeatureChecks { } } + public static class SupportsXmlexists implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return definesFunction( dialect, "xmlexists" ); + } + } + public static class IsJtds implements DialectFeatureCheck { public boolean apply(Dialect dialect) { return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;