HHH-18497 Add xmlagg function
This commit is contained in:
parent
af09813ced
commit
3b07ed91c3
|
@ -2178,6 +2178,7 @@ it is necessary to enable the `hibernate.query.hql.xml_functions_enabled` config
|
||||||
| `xmlpi()` | Constructs an XML processing instruction
|
| `xmlpi()` | Constructs an XML processing instruction
|
||||||
| `xmlquery()` | Extracts content from XML document using XQuery or XPath
|
| `xmlquery()` | Extracts content from XML document using XQuery or XPath
|
||||||
| `xmlexists()` | Checks if an XQuery or XPath expression exists in an XML document
|
| `xmlexists()` | Checks if an XQuery or XPath expression exists in an XML document
|
||||||
|
| `xmlagg()` | Aggregates XML elements by concatenation
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -2350,6 +2351,30 @@ include::{xml-example-dir-hql}/XmlExistsTest.java[tags=hql-xmlexists-example]
|
||||||
|
|
||||||
WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function.
|
WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function.
|
||||||
|
|
||||||
|
[[hql-xmlagg-function]]
|
||||||
|
===== `xmlagg()`
|
||||||
|
|
||||||
|
Aggregates XML elements by concatenation.
|
||||||
|
|
||||||
|
[[hql-xmlexists-bnf]]
|
||||||
|
[source, antlrv4, indent=0]
|
||||||
|
----
|
||||||
|
include::{extrasdir}/xmlagg_bnf.txt[]
|
||||||
|
----
|
||||||
|
|
||||||
|
This aggregate function is similar to an <<hql-aggregate-functions-orderedset,_ordered set aggregate function_>>
|
||||||
|
since it allows to specify the order in which elements are aggregated, but uses a special syntax.
|
||||||
|
|
||||||
|
[[hql-xmlagg-example]]
|
||||||
|
====
|
||||||
|
[source, java, indent=0]
|
||||||
|
----
|
||||||
|
include::{xml-example-dir-hql}/XmlAggTest.java[tags=hql-xmlagg-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 @@
|
||||||
|
"xmlagg(" expression orderByClause? ")" filterClause? overClause?
|
|
@ -448,6 +448,7 @@ public class DB2LegacyDialect extends Dialect {
|
||||||
functionFactory.xmlpi();
|
functionFactory.xmlpi();
|
||||||
functionFactory.xmlquery_db2();
|
functionFactory.xmlquery_db2();
|
||||||
functionFactory.xmlexists();
|
functionFactory.xmlexists();
|
||||||
|
functionFactory.xmlagg();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -333,6 +333,7 @@ public class OracleLegacyDialect extends Dialect {
|
||||||
functionFactory.xmlpi();
|
functionFactory.xmlpi();
|
||||||
functionFactory.xmlquery_oracle();
|
functionFactory.xmlquery_oracle();
|
||||||
functionFactory.xmlexists();
|
functionFactory.xmlexists();
|
||||||
|
functionFactory.xmlagg();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -676,6 +676,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
|
||||||
functionFactory.xmlpi();
|
functionFactory.xmlpi();
|
||||||
functionFactory.xmlquery_postgresql();
|
functionFactory.xmlquery_postgresql();
|
||||||
functionFactory.xmlexists();
|
functionFactory.xmlexists();
|
||||||
|
functionFactory.xmlagg();
|
||||||
|
|
||||||
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
if ( getVersion().isSameOrAfter( 9, 4 ) ) {
|
||||||
functionFactory.makeDateTimeTimestamp();
|
functionFactory.makeDateTimeTimestamp();
|
||||||
|
|
|
@ -420,6 +420,7 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
||||||
functionFactory.xmlpi_sqlserver();
|
functionFactory.xmlpi_sqlserver();
|
||||||
functionFactory.xmlquery_sqlserver();
|
functionFactory.xmlquery_sqlserver();
|
||||||
functionFactory.xmlexists_sqlserver();
|
functionFactory.xmlexists_sqlserver();
|
||||||
|
functionFactory.xmlagg_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 ) );
|
||||||
|
|
|
@ -330,6 +330,7 @@ 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];
|
||||||
|
XMLAGG : [xX] [mM] [lL] [aA] [gG] [gG];
|
||||||
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];
|
||||||
XMLEXISTS : [xX] [mM] [lL] [eE] [xX] [iI] [sS] [tT] [sS];
|
XMLEXISTS : [xX] [mM] [lL] [eE] [xX] [iI] [sS] [tT] [sS];
|
||||||
|
|
|
@ -1723,6 +1723,7 @@ xmlFunction
|
||||||
| xmlpiFunction
|
| xmlpiFunction
|
||||||
| xmlqueryFunction
|
| xmlqueryFunction
|
||||||
| xmlexistsFunction
|
| xmlexistsFunction
|
||||||
|
| xmlaggFunction
|
||||||
;
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1767,6 +1768,13 @@ xmlexistsFunction
|
||||||
: XMLEXISTS LEFT_PAREN expression PASSING expression RIGHT_PAREN
|
: XMLEXISTS LEFT_PAREN expression PASSING expression RIGHT_PAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'xmlexists()' function
|
||||||
|
*/
|
||||||
|
xmlaggFunction
|
||||||
|
: XMLAGG LEFT_PAREN expression orderByClause? RIGHT_PAREN filterClause? overClause?
|
||||||
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for "soft" keywords which may be used as identifiers
|
* Support for "soft" keywords which may be used as identifiers
|
||||||
*
|
*
|
||||||
|
@ -1973,6 +1981,7 @@ xmlexistsFunction
|
||||||
| WITHIN
|
| WITHIN
|
||||||
| WITHOUT
|
| WITHOUT
|
||||||
| WRAPPER
|
| WRAPPER
|
||||||
|
| XMLAGG
|
||||||
| XMLATTRIBUTES
|
| XMLATTRIBUTES
|
||||||
| XMLELEMENT
|
| XMLELEMENT
|
||||||
| XMLEXISTS
|
| XMLEXISTS
|
||||||
|
|
|
@ -433,6 +433,7 @@ public class DB2Dialect extends Dialect {
|
||||||
functionFactory.xmlpi();
|
functionFactory.xmlpi();
|
||||||
functionFactory.xmlquery_db2();
|
functionFactory.xmlquery_db2();
|
||||||
functionFactory.xmlexists();
|
functionFactory.xmlexists();
|
||||||
|
functionFactory.xmlagg();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -423,6 +423,7 @@ public class OracleDialect extends Dialect {
|
||||||
functionFactory.xmlpi();
|
functionFactory.xmlpi();
|
||||||
functionFactory.xmlquery_oracle();
|
functionFactory.xmlquery_oracle();
|
||||||
functionFactory.xmlexists();
|
functionFactory.xmlexists();
|
||||||
|
functionFactory.xmlagg();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -637,6 +637,7 @@ public class PostgreSQLDialect extends Dialect {
|
||||||
functionFactory.xmlpi();
|
functionFactory.xmlpi();
|
||||||
functionFactory.xmlquery_postgresql();
|
functionFactory.xmlquery_postgresql();
|
||||||
functionFactory.xmlexists();
|
functionFactory.xmlexists();
|
||||||
|
functionFactory.xmlagg();
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -438,6 +438,7 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
||||||
functionFactory.xmlpi_sqlserver();
|
functionFactory.xmlpi_sqlserver();
|
||||||
functionFactory.xmlquery_sqlserver();
|
functionFactory.xmlquery_sqlserver();
|
||||||
functionFactory.xmlexists_sqlserver();
|
functionFactory.xmlexists_sqlserver();
|
||||||
|
functionFactory.xmlagg_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 ) );
|
||||||
|
|
|
@ -158,13 +158,17 @@ import org.hibernate.dialect.function.xml.H2XmlElementFunction;
|
||||||
import org.hibernate.dialect.function.xml.H2XmlForestFunction;
|
import org.hibernate.dialect.function.xml.H2XmlForestFunction;
|
||||||
import org.hibernate.dialect.function.xml.H2XmlPiFunction;
|
import org.hibernate.dialect.function.xml.H2XmlPiFunction;
|
||||||
import org.hibernate.dialect.function.xml.PostgreSQLXmlQueryFunction;
|
import org.hibernate.dialect.function.xml.PostgreSQLXmlQueryFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.SQLServerXmlAggFunction;
|
||||||
import org.hibernate.dialect.function.xml.SQLServerXmlConcatFunction;
|
import org.hibernate.dialect.function.xml.SQLServerXmlConcatFunction;
|
||||||
import org.hibernate.dialect.function.xml.SQLServerXmlElementFunction;
|
import org.hibernate.dialect.function.xml.SQLServerXmlElementFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.SQLServerXmlExistsFunction;
|
||||||
import org.hibernate.dialect.function.xml.SQLServerXmlForestFunction;
|
import org.hibernate.dialect.function.xml.SQLServerXmlForestFunction;
|
||||||
import org.hibernate.dialect.function.xml.SQLServerXmlPiFunction;
|
import org.hibernate.dialect.function.xml.SQLServerXmlPiFunction;
|
||||||
import org.hibernate.dialect.function.xml.SQLServerXmlQueryFunction;
|
import org.hibernate.dialect.function.xml.SQLServerXmlQueryFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.XmlAggFunction;
|
||||||
import org.hibernate.dialect.function.xml.XmlConcatFunction;
|
import org.hibernate.dialect.function.xml.XmlConcatFunction;
|
||||||
import org.hibernate.dialect.function.xml.XmlElementFunction;
|
import org.hibernate.dialect.function.xml.XmlElementFunction;
|
||||||
|
import org.hibernate.dialect.function.xml.XmlExistsFunction;
|
||||||
import org.hibernate.dialect.function.xml.XmlForestFunction;
|
import org.hibernate.dialect.function.xml.XmlForestFunction;
|
||||||
import org.hibernate.dialect.function.xml.XmlPiFunction;
|
import org.hibernate.dialect.function.xml.XmlPiFunction;
|
||||||
import org.hibernate.dialect.function.xml.XmlQueryFunction;
|
import org.hibernate.dialect.function.xml.XmlQueryFunction;
|
||||||
|
@ -4246,4 +4250,32 @@ public class CommonFunctionFactory {
|
||||||
public void xmlquery_sqlserver() {
|
public void xmlquery_sqlserver() {
|
||||||
functionRegistry.register( "xmlquery", new SQLServerXmlQueryFunction( typeConfiguration ) );
|
functionRegistry.register( "xmlquery", new SQLServerXmlQueryFunction( typeConfiguration ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard xmlexists() function
|
||||||
|
*/
|
||||||
|
public void xmlexists() {
|
||||||
|
functionRegistry.register( "xmlexists", new XmlExistsFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server xmlexists() function
|
||||||
|
*/
|
||||||
|
public void xmlexists_sqlserver() {
|
||||||
|
functionRegistry.register( "xmlexists", new SQLServerXmlExistsFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard xmlagg() function
|
||||||
|
*/
|
||||||
|
public void xmlagg() {
|
||||||
|
functionRegistry.register( "xmlagg", new XmlAggFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server xmlagg() function
|
||||||
|
*/
|
||||||
|
public void xmlagg_sqlserver() {
|
||||||
|
functionRegistry.register( "xmlagg", new SQLServerXmlAggFunction( typeConfiguration ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.dialect.function.xml;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hibernate.dialect.function.json.ExpressionTypeHelper;
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.query.spi.QueryEngine;
|
||||||
|
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
|
||||||
|
import org.hibernate.query.sqm.function.SelfRenderingOrderedSetAggregateFunctionSqlAstExpression;
|
||||||
|
import org.hibernate.query.sqm.function.SelfRenderingSqmOrderedSetAggregateFunction;
|
||||||
|
import org.hibernate.query.sqm.spi.SqmCreationHelper;
|
||||||
|
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||||
|
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||||
|
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
|
||||||
|
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
|
||||||
|
import org.hibernate.spi.NavigablePath;
|
||||||
|
import org.hibernate.sql.ast.Clause;
|
||||||
|
import org.hibernate.sql.ast.SqlAstJoinType;
|
||||||
|
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||||
|
import org.hibernate.sql.ast.spi.AbstractSqlAstWalker;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||||
|
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.ColumnReference;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Distinct;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.FunctionExpression;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
|
||||||
|
import org.hibernate.sql.ast.tree.from.FunctionTableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||||
|
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||||
|
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server xmlagg function.
|
||||||
|
*/
|
||||||
|
public class SQLServerXmlAggFunction extends XmlAggFunction {
|
||||||
|
|
||||||
|
public SQLServerXmlAggFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super( typeConfiguration );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> SelfRenderingSqmOrderedSetAggregateFunction<T> generateSqmOrderedSetAggregateFunctionExpression(
|
||||||
|
List<? extends SqmTypedNode<?>> arguments,
|
||||||
|
SqmPredicate filter,
|
||||||
|
SqmOrderByClause withinGroupClause,
|
||||||
|
ReturnableType<T> impliedResultType,
|
||||||
|
QueryEngine queryEngine) {
|
||||||
|
return new SelfRenderingSqmOrderedSetAggregateFunction<>(
|
||||||
|
this,
|
||||||
|
this,
|
||||||
|
arguments,
|
||||||
|
filter,
|
||||||
|
withinGroupClause,
|
||||||
|
impliedResultType,
|
||||||
|
getArgumentsValidator(),
|
||||||
|
getReturnTypeResolver(),
|
||||||
|
queryEngine.getCriteriaBuilder(),
|
||||||
|
getName()
|
||||||
|
) {
|
||||||
|
@Override
|
||||||
|
public Expression convertToSqlAst(SqmToSqlAstConverter walker) {
|
||||||
|
// SQL Server can't aggregate an argument that contains a subquery,
|
||||||
|
// which is a bummer because xmlelement and xmlforest implementations require subqueries,
|
||||||
|
// but we can apply a trick to make this still work.
|
||||||
|
// Essentially, we try to move the subquery into the from clause and mark it as lateral.
|
||||||
|
// Then we can replace the original expression with that new table reference.
|
||||||
|
final SelfRenderingOrderedSetAggregateFunctionSqlAstExpression expression = (SelfRenderingOrderedSetAggregateFunctionSqlAstExpression) super.convertToSqlAst( walker );
|
||||||
|
final Expression xml = (Expression) expression.getArguments().get( 0 );
|
||||||
|
final Set<String> qualifiers = ColumnQualifierCollectorSqlAstWalker.determineColumnQualifiers( xml );
|
||||||
|
// If the argument contains a subquery, we will receive the column qualifiers that are used
|
||||||
|
if ( !qualifiers.isEmpty() ) {
|
||||||
|
// Register a query transformer to register the lateral table group join
|
||||||
|
walker.registerQueryTransformer( (cteContainer, querySpec, converter) -> {
|
||||||
|
// Find the table group which the subquery refers to
|
||||||
|
final TableGroup tableGroup = querySpec.getFromClause().queryTableGroups(
|
||||||
|
tg -> {
|
||||||
|
final String primaryVariable = tg.getPrimaryTableReference()
|
||||||
|
.getIdentificationVariable();
|
||||||
|
if ( qualifiers.contains( primaryVariable ) ) {
|
||||||
|
return tg;
|
||||||
|
}
|
||||||
|
for ( TableReferenceJoin tableReferenceJoin : tg.getTableReferenceJoins() ) {
|
||||||
|
final String variable = tableReferenceJoin.getJoinedTableReference()
|
||||||
|
.getIdentificationVariable();
|
||||||
|
if ( qualifiers.contains( variable ) ) {
|
||||||
|
return tg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if ( tableGroup != null ) {
|
||||||
|
// Generate the lateral subquery
|
||||||
|
final String alias = "gen" + SqmCreationHelper.acquireUniqueAlias();
|
||||||
|
final FunctionTableGroup lateralGroup = new FunctionTableGroup(
|
||||||
|
new NavigablePath( "generated", alias ),
|
||||||
|
null,
|
||||||
|
new SelfRenderingFunctionSqlAstExpression(
|
||||||
|
"helper",
|
||||||
|
(sqlAppender, sqlAstArguments, returnType, walker1) -> {
|
||||||
|
sqlAppender.appendSql( "(select " );
|
||||||
|
xml.accept( walker1 );
|
||||||
|
sqlAppender.appendSql( " v)" );
|
||||||
|
},
|
||||||
|
List.of(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
alias,
|
||||||
|
List.of("v"),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
tableGroup.addTableGroupJoin(
|
||||||
|
new TableGroupJoin(
|
||||||
|
lateralGroup.getNavigablePath(),
|
||||||
|
SqlAstJoinType.INNER,
|
||||||
|
lateralGroup
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Replace the original expression that contains a subquery with a simple column reference,
|
||||||
|
// that points to the newly created lateral table group
|
||||||
|
//noinspection unchecked
|
||||||
|
( (List<SqlAstNode>) expression.getArguments() ).set(
|
||||||
|
0,
|
||||||
|
new ColumnReference(
|
||||||
|
lateralGroup.getPrimaryTableReference(),
|
||||||
|
"v",
|
||||||
|
expression.getJdbcMapping()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return querySpec;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ColumnQualifierCollectorSqlAstWalker extends AbstractSqlAstWalker {
|
||||||
|
|
||||||
|
private static final Set<String> POTENTIAL_SUBQUERY_FUNCTIONS = Set.of(
|
||||||
|
"xmlelement",
|
||||||
|
"xmlforest"
|
||||||
|
);
|
||||||
|
private final Set<String> columnQualifiers = new HashSet<>();
|
||||||
|
private boolean potentialSubquery;
|
||||||
|
|
||||||
|
public static Set<String> determineColumnQualifiers(SqlAstNode node) {
|
||||||
|
final ColumnQualifierCollectorSqlAstWalker walker = new ColumnQualifierCollectorSqlAstWalker();
|
||||||
|
node.accept( walker );
|
||||||
|
return walker.potentialSubquery ? walker.columnQualifiers : Set.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitSelfRenderingExpression(SelfRenderingExpression expression) {
|
||||||
|
if ( expression instanceof FunctionExpression functionExpression
|
||||||
|
&& POTENTIAL_SUBQUERY_FUNCTIONS.contains( functionExpression.getFunctionName() ) ) {
|
||||||
|
potentialSubquery = true;
|
||||||
|
}
|
||||||
|
super.visitSelfRenderingExpression( expression );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitColumnReference(ColumnReference columnReference) {
|
||||||
|
if ( columnReference.getQualifier() != null ) {
|
||||||
|
columnQualifiers.add( columnReference.getQualifier() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
Predicate filter,
|
||||||
|
List<SortSpecification> withinGroup,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> translator) {
|
||||||
|
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
|
||||||
|
sqlAppender.appendSql( "cast(string_agg(" );
|
||||||
|
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
|
||||||
|
final Expression arg;
|
||||||
|
if ( firstArg instanceof Distinct ) {
|
||||||
|
sqlAppender.appendSql( "distinct " );
|
||||||
|
arg = ( (Distinct) firstArg ).getExpression();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
arg = (Expression) firstArg;
|
||||||
|
}
|
||||||
|
final boolean needsCast = ExpressionTypeHelper.isXml( arg );
|
||||||
|
if ( needsCast ) {
|
||||||
|
sqlAppender.appendSql( "cast(" );
|
||||||
|
}
|
||||||
|
if ( caseWrapper ) {
|
||||||
|
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||||
|
sqlAppender.appendSql( "case when " );
|
||||||
|
filter.accept( translator );
|
||||||
|
sqlAppender.appendSql( " then " );
|
||||||
|
arg.accept( translator );
|
||||||
|
sqlAppender.appendSql( " else null end" );
|
||||||
|
translator.getCurrentClauseStack().pop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
arg.accept( translator );
|
||||||
|
}
|
||||||
|
if ( needsCast ) {
|
||||||
|
sqlAppender.appendSql( " as nvarchar(max))" );
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( ",'')" );
|
||||||
|
if ( withinGroup != null && !withinGroup.isEmpty() ) {
|
||||||
|
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
|
||||||
|
sqlAppender.appendSql( " within group (order by " );
|
||||||
|
withinGroup.get( 0 ).accept( translator );
|
||||||
|
for ( int i = 1; i < withinGroup.size(); i++ ) {
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
withinGroup.get( i ).accept( translator );
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
translator.getCurrentClauseStack().pop();
|
||||||
|
}
|
||||||
|
if ( !caseWrapper && filter != null ) {
|
||||||
|
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||||
|
sqlAppender.appendSql( " filter (where " );
|
||||||
|
filter.accept( translator );
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
translator.getCurrentClauseStack().pop();
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( " as xml)" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
* Copyright Red Hat Inc. and Hibernate Authors
|
||||||
|
*/
|
||||||
|
package org.hibernate.dialect.function.xml;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.dialect.function.json.ExpressionTypeHelper;
|
||||||
|
import org.hibernate.query.ReturnableType;
|
||||||
|
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
||||||
|
import org.hibernate.query.sqm.function.FunctionKind;
|
||||||
|
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||||
|
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||||
|
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||||
|
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||||
|
import org.hibernate.sql.ast.Clause;
|
||||||
|
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.Distinct;
|
||||||
|
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||||
|
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||||
|
import org.hibernate.sql.ast.tree.select.SortSpecification;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
import org.hibernate.type.spi.TypeConfiguration;
|
||||||
|
|
||||||
|
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.XML;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard xmlagg function.
|
||||||
|
*/
|
||||||
|
public class XmlAggFunction extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||||
|
|
||||||
|
public XmlAggFunction(TypeConfiguration typeConfiguration) {
|
||||||
|
super(
|
||||||
|
"xmlagg",
|
||||||
|
FunctionKind.ORDERED_SET_AGGREGATE,
|
||||||
|
StandardArgumentsValidators.composite(
|
||||||
|
new ArgumentTypesValidator( null, XML )
|
||||||
|
),
|
||||||
|
StandardFunctionReturnTypeResolvers.invariant(
|
||||||
|
typeConfiguration.getBasicTypeRegistry().resolve( String.class, SqlTypes.SQLXML )
|
||||||
|
),
|
||||||
|
StandardFunctionArgumentTypeResolvers.impliedOrInvariant( typeConfiguration, XML )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
render( sqlAppender, sqlAstArguments, null, Collections.emptyList(), returnType, walker );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
Predicate filter,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> walker) {
|
||||||
|
render( sqlAppender, sqlAstArguments, filter, Collections.emptyList(), returnType, walker );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(
|
||||||
|
SqlAppender sqlAppender,
|
||||||
|
List<? extends SqlAstNode> sqlAstArguments,
|
||||||
|
Predicate filter,
|
||||||
|
List<SortSpecification> withinGroup,
|
||||||
|
ReturnableType<?> returnType,
|
||||||
|
SqlAstTranslator<?> translator) {
|
||||||
|
final boolean caseWrapper = filter != null && !translator.supportsFilterClause();
|
||||||
|
sqlAppender.appendSql( "xmlagg(" );
|
||||||
|
final SqlAstNode firstArg = sqlAstArguments.get( 0 );
|
||||||
|
final Expression arg;
|
||||||
|
if ( firstArg instanceof Distinct ) {
|
||||||
|
sqlAppender.appendSql( "distinct " );
|
||||||
|
arg = ( (Distinct) firstArg ).getExpression();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
arg = (Expression) firstArg;
|
||||||
|
}
|
||||||
|
final boolean needsCast = !ExpressionTypeHelper.isXml( arg );
|
||||||
|
if ( needsCast ) {
|
||||||
|
sqlAppender.appendSql( "cast(" );
|
||||||
|
}
|
||||||
|
if ( caseWrapper ) {
|
||||||
|
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||||
|
sqlAppender.appendSql( "case when " );
|
||||||
|
filter.accept( translator );
|
||||||
|
sqlAppender.appendSql( " then " );
|
||||||
|
arg.accept( translator );
|
||||||
|
sqlAppender.appendSql( " else null end" );
|
||||||
|
translator.getCurrentClauseStack().pop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
arg.accept( translator );
|
||||||
|
}
|
||||||
|
if ( needsCast ) {
|
||||||
|
sqlAppender.appendSql( " as xml)" );
|
||||||
|
}
|
||||||
|
if ( withinGroup != null && !withinGroup.isEmpty() ) {
|
||||||
|
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
|
||||||
|
sqlAppender.appendSql( " order by " );
|
||||||
|
withinGroup.get( 0 ).accept( translator );
|
||||||
|
for ( int i = 1; i < withinGroup.size(); i++ ) {
|
||||||
|
sqlAppender.appendSql( ',' );
|
||||||
|
withinGroup.get( i ).accept( translator );
|
||||||
|
}
|
||||||
|
translator.getCurrentClauseStack().pop();
|
||||||
|
}
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
if ( !caseWrapper && filter != null ) {
|
||||||
|
translator.getCurrentClauseStack().push( Clause.WHERE );
|
||||||
|
sqlAppender.appendSql( " filter (where " );
|
||||||
|
filter.accept( translator );
|
||||||
|
sqlAppender.appendSql( ')' );
|
||||||
|
translator.getCurrentClauseStack().pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4145,6 +4145,43 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
|
||||||
@Incubating
|
@Incubating
|
||||||
JpaExpression<Boolean> xmlexists(Expression<String> query, Expression<?> xmlDocument);
|
JpaExpression<Boolean> xmlexists(Expression<String> query, Expression<?> xmlDocument);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #xmlagg(JpaOrder, JpaPredicate, JpaWindow, Expression)
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> xmlagg(JpaOrder order, Expression<?> argument);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #xmlagg(JpaOrder, JpaPredicate, JpaWindow, Expression)
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> xmlagg(JpaOrder order, JpaPredicate filter, Expression<?> argument);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #xmlagg(JpaOrder, JpaPredicate, JpaWindow, Expression)
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> xmlagg(JpaOrder order, JpaWindow window, Expression<?> argument);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@code xmlagg} ordered set-aggregate function expression.
|
||||||
|
*
|
||||||
|
* @param order order by clause used in within group
|
||||||
|
* @param filter optional filter clause
|
||||||
|
* @param window optional window over which to apply the function
|
||||||
|
* @param argument values to join
|
||||||
|
*
|
||||||
|
* @return ordered set-aggregate expression
|
||||||
|
*
|
||||||
|
* @see #functionWithinGroup(String, Class, JpaOrder, JpaPredicate, JpaWindow, Expression...)
|
||||||
|
*/
|
||||||
|
@Incubating
|
||||||
|
JpaExpression<String> xmlagg(
|
||||||
|
JpaOrder order,
|
||||||
|
JpaPredicate filter,
|
||||||
|
JpaWindow window,
|
||||||
|
Expression<?> argument);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a named expression. The name is important for the result of the expression,
|
* 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.
|
* e.g. when building an {@code xmlforest}, the name acts as the XML element name.
|
||||||
|
|
|
@ -3705,6 +3705,30 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
|
||||||
return criteriaBuilder.xmlexists( query, xmlDocument );
|
return criteriaBuilder.xmlexists( query, xmlDocument );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> xmlagg(JpaOrder order, Expression<?> argument) {
|
||||||
|
return criteriaBuilder.xmlagg( order, argument );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> xmlagg(JpaOrder order, JpaPredicate filter, Expression<?> argument) {
|
||||||
|
return criteriaBuilder.xmlagg( order, filter, argument );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> xmlagg(JpaOrder order, JpaWindow window, Expression<?> argument) {
|
||||||
|
return criteriaBuilder.xmlagg( order, window, argument );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Incubating
|
||||||
|
public JpaExpression<String> xmlagg(JpaOrder order, JpaPredicate filter, JpaWindow window, Expression<?> argument) {
|
||||||
|
return criteriaBuilder.xmlagg( order, filter, window, argument );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Incubating
|
@Incubating
|
||||||
public <T> JpaExpression<T> named(Expression<T> expression, String name) {
|
public <T> JpaExpression<T> named(Expression<T> expression, String name) {
|
||||||
|
|
|
@ -3059,6 +3059,26 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
||||||
return creationContext.getNodeBuilder().xmlexists( query, xmlDocument );
|
return creationContext.getNodeBuilder().xmlexists( query, xmlDocument );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<?> visitXmlaggFunction(HqlParser.XmlaggFunctionContext ctx) {
|
||||||
|
checkXmlFunctionsEnabled( ctx );
|
||||||
|
final ArrayList<SqmTypedNode<?>> arguments = new ArrayList<>( 1 );
|
||||||
|
arguments.add( (SqmTypedNode<?>) ctx.expression().accept( this ) );
|
||||||
|
|
||||||
|
return applyOverClause(
|
||||||
|
ctx.overClause(),
|
||||||
|
getFunctionDescriptor( "xmlagg" ).generateOrderedSetAggregateSqmExpression(
|
||||||
|
arguments,
|
||||||
|
getFilterExpression( ctx ),
|
||||||
|
ctx.orderByClause() == null
|
||||||
|
? null
|
||||||
|
: visitOrderByClause( ctx.orderByClause(), false ),
|
||||||
|
null,
|
||||||
|
creationContext.getQueryEngine()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkXmlFunctionsEnabled(ParserRuleContext ctx) {
|
private void checkXmlFunctionsEnabled(ParserRuleContext ctx) {
|
||||||
if ( !creationOptions.isXmlFunctionsEnabled() ) {
|
if ( !creationOptions.isXmlFunctionsEnabled() ) {
|
||||||
throw new SemanticException(
|
throw new SemanticException(
|
||||||
|
|
|
@ -789,6 +789,18 @@ public interface NodeBuilder extends HibernateCriteriaBuilder, BindingContext {
|
||||||
@Override
|
@Override
|
||||||
SqmExpression<Boolean> xmlexists(Expression<String> query, Expression<?> xmlDocument);
|
SqmExpression<Boolean> xmlexists(Expression<String> query, Expression<?> xmlDocument);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> xmlagg(JpaOrder order, Expression<?> argument);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> xmlagg(JpaOrder order, JpaPredicate filter, Expression<?> argument);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> xmlagg(JpaOrder order, JpaWindow window, Expression<?> argument);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SqmExpression<String> xmlagg(JpaOrder order, JpaPredicate filter, JpaWindow window, Expression<?> argument);
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Covariant overrides
|
// Covariant overrides
|
||||||
|
|
||||||
|
|
|
@ -201,7 +201,7 @@ public class NamedSqmFunctionDescriptor
|
||||||
|
|
||||||
if ( withinGroup != null && !withinGroup.isEmpty() ) {
|
if ( withinGroup != null && !withinGroup.isEmpty() ) {
|
||||||
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
|
translator.getCurrentClauseStack().push( Clause.WITHIN_GROUP );
|
||||||
sqlAppender.appendSql( " within group (order by" );
|
sqlAppender.appendSql( " within group (order by " );
|
||||||
translator.render( withinGroup.get( 0 ), argumentRenderingMode );
|
translator.render( withinGroup.get( 0 ), argumentRenderingMode );
|
||||||
for ( int i = 1; i < withinGroup.size(); i++ ) {
|
for ( int i = 1; i < withinGroup.size(); i++ ) {
|
||||||
sqlAppender.appendSql( SqlAppender.COMMA_SEPARATOR_CHAR );
|
sqlAppender.appendSql( SqlAppender.COMMA_SEPARATOR_CHAR );
|
||||||
|
|
|
@ -5792,4 +5792,24 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, Serializable {
|
||||||
queryEngine
|
queryEngine
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> xmlagg(JpaOrder order, Expression<?> argument) {
|
||||||
|
return xmlagg( order, null, null, argument );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> xmlagg(JpaOrder order, JpaPredicate filter, Expression<?> argument) {
|
||||||
|
return xmlagg( order, filter, null, argument );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> xmlagg(JpaOrder order, JpaWindow window, Expression<?> argument) {
|
||||||
|
return xmlagg( order, null, window, argument );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqmExpression<String> xmlagg(JpaOrder order, JpaPredicate filter, JpaWindow window, Expression<?> argument) {
|
||||||
|
return functionWithinGroup( "xmlagg", String.class, order, filter, window, argument );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class FunctionTableReference extends DerivedTableReference {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(SqlAstWalker sqlTreeWalker) {
|
public void accept(SqlAstWalker sqlTreeWalker) {
|
||||||
functionExpression.accept( sqlTreeWalker );
|
sqlTreeWalker.visitFunctionTableReference( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.domain.StandardDomainModel;
|
||||||
|
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(standardModels = StandardDomainModel.GAMBIT)
|
||||||
|
@SessionFactory
|
||||||
|
@ServiceRegistry(settings = @Setting(name = QuerySettings.XML_FUNCTIONS_ENABLED, value = "true"))
|
||||||
|
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsXmlagg.class)
|
||||||
|
public class XmlAggTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimple(SessionFactoryScope scope) {
|
||||||
|
scope.inSession( em -> {
|
||||||
|
//tag::hql-xmlagg-example[]
|
||||||
|
em.createQuery( "select xmlagg(xmlelement(name a, e.theString) order by e.id) from EntityOfBasics e" ).getResultList();
|
||||||
|
//end::hql-xmlagg-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -230,6 +230,21 @@ public class XmlFunctionTests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlagg.class)
|
||||||
|
public void testXmlagg(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction(
|
||||||
|
session -> {
|
||||||
|
Tuple tuple = session.createQuery(
|
||||||
|
"select xmlagg(xmlelement(name a, e.theString) order by e.id) " +
|
||||||
|
"from from EntityOfBasics e",
|
||||||
|
Tuple.class
|
||||||
|
).getSingleResult();
|
||||||
|
assertXmlEquals( "<r><a>Dog</a><a>Cat</a></r>", "<r>" + tuple.get( 0, 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 ) );
|
||||||
|
|
|
@ -880,6 +880,12 @@ abstract public class DialectFeatureChecks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SupportsXmlagg implements DialectFeatureCheck {
|
||||||
|
public boolean apply(Dialect dialect) {
|
||||||
|
return definesFunction( dialect, "xmlagg" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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