From f10ec5db1d27f73f08e6f5f8716c0a14655286c9 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 20 Sep 2024 22:34:16 +0200 Subject: [PATCH] HHH-18497 Add xmlcomment function --- .../chapters/query/hql/QueryLanguage.adoc | 16 ++++++++ .../community/dialect/DB2LegacyDialect.java | 1 + .../community/dialect/H2LegacyDialect.java | 1 + .../dialect/OracleLegacyDialect.java | 1 + .../dialect/PostgreSQLLegacyDialect.java | 1 + .../dialect/SQLServerLegacyDialect.java | 1 + .../org/hibernate/dialect/DB2Dialect.java | 1 + .../java/org/hibernate/dialect/H2Dialect.java | 1 + .../org/hibernate/dialect/OracleDialect.java | 1 + .../hibernate/dialect/PostgreSQLDialect.java | 1 + .../hibernate/dialect/SQLServerDialect.java | 1 + .../function/CommonFunctionFactory.java | 22 ++++++++++ .../criteria/HibernateCriteriaBuilder.java | 8 ++++ .../spi/HibernateCriteriaBuilderDelegate.java | 6 +++ .../org/hibernate/query/sqm/NodeBuilder.java | 3 ++ .../sqm/internal/SqmCriteriaNodeBuilder.java | 9 +++++ .../orm/test/function/xml/XmlCommentTest.java | 36 +++++++++++++++++ .../orm/test/query/hql/XmlFunctionTests.java | 40 ++++++++++++++----- .../orm/junit/DialectFeatureChecks.java | 6 +++ 19 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlCommentTest.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 8af68a4983..d90ebb4306 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -2172,6 +2172,7 @@ it is necessary to enable the `hibernate.query.hql.xml_functions_enabled` config | Function | Purpose | `xmlelement()` | Constructs an XML element from arguments +| `xmlcomment()` | Constructs an XML comment from the single argument |=== @@ -2209,6 +2210,21 @@ include::{xml-example-dir-hql}/XmlElementTest.java[tags=hql-xmlelement-attribute WARNING: SAP HANA, MySQL, MariaDB, H2 and HSQLDB do not support this function. +[[hql-xmlcomment-function]] +===== `xmlcomment()` + +Constructs an XML comment from the single string argument. + +[[hql-xmlcomment-example]] +==== +[source, java, indent=0] +---- +include::{xml-example-dir-hql}/XmlCommentTest.java[tags=hql-xmlcomment-example] +---- +==== + +WARNING: SAP HANA, MySQL, MariaDB, H2 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 abb2904efd..e66da1d15c 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 @@ -442,6 +442,7 @@ public class DB2LegacyDialect extends Dialect { } functionFactory.xmlelement(); + functionFactory.xmlcomment(); } @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 233ab7223b..8d64e7f08b 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 @@ -419,6 +419,7 @@ public class H2LegacyDialect extends Dialect { } functionFactory.xmlelement_h2(); + functionFactory.xmlcomment(); } 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 d69f49a310..6fc5b4f843 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 @@ -327,6 +327,7 @@ public class OracleLegacyDialect extends Dialect { } functionFactory.xmlelement(); + functionFactory.xmlcomment(); } @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 ceab48f54f..14799a1d7e 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 @@ -670,6 +670,7 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.jsonArrayInsert_postgresql(); functionFactory.xmlelement(); + functionFactory.xmlcomment(); 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 05451294aa..c8d3190216 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 @@ -414,6 +414,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { functionFactory.jsonArrayInsert_sqlserver(); } functionFactory.xmlelement_sqlserver(); + functionFactory.xmlcomment_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 beadca37d0..0063cfb16e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -427,6 +427,7 @@ public class DB2Dialect extends Dialect { } functionFactory.xmlelement(); + functionFactory.xmlcomment(); } @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 ee2ce1db82..23da263062 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -354,6 +354,7 @@ public class H2Dialect extends Dialect { } functionFactory.xmlelement_h2(); + functionFactory.xmlcomment(); } /** 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 fa111e3d54..d3632739bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -417,6 +417,7 @@ public class OracleDialect extends Dialect { functionFactory.jsonArrayInsert_oracle(); functionFactory.xmlelement(); + functionFactory.xmlcomment(); } @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 fd7b9a1c36..53c42a79c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -631,6 +631,7 @@ public class PostgreSQLDialect extends Dialect { functionFactory.jsonArrayInsert_postgresql(); functionFactory.xmlelement(); + functionFactory.xmlcomment(); 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 155b8f120e..d40c92448e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -432,6 +432,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { functionFactory.jsonArrayInsert_sqlserver(); } functionFactory.xmlelement_sqlserver(); + functionFactory.xmlcomment_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 ad3ea3a4f7..e27c40606c 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 @@ -4121,4 +4121,26 @@ public class CommonFunctionFactory { public void xmlelement_sqlserver() { functionRegistry.register( "xmlelement", new SQLServerXmlElementFunction( typeConfiguration ) ); } + + /** + * Standard xmlcomment() function + */ + public void xmlcomment() { + functionRegistry.namedDescriptorBuilder( "xmlcomment" ) + .setExactArgumentCount( 1 ) + .setParameterTypes( STRING ) + .setInvariantType( typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML ) ) + .register(); + } + + /** + * Standard xmlcomment() function + */ + public void xmlcomment_sqlserver() { + functionRegistry.patternDescriptorBuilder( "xmlcomment", "cast(('') AS xml)" ) + .setExactArgumentCount( 1 ) + .setParameterTypes( STRING ) + .setInvariantType( typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML ) ) + .register(); + } } 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 dc64d90765..813bc2b2e1 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 @@ -4055,6 +4055,14 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder { @Incubating JpaXmlElementExpression xmlelement(String elementName); + /** + * Creates an XML comment with the given argument as content. + * + * @since 7.0 + */ + @Incubating + JpaExpression xmlcomment(String comment); + @Override JpaPredicate and(List restrictions); 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 ceb1503abc..ad7af99c2d 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 @@ -3638,4 +3638,10 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde public JpaXmlElementExpression xmlelement(String elementName) { return criteriaBuilder.xmlelement( elementName ); } + + @Override + @Incubating + public JpaExpression xmlcomment(String comment) { + return criteriaBuilder.xmlcomment( comment ); + } } 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 e85a6219a5..a9af77f375 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 @@ -753,6 +753,9 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext { @Override SqmXmlElementExpression xmlelement(String elementName); + @Override + SqmExpression xmlcomment(String comment); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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 4b58cc1490..eadf203369 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 @@ -5687,4 +5687,13 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable { queryEngine ); } + + @Override + public SqmExpression xmlcomment(String comment) { + return getFunctionDescriptor( "xmlcomment" ).generateSqmExpression( + List.of( value( comment ) ), + null, + queryEngine + ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlCommentTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlCommentTest.java new file mode 100644 index 0000000000..f226413576 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/function/xml/XmlCommentTest.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.SupportsXmlcomment.class) +public class XmlCommentTest { + + @Test + public void testSimple(SessionFactoryScope scope) { + scope.inSession( em -> { + //tag::hql-xmlcomment-example[] + em.createQuery( "select xmlcomment('This is my comment')" ).getResultList(); + //end::hql-xmlcomment-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 f89b4ef72c..4cccf6e2ab 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 @@ -43,7 +43,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Tuple; import org.w3c.dom.Document; -import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; @@ -137,24 +136,40 @@ public class XmlFunctionTests { ); } - private void assertXmlEquals(String doc1, String doc2) { - final Document d1 = parseXml( xmlNormalize( doc1 ) ); - final Document d2 = parseXml( xmlNormalize( doc2 ) ); - normalize( d1 ); - normalize( d2 ); - assertEquals( toXml( d1 ), toXml( d2 ) ); + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlcomment.class) + public void testXmlcomment(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Tuple tuple = session.createQuery( + "select " + + "xmlcomment('Abc'), " + + "xmlcomment('<>')", + Tuple.class + ).getSingleResult(); + assertXmlEquals( "", tuple.get( 0, String.class ) + "" ); + assertXmlEquals( "", tuple.get( 1 , String.class ) + "" ); + } + ); + } + + private void assertXmlEquals(String expected, String actual) { + final Document expectedDoc = parseXml( xmlNormalize( expected ) ); + final Document actualDoc = parseXml( xmlNormalize( actual ) ); + normalize( expectedDoc ); + normalize( actualDoc ); + assertEquals( toXml( expectedDoc ).trim(), toXml( actualDoc ).trim() ); } private void normalize(Document document) { - normalize( document.getDocumentElement() ); + normalize( document.getChildNodes() ); } - private void normalize(Element element) { - final NodeList childNodes = element.getChildNodes(); + private void normalize(NodeList childNodes) { for ( int i = 0; i < childNodes.getLength(); i++ ) { final Node childNode = childNodes.item( i ); if ( childNode.getNodeType() == Node.ELEMENT_NODE ) { - normalize( (Element) childNode ); + normalize( childNode.getChildNodes() ); } else if ( childNode.getNodeType() == Node.TEXT_NODE ) { if ( childNode.getNodeValue().isBlank() ) { @@ -164,6 +179,9 @@ public class XmlFunctionTests { childNode.setNodeValue( childNode.getNodeValue().trim() ); } } + else if ( childNode.getNodeType() == Node.COMMENT_NODE ) { + childNode.setNodeValue( childNode.getNodeValue().trim() ); + } } } 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 77f249928e..5cdd0714ba 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 @@ -844,6 +844,12 @@ abstract public class DialectFeatureChecks { } } + public static class SupportsXmlcomment implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return definesFunction( dialect, "xmlcomment" ); + } + } + public static class IsJtds implements DialectFeatureCheck { public boolean apply(Dialect dialect) { return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;