HHH-18497 Add xmlelement function
This commit is contained in:
parent
7ff0567383
commit
4baba673cb
|
@ -7,6 +7,7 @@
|
||||||
:example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/hql
|
:example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/hql
|
||||||
:array-example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/function/array
|
:array-example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/function/array
|
||||||
:json-example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/function/json
|
:json-example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/function/json
|
||||||
|
:xml-example-dir-hql: {core-project-dir}/src/test/java/org/hibernate/orm/test/function/xml
|
||||||
:extrasdir: extras
|
:extrasdir: extras
|
||||||
|
|
||||||
This chapter describes Hibernate Query Language (HQL) and Jakarta Persistence Query Language (JPQL).
|
This chapter describes Hibernate Query Language (HQL) and Jakarta Persistence Query Language (JPQL).
|
||||||
|
@ -2158,6 +2159,56 @@ include::{json-example-dir-hql}/JsonArrayInsertTest.java[tags=hql-json-array-ins
|
||||||
|
|
||||||
WARNING: SAP HANA, DB2, H2 and HSQLDB do not support this function.
|
WARNING: SAP HANA, DB2, H2 and HSQLDB do not support this function.
|
||||||
|
|
||||||
|
[[hql-functions-xml]]
|
||||||
|
==== Functions for dealing with XML
|
||||||
|
|
||||||
|
The following functions deal with SQL XML types, which are not supported on every database.
|
||||||
|
|
||||||
|
NOTE: The following functions are incubating/tech-preview and to use them in HQL,
|
||||||
|
it is necessary to enable the `hibernate.query.hql.xml_functions_enabled` configuration setting.
|
||||||
|
|
||||||
|
[[hql-xml-functions]]
|
||||||
|
|===
|
||||||
|
| Function | Purpose
|
||||||
|
|
||||||
|
| `xmlelement()` | Constructs an XML element from arguments
|
||||||
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
[[hql-xmlelement-function]]
|
||||||
|
===== `xmlelement()`
|
||||||
|
|
||||||
|
Constructs an XML element from the arguments.
|
||||||
|
|
||||||
|
[[hql-xmlelement-bnf]]
|
||||||
|
[source, antlrv4, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/xmlelement_bnf.txt[]
|
||||||
|
----
|
||||||
|
|
||||||
|
The identifier represents the XML element name and can be quoted by using backticks.
|
||||||
|
|
||||||
|
[[hql-xmlelement-example]]
|
||||||
|
====
|
||||||
|
[source, java, indent=0]
|
||||||
|
----
|
||||||
|
include::{xml-example-dir-hql}/XmlElementTest.java[tags=hql-xmlelement-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
XML element attributes can be defined by using the `xmlattributes` function as second argument.
|
||||||
|
All following arguments represent the XML content.
|
||||||
|
|
||||||
|
[[hql-xmlelement-attributes-content-example]]
|
||||||
|
====
|
||||||
|
[source, java, indent=0]
|
||||||
|
----
|
||||||
|
include::{xml-example-dir-hql}/XmlElementTest.java[tags=hql-xmlelement-attributes-content-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
WARNING: SAP HANA, MySQL, MariaDB, H2 and HSQLDB do not support this function.
|
||||||
|
|
||||||
[[hql-user-defined-functions]]
|
[[hql-user-defined-functions]]
|
||||||
==== Native and user-defined functions
|
==== Native and user-defined functions
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
"xmlelement(name " identifier xmlattributes? ("," expressionOrPredicate)* ")"
|
||||||
|
|
||||||
|
xmlattributes
|
||||||
|
: "xmlattributes(" expressionOrPredicate " as " identifier ("," expressionOrPredicate " as " identifier)* ")"
|
||||||
|
;
|
|
@ -440,6 +440,8 @@ public class DB2LegacyDialect extends Dialect {
|
||||||
functionFactory.jsonObjectAgg_db2();
|
functionFactory.jsonObjectAgg_db2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
functionFactory.xmlelement();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -417,6 +417,8 @@ public class H2LegacyDialect extends Dialect {
|
||||||
// Use group_concat until 2.x as listagg was buggy
|
// Use group_concat until 2.x as listagg was buggy
|
||||||
functionFactory.listagg_groupConcat();
|
functionFactory.listagg_groupConcat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
functionFactory.xmlelement_h2();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
functionFactory.listagg_groupConcat();
|
functionFactory.listagg_groupConcat();
|
||||||
|
|
|
@ -325,6 +325,8 @@ public class OracleLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonArrayAppend_oracle();
|
functionFactory.jsonArrayAppend_oracle();
|
||||||
functionFactory.jsonArrayInsert_oracle();
|
functionFactory.jsonArrayInsert_oracle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
functionFactory.xmlelement();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -669,6 +669,8 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
||||||
functionFactory.jsonArrayAppend_postgresql( getVersion().isSameOrAfter( 13 ) );
|
functionFactory.jsonArrayAppend_postgresql( getVersion().isSameOrAfter( 13 ) );
|
||||||
functionFactory.jsonArrayInsert_postgresql();
|
functionFactory.jsonArrayInsert_postgresql();
|
||||||
|
|
||||||
|
functionFactory.xmlelement();
|
||||||
|
|
||||||
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
||||||
|
|
|
@ -413,6 +413,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
||||||
functionFactory.jsonArrayAppend_sqlserver( getVersion().isSameOrAfter( 16 ) );
|
functionFactory.jsonArrayAppend_sqlserver( getVersion().isSameOrAfter( 16 ) );
|
||||||
functionFactory.jsonArrayInsert_sqlserver();
|
functionFactory.jsonArrayInsert_sqlserver();
|
||||||
}
|
}
|
||||||
|
functionFactory.xmlelement_sqlserver();
|
||||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||||
functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
|
functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
|
||||||
|
|
|
@ -258,6 +258,7 @@ MINELEMENT : [mM] [iI] [nN] [eE] [lL] [eE] [mM] [eE] [nN] [tT];
|
||||||
MININDEX : [mM] [iI] [nN] [iI] [nN] [dD] [eE] [xX];
|
MININDEX : [mM] [iI] [nN] [iI] [nN] [dD] [eE] [xX];
|
||||||
MINUTE : [mM] [iI] [nN] [uU] [tT] [eE];
|
MINUTE : [mM] [iI] [nN] [uU] [tT] [eE];
|
||||||
MONTH : [mM] [oO] [nN] [tT] [hH];
|
MONTH : [mM] [oO] [nN] [tT] [hH];
|
||||||
|
NAME : [nN] [aA] [mM] [eE];
|
||||||
NANOSECOND : [nN] [aA] [nN] [oO] [sS] [eE] [cC] [oO] [nN] [dD];
|
NANOSECOND : [nN] [aA] [nN] [oO] [sS] [eE] [cC] [oO] [nN] [dD];
|
||||||
NEW : [nN] [eE] [wW];
|
NEW : [nN] [eE] [wW];
|
||||||
NEXT : [nN] [eE] [xX] [tT];
|
NEXT : [nN] [eE] [xX] [tT];
|
||||||
|
@ -329,6 +330,8 @@ WITH : [wW] [iI] [tT] [hH];
|
||||||
WITHIN : [wW] [iI] [tT] [hH] [iI] [nN];
|
WITHIN : [wW] [iI] [tT] [hH] [iI] [nN];
|
||||||
WITHOUT : [wW] [iI] [tT] [hH] [oO] [uU] [tT];
|
WITHOUT : [wW] [iI] [tT] [hH] [oO] [uU] [tT];
|
||||||
WRAPPER : [wW] [rR] [aA] [pP] [pP] [eE] [rR];
|
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];
|
||||||
YEAR : [yY] [eE] [aA] [rR];
|
YEAR : [yY] [eE] [aA] [rR];
|
||||||
ZONED : [zZ] [oO] [nN] [eE] [dD];
|
ZONED : [zZ] [oO] [nN] [eE] [dD];
|
||||||
|
|
||||||
|
|
|
@ -1110,6 +1110,7 @@ function
|
||||||
| jpaNonstandardFunction
|
| jpaNonstandardFunction
|
||||||
| columnFunction
|
| columnFunction
|
||||||
| jsonFunction
|
| jsonFunction
|
||||||
|
| xmlFunction
|
||||||
| genericFunction
|
| genericFunction
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -1716,6 +1717,24 @@ jsonUniqueKeysClause
|
||||||
: (WITH|WITHOUT) UNIQUE KEYS
|
: (WITH|WITHOUT) UNIQUE KEYS
|
||||||
;
|
;
|
||||||
|
|
||||||
|
xmlFunction
|
||||||
|
: xmlelementFunction
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'xmlelement()' function
|
||||||
|
*/
|
||||||
|
xmlelementFunction
|
||||||
|
: XMLELEMENT LEFT_PAREN NAME identifier (COMMA xmlattributesFunction)? (COMMA expressionOrPredicate)* RIGHT_PAREN
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'xmlattributes()' function
|
||||||
|
*/
|
||||||
|
xmlattributesFunction
|
||||||
|
: XMLATTRIBUTES LEFT_PAREN expressionOrPredicate AS identifier (COMMA expressionOrPredicate AS identifier)* RIGHT_PAREN
|
||||||
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for "soft" keywords which may be used as identifiers
|
* Support for "soft" keywords which may be used as identifiers
|
||||||
*
|
*
|
||||||
|
@ -1847,6 +1866,7 @@ jsonUniqueKeysClause
|
||||||
| MININDEX
|
| MININDEX
|
||||||
| MINUTE
|
| MINUTE
|
||||||
| MONTH
|
| MONTH
|
||||||
|
| NAME
|
||||||
| NANOSECOND
|
| NANOSECOND
|
||||||
| NATURALID
|
| NATURALID
|
||||||
| NEW
|
| NEW
|
||||||
|
@ -1921,6 +1941,8 @@ jsonUniqueKeysClause
|
||||||
| WITHIN
|
| WITHIN
|
||||||
| WITHOUT
|
| WITHOUT
|
||||||
| WRAPPER
|
| WRAPPER
|
||||||
|
| XMLATTRIBUTES
|
||||||
|
| XMLELEMENT
|
||||||
| YEAR
|
| YEAR
|
||||||
| ZONED) {
|
| ZONED) {
|
||||||
logUseOfReservedWordAsIdentifier( getCurrentToken() );
|
logUseOfReservedWordAsIdentifier( getCurrentToken() );
|
||||||
|
|
|
@ -131,6 +131,7 @@ import static org.hibernate.cfg.PersistenceSettings.UNOWNED_ASSOCIATION_TRANSIEN
|
||||||
import static org.hibernate.cfg.QuerySettings.DEFAULT_NULL_ORDERING;
|
import static org.hibernate.cfg.QuerySettings.DEFAULT_NULL_ORDERING;
|
||||||
import static org.hibernate.cfg.QuerySettings.JSON_FUNCTIONS_ENABLED;
|
import static org.hibernate.cfg.QuerySettings.JSON_FUNCTIONS_ENABLED;
|
||||||
import static org.hibernate.cfg.QuerySettings.PORTABLE_INTEGER_DIVISION;
|
import static org.hibernate.cfg.QuerySettings.PORTABLE_INTEGER_DIVISION;
|
||||||
|
import static org.hibernate.cfg.QuerySettings.XML_FUNCTIONS_ENABLED;
|
||||||
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
|
import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN;
|
||||||
import static org.hibernate.internal.CoreLogging.messageLogger;
|
import static org.hibernate.internal.CoreLogging.messageLogger;
|
||||||
import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER;
|
import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER;
|
||||||
|
@ -276,6 +277,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
||||||
|
|
||||||
private final boolean portableIntegerDivisionEnabled;
|
private final boolean portableIntegerDivisionEnabled;
|
||||||
private final boolean jsonFunctionsEnabled;
|
private final boolean jsonFunctionsEnabled;
|
||||||
|
private final boolean xmlFunctionsEnabled;
|
||||||
|
|
||||||
private final int queryStatisticsMaxSize;
|
private final int queryStatisticsMaxSize;
|
||||||
|
|
||||||
|
@ -614,6 +616,10 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
||||||
JSON_FUNCTIONS_ENABLED,
|
JSON_FUNCTIONS_ENABLED,
|
||||||
configurationSettings
|
configurationSettings
|
||||||
);
|
);
|
||||||
|
this.xmlFunctionsEnabled = getBoolean(
|
||||||
|
XML_FUNCTIONS_ENABLED,
|
||||||
|
configurationSettings
|
||||||
|
);
|
||||||
|
|
||||||
this.queryStatisticsMaxSize = getInt(
|
this.queryStatisticsMaxSize = getInt(
|
||||||
QUERY_STATISTICS_MAX_SIZE,
|
QUERY_STATISTICS_MAX_SIZE,
|
||||||
|
@ -1244,6 +1250,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
||||||
return jsonFunctionsEnabled;
|
return jsonFunctionsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isXmlFunctionsEnabled() {
|
||||||
|
return xmlFunctionsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPortableIntegerDivisionEnabled() {
|
public boolean isPortableIntegerDivisionEnabled() {
|
||||||
return portableIntegerDivisionEnabled;
|
return portableIntegerDivisionEnabled;
|
||||||
|
|
|
@ -433,6 +433,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
|
||||||
return delegate.isJsonFunctionsEnabled();
|
return delegate.isJsonFunctionsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isXmlFunctionsEnabled() {
|
||||||
|
return delegate.isXmlFunctionsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPortableIntegerDivisionEnabled() {
|
public boolean isPortableIntegerDivisionEnabled() {
|
||||||
return delegate.isPortableIntegerDivisionEnabled();
|
return delegate.isPortableIntegerDivisionEnabled();
|
||||||
|
|
|
@ -285,6 +285,14 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.hibernate.cfg.AvailableSettings#XML_FUNCTIONS_ENABLED
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
default boolean isXmlFunctionsEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
|
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -24,6 +24,16 @@ public interface QuerySettings {
|
||||||
*/
|
*/
|
||||||
@Incubating
|
@Incubating
|
||||||
String JSON_FUNCTIONS_ENABLED = "hibernate.query.hql.json_functions_enabled";
|
String JSON_FUNCTIONS_ENABLED = "hibernate.query.hql.json_functions_enabled";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean setting to control if the use of tech preview XML functions in HQL is enabled.
|
||||||
|
* By default, this is {@code false} i.e. disabled since the functions are still incubating.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
String XML_FUNCTIONS_ENABLED = "hibernate.query.hql.xml_functions_enabled";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies that division of two integers should produce an integer on all
|
* Specifies that division of two integers should produce an integer on all
|
||||||
* databases. By default, integer division in HQL can produce a non-integer
|
* databases. By default, integer division in HQL can produce a non-integer
|
||||||
|
|
|
@ -425,6 +425,8 @@ public class DB2Dialect extends Dialect {
|
||||||
functionFactory.jsonArrayAgg_db2();
|
functionFactory.jsonArrayAgg_db2();
|
||||||
functionFactory.jsonObjectAgg_db2();
|
functionFactory.jsonObjectAgg_db2();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
functionFactory.xmlelement();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -352,6 +352,8 @@ public class H2Dialect extends Dialect {
|
||||||
functionFactory.jsonArrayAgg_h2();
|
functionFactory.jsonArrayAgg_h2();
|
||||||
functionFactory.jsonObjectAgg_h2();
|
functionFactory.jsonObjectAgg_h2();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
functionFactory.xmlelement_h2();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -415,6 +415,8 @@ public class OracleDialect extends Dialect {
|
||||||
functionFactory.jsonMergepatch_oracle();
|
functionFactory.jsonMergepatch_oracle();
|
||||||
functionFactory.jsonArrayAppend_oracle();
|
functionFactory.jsonArrayAppend_oracle();
|
||||||
functionFactory.jsonArrayInsert_oracle();
|
functionFactory.jsonArrayInsert_oracle();
|
||||||
|
|
||||||
|
functionFactory.xmlelement();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -630,6 +630,8 @@ public class PostgreSQLDialect extends Dialect {
|
||||||
functionFactory.jsonArrayAppend_postgresql( getVersion().isSameOrAfter( 13 ) );
|
functionFactory.jsonArrayAppend_postgresql( getVersion().isSameOrAfter( 13 ) );
|
||||||
functionFactory.jsonArrayInsert_postgresql();
|
functionFactory.jsonArrayInsert_postgresql();
|
||||||
|
|
||||||
|
functionFactory.xmlelement();
|
||||||
|
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
|
||||||
functionFactory.inverseDistributionOrderedSetAggregates();
|
functionFactory.inverseDistributionOrderedSetAggregates();
|
||||||
|
|
|
@ -431,6 +431,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
||||||
functionFactory.jsonArrayAppend_sqlserver( getVersion().isSameOrAfter( 16 ) );
|
functionFactory.jsonArrayAppend_sqlserver( getVersion().isSameOrAfter( 16 ) );
|
||||||
functionFactory.jsonArrayInsert_sqlserver();
|
functionFactory.jsonArrayInsert_sqlserver();
|
||||||
}
|
}
|
||||||
|
functionFactory.xmlelement_sqlserver();
|
||||||
if ( getVersion().isSameOrAfter( 14 ) ) {
|
if ( getVersion().isSameOrAfter( 14 ) ) {
|
||||||
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
functionFactory.listagg_stringAggWithinGroup( "varchar(max)" );
|
||||||
functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
|
functionFactory.jsonArrayAgg_sqlserver( getVersion().isSameOrAfter( 16 ) );
|
||||||
|
|
|
@ -153,6 +153,9 @@ import org.hibernate.dialect.function.json.SQLServerJsonRemoveFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonReplaceFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonReplaceFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonSetFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonSetFunction;
|
||||||
import org.hibernate.dialect.function.json.SQLServerJsonValueFunction;
|
import org.hibernate.dialect.function.json.SQLServerJsonValueFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.H2XmlElementFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.SQLServerXmlElementFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.XmlElementFunction;
|
||||||
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
||||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||||
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
|
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
|
||||||
|
@ -4097,4 +4100,25 @@ public class CommonFunctionFactory {
|
||||||
public void jsonArrayInsert_sqlserver() {
|
public void jsonArrayInsert_sqlserver() {
|
||||||
functionRegistry.register( "json_array_insert", new SQLServerJsonArrayInsertFunction( typeConfiguration ) );
|
functionRegistry.register( "json_array_insert", new SQLServerJsonArrayInsertFunction( typeConfiguration ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard xmlelement() function
|
||||||
|
*/
|
||||||
|
public void xmlelement() {
|
||||||
|
functionRegistry.register( "xmlelement", new XmlElementFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H2 xmlelement() function
|
||||||
|
*/
|
||||||
|
public void xmlelement_h2() {
|
||||||
|
functionRegistry.register( "xmlelement", new H2XmlElementFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server xmlelement() function
|
||||||
|
*/
|
||||||
|
public void xmlelement_sqlserver() {
|
||||||
|
functionRegistry.register( "xmlelement", new SQLServerXmlElementFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.Map;
|
||||||
|
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H2 xmlelement function.
|
||||||
|
*/
|
||||||
|
public class H2XmlElementFunction extends XmlElementFunction {
|
||||||
|
|
||||||
|
public H2XmlElementFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super( typeConfiguration );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
XmlElementArguments arguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
sqlAppender.appendSql( "xmlnode(" );
|
||||||
|
sqlAppender.appendSingleQuoteEscapedString( arguments.elementName() );
|
||||||
|
if ( arguments.attributes() != null ) {
|
||||||
|
String separator = ",";
|
||||||
|
for ( Map.Entry<String, Expression> entry : arguments.attributes().getAttributes().entrySet() ) {
|
||||||
|
sqlAppender.appendSql( separator );
|
||||||
|
sqlAppender.appendSql( "xmlattr(" );
|
||||||
|
sqlAppender.appendSingleQuoteEscapedString( entry.getKey() );
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
entry.getValue().accept( walker );
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
separator = "||";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sqlAppender.appendSql( ",null" );
|
||||||
|
}
|
||||||
|
if ( !arguments.content().isEmpty() ) {
|
||||||
|
String separator = ",";
|
||||||
|
for ( Expression expression : arguments.content() ) {
|
||||||
|
sqlAppender.appendSql( separator );
|
||||||
|
expression.accept( walker );
|
||||||
|
separator = "||";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sqlAppender.appendSql( ",null" );
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( ",false)" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.dialect.function.xml;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server xmlelement function.
|
||||||
|
*/
|
||||||
|
public class SQLServerXmlElementFunction extends XmlElementFunction {
|
||||||
|
|
||||||
|
public SQLServerXmlElementFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super( typeConfiguration );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
XmlElementArguments arguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
sqlAppender.appendSql( "(select 1 tag,null parent" );
|
||||||
|
final String aliasPrefix = " [" + arguments.elementName() + "!1";
|
||||||
|
if ( arguments.attributes() != null ) {
|
||||||
|
for ( Map.Entry<String, Expression> entry : arguments.attributes().getAttributes().entrySet() ) {
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
entry.getValue().accept( walker );
|
||||||
|
sqlAppender.appendSql( aliasPrefix );
|
||||||
|
sqlAppender.appendSql( '!' );
|
||||||
|
sqlAppender.appendSql( entry.getKey() );
|
||||||
|
sqlAppender.appendSql( ']' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( arguments.content().isEmpty() ) {
|
||||||
|
sqlAppender.appendSql( ",null" );
|
||||||
|
sqlAppender.appendSql( aliasPrefix );
|
||||||
|
sqlAppender.appendSql( ']' );
|
||||||
|
}
|
||||||
|
if ( !arguments.content().isEmpty() ) {
|
||||||
|
for ( Expression expression : arguments.content() ) {
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
expression.accept( walker );
|
||||||
|
sqlAppender.appendSql( aliasPrefix );
|
||||||
|
sqlAppender.appendSql( ']' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( " for xml explicit, type)" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
/*
|
||||||
|
* 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 java.util.Map;
|
||||||
|
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.query.spi.QueryEngine;
|
||||||
|
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
||||||
|
import org.hibernate.query.sqm.function.FunctionKind;
|
||||||
|
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||||
|
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||||
|
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
|
||||||
|
import org.hibernate.query.sqm.produce.function.FunctionArgumentException;
|
||||||
|
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||||
|
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||||
|
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmXmlAttributesExpression;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmXmlElementExpression;
|
||||||
|
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.sql.ast.tree.expression.Literal;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.XmlAttributes;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
import static java.lang.Character.isLetter;
|
||||||
|
import static java.lang.Character.isLetterOrDigit;
|
||||||
|
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard xmlelement function.
|
||||||
|
*/
|
||||||
|
public class XmlElementFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||||
|
|
||||||
|
public XmlElementFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super(
|
||||||
|
"xmlelement",
|
||||||
|
FunctionKind.NORMAL,
|
||||||
|
StandardArgumentsValidators.composite(
|
||||||
|
new ArgumentTypesValidator( StandardArgumentsValidators.min( 1 ), STRING ),
|
||||||
|
new ArgumentsValidator() {
|
||||||
|
@Override
|
||||||
|
public void validate(
|
||||||
|
List<? extends SqmTypedNode<?>> arguments,
|
||||||
|
String functionName,
|
||||||
|
TypeConfiguration typeConfiguration) {
|
||||||
|
//noinspection unchecked
|
||||||
|
final String elementName = ( (SqmLiteral<String>) arguments.get( 0 ) ).getLiteralValue();
|
||||||
|
if ( !isValidXmlName( elementName ) ) {
|
||||||
|
throw new FunctionArgumentException(
|
||||||
|
String.format(
|
||||||
|
"Invalid XML element name passed to 'xmlelement()': %s",
|
||||||
|
1,
|
||||||
|
elementName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( arguments.size() > 1
|
||||||
|
&& arguments.get( 1 ) instanceof SqmXmlAttributesExpression attributesExpression ) {
|
||||||
|
final Map<String, SqmExpression<?>> attributes = attributesExpression.getAttributes();
|
||||||
|
for ( Map.Entry<String, SqmExpression<?>> entry : attributes.entrySet() ) {
|
||||||
|
if ( !isValidXmlName( entry.getKey() ) ) {
|
||||||
|
throw new FunctionArgumentException(
|
||||||
|
String.format(
|
||||||
|
"Invalid XML attribute name passed to 'xmlattributes()': %s",
|
||||||
|
1,
|
||||||
|
entry.getKey()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidXmlName(String name) {
|
||||||
|
if ( name.isEmpty()
|
||||||
|
|| !isValidXmlNameStart( name.charAt( 0 ) )
|
||||||
|
|| name.regionMatches( true, 0, "xml", 0, 3 ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for ( int i = 1; i < name.length(); i++ ) {
|
||||||
|
if ( !isValidXmlNameChar( name.charAt( i ) ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidXmlNameStart(char c) {
|
||||||
|
return isLetter( c ) || c == '_' || c == ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidXmlNameChar(char c) {
|
||||||
|
return isLetterOrDigit( c ) || c == '_' || c == ':' || c == '-' || c == '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
),
|
||||||
|
StandardFunctionReturnTypeResolvers.invariant(
|
||||||
|
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML )
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
|
||||||
|
List<? extends SqmTypedNode<?>> arguments,
|
||||||
|
ReturnableType<T> impliedResultType,
|
||||||
|
QueryEngine queryEngine) {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (SelfRenderingSqmFunction<T>) new SqmXmlElementExpression(
|
||||||
|
this,
|
||||||
|
this,
|
||||||
|
arguments,
|
||||||
|
(ReturnableType<String>) impliedResultType,
|
||||||
|
getArgumentsValidator(),
|
||||||
|
getReturnTypeResolver(),
|
||||||
|
queryEngine.getCriteriaBuilder(),
|
||||||
|
getName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
render( sqlAppender, XmlElementArguments.extract( sqlAstArguments ), returnType, walker );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
XmlElementArguments arguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
sqlAppender.appendSql( "xmlelement(name " );
|
||||||
|
sqlAppender.appendDoubleQuoteEscapedString( arguments.elementName() );
|
||||||
|
if ( arguments.attributes() != null ) {
|
||||||
|
sqlAppender.appendSql( ",xmlattributes" );
|
||||||
|
char separator = '(';
|
||||||
|
for ( Map.Entry<String, Expression> entry : arguments.attributes().getAttributes().entrySet() ) {
|
||||||
|
sqlAppender.appendSql( separator );
|
||||||
|
entry.getValue().accept( walker );
|
||||||
|
sqlAppender.appendSql( " as " );
|
||||||
|
sqlAppender.appendDoubleQuoteEscapedString( entry.getKey() );
|
||||||
|
separator = ',';
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
}
|
||||||
|
if ( !arguments.content().isEmpty() ) {
|
||||||
|
for ( Expression expression : arguments.content() ) {
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
expression.accept( walker );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected record XmlElementArguments(
|
||||||
|
String elementName,
|
||||||
|
@Nullable XmlAttributes attributes,
|
||||||
|
List<Expression> content) {
|
||||||
|
static XmlElementArguments extract(List<? extends SqlAstNode> arguments) {
|
||||||
|
final Literal elementName = (Literal) arguments.get( 0 );
|
||||||
|
final XmlAttributes attributes;
|
||||||
|
final List<Expression> content;
|
||||||
|
|
||||||
|
int index = 1;
|
||||||
|
if ( arguments.size() > index && arguments.get( index ) instanceof XmlAttributes ) {
|
||||||
|
attributes = (XmlAttributes) arguments.get( index );
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
attributes = null;
|
||||||
|
}
|
||||||
|
//noinspection unchecked
|
||||||
|
content = (List<Expression>) arguments.subList( index, arguments.size() );
|
||||||
|
return new XmlElementArguments( (String) elementName.getLiteralValue(), attributes, content );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4047,6 +4047,14 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
||||||
@Incubating
|
@Incubating
|
||||||
JpaExpression<String> jsonMergepatch(String document, Expression<?> patch);
|
JpaExpression<String> jsonMergepatch(String document, Expression<?> patch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an XML element with the given element name.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaXmlElementExpression xmlelement(String elementName);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
JpaPredicate and(List<Predicate> restrictions);
|
JpaPredicate and(List<Predicate> restrictions);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.criteria;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.Incubating;
|
||||||
|
|
||||||
|
import jakarta.persistence.criteria.Expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A special expression for the {@code xmlelement} function.
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
public interface JpaXmlElementExpression extends JpaExpression<String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes the given {@link Expression} as value for the XML attribute with the given name.
|
||||||
|
*
|
||||||
|
* @return {@code this} for method chaining
|
||||||
|
*/
|
||||||
|
JpaXmlElementExpression attribute(String attributeName, Expression<?> expression);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes the given {@link Expression}s as value for the XML content of this element.
|
||||||
|
*
|
||||||
|
* @return {@code this} for method chaining
|
||||||
|
*/
|
||||||
|
JpaXmlElementExpression content(List<? extends Expression<?>> expressions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes the given {@link Expression}s as value for the XML content of this element.
|
||||||
|
*
|
||||||
|
* @return {@code this} for method chaining
|
||||||
|
*/
|
||||||
|
JpaXmlElementExpression content(Expression<?>... expressions);
|
||||||
|
}
|
|
@ -57,6 +57,7 @@ import org.hibernate.query.criteria.JpaSubQuery;
|
||||||
import org.hibernate.query.criteria.JpaValues;
|
import org.hibernate.query.criteria.JpaValues;
|
||||||
import org.hibernate.query.criteria.JpaWindow;
|
import org.hibernate.query.criteria.JpaWindow;
|
||||||
import org.hibernate.query.criteria.JpaWindowFrame;
|
import org.hibernate.query.criteria.JpaWindowFrame;
|
||||||
|
import org.hibernate.query.criteria.JpaXmlElementExpression;
|
||||||
import org.hibernate.query.sqm.TemporalUnit;
|
import org.hibernate.query.sqm.TemporalUnit;
|
||||||
|
|
||||||
import jakarta.persistence.Tuple;
|
import jakarta.persistence.Tuple;
|
||||||
|
@ -3631,4 +3632,10 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
||||||
public JpaExpression<String> jsonMergepatch(String document, Expression<?> patch) {
|
public JpaExpression<String> jsonMergepatch(String document, Expression<?> patch) {
|
||||||
return criteriaBuilder.jsonMergepatch( document, patch );
|
return criteriaBuilder.jsonMergepatch( document, patch );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaXmlElementExpression xmlelement(String elementName) {
|
||||||
|
return criteriaBuilder.xmlelement( elementName );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,7 @@ import org.hibernate.query.sqm.tree.expression.SqmToDuration;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
|
import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
|
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmXmlElementExpression;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
|
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
|
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
|
||||||
|
@ -229,6 +230,7 @@ import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import jakarta.persistence.criteria.Expression;
|
||||||
import jakarta.persistence.criteria.Predicate;
|
import jakarta.persistence.criteria.Predicate;
|
||||||
import jakarta.persistence.metamodel.Bindable;
|
import jakarta.persistence.metamodel.Bindable;
|
||||||
import jakarta.persistence.metamodel.SingularAttribute;
|
import jakarta.persistence.metamodel.SingularAttribute;
|
||||||
|
@ -2982,6 +2984,36 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<?> visitXmlelementFunction(HqlParser.XmlelementFunctionContext ctx) {
|
||||||
|
checkXmlFunctionsEnabled( ctx );
|
||||||
|
final String elementName = visitIdentifier( ctx.identifier() );
|
||||||
|
final SqmXmlElementExpression xmlelement = creationContext.getNodeBuilder().xmlelement( elementName );
|
||||||
|
final HqlParser.XmlattributesFunctionContext attributeCtx = ctx.xmlattributesFunction();
|
||||||
|
if ( attributeCtx != null ) {
|
||||||
|
final List<HqlParser.ExpressionOrPredicateContext> expressions = attributeCtx.expressionOrPredicate();
|
||||||
|
final List<HqlParser.IdentifierContext> attributeNames = attributeCtx.identifier();
|
||||||
|
for ( int i = 0; i < expressions.size(); i++ ) {
|
||||||
|
xmlelement.attribute(
|
||||||
|
visitIdentifier( attributeNames.get( i ) ),
|
||||||
|
(Expression<?>) expressions.get( i ).accept( this )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xmlelement.content( visitExpressions( ctx ) );
|
||||||
|
return xmlelement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkXmlFunctionsEnabled(ParserRuleContext ctx) {
|
||||||
|
if ( !creationOptions.isXmlFunctionsEnabled() ) {
|
||||||
|
throw new SemanticException(
|
||||||
|
"Can't use function '" + ctx.children.get( 0 ).getText() +
|
||||||
|
"', because tech preview XML functions are not enabled. To enable, set the '" + QuerySettings.XML_FUNCTIONS_ENABLED + "' setting to 'true'.",
|
||||||
|
query
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SqmPredicate visitIncludesPredicate(HqlParser.IncludesPredicateContext ctx) {
|
public SqmPredicate visitIncludesPredicate(HqlParser.IncludesPredicateContext ctx) {
|
||||||
final boolean negated = ctx.NOT() != null;
|
final boolean negated = ctx.NOT() != null;
|
||||||
|
|
|
@ -31,6 +31,13 @@ public interface SqmCreationOptions {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.hibernate.cfg.AvailableSettings#XML_FUNCTIONS_ENABLED
|
||||||
|
*/
|
||||||
|
default boolean isXmlFunctionsEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
|
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -81,6 +81,11 @@ public interface QueryEngineOptions {
|
||||||
*/
|
*/
|
||||||
boolean isJsonFunctionsEnabled();
|
boolean isJsonFunctionsEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.hibernate.cfg.AvailableSettings#XML_FUNCTIONS_ENABLED
|
||||||
|
*/
|
||||||
|
boolean isXmlFunctionsEnabled();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
|
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -47,6 +47,7 @@ import org.hibernate.query.sqm.tree.expression.SqmJsonQueryExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmXmlElementExpression;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||||
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
||||||
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
|
import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement;
|
||||||
|
@ -749,6 +750,9 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
|
||||||
@Override
|
@Override
|
||||||
SqmExpression<String> jsonMergepatch(Expression<?> document, Expression<?> patch);
|
SqmExpression<String> jsonMergepatch(Expression<?> document, Expression<?> patch);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmXmlElementExpression xmlelement(String elementName);
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Covariant overrides
|
// Covariant overrides
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,11 @@ public class SqmCreationOptionsStandard implements SqmCreationOptions {
|
||||||
return queryEngineOptions.isJsonFunctionsEnabled();
|
return queryEngineOptions.isJsonFunctionsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isXmlFunctionsEnabled() {
|
||||||
|
return queryEngineOptions.isXmlFunctionsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPortableIntegerDivisionEnabled() {
|
public boolean isPortableIntegerDivisionEnabled() {
|
||||||
return queryEngineOptions.isPortableIntegerDivisionEnabled();
|
return queryEngineOptions.isPortableIntegerDivisionEnabled();
|
||||||
|
|
|
@ -136,6 +136,7 @@ import org.hibernate.query.sqm.tree.expression.SqmTuple;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
|
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmWindow;
|
import org.hibernate.query.sqm.tree.expression.SqmWindow;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmWindowFrame;
|
import org.hibernate.query.sqm.tree.expression.SqmWindowFrame;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmXmlElementExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter;
|
import org.hibernate.query.sqm.tree.expression.ValueBindJpaCriteriaParameter;
|
||||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||||
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
|
||||||
|
@ -218,6 +219,7 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
||||||
private transient BasicType<Integer> integerType;
|
private transient BasicType<Integer> integerType;
|
||||||
private transient BasicType<Long> longType;
|
private transient BasicType<Long> longType;
|
||||||
private transient BasicType<Character> characterType;
|
private transient BasicType<Character> characterType;
|
||||||
|
private transient BasicType<String> stringType;
|
||||||
private transient FunctionReturnTypeResolver sumReturnTypeResolver;
|
private transient FunctionReturnTypeResolver sumReturnTypeResolver;
|
||||||
private transient FunctionReturnTypeResolver avgReturnTypeResolver;
|
private transient FunctionReturnTypeResolver avgReturnTypeResolver;
|
||||||
private final transient Map<Class<? extends HibernateCriteriaBuilder>, HibernateCriteriaBuilder> extensions;
|
private final transient Map<Class<? extends HibernateCriteriaBuilder>, HibernateCriteriaBuilder> extensions;
|
||||||
|
@ -311,6 +313,16 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
||||||
return characterType;
|
return characterType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BasicType<String> getStringType() {
|
||||||
|
final BasicType<String> stringType = this.stringType;
|
||||||
|
if ( stringType == null ) {
|
||||||
|
return this.stringType =
|
||||||
|
getTypeConfiguration().getBasicTypeRegistry()
|
||||||
|
.resolve( StandardBasicTypes.STRING );
|
||||||
|
}
|
||||||
|
return stringType;
|
||||||
|
}
|
||||||
|
|
||||||
public FunctionReturnTypeResolver getSumReturnTypeResolver() {
|
public FunctionReturnTypeResolver getSumReturnTypeResolver() {
|
||||||
final FunctionReturnTypeResolver resolver = sumReturnTypeResolver;
|
final FunctionReturnTypeResolver resolver = sumReturnTypeResolver;
|
||||||
if ( resolver == null ) {
|
if ( resolver == null ) {
|
||||||
|
@ -5664,4 +5676,15 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
||||||
queryEngine
|
queryEngine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmXmlElementExpression xmlelement(String elementName) {
|
||||||
|
final List<SqmTypedNode<?>> arguments = new ArrayList<>( 3 );
|
||||||
|
arguments.add( new SqmLiteral<>( elementName, getStringType(), this ) );
|
||||||
|
return (SqmXmlElementExpression) getFunctionDescriptor( "xmlelement" ).<String>generateSqmExpression(
|
||||||
|
arguments,
|
||||||
|
null,
|
||||||
|
queryEngine
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ public enum SqmJsonNullBehavior implements SqmTypedNode<Object> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NodeBuilder nodeBuilder() {
|
public NodeBuilder nodeBuilder() {
|
||||||
return null;
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -35,7 +35,7 @@ public enum SqmJsonObjectAggUniqueKeysBehavior implements SqmTypedNode<Object> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NodeBuilder nodeBuilder() {
|
public NodeBuilder nodeBuilder() {
|
||||||
return null;
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.tree.expression;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hibernate.Incubating;
|
||||||
|
import org.hibernate.query.sqm.NodeBuilder;
|
||||||
|
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||||
|
import org.hibernate.query.sqm.SqmExpressible;
|
||||||
|
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||||
|
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.XmlAttributes;
|
||||||
|
|
||||||
|
import jakarta.persistence.criteria.Expression;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special expression for the json_query function that also captures special syntax elements like error and empty behavior.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
public class SqmXmlAttributesExpression implements SqmTypedNode<Object> {
|
||||||
|
|
||||||
|
private final Map<String, SqmExpression<?>> attributes;
|
||||||
|
|
||||||
|
public SqmXmlAttributesExpression(String attributeName, Expression<?> expression) {
|
||||||
|
final Map<String, SqmExpression<?>> attributes = new LinkedHashMap<>();
|
||||||
|
attributes.put( attributeName, (SqmExpression<?>) expression );
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SqmXmlAttributesExpression(Map<String, SqmExpression<?>> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attribute(String attributeName, Expression<?> expression) {
|
||||||
|
attributes.put( attributeName, (SqmExpression<?>) expression );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, SqmExpression<?>> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable SqmExpressible<Object> getNodeType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeBuilder nodeBuilder() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <X> X accept(SemanticQueryWalker<X> walker) {
|
||||||
|
final Map<String, org.hibernate.sql.ast.tree.expression.Expression> attributes = new LinkedHashMap<>();
|
||||||
|
for ( Map.Entry<String, SqmExpression<?>> entry : this.attributes.entrySet() ) {
|
||||||
|
attributes.put( entry.getKey(), (org.hibernate.sql.ast.tree.expression.Expression) entry.getValue().accept( walker ) );
|
||||||
|
}
|
||||||
|
//noinspection unchecked
|
||||||
|
return (X) new XmlAttributes( attributes );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmXmlAttributesExpression copy(SqmCopyContext context) {
|
||||||
|
final SqmXmlAttributesExpression existing = context.getCopy( this );
|
||||||
|
if ( existing != null ) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
final Map<String, SqmExpression<?>> attributes = new LinkedHashMap<>();
|
||||||
|
for ( Map.Entry<String, SqmExpression<?>> entry : this.attributes.entrySet() ) {
|
||||||
|
attributes.put( entry.getKey(), entry.getValue().copy( context ) );
|
||||||
|
}
|
||||||
|
return context.registerCopy( this, new SqmXmlAttributesExpression( attributes ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendHqlString(StringBuilder sb) {
|
||||||
|
String separator = "xmlattributes(";
|
||||||
|
for ( Map.Entry<String, SqmExpression<?>> entry : attributes.entrySet() ) {
|
||||||
|
sb.append( separator );
|
||||||
|
entry.getValue().appendHqlString( sb );
|
||||||
|
sb.append( " as " );
|
||||||
|
sb.append( entry.getKey() );
|
||||||
|
separator = ", ";
|
||||||
|
}
|
||||||
|
sb.append( ')' );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.tree.expression;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.Incubating;
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.query.criteria.JpaXmlElementExpression;
|
||||||
|
import org.hibernate.query.sqm.NodeBuilder;
|
||||||
|
import org.hibernate.query.sqm.function.FunctionRenderer;
|
||||||
|
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||||
|
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
|
||||||
|
import org.hibernate.query.sqm.produce.function.ArgumentsValidator;
|
||||||
|
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
|
||||||
|
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||||
|
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||||
|
|
||||||
|
import jakarta.persistence.criteria.Expression;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special expression for the xmlelement function that also captures special syntax elements like xmlattributes.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
public class SqmXmlElementExpression extends SelfRenderingSqmFunction<String> implements JpaXmlElementExpression {
|
||||||
|
|
||||||
|
public SqmXmlElementExpression(
|
||||||
|
SqmFunctionDescriptor descriptor,
|
||||||
|
FunctionRenderer renderer,
|
||||||
|
List<? extends SqmTypedNode<?>> arguments,
|
||||||
|
@Nullable ReturnableType<String> impliedResultType,
|
||||||
|
@Nullable ArgumentsValidator argumentsValidator,
|
||||||
|
FunctionReturnTypeResolver returnTypeResolver,
|
||||||
|
NodeBuilder nodeBuilder,
|
||||||
|
String name) {
|
||||||
|
super(
|
||||||
|
descriptor,
|
||||||
|
renderer,
|
||||||
|
arguments,
|
||||||
|
impliedResultType,
|
||||||
|
argumentsValidator,
|
||||||
|
returnTypeResolver,
|
||||||
|
nodeBuilder,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmXmlElementExpression attribute(String attributeName, Expression<?> expression) {
|
||||||
|
//noinspection unchecked
|
||||||
|
final List<SqmTypedNode<?>> arguments = (List<SqmTypedNode<?>>) getArguments();
|
||||||
|
if ( arguments.size() > 1 && arguments.get( 1 ) instanceof SqmXmlAttributesExpression attributesExpression ) {
|
||||||
|
attributesExpression.attribute( attributeName, expression );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
arguments.add( 1, new SqmXmlAttributesExpression( attributeName, expression ) );
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmXmlElementExpression content(Expression<?>... expressions) {
|
||||||
|
return content( Arrays.asList(expressions) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmXmlElementExpression content(List<? extends Expression<?>> expressions) {
|
||||||
|
//noinspection unchecked
|
||||||
|
final List<SqmTypedNode<?>> arguments = (List<SqmTypedNode<?>>) getArguments();
|
||||||
|
int contentIndex = 1;
|
||||||
|
if ( arguments.size() > contentIndex ) {
|
||||||
|
if ( arguments.get( contentIndex ) instanceof SqmXmlAttributesExpression ) {
|
||||||
|
contentIndex++;
|
||||||
|
}
|
||||||
|
while ( contentIndex < arguments.size() ) {
|
||||||
|
arguments.remove( arguments.size() - 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ( Expression<?> expression : expressions ) {
|
||||||
|
arguments.add( (SqmTypedNode<?>) expression );
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmXmlElementExpression copy(SqmCopyContext context) {
|
||||||
|
final SqmXmlElementExpression existing = context.getCopy( this );
|
||||||
|
if ( existing != null ) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
final List<SqmTypedNode<?>> arguments = new ArrayList<>( getArguments().size() );
|
||||||
|
for ( SqmTypedNode<?> argument : getArguments() ) {
|
||||||
|
arguments.add( argument.copy( context ) );
|
||||||
|
}
|
||||||
|
return context.registerCopy(
|
||||||
|
this,
|
||||||
|
new SqmXmlElementExpression(
|
||||||
|
getFunctionDescriptor(),
|
||||||
|
getFunctionRenderer(),
|
||||||
|
arguments,
|
||||||
|
getImpliedResultType(),
|
||||||
|
getArgumentsValidator(),
|
||||||
|
getReturnTypeResolver(),
|
||||||
|
nodeBuilder(),
|
||||||
|
getFunctionName()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendHqlString(StringBuilder sb) {
|
||||||
|
final List<? extends SqmTypedNode<?>> arguments = getArguments();
|
||||||
|
sb.append( "xmlelement(name " );
|
||||||
|
arguments.get( 0 ).appendHqlString( sb );
|
||||||
|
for ( int i = 1; i < arguments.size(); i++ ) {
|
||||||
|
sb.append( ',' );
|
||||||
|
arguments.get( i ).appendHqlString( sb );
|
||||||
|
}
|
||||||
|
sb.append( ')' );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.sql.ast.tree.expression;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hibernate.sql.ast.SqlAstWalker;
|
||||||
|
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
public class XmlAttributes implements SqlAstNode {
|
||||||
|
|
||||||
|
private final Map<String, Expression> attributes;
|
||||||
|
|
||||||
|
public XmlAttributes(Map<String, Expression> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Expression> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||||
|
throw new UnsupportedOperationException("XmlAttributes doesn't support walking");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.SupportsXmlelement.class)
|
||||||
|
public class XmlElementTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimple(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
//tag::hql-xmlelement-example[]
|
||||||
|
em.createQuery( "select xmlelement(name myelement)" ).getResultList();
|
||||||
|
//end::hql-xmlelement-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttributesAndContent(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
//tag::hql-xmlelement-attributes-content-example[]
|
||||||
|
em.createQuery("select xmlelement(name `my-element`, xmlattributes(123 as attr1, '456' as `attr-2`), 'myContent', xmlelement(name empty))" ).getResultList();
|
||||||
|
//end::hql-xmlelement-attributes-content-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.orm.test.query.hql;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.transform.OutputKeys;
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import javax.xml.transform.TransformerException;
|
||||||
|
import javax.xml.transform.TransformerFactory;
|
||||||
|
import javax.xml.transform.dom.DOMSource;
|
||||||
|
import javax.xml.transform.stream.StreamResult;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
|
import org.hibernate.cfg.QuerySettings;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
|
||||||
|
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.Jira;
|
||||||
|
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.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
@DomainModel( annotatedClasses = {
|
||||||
|
XmlFunctionTests.XmlHolder.class,
|
||||||
|
EntityOfBasics.class
|
||||||
|
})
|
||||||
|
@ServiceRegistry(settings = @Setting(name = QuerySettings.XML_FUNCTIONS_ENABLED, value = "true"))
|
||||||
|
@SessionFactory
|
||||||
|
@Jira("https://hibernate.atlassian.net/browse/HHH-18497")
|
||||||
|
public class XmlFunctionTests {
|
||||||
|
|
||||||
|
XmlHolder entity;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void prepareData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
em -> {
|
||||||
|
entity = new XmlHolder();
|
||||||
|
entity.id = 1L;
|
||||||
|
entity.xml = new HashMap<>();
|
||||||
|
entity.xml.put( "theInt", 1 );
|
||||||
|
entity.xml.put( "theFloat", 0.1 );
|
||||||
|
entity.xml.put( "theString", "abc" );
|
||||||
|
entity.xml.put( "theBoolean", true );
|
||||||
|
entity.xml.put( "theNull", null );
|
||||||
|
entity.xml.put( "theArray", new String[] { "a", "b", "c" } );
|
||||||
|
entity.xml.put( "theObject", new HashMap<>( entity.xml ) );
|
||||||
|
entity.xml.put(
|
||||||
|
"theNestedObjects",
|
||||||
|
List.of(
|
||||||
|
Map.of( "id", 1, "name", "val1" ),
|
||||||
|
Map.of( "id", 2, "name", "val2" ),
|
||||||
|
Map.of( "id", 3, "name", "val3" )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
em.persist(entity);
|
||||||
|
|
||||||
|
EntityOfBasics e1 = new EntityOfBasics();
|
||||||
|
e1.setId( 1 );
|
||||||
|
e1.setTheString( "Dog" );
|
||||||
|
e1.setTheInteger( 0 );
|
||||||
|
e1.setTheUuid( UUID.randomUUID() );
|
||||||
|
EntityOfBasics e2 = new EntityOfBasics();
|
||||||
|
e2.setId( 2 );
|
||||||
|
e2.setTheString( "Cat" );
|
||||||
|
e2.setTheInteger( 0 );
|
||||||
|
|
||||||
|
em.persist( e1 );
|
||||||
|
em.persist( e2 );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void cleanupData(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
em -> {
|
||||||
|
em.createMutationQuery( "delete from EntityOfBasics" ).executeUpdate();
|
||||||
|
em.createMutationQuery( "delete from XmlHolder" ).executeUpdate();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlelement.class)
|
||||||
|
public void testXmlelement(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
Tuple tuple = session.createQuery(
|
||||||
|
"select " +
|
||||||
|
"xmlelement(name empty), " +
|
||||||
|
"xmlelement(name `the-element`), " +
|
||||||
|
"xmlelement(name myElement, 'myContent'), " +
|
||||||
|
"xmlelement(name myElement, xmlattributes('123' as attr1)), " +
|
||||||
|
"xmlelement(name myElement, xmlattributes('123' as attr1, '456' as `attr-2`)), " +
|
||||||
|
"xmlelement(name myElement, xmlattributes('123' as attr1), 'myContent', xmlelement(name empty))",
|
||||||
|
Tuple.class
|
||||||
|
).getSingleResult();
|
||||||
|
assertXmlEquals( "<empty/>", tuple.get( 0, String.class ) );
|
||||||
|
assertXmlEquals( "<the-element/>", tuple.get( 1 , String.class ) );
|
||||||
|
assertXmlEquals( "<myElement>myContent</myElement>", tuple.get( 2, String.class ) );
|
||||||
|
assertXmlEquals( "<myElement attr1=\"123\"/>", tuple.get( 3, String.class ) );
|
||||||
|
assertXmlEquals( "<myElement attr1=\"123\" attr-2=\"456\"/>", tuple.get( 4, String.class ) );
|
||||||
|
assertXmlEquals( "<myElement attr1=\"123\">myContent<empty/></myElement>", tuple.get( 5, String.class ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalize(Document document) {
|
||||||
|
normalize( document.getDocumentElement() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalize(Element element) {
|
||||||
|
final NodeList childNodes = element.getChildNodes();
|
||||||
|
for ( int i = 0; i < childNodes.getLength(); i++ ) {
|
||||||
|
final Node childNode = childNodes.item( i );
|
||||||
|
if ( childNode.getNodeType() == Node.ELEMENT_NODE ) {
|
||||||
|
normalize( (Element) childNode );
|
||||||
|
}
|
||||||
|
else if ( childNode.getNodeType() == Node.TEXT_NODE ) {
|
||||||
|
if ( childNode.getNodeValue().isBlank() ) {
|
||||||
|
childNode.getParentNode().removeChild( childNode );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
childNode.setNodeValue( childNode.getNodeValue().trim() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String xmlNormalize(String doc) {
|
||||||
|
final String prefix = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
|
||||||
|
return doc.startsWith( "<?xml" ) ? doc : prefix + doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Document parseXml(String document) {
|
||||||
|
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
try {
|
||||||
|
final DocumentBuilder db = dbf.newDocumentBuilder();
|
||||||
|
return db.parse( new InputSource( new StringReader( document ) ) );
|
||||||
|
}
|
||||||
|
catch (ParserConfigurationException | IOException | SAXException e) {
|
||||||
|
throw new RuntimeException( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toXml(Document document) {
|
||||||
|
final TransformerFactory tf = TransformerFactory.newInstance();
|
||||||
|
try {
|
||||||
|
final Transformer transformer = tf.newTransformer();
|
||||||
|
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||||
|
transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
|
||||||
|
final StringWriter writer = new StringWriter();
|
||||||
|
transformer.transform( new DOMSource( document ), new StreamResult( writer ) );
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
catch (TransformerException e) {
|
||||||
|
throw new RuntimeException( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "XmlHolder")
|
||||||
|
public static class XmlHolder {
|
||||||
|
@Id
|
||||||
|
Long id;
|
||||||
|
@JdbcTypeCode(SqlTypes.SQLXML)
|
||||||
|
Map<String, Object> xml;
|
||||||
|
}
|
||||||
|
}
|
|
@ -838,6 +838,12 @@ abstract public class DialectFeatureChecks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SupportsXmlelement implements DialectFeatureCheck {
|
||||||
|
public boolean apply(Dialect dialect) {
|
||||||
|
return definesFunction( dialect, "xmlelement" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class IsJtds implements DialectFeatureCheck {
|
public static class IsJtds implements DialectFeatureCheck {
|
||||||
public boolean apply(Dialect dialect) {
|
public boolean apply(Dialect dialect) {
|
||||||
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;
|
return dialect instanceof SybaseDialect && ( (SybaseDialect) dialect ).getDriverKind() == SybaseDriverKind.JTDS;
|
||||||
|
|
Loading…
Reference in New Issue