Skip to content

Commit

Permalink
HHH-18759 Add xmltable() set-returning function
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Oct 31, 2024
1 parent 8dd5094 commit 4658e47
Show file tree
Hide file tree
Showing 45 changed files with 2,602 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2257,14 +2257,15 @@ it is necessary to enable the `hibernate.query.hql.xml_functions_enabled` config
|===
| Function | Purpose
| `xmlelement()` | Constructs an XML element from arguments
| `xmlcomment()` | Constructs an XML comment from the single argument
| `xmlforest()` | Constructs an XML forest from the arguments
| `xmlconcat()` | Concatenates multiple XML fragments to each other
| `xmlpi()` | Constructs an XML processing instruction
| `xmlquery()` | Extracts content from XML document using XQuery or XPath
| `xmlexists()` | Checks if an XQuery or XPath expression exists in an XML document
| `xmlagg()` | Aggregates XML elements by concatenation
| <<hql-xmlelement-function,`xmlelement()`>> | Constructs an XML element from arguments
| <<hql-xmlcomment-function,`xmlcomment()`>> | Constructs an XML comment from the single argument
| <<hql-xmlforest-function,`xmlforest()`>> | Constructs an XML forest from the arguments
| <<hql-xmlconcat-function,`xmlconcat()`>> | Concatenates multiple XML fragments to each other
| <<hql-xmlpi-function,`xmlpi()`>> | Constructs an XML processing instruction
| <<hql-xmlquery-function,`xmlquery()`>> | Extracts content from XML document using XQuery or XPath
| <<hql-xmlexists-function,`xmlexists()`>> | Checks if an XQuery or XPath expression exists in an XML document
| <<hql-xmlagg-function,`xmlagg()`>> | Aggregates XML elements by concatenation
| <<hql-xmltable-function,`xmltable()`>> | Turns an XML document into rows
|===
Expand Down Expand Up @@ -2461,6 +2462,39 @@ include::{xml-example-dir-hql}/XmlAggTest.java[tags=hql-xmlagg-example]
WARNING: SAP HANA, MySQL, MariaDB and HSQLDB do not support this function.
[[hql-xmltable-function]]
===== `xmltable()`
A <<hql-from-set-returning-functions,set-returning function>>, which turns an XML document argument into rows.
Returns no rows if the document is `null` or the XPath expression resolves to no nodes.
[[hql-xmltable-bnf]]
[source, antlrv4, indent=0]
----
include::{extrasdir}/xmltable_bnf.txt[]
----
The first argument is the XPath expression. The second argument represents the XML document expression.
Columns that ought to be accessible via the `from` node alias are defined in the `columns` clause,
which can be of varying forms:
* Value attributes - denoted by a `castTarget` after the name, will cast the content of the XML node matching the XPath expression of the column
* Query attributes - denoted by the `xml` type after the name, returns the XML node matching the XPath expression of the column
* Ordinal attributes - denoted by the `for ordinality` syntax after the name, gives access to the 1-based index of the currently processed XML node
[[hql-xmltable-simple-example]]
====
[source, java, indent=0]
----
include::{xml-example-dir-hql}/XmlTableTest.java[tags=hql-xml-table-example]
----
====
The `lateral` keyword is mandatory if one of the arguments refer to a from node item of the same query level.
WARNING: H2, MySQL, MariaDB and HSQLDB do not support this function.
[[hql-user-defined-functions]]
==== Native and user-defined functions
Expand Down Expand Up @@ -3001,7 +3035,8 @@ The following set-returning functions are available on many platforms:
| <<hql-array-unnest,`unnest()`>> | Turns an array into rows
| <<hql-from-set-returning-functions-generate-series,`generate_series()`>> | Creates a series of values as rows
| <<hql-json-table,`json_table()`>> | Turns a JSON document into rows
| <<hql-json-table-function,`json_table()`>> | Turns a JSON document into rows
| <<hql-xmltable-function,`xmltable()`>> | Turns an XML document into rows
|===
To use set returning functions defined in the database, it is required to register them in a `FunctionContributor`:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"xmltable(" expression "passing" expression columnsClause ")"

columnsClause
: "columns" column ("," column)*

column
: attributeName "xml" ("path" STRING_LITERAL)? defaultClause?
| attributeName "for ordinality"
| attributeName castTarget ("path" STRING_LITERAL)? defaultClause?

defaultClause
: "default" expression;
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlexists_db2_legacy();
}
functionFactory.xmlagg();
functionFactory.xmltable_db2();

functionFactory.unnest_emulated();
if ( supportsRecursiveCTE() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonObjectAgg_hana();
}

// functionFactory.xmltable();
functionFactory.xmltable_hana();
}

// functionFactory.xmlextract();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlquery_oracle();
functionFactory.xmlexists();
functionFactory.xmlagg();
functionFactory.xmltable_oracle();

functionFactory.unnest_oracle();
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), true, false );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlquery_postgresql();
functionFactory.xmlexists();
functionFactory.xmlagg();
functionFactory.xmltable( true );

if ( getVersion().isSameOrAfter( 9, 4 ) ) {
functionFactory.makeDateTimeTimestamp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlquery_sqlserver();
functionFactory.xmlexists_sqlserver();
functionFactory.xmlagg_sqlserver();
functionFactory.xmltable_sqlserver();

functionFactory.unnest_sqlserver();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio

functionFactory.unnest_sybasease();
functionFactory.generateSeries_sybasease( getMaximumSeriesSize() );
functionFactory.xmltable_sybasease();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,13 +336,15 @@ WITH : [wW] [iI] [tT] [hH];
WITHIN : [wW] [iI] [tT] [hH] [iI] [nN];
WITHOUT : [wW] [iI] [tT] [hH] [oO] [uU] [tT];
WRAPPER : [wW] [rR] [aA] [pP] [pP] [eE] [rR];
XML : [xX] [mM] [lL];
XMLAGG : [xX] [mM] [lL] [aA] [gG] [gG];
XMLATTRIBUTES : [xX] [mM] [lL] [aA] [tT] [tT] [rR] [iI] [bB] [uU] [tT] [eE] [sS];
XMLELEMENT : [xX] [mM] [lL] [eE] [lL] [eE] [mM] [eE] [nN] [tT];
XMLEXISTS : [xX] [mM] [lL] [eE] [xX] [iI] [sS] [tT] [sS];
XMLFOREST : [xX] [mM] [lL] [fF] [oO] [rR] [eE] [sS] [tT];
XMLPI : [xX] [mM] [lL] [pP] [iI];
XMLQUERY : [xX] [mM] [lL] [qQ] [uU] [eE] [rR] [yY];
XMLTABLE : [xX] [mM] [lL] [tT] [aA] [bB] [lL] [eE];
YEAR : [yY] [eE] [aA] [rR];
ZONED : [zZ] [oO] [nN] [eE] [dD];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,7 @@ function
setReturningFunction
: simpleSetReturningFunction
| jsonTableFunction
| xmltableFunction
;

/**
Expand Down Expand Up @@ -1813,6 +1814,24 @@ xmlaggFunction
: XMLAGG LEFT_PAREN expression orderByClause? RIGHT_PAREN filterClause? overClause?
;

xmltableFunction
: XMLTABLE LEFT_PAREN expression PASSING expression xmltableColumnsClause RIGHT_PAREN
;

xmltableColumnsClause
: COLUMNS xmltableColumn (COMMA xmltableColumn)*
;

xmltableColumn
: identifier XML (PATH STRING_LITERAL)? xmltableDefaultClause? # XmlTableQueryColumn
| identifier FOR ORDINALITY # XmlTableOrdinalityColumn
| identifier castTarget (PATH STRING_LITERAL)? xmltableDefaultClause? # XmlTableValueColumn
;

xmltableDefaultClause
: DEFAULT expression
;

/**
* Support for "soft" keywords which may be used as identifiers
*
Expand Down Expand Up @@ -2025,13 +2044,15 @@ xmlaggFunction
| WITHIN
| WITHOUT
| WRAPPER
| XML
| XMLAGG
| XMLATTRIBUTES
| XMLELEMENT
| XMLEXISTS
| XMLFOREST
| XMLPI
| XMLQUERY
| XMLTABLE
| YEAR
| ZONED) {
logUseOfReservedWordAsIdentifier( getCurrentToken() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlexists_db2_legacy();
}
functionFactory.xmlagg();
functionFactory.xmltable_db2();

functionFactory.unnest_emulated();
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), false, true );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.jsonArrayAgg_hana();
functionFactory.jsonObjectAgg_hana();

// functionFactory.xmltable();
functionFactory.xmltable_hana();

// functionFactory.xmlextract();
functionFactory.generateSeries_hana( getMaximumSeriesSize() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlquery_oracle();
functionFactory.xmlexists();
functionFactory.xmlagg();
functionFactory.xmltable_oracle();

functionFactory.unnest_oracle();
functionFactory.generateSeries_recursive( getMaximumSeriesSize(), true, false );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlquery_postgresql();
functionFactory.xmlexists();
functionFactory.xmlagg();
functionFactory.xmltable( true );

functionFactory.makeDateTimeTimestamp();
// Note that PostgreSQL doesn't support the OVER clause for ordered set-aggregate functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.xmlquery_sqlserver();
functionFactory.xmlexists_sqlserver();
functionFactory.xmlagg_sqlserver();
functionFactory.xmltable_sqlserver();

functionFactory.unnest_sqlserver();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio

functionFactory.unnest_sybasease();
functionFactory.generateSeries_sybasease( getMaximumSeriesSize() );
functionFactory.xmltable_sybasease();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@

import org.hibernate.dialect.function.array.*;
import org.hibernate.dialect.function.json.*;
import org.hibernate.dialect.function.xml.DB2XmlTableFunction;
import org.hibernate.dialect.function.xml.H2XmlConcatFunction;
import org.hibernate.dialect.function.xml.H2XmlElementFunction;
import org.hibernate.dialect.function.xml.H2XmlForestFunction;
import org.hibernate.dialect.function.xml.H2XmlPiFunction;
import org.hibernate.dialect.function.xml.HANAXmlTableFunction;
import org.hibernate.dialect.function.xml.LegacyDB2XmlExistsFunction;
import org.hibernate.dialect.function.xml.LegacyDB2XmlQueryFunction;
import org.hibernate.dialect.function.xml.OracleXmlTableFunction;
import org.hibernate.dialect.function.xml.PostgreSQLXmlQueryFunction;
import org.hibernate.dialect.function.xml.SQLServerXmlAggFunction;
import org.hibernate.dialect.function.xml.SQLServerXmlConcatFunction;
Expand All @@ -27,13 +30,16 @@
import org.hibernate.dialect.function.xml.SQLServerXmlForestFunction;
import org.hibernate.dialect.function.xml.SQLServerXmlPiFunction;
import org.hibernate.dialect.function.xml.SQLServerXmlQueryFunction;
import org.hibernate.dialect.function.xml.SQLServerXmlTableFunction;
import org.hibernate.dialect.function.xml.SybaseASEXmlTableFunction;
import org.hibernate.dialect.function.xml.XmlAggFunction;
import org.hibernate.dialect.function.xml.XmlConcatFunction;
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.XmlPiFunction;
import org.hibernate.dialect.function.xml.XmlQueryFunction;
import org.hibernate.dialect.function.xml.XmlTableFunction;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
import org.hibernate.query.sqm.produce.function.FunctionParameterType;
Expand Down Expand Up @@ -4323,4 +4329,46 @@ public void jsonTable_sqlserver() {
public void jsonTable_h2(int maximumArraySize) {
functionRegistry.register( "json_table", new H2JsonTableFunction( maximumArraySize, typeConfiguration ) );
}

/**
* Standard xmltable() function
*/
public void xmltable(boolean supportsParametersInDefault) {
functionRegistry.register( "xmltable", new XmlTableFunction( supportsParametersInDefault, typeConfiguration ) );
}

/**
* Oracle xmltable() function
*/
public void xmltable_oracle() {
functionRegistry.register( "xmltable", new OracleXmlTableFunction( typeConfiguration ) );
}

/**
* DB2 xmltable() function
*/
public void xmltable_db2() {
functionRegistry.register( "xmltable", new DB2XmlTableFunction( typeConfiguration ) );
}

/**
* HANA xmltable() function
*/
public void xmltable_hana() {
functionRegistry.register( "xmltable", new HANAXmlTableFunction( typeConfiguration ) );
}

/**
* SQL Server xmltable() function
*/
public void xmltable_sqlserver() {
functionRegistry.register( "xmltable", new SQLServerXmlTableFunction( typeConfiguration ) );
}

/**
* Sybase ASE xmltable() function
*/
public void xmltable_sybasease() {
functionRegistry.register( "xmltable", new SybaseASEXmlTableFunction( typeConfiguration ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ public void renderToSql(
sessionFactory
);

// Produce a XML string e.g. <root id="1">...</root>
// Produce an XML string e.g. <root id="1">...</root>
// which will contain the original XML as well as id column information for correlation
sqlAppender.appendSql( "trim('/>' from (select" );
char separator = ' ';
Expand All @@ -424,8 +424,9 @@ public void renderToSql(
sqlAppender.appendDoubleQuoteEscapedString( columnInfo.name() );
separator = ',';
}
sqlAppender.appendSql( " from sys.dummy for xml('root'='no','columnstyle'='attribute','rowname'='Strings','format'='no')))||" );
sqlAppender.appendSql( "substring(" );
sqlAppender.appendSql( " from sys.dummy for xml('root'='no','columnstyle'='attribute','rowname'='" );
sqlAppender.appendSql( collectionTags.rootName() );
sqlAppender.appendSql( "','format'='no')))||substring(" );
argument.accept( walker );
sqlAppender.appendSql( ",locate('<" );
sqlAppender.appendSql( collectionTags.rootName() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ protected void renderReturningClause(SqlAppender sqlAppender, JsonValueArguments
}
}

static boolean isEncodedBoolean(JdbcMapping type) {
public static boolean isEncodedBoolean(JdbcMapping type) {
return type.getJdbcType().isBoolean() && type.getJdbcType().getDdlTypeCode() != SqlTypes.BOOLEAN;
}
}
Loading

0 comments on commit 4658e47

Please sign in to comment.