HHH-18497 Add xmlforest function
This commit is contained in:
parent
f10ec5db1d
commit
1abfd4eea6
|
@ -2173,6 +2173,7 @@ it is necessary to enable the `hibernate.query.hql.xml_functions_enabled` config
|
||||||
|
|
||||||
| `xmlelement()` | Constructs an XML element from arguments
|
| `xmlelement()` | Constructs an XML element from arguments
|
||||||
| `xmlcomment()` | Constructs an XML comment from the single argument
|
| `xmlcomment()` | Constructs an XML comment from the single argument
|
||||||
|
| `xmlforest()` | Constructs an XML forest from thearguments
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -2208,7 +2209,7 @@ include::{xml-example-dir-hql}/XmlElementTest.java[tags=hql-xmlelement-attribute
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
WARNING: SAP HANA, MySQL, MariaDB, H2 and HSQLDB do not support this function.
|
WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function.
|
||||||
|
|
||||||
[[hql-xmlcomment-function]]
|
[[hql-xmlcomment-function]]
|
||||||
===== `xmlcomment()`
|
===== `xmlcomment()`
|
||||||
|
@ -2223,7 +2224,31 @@ include::{xml-example-dir-hql}/XmlCommentTest.java[tags=hql-xmlcomment-example]
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
WARNING: SAP HANA, MySQL, MariaDB, H2 and HSQLDB do not support this function.
|
WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function.
|
||||||
|
|
||||||
|
[[hql-xmlforest-function]]
|
||||||
|
===== `xmlforest()`
|
||||||
|
|
||||||
|
Constructs an XML forest from the arguments. A forest is a sequence of XML elements.
|
||||||
|
|
||||||
|
[[hql-xmlforest-bnf]]
|
||||||
|
[source, antlrv4, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/xmlforest_bnf.txt[]
|
||||||
|
----
|
||||||
|
|
||||||
|
The optional name specifies the XML element name to use for the content as produced by the expression.
|
||||||
|
The name can be omitted if a path expression is passed, in which case the last attribute name is used as element name.
|
||||||
|
|
||||||
|
[[hql-xmlforest-example]]
|
||||||
|
====
|
||||||
|
[source, java, indent=0]
|
||||||
|
----
|
||||||
|
include::{xml-example-dir-hql}/XmlForestTest.java[tags=hql-xmlforest-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
WARNING: SAP HANA, MySQL, MariaDB 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 @@
|
||||||
|
"xmlforest(expressionOrPredicate ("as" identifier)? ("," expressionOrPredicate ("as" identifier)?)* ")"
|
|
@ -443,6 +443,7 @@ public class DB2LegacyDialect extends Dialect {
|
||||||
|
|
||||||
functionFactory.xmlelement();
|
functionFactory.xmlelement();
|
||||||
functionFactory.xmlcomment();
|
functionFactory.xmlcomment();
|
||||||
|
functionFactory.xmlforest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -420,6 +420,7 @@ public class H2LegacyDialect extends Dialect {
|
||||||
|
|
||||||
functionFactory.xmlelement_h2();
|
functionFactory.xmlelement_h2();
|
||||||
functionFactory.xmlcomment();
|
functionFactory.xmlcomment();
|
||||||
|
functionFactory.xmlforest_h2();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
functionFactory.listagg_groupConcat();
|
functionFactory.listagg_groupConcat();
|
||||||
|
|
|
@ -328,6 +328,7 @@ public class OracleLegacyDialect extends Dialect {
|
||||||
|
|
||||||
functionFactory.xmlelement();
|
functionFactory.xmlelement();
|
||||||
functionFactory.xmlcomment();
|
functionFactory.xmlcomment();
|
||||||
|
functionFactory.xmlforest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -671,6 +671,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
||||||
|
|
||||||
functionFactory.xmlelement();
|
functionFactory.xmlelement();
|
||||||
functionFactory.xmlcomment();
|
functionFactory.xmlcomment();
|
||||||
|
functionFactory.xmlforest();
|
||||||
|
|
||||||
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
|
|
|
@ -415,6 +415,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
||||||
}
|
}
|
||||||
functionFactory.xmlelement_sqlserver();
|
functionFactory.xmlelement_sqlserver();
|
||||||
functionFactory.xmlcomment_sqlserver();
|
functionFactory.xmlcomment_sqlserver();
|
||||||
|
functionFactory.xmlforest_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 ) );
|
||||||
|
|
|
@ -332,6 +332,7 @@ 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];
|
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];
|
XMLELEMENT : [xX] [mM] [lL] [eE] [lL] [eE] [mM] [eE] [nN] [tT];
|
||||||
|
XMLFOREST : [xX] [mM] [lL] [fF] [oO] [rR] [eE] [sS] [tT];
|
||||||
YEAR : [yY] [eE] [aA] [rR];
|
YEAR : [yY] [eE] [aA] [rR];
|
||||||
ZONED : [zZ] [oO] [nN] [eE] [dD];
|
ZONED : [zZ] [oO] [nN] [eE] [dD];
|
||||||
|
|
||||||
|
|
|
@ -1719,6 +1719,7 @@ jsonUniqueKeysClause
|
||||||
|
|
||||||
xmlFunction
|
xmlFunction
|
||||||
: xmlelementFunction
|
: xmlelementFunction
|
||||||
|
| xmlforestFunction
|
||||||
;
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1735,6 +1736,13 @@ xmlattributesFunction
|
||||||
: XMLATTRIBUTES LEFT_PAREN expressionOrPredicate AS identifier (COMMA expressionOrPredicate AS identifier)* RIGHT_PAREN
|
: XMLATTRIBUTES LEFT_PAREN expressionOrPredicate AS identifier (COMMA expressionOrPredicate AS identifier)* RIGHT_PAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'xmlforest()' function
|
||||||
|
*/
|
||||||
|
xmlforestFunction
|
||||||
|
: XMLFOREST 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
|
||||||
*
|
*
|
||||||
|
@ -1943,6 +1951,7 @@ xmlattributesFunction
|
||||||
| WRAPPER
|
| WRAPPER
|
||||||
| XMLATTRIBUTES
|
| XMLATTRIBUTES
|
||||||
| XMLELEMENT
|
| XMLELEMENT
|
||||||
|
| XMLFOREST
|
||||||
| YEAR
|
| YEAR
|
||||||
| ZONED) {
|
| ZONED) {
|
||||||
logUseOfReservedWordAsIdentifier( getCurrentToken() );
|
logUseOfReservedWordAsIdentifier( getCurrentToken() );
|
||||||
|
|
|
@ -428,6 +428,7 @@ public class DB2Dialect extends Dialect {
|
||||||
|
|
||||||
functionFactory.xmlelement();
|
functionFactory.xmlelement();
|
||||||
functionFactory.xmlcomment();
|
functionFactory.xmlcomment();
|
||||||
|
functionFactory.xmlforest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -355,6 +355,7 @@ public class H2Dialect extends Dialect {
|
||||||
|
|
||||||
functionFactory.xmlelement_h2();
|
functionFactory.xmlelement_h2();
|
||||||
functionFactory.xmlcomment();
|
functionFactory.xmlcomment();
|
||||||
|
functionFactory.xmlforest_h2();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -418,6 +418,7 @@ public class OracleDialect extends Dialect {
|
||||||
|
|
||||||
functionFactory.xmlelement();
|
functionFactory.xmlelement();
|
||||||
functionFactory.xmlcomment();
|
functionFactory.xmlcomment();
|
||||||
|
functionFactory.xmlforest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -632,6 +632,7 @@ public class PostgreSQLDialect extends Dialect {
|
||||||
|
|
||||||
functionFactory.xmlelement();
|
functionFactory.xmlelement();
|
||||||
functionFactory.xmlcomment();
|
functionFactory.xmlcomment();
|
||||||
|
functionFactory.xmlforest();
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -433,6 +433,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
||||||
}
|
}
|
||||||
functionFactory.xmlelement_sqlserver();
|
functionFactory.xmlelement_sqlserver();
|
||||||
functionFactory.xmlcomment_sqlserver();
|
functionFactory.xmlcomment_sqlserver();
|
||||||
|
functionFactory.xmlforest_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 ) );
|
||||||
|
|
|
@ -154,8 +154,11 @@ 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.H2XmlElementFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.H2XmlForestFunction;
|
||||||
import org.hibernate.dialect.function.xml.SQLServerXmlElementFunction;
|
import org.hibernate.dialect.function.xml.SQLServerXmlElementFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.SQLServerXmlForestFunction;
|
||||||
import org.hibernate.dialect.function.xml.XmlElementFunction;
|
import org.hibernate.dialect.function.xml.XmlElementFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.XmlForestFunction;
|
||||||
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;
|
||||||
|
@ -4134,7 +4137,7 @@ public class CommonFunctionFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard xmlcomment() function
|
* SQL Server xmlcomment() function
|
||||||
*/
|
*/
|
||||||
public void xmlcomment_sqlserver() {
|
public void xmlcomment_sqlserver() {
|
||||||
functionRegistry.patternDescriptorBuilder( "xmlcomment", "cast(('<!--'+?1+'-->') AS xml)" )
|
functionRegistry.patternDescriptorBuilder( "xmlcomment", "cast(('<!--'+?1+'-->') AS xml)" )
|
||||||
|
@ -4143,4 +4146,25 @@ public class CommonFunctionFactory {
|
||||||
.setInvariantType( typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML ) )
|
.setInvariantType( typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML ) )
|
||||||
.register();
|
.register();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard xmlforest() function
|
||||||
|
*/
|
||||||
|
public void xmlforest() {
|
||||||
|
functionRegistry.register( "xmlforest", new XmlForestFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H2 xmlforest() function
|
||||||
|
*/
|
||||||
|
public void xmlforest_h2() {
|
||||||
|
functionRegistry.register( "xmlforest", new H2XmlForestFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server xmlforest() function
|
||||||
|
*/
|
||||||
|
public void xmlforest_sqlserver() {
|
||||||
|
functionRegistry.register( "xmlforest", new SQLServerXmlForestFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.dialect.function.xml;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||||
|
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.AliasedExpression;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H2 xmlforest function.
|
||||||
|
*/
|
||||||
|
public class H2XmlForestFunction extends XmlForestFunction {
|
||||||
|
|
||||||
|
public H2XmlForestFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super( typeConfiguration );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
String separator = "";
|
||||||
|
sqlAppender.appendSql( '(' );
|
||||||
|
for ( SqlAstNode sqlAstArgument : sqlAstArguments ) {
|
||||||
|
final AliasedExpression expression = (AliasedExpression) sqlAstArgument;
|
||||||
|
sqlAppender.appendSql( separator );
|
||||||
|
sqlAppender.appendSql( "xmlnode(" );
|
||||||
|
sqlAppender.appendSingleQuoteEscapedString( expression.getAlias() );
|
||||||
|
sqlAppender.appendSql( ",null," );
|
||||||
|
expression.getExpression().accept( walker );
|
||||||
|
sqlAppender.appendSql( ",false)" );
|
||||||
|
separator = "||";
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.dialect.function.xml;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||||
|
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.AliasedExpression;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server xmlforest function.
|
||||||
|
*/
|
||||||
|
public class SQLServerXmlForestFunction extends XmlForestFunction {
|
||||||
|
|
||||||
|
public SQLServerXmlForestFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super( typeConfiguration );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
sqlAppender.appendSql( "cast" );
|
||||||
|
char separator = '(';
|
||||||
|
for ( SqlAstNode sqlAstArgument : sqlAstArguments ) {
|
||||||
|
final AliasedExpression expression = (AliasedExpression) sqlAstArgument;
|
||||||
|
sqlAppender.appendSql( separator );
|
||||||
|
sqlAppender.appendSql( "cast((select 1 tag,null parent," );
|
||||||
|
expression.getExpression().accept( walker );
|
||||||
|
sqlAppender.appendSql( " [" );
|
||||||
|
sqlAppender.appendSql( expression.getAlias() );
|
||||||
|
sqlAppender.appendSql( "!1] for xml explicit,type) as nvarchar(max))" );
|
||||||
|
separator = '+';
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( " as xml)" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,6 @@ public class XmlElementFunction extends AbstractSqmSelfRenderingFunctionDescript
|
||||||
throw new FunctionArgumentException(
|
throw new FunctionArgumentException(
|
||||||
String.format(
|
String.format(
|
||||||
"Invalid XML element name passed to 'xmlelement()': %s",
|
"Invalid XML element name passed to 'xmlelement()': %s",
|
||||||
1,
|
|
||||||
elementName
|
elementName
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -73,7 +72,6 @@ public class XmlElementFunction extends AbstractSqmSelfRenderingFunctionDescript
|
||||||
throw new FunctionArgumentException(
|
throw new FunctionArgumentException(
|
||||||
String.format(
|
String.format(
|
||||||
"Invalid XML attribute name passed to 'xmlattributes()': %s",
|
"Invalid XML attribute name passed to 'xmlattributes()': %s",
|
||||||
1,
|
|
||||||
entry.getKey()
|
entry.getKey()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.dialect.function.xml;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
||||||
|
import org.hibernate.query.sqm.function.FunctionKind;
|
||||||
|
import org.hibernate.query.sqm.produce.function.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.SqmNamedExpression;
|
||||||
|
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.AliasedExpression;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
import static java.lang.Character.isLetter;
|
||||||
|
import static java.lang.Character.isLetterOrDigit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard XmlForestFunction function.
|
||||||
|
*/
|
||||||
|
public class XmlForestFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||||
|
|
||||||
|
public XmlForestFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super(
|
||||||
|
"xmlforest",
|
||||||
|
FunctionKind.NORMAL,
|
||||||
|
StandardArgumentsValidators.composite(
|
||||||
|
StandardArgumentsValidators.min( 1 ),
|
||||||
|
new ArgumentsValidator() {
|
||||||
|
@Override
|
||||||
|
public void validate(
|
||||||
|
List<? extends SqmTypedNode<?>> arguments,
|
||||||
|
String functionName,
|
||||||
|
TypeConfiguration typeConfiguration) {
|
||||||
|
for ( int i = 0; i < arguments.size(); i++ ) {
|
||||||
|
SqmTypedNode<?> argument = arguments.get( i );
|
||||||
|
if ( !( argument instanceof SqmNamedExpression<?> namedExpression ) ) {
|
||||||
|
throw new FunctionArgumentException(
|
||||||
|
String.format(
|
||||||
|
"Parameter %d of function 'xmlforest()' is not named",
|
||||||
|
i
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !isValidXmlName( namedExpression.getName() ) ) {
|
||||||
|
throw new FunctionArgumentException(
|
||||||
|
String.format(
|
||||||
|
"Invalid XML element name passed to 'xmlforest()': %s",
|
||||||
|
namedExpression.getName()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
sqlAppender.appendSql( "xmlforest" );
|
||||||
|
char separator = '(';
|
||||||
|
for ( SqlAstNode sqlAstArgument : sqlAstArguments ) {
|
||||||
|
sqlAppender.appendSql( separator );
|
||||||
|
if ( sqlAstArgument instanceof AliasedExpression aliasedExpression ) {
|
||||||
|
aliasedExpression.getExpression().accept( walker );
|
||||||
|
sqlAppender.appendSql( " as " );
|
||||||
|
sqlAppender.appendDoubleQuoteEscapedString( aliasedExpression.getAlias() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sqlAstArgument.accept( walker );
|
||||||
|
}
|
||||||
|
separator = ',';
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
}
|
||||||
|
}
|
|
@ -4063,6 +4063,33 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
||||||
@Incubating
|
@Incubating
|
||||||
JpaExpression<String> xmlcomment(String comment);
|
JpaExpression<String> xmlcomment(String comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an XML forest from the given XML element expressions.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> xmlforest(Expression<?>... elements);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an XML forest from the given XML element expressions.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> xmlforest(List<? extends Expression<?>> elements);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a named expression. The name is important for the result of the expression,
|
||||||
|
* e.g. when building an {@code xmlforest}, the name acts as the XML element name.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
* @see #xmlforest(Expression[])
|
||||||
|
* @see #xmlforest(List)
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
<T> JpaExpression<T> named(Expression<T> expression, String name);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
JpaPredicate and(List<Predicate> restrictions);
|
JpaPredicate and(List<Predicate> restrictions);
|
||||||
|
|
||||||
|
|
|
@ -3644,4 +3644,22 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
||||||
public JpaExpression<String> xmlcomment(String comment) {
|
public JpaExpression<String> xmlcomment(String comment) {
|
||||||
return criteriaBuilder.xmlcomment( comment );
|
return criteriaBuilder.xmlcomment( comment );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> xmlforest(Expression<?>... elements) {
|
||||||
|
return criteriaBuilder.xmlforest( elements );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> xmlforest(List<? extends Expression<?>> elements) {
|
||||||
|
return criteriaBuilder.xmlforest( elements );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public <T> JpaExpression<T> named(Expression<T> expression, String name) {
|
||||||
|
return criteriaBuilder.named( expression, name );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,7 @@ import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmNamedExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
|
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
|
||||||
|
@ -3004,6 +3005,33 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
return xmlelement;
|
return xmlelement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<?> visitXmlforestFunction(HqlParser.XmlforestFunctionContext ctx) {
|
||||||
|
checkXmlFunctionsEnabled( ctx );
|
||||||
|
final ArrayList<SqmExpression<?>> elementExpressions = new ArrayList<>( ctx.getChildCount() >> 1 );
|
||||||
|
for ( int i = 2; i < ctx.getChildCount(); i++ ) {
|
||||||
|
if ( ctx.getChild( i ) instanceof HqlParser.ExpressionOrPredicateContext exprCtx ) {
|
||||||
|
final SqmExpression<?> expression = (SqmExpression<?>) exprCtx.accept( this );
|
||||||
|
if ( i + 2 < ctx.getChildCount() && ctx.getChild( i + 2 ) instanceof HqlParser.IdentifierContext identifierContext ) {
|
||||||
|
final String name = visitIdentifier( identifierContext );
|
||||||
|
elementExpressions.add( new SqmNamedExpression<>( expression, name ) );
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( !( expression instanceof SqmPath<?> path ) || !( path.getModel() instanceof PersistentAttribute<?, ?> attribute ) ) {
|
||||||
|
throw new SemanticException(
|
||||||
|
"Can't use expression '" + exprCtx.getText() + " without explicit name in xmlforest function" +
|
||||||
|
", because XML element names can only be derived from path expressions.",
|
||||||
|
query
|
||||||
|
);
|
||||||
|
}
|
||||||
|
elementExpressions.add( new SqmNamedExpression<>( expression, attribute.getName() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return creationContext.getNodeBuilder().xmlforest( elementExpressions );
|
||||||
|
}
|
||||||
|
|
||||||
private void checkXmlFunctionsEnabled(ParserRuleContext ctx) {
|
private void checkXmlFunctionsEnabled(ParserRuleContext ctx) {
|
||||||
if ( !creationOptions.isXmlFunctionsEnabled() ) {
|
if ( !creationOptions.isXmlFunctionsEnabled() ) {
|
||||||
throw new SemanticException(
|
throw new SemanticException(
|
||||||
|
|
|
@ -756,6 +756,15 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
|
||||||
@Override
|
@Override
|
||||||
SqmExpression<String> xmlcomment(String comment);
|
SqmExpression<String> xmlcomment(String comment);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
<T> SqmExpression<T> named(Expression<T> expression, String name);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> xmlforest(List<? extends Expression<?>> elements);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> xmlforest(Expression<?>... elements);
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Covariant overrides
|
// Covariant overrides
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEmbeddableType;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralEmbeddableType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmNamedExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
|
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
|
||||||
|
@ -418,4 +419,6 @@ public interface SemanticQueryWalker<T> {
|
||||||
T visitFullyQualifiedClass(Class<?> namedClass);
|
T visitFullyQualifiedClass(Class<?> namedClass);
|
||||||
|
|
||||||
T visitAsWrapperExpression(AsWrapperSqmExpression<?> expression);
|
T visitAsWrapperExpression(AsWrapperSqmExpression<?> expression);
|
||||||
|
|
||||||
|
T visitNamedExpression(SqmNamedExpression<?> expression);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
import org.hibernate.jpa.spi.JpaCompliance;
|
import org.hibernate.jpa.spi.JpaCompliance;
|
||||||
import org.hibernate.metamodel.model.domain.DomainType;
|
import org.hibernate.metamodel.model.domain.DomainType;
|
||||||
import org.hibernate.metamodel.model.domain.JpaMetamodel;
|
import org.hibernate.metamodel.model.domain.JpaMetamodel;
|
||||||
|
import org.hibernate.metamodel.model.domain.PersistentAttribute;
|
||||||
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
||||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
import org.hibernate.query.BindableType;
|
import org.hibernate.query.BindableType;
|
||||||
|
@ -128,6 +129,7 @@ import org.hibernate.query.sqm.tree.expression.SqmJsonValueExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmNamedExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmStar;
|
import org.hibernate.query.sqm.tree.expression.SqmStar;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmToDuration;
|
import org.hibernate.query.sqm.tree.expression.SqmToDuration;
|
||||||
|
@ -5696,4 +5698,38 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
||||||
queryEngine
|
queryEngine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> SqmExpression<T> named(Expression<T> expression, String name) {
|
||||||
|
return new SqmNamedExpression<>( (SqmExpression<T>) expression, name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> xmlforest(Expression<?>... elements) {
|
||||||
|
return xmlforest( Arrays.asList( elements ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> xmlforest(List<? extends Expression<?>> elements) {
|
||||||
|
final ArrayList<SqmExpression<?>> arguments = new ArrayList<>( elements.size() );
|
||||||
|
for ( Expression<?> expression : elements ) {
|
||||||
|
if ( expression instanceof SqmNamedExpression<?> ) {
|
||||||
|
arguments.add( (SqmNamedExpression<?>) expression );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( !( expression instanceof SqmPath<?> path ) || !( path.getModel() instanceof PersistentAttribute<?, ?> attribute ) ) {
|
||||||
|
throw new SemanticException(
|
||||||
|
"Can't use expression '" + expression + " without explicit name in xmlforest function"+
|
||||||
|
", because XML element names can only be derived from path expressions."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
arguments.add( new SqmNamedExpression<>( (SqmExpression<?>) expression, attribute.getName() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getFunctionDescriptor( "xmlforest" ).generateSqmExpression(
|
||||||
|
arguments,
|
||||||
|
null,
|
||||||
|
queryEngine
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEmbeddableType;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralEmbeddableType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmNamedExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
|
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
|
||||||
|
@ -1198,6 +1199,11 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitNamedExpression(SqmNamedExpression<?> expression) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object visitModifiedSubQueryExpression(SqmModifiedSubQueryExpression expression) {
|
public Object visitModifiedSubQueryExpression(SqmModifiedSubQueryExpression expression) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -60,6 +60,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEmbeddableType;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralEmbeddableType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmNamedExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOrderedSetAggregateFunction;
|
import org.hibernate.query.sqm.tree.expression.SqmOrderedSetAggregateFunction;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
||||||
|
@ -988,4 +989,10 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
|
||||||
expression.getExpression().accept( this );
|
expression.getExpression().accept( this );
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitNamedExpression(SqmNamedExpression<?> expression) {
|
||||||
|
expression.getExpression().accept( this );
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,6 +224,7 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteralEmbeddableType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
|
import org.hibernate.query.sqm.tree.expression.SqmLiteralNull;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
import org.hibernate.query.sqm.tree.expression.SqmModifiedSubQueryExpression;
|
||||||
|
import org.hibernate.query.sqm.tree.expression.SqmNamedExpression;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
import org.hibernate.query.sqm.tree.expression.SqmOver;
|
||||||
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
|
import org.hibernate.query.sqm.tree.expression.SqmOverflow;
|
||||||
|
@ -314,6 +315,7 @@ import org.hibernate.sql.ast.tree.cte.CteTable;
|
||||||
import org.hibernate.sql.ast.tree.cte.CteTableGroup;
|
import org.hibernate.sql.ast.tree.cte.CteTableGroup;
|
||||||
import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification;
|
import org.hibernate.sql.ast.tree.cte.SearchClauseSpecification;
|
||||||
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
import org.hibernate.sql.ast.tree.delete.DeleteStatement;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.AliasedExpression;
|
||||||
import org.hibernate.sql.ast.tree.expression.Any;
|
import org.hibernate.sql.ast.tree.expression.Any;
|
||||||
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
|
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
|
||||||
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
|
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
|
||||||
|
@ -8325,6 +8327,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitNamedExpression(SqmNamedExpression<?> expression) {
|
||||||
|
return new AliasedExpression( (Expression) expression.getExpression().accept( this ), expression.getName() );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) {
|
public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) {
|
||||||
final EntityIdentifierMapping identifierMapping = fetchParent.getReferencedMappingContainer()
|
final EntityIdentifierMapping identifierMapping = fetchParent.getReferencedMappingContainer()
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.query.sqm.tree.expression;
|
||||||
|
|
||||||
|
import org.hibernate.Incubating;
|
||||||
|
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||||
|
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A named expression. Used when the name of the expression matters
|
||||||
|
* e.g. in XML generation.
|
||||||
|
*
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
public class SqmNamedExpression<T> extends AbstractSqmExpression<T> {
|
||||||
|
|
||||||
|
private final SqmExpression<T> expression;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public SqmNamedExpression(SqmExpression<T> expression, String name) {
|
||||||
|
super( expression.getExpressible(), expression.nodeBuilder() );
|
||||||
|
this.expression = expression;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmNamedExpression<T> copy(SqmCopyContext context) {
|
||||||
|
final SqmNamedExpression<T> existing = context.getCopy( this );
|
||||||
|
if ( existing != null ) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
final SqmNamedExpression<T> expression = context.registerCopy(
|
||||||
|
this,
|
||||||
|
new SqmNamedExpression<>( this.expression.copy( context ), name )
|
||||||
|
);
|
||||||
|
copyTo( expression, context );
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqmExpression<T> getExpression() {
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <X> X accept(SemanticQueryWalker<X> walker) {
|
||||||
|
return walker.visitNamedExpression( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendHqlString(StringBuilder sb) {
|
||||||
|
expression.appendHqlString( sb );
|
||||||
|
sb.append( " as " );
|
||||||
|
sb.append( name );
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,4 +36,11 @@ public class AliasedExpression implements SelfRenderingExpression {
|
||||||
return expression.getExpressionType();
|
return expression.getExpressionType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Expression getExpression() {
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.SupportsXmlforest.class)
|
||||||
|
public class XmlForestTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimple(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
//tag::hql-xmlforest-example[]
|
||||||
|
em.createQuery( "select xmlforest(123 as e1, 'text' as e2)" ).getResultList();
|
||||||
|
//end::hql-xmlforest-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -153,6 +153,23 @@ public class XmlFunctionTests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlforest.class)
|
||||||
|
public void testXmlforest(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
Tuple tuple = session.createQuery(
|
||||||
|
"select xmlforest(123 as e1, 'text' as e2)," +
|
||||||
|
"xmlforest(e.id, e.theString) " +
|
||||||
|
"from EntityOfBasics e where e.id = 1",
|
||||||
|
Tuple.class
|
||||||
|
).getSingleResult();
|
||||||
|
assertXmlEquals( "<r><e1>123</e1><e2>text</e2></r>", "<r>" + tuple.get( 0, String.class ) + "</r>" );
|
||||||
|
assertXmlEquals( "<r><id>1</id><theString>Dog</theString></r>", "<r>" + tuple.get( 1, String.class ) + "</r>" );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertXmlEquals(String expected, String actual) {
|
private void assertXmlEquals(String expected, String actual) {
|
||||||
final Document expectedDoc = parseXml( xmlNormalize( expected ) );
|
final Document expectedDoc = parseXml( xmlNormalize( expected ) );
|
||||||
final Document actualDoc = parseXml( xmlNormalize( actual ) );
|
final Document actualDoc = parseXml( xmlNormalize( actual ) );
|
||||||
|
|
|
@ -850,6 +850,12 @@ abstract public class DialectFeatureChecks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SupportsXmlforest implements DialectFeatureCheck {
|
||||||
|
public boolean apply(Dialect dialect) {
|
||||||
|
return definesFunction( dialect, "xmlforest" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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