diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java
index 73f12a3357..e906643aa9 100644
--- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java
+++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java
@@ -53,6 +53,8 @@ import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
+import org.hibernate.mapping.AggregateColumn;
+import org.hibernate.mapping.Table;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport;
@@ -76,6 +78,8 @@ import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
+import org.hibernate.tool.schema.internal.StandardTableExporter;
+import org.hibernate.tool.schema.spi.Exporter;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
@@ -143,6 +147,17 @@ public class PostgreSQLLegacyDialect extends Dialect {
protected final PostgreSQLDriverKind driverKind;
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
+ private final StandardTableExporter postgresqlTableExporter = new StandardTableExporter( this ) {
+ @Override
+ protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
+ final JdbcType jdbcType = aggregateColumn.getType().getJdbcType();
+ if ( jdbcType.isXml() ) {
+ // Requires the use of xmltable which is not supported in check constraints
+ return;
+ }
+ super.applyAggregateColumnCheck( buf, aggregateColumn );
+ }
+ };
public PostgreSQLLegacyDialect() {
this( DatabaseVersion.make( 8, 0 ) );
@@ -1536,6 +1551,11 @@ public class PostgreSQLLegacyDialect extends Dialect {
return uniqueDelegate;
}
+ @Override
+ public Exporter
getTableExporter() {
+ return postgresqlTableExporter;
+ }
+
/**
* @return {@code true}, but only because we can "batch" truncate
*/
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java
index a46cd13311..3707cee2bc 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java
@@ -50,6 +50,8 @@ import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
+import org.hibernate.mapping.AggregateColumn;
+import org.hibernate.mapping.Table;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
@@ -78,6 +80,8 @@ import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
+import org.hibernate.tool.schema.internal.StandardTableExporter;
+import org.hibernate.tool.schema.spi.Exporter;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.BlobJdbcType;
@@ -145,6 +149,17 @@ public class PostgreSQLDialect extends Dialect {
protected final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 12 );
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
+ private final StandardTableExporter postgresqlTableExporter = new StandardTableExporter( this ) {
+ @Override
+ protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
+ final JdbcType jdbcType = aggregateColumn.getType().getJdbcType();
+ if ( jdbcType.isXml() ) {
+ // Requires the use of xmltable which is not supported in check constraints
+ return;
+ }
+ super.applyAggregateColumnCheck( buf, aggregateColumn );
+ }
+ };
protected final PostgreSQLDriverKind driverKind;
private final OptionalTableUpdateStrategy optionalTableUpdateStrategy;
@@ -1474,6 +1489,11 @@ public class PostgreSQLDialect extends Dialect {
return uniqueDelegate;
}
+ @Override
+ public Exporter getTableExporter() {
+ return postgresqlTableExporter;
+ }
+
/**
* @return {@code true}, but only because we can "batch" truncate
*/
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/PostgreSQLAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/PostgreSQLAggregateSupport.java
index c1fa526bb1..a9743f0f26 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/PostgreSQLAggregateSupport.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/PostgreSQLAggregateSupport.java
@@ -8,8 +8,10 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.hibernate.dialect.Dialect;
+import org.hibernate.dialect.XmlHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Column;
+import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SelectablePath;
@@ -18,6 +20,8 @@ import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.type.BasicPluralType;
+import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
+import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.type.SqlTypes.ARRAY;
@@ -31,16 +35,25 @@ import static org.hibernate.type.SqlTypes.JSON;
import static org.hibernate.type.SqlTypes.JSON_ARRAY;
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
import static org.hibernate.type.SqlTypes.SMALLINT;
+import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.STRUCT;
import static org.hibernate.type.SqlTypes.STRUCT_ARRAY;
import static org.hibernate.type.SqlTypes.STRUCT_TABLE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.VARBINARY;
+import static org.hibernate.type.SqlTypes.XML_ARRAY;
public class PostgreSQLAggregateSupport extends AggregateSupportImpl {
private static final AggregateSupport INSTANCE = new PostgreSQLAggregateSupport();
+ private static final String XML_EXTRACT_START = "xmlelement(name \"" + XmlHelper.ROOT_TAG + "\",(select xmlagg(t.v) from xmltable(";
+ private static final String XML_EXTRACT_SEPARATOR = "/*' passing ";
+ private static final String XML_EXTRACT_END = " columns v xml path '.')t))";
+ private static final String XML_QUERY_START = "(select xmlagg(t.v) from xmltable(";
+ private static final String XML_QUERY_SEPARATOR = "' passing ";
+ private static final String XML_QUERY_END = " columns v xml path '.')t)";
+
public static AggregateSupport valueOf(Dialect dialect) {
return PostgreSQLAggregateSupport.INSTANCE;
}
@@ -109,6 +122,40 @@ public class PostgreSQLAggregateSupport extends AggregateSupportImpl {
"cast(" + aggregateParentReadExpression + "->>'" + columnExpression + "' as " + column.getColumnDefinition() + ')'
);
}
+ case XML_ARRAY:
+ case SQLXML:
+ switch ( column.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode() ) {
+ case SQLXML:
+ return template.replace(
+ placeholder,
+ XML_EXTRACT_START + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/*" ) + XML_EXTRACT_END
+ );
+ case XML_ARRAY:
+ if ( typeConfiguration.getCurrentBaseSqlTypeIndicators().isXmlFormatMapperLegacyFormatEnabled() ) {
+ throw new IllegalArgumentException( "XML array '" + columnExpression + "' in '" + aggregateParentReadExpression + "' is not supported with legacy format enabled." );
+ }
+ else {
+ return template.replace(
+ placeholder,
+ "xmlelement(name \"Collection\",(select xmlagg(t.v order by t.i) from xmltable(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/*" ) + " columns v xml path '.', i for ordinality)t))"
+ );
+ }
+ case BINARY:
+ case VARBINARY:
+ case LONG32VARBINARY:
+ // We encode binary data as hex, so we have to decode here
+ return template.replace(
+ placeholder,
+ "decode((select t.v from xmltable(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression )+ " columns v text path '.') t),'hex')"
+ );
+ case ARRAY:
+ throw new UnsupportedOperationException( "Transforming XML_ARRAY to native arrays is not supported on PostgreSQL!" );
+ default:
+ return template.replace(
+ placeholder,
+ "(select t.v from xmltable(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression ) + " columns v " + column.getColumnDefinition() + " path '.') t)"
+ );
+ }
case STRUCT:
case STRUCT_ARRAY:
case STRUCT_TABLE:
@@ -117,6 +164,35 @@ public class PostgreSQLAggregateSupport extends AggregateSupportImpl {
throw new IllegalArgumentException( "Unsupported aggregate SQL type: " + aggregateColumnTypeCode );
}
+ private static String xmlExtractArguments(String aggregateParentReadExpression, String xpathFragment) {
+ final String extractArguments;
+ int separatorIndex;
+ if ( aggregateParentReadExpression.startsWith( XML_EXTRACT_START )
+ && aggregateParentReadExpression.endsWith( XML_EXTRACT_END )
+ && (separatorIndex = aggregateParentReadExpression.indexOf( XML_EXTRACT_SEPARATOR )) != -1 ) {
+ final StringBuilder sb = new StringBuilder( aggregateParentReadExpression.length() - XML_EXTRACT_START.length() + xpathFragment.length() );
+ sb.append( aggregateParentReadExpression, XML_EXTRACT_START.length(), separatorIndex );
+ sb.append( '/' );
+ sb.append( xpathFragment );
+ sb.append( aggregateParentReadExpression, separatorIndex + 2, aggregateParentReadExpression.length() - XML_EXTRACT_END.length() );
+ extractArguments = sb.toString();
+ }
+ else if ( aggregateParentReadExpression.startsWith( XML_QUERY_START )
+ && aggregateParentReadExpression.endsWith( XML_QUERY_END )
+ && (separatorIndex = aggregateParentReadExpression.indexOf( XML_QUERY_SEPARATOR )) != -1 ) {
+ final StringBuilder sb = new StringBuilder( aggregateParentReadExpression.length() - XML_QUERY_START.length() + xpathFragment.length() );
+ sb.append( aggregateParentReadExpression, XML_QUERY_START.length(), separatorIndex );
+ sb.append( '/' );
+ sb.append( xpathFragment );
+ sb.append( aggregateParentReadExpression, separatorIndex, aggregateParentReadExpression.length() - XML_QUERY_END.length() );
+ extractArguments = sb.toString();
+ }
+ else {
+ extractArguments = "'/" + XmlHelper.ROOT_TAG + "/" + xpathFragment + "' passing " + aggregateParentReadExpression;
+ }
+ return extractArguments;
+ }
+
private static String jsonCustomWriteExpression(String customWriteExpression, JdbcMapping jdbcMapping) {
final int sqlTypeCode = jdbcMapping.getJdbcType().getDefaultSqlTypeCode();
switch ( sqlTypeCode ) {
@@ -141,6 +217,30 @@ public class PostgreSQLAggregateSupport extends AggregateSupportImpl {
}
}
+ private static String xmlCustomWriteExpression(String customWriteExpression, JdbcMapping jdbcMapping) {
+ final int sqlTypeCode = jdbcMapping.getJdbcType().getDefaultSqlTypeCode();
+ switch ( sqlTypeCode ) {
+ case BINARY:
+ case VARBINARY:
+ case LONG32VARBINARY:
+ // We encode binary data as hex
+ return "encode(" + customWriteExpression + ",'hex')";
+// case ARRAY:
+// final BasicPluralType, ?> pluralType = (BasicPluralType, ?>) jdbcMapping;
+// switch ( pluralType.getElementType().getJdbcType().getDefaultSqlTypeCode() ) {
+// case BINARY:
+// case VARBINARY:
+// case LONG32VARBINARY:
+// // We encode binary data as hex
+// return "to_jsonb(array(select encode(unnest(" + customWriteExpression + "),'hex')))";
+// default:
+// return "to_jsonb(" + customWriteExpression + ")";
+// }
+ default:
+ return customWriteExpression;
+ }
+ }
+
@Override
public String aggregateComponentAssignmentExpression(
String aggregateParentAssignmentExpression,
@@ -150,7 +250,9 @@ public class PostgreSQLAggregateSupport extends AggregateSupportImpl {
switch ( aggregateColumnTypeCode ) {
case JSON:
case JSON_ARRAY:
- // For JSON we always have to replace the whole object
+ case SQLXML:
+ case XML_ARRAY:
+ // For JSON/XML we always have to replace the whole object
return aggregateParentAssignmentExpression;
case STRUCT:
case STRUCT_ARRAY:
@@ -164,6 +266,7 @@ public class PostgreSQLAggregateSupport extends AggregateSupportImpl {
public boolean requiresAggregateCustomWriteExpressionRenderer(int aggregateSqlTypeCode) {
switch ( aggregateSqlTypeCode ) {
case JSON:
+ case SQLXML:
return true;
}
return false;
@@ -183,17 +286,13 @@ public class PostgreSQLAggregateSupport extends AggregateSupportImpl {
final int aggregateSqlTypeCode = aggregateColumn.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode();
switch ( aggregateSqlTypeCode ) {
case JSON:
- return jsonAggregateColumnWriter( aggregateColumn, columnsToUpdate );
+ return new RootJsonWriteExpression( aggregateColumn, columnsToUpdate );
+ case SQLXML:
+ return new RootXmlWriteExpression( aggregateColumn, columnsToUpdate );
}
throw new IllegalArgumentException( "Unsupported aggregate SQL type: " + aggregateSqlTypeCode );
}
- private WriteExpressionRenderer jsonAggregateColumnWriter(
- SelectableMapping aggregateColumn,
- SelectableMapping[] columns) {
- return new RootJsonWriteExpression( aggregateColumn, columns );
- }
-
interface JsonWriteExpression {
void append(
SqlAppender sb,
@@ -329,4 +428,198 @@ public class PostgreSQLAggregateSupport extends AggregateSupportImpl {
}
}
+ interface XmlWriteExpression {
+ void append(
+ SqlAppender sb,
+ String path,
+ SqlAstTranslator> translator,
+ AggregateColumnWriteExpression expression);
+ }
+ private static class AggregateXmlWriteExpression implements XmlWriteExpression {
+
+ private final SelectableMapping selectableMapping;
+ private final String columnDefinition;
+ private final LinkedHashMap subExpressions = new LinkedHashMap<>();
+
+ private AggregateXmlWriteExpression(SelectableMapping selectableMapping, String columnDefinition) {
+ this.selectableMapping = selectableMapping;
+ this.columnDefinition = columnDefinition;
+ }
+
+ protected void initializeSubExpressions(SelectableMapping aggregateColumn, SelectableMapping[] columns) {
+ for ( SelectableMapping column : columns ) {
+ final SelectablePath selectablePath = column.getSelectablePath();
+ final SelectablePath[] parts = selectablePath.getParts();
+ AggregateXmlWriteExpression currentAggregate = this;
+ for ( int i = 1; i < parts.length - 1; i++ ) {
+ final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) currentAggregate.selectableMapping.getJdbcMapping().getJdbcType();
+ final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType();
+ final int selectableIndex = embeddableMappingType.getSelectableIndex( parts[i].getSelectableName() );
+ currentAggregate = (AggregateXmlWriteExpression) currentAggregate.subExpressions.computeIfAbsent(
+ parts[i].getSelectableName(),
+ k -> new AggregateXmlWriteExpression( embeddableMappingType.getJdbcValueSelectable( selectableIndex ), columnDefinition )
+ );
+ }
+ final String customWriteExpression = column.getWriteExpression();
+ currentAggregate.subExpressions.put(
+ parts[parts.length - 1].getSelectableName(),
+ new BasicXmlWriteExpression(
+ column,
+ xmlCustomWriteExpression( customWriteExpression, column.getJdbcMapping() )
+ )
+ );
+ }
+ passThroughUnsetSubExpressions( aggregateColumn );
+ }
+
+ protected void passThroughUnsetSubExpressions(SelectableMapping aggregateColumn) {
+ final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) aggregateColumn.getJdbcMapping().getJdbcType();
+ final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType();
+ final int jdbcValueCount = embeddableMappingType.getJdbcValueCount();
+ for ( int i = 0; i < jdbcValueCount; i++ ) {
+ final SelectableMapping selectableMapping = embeddableMappingType.getJdbcValueSelectable( i );
+
+ final XmlWriteExpression xmlWriteExpression = subExpressions.get( selectableMapping.getSelectableName() );
+ if ( xmlWriteExpression == null ) {
+ subExpressions.put(
+ selectableMapping.getSelectableName(),
+ new PassThroughXmlWriteExpression( selectableMapping )
+ );
+ }
+ else if ( xmlWriteExpression instanceof AggregateXmlWriteExpression writeExpression ) {
+ writeExpression.passThroughUnsetSubExpressions( selectableMapping );
+ }
+ }
+ }
+
+ protected String getTagName() {
+ return selectableMapping.getSelectableName();
+ }
+
+ @Override
+ public void append(
+ SqlAppender sb,
+ String path,
+ SqlAstTranslator> translator,
+ AggregateColumnWriteExpression expression) {
+ sb.append( "xmlelement(name " );
+ sb.appendDoubleQuoteEscapedString( getTagName() );
+ sb.append( ",xmlconcat" );
+ char separator = '(';
+ for ( Map.Entry entry : subExpressions.entrySet() ) {
+ sb.append( separator );
+
+ final XmlWriteExpression value = entry.getValue();
+ if ( value instanceof AggregateXmlWriteExpression ) {
+ final String subPath = XML_QUERY_START + xmlExtractArguments( path, entry.getKey() ) + XML_QUERY_END;
+ value.append( sb, subPath, translator, expression );
+ }
+ else {
+ value.append( sb, path, translator, expression );
+ }
+ separator = ',';
+ }
+ sb.append( "))" );
+ }
+ }
+
+ private static class RootXmlWriteExpression extends AggregateXmlWriteExpression
+ implements WriteExpressionRenderer {
+ private final String path;
+
+ RootXmlWriteExpression(SelectableMapping aggregateColumn, SelectableMapping[] columns) {
+ super( aggregateColumn, aggregateColumn.getColumnDefinition() );
+ path = aggregateColumn.getSelectionExpression();
+ initializeSubExpressions( aggregateColumn, columns );
+ }
+
+ @Override
+ protected String getTagName() {
+ return XmlHelper.ROOT_TAG;
+ }
+
+ @Override
+ public void render(
+ SqlAppender sqlAppender,
+ SqlAstTranslator> translator,
+ AggregateColumnWriteExpression aggregateColumnWriteExpression,
+ String qualifier) {
+ final String basePath;
+ if ( qualifier == null || qualifier.isBlank() ) {
+ basePath = path;
+ }
+ else {
+ basePath = qualifier + "." + path;
+ }
+ append( sqlAppender, XML_QUERY_START + "'/" + getTagName() + "' passing " + basePath + XML_QUERY_END, translator, aggregateColumnWriteExpression );
+ }
+ }
+ private static class BasicXmlWriteExpression implements XmlWriteExpression {
+
+ private final SelectableMapping selectableMapping;
+ private final String[] customWriteExpressionParts;
+
+ BasicXmlWriteExpression(SelectableMapping selectableMapping, String customWriteExpression) {
+ this.selectableMapping = selectableMapping;
+ if ( customWriteExpression.equals( "?" ) ) {
+ this.customWriteExpressionParts = new String[]{ "", "" };
+ }
+ else {
+ assert !customWriteExpression.startsWith( "?" );
+ final String[] parts = StringHelper.split( "?", customWriteExpression );
+ assert parts.length == 2 || (parts.length & 1) == 1;
+ this.customWriteExpressionParts = parts;
+ }
+ }
+
+ @Override
+ public void append(
+ SqlAppender sb,
+ String path,
+ SqlAstTranslator> translator,
+ AggregateColumnWriteExpression expression) {
+ final JdbcType jdbcType = selectableMapping.getJdbcMapping().getJdbcType();
+ final boolean isArray = jdbcType.getDefaultSqlTypeCode() == XML_ARRAY;
+ sb.append( "xmlelement(name " );
+ sb.appendDoubleQuoteEscapedString( selectableMapping.getSelectableName() );
+ sb.append( ',' );
+ if ( isArray ) {
+ // Remove the tag to wrap the value into the selectable specific tag
+ sb.append( "(select xmlagg(t.v order by t.i) from xmltable('/Collection/*' passing " );
+ }
+ sb.append( customWriteExpressionParts[0] );
+ for ( int i = 1; i < customWriteExpressionParts.length; i++ ) {
+ // We use NO_UNTYPED here so that expressions which require type inference are casted explicitly,
+ // since we don't know how the custom write expression looks like where this is embedded,
+ // so we have to be pessimistic and avoid ambiguities
+ translator.render( expression.getValueExpression( selectableMapping ), SqlAstNodeRenderingMode.NO_UNTYPED );
+ sb.append( customWriteExpressionParts[i] );
+ }
+ if ( isArray ) {
+ sb.append( " columns v xml path '.', i for ordinality)t)" );
+ }
+ sb.append( ')' );
+ }
+ }
+
+ private static class PassThroughXmlWriteExpression implements XmlWriteExpression {
+
+ private final SelectableMapping selectableMapping;
+
+ PassThroughXmlWriteExpression(SelectableMapping selectableMapping) {
+ this.selectableMapping = selectableMapping;
+ }
+
+ @Override
+ public void append(
+ SqlAppender sb,
+ String path,
+ SqlAstTranslator> translator,
+ AggregateColumnWriteExpression expression) {
+ sb.append( XML_QUERY_START );
+ sb.append( xmlExtractArguments( path, selectableMapping.getSelectableName() ) );
+ sb.append( XML_QUERY_END );
+ }
+ }
+
}
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLUnnestFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLUnnestFunction.java
index 859f3cdb7b..4bb275393f 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLUnnestFunction.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/PostgreSQLUnnestFunction.java
@@ -5,6 +5,7 @@
package org.hibernate.dialect.function.array;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.hibernate.dialect.XmlHelper;
import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.SqlTypedMapping;
@@ -14,6 +15,7 @@ import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.SqlTypes;
+import org.hibernate.type.descriptor.java.BasicPluralJavaType;
/**
@@ -71,4 +73,27 @@ public class PostgreSQLUnnestFunction extends UnnestFunction {
sqlAppender.appendSql( " t(v))" );
}
}
+
+ protected void renderXmlTable(
+ SqlAppender sqlAppender,
+ Expression array,
+ BasicPluralType, ?> pluralType,
+ @Nullable SqlTypedMapping sqlTypedMapping,
+ AnonymousTupleTableGroupProducer tupleType,
+ String tableIdentifierVariable,
+ SqlAstTranslator> walker) {
+ final XmlHelper.CollectionTags collectionTags = XmlHelper.determineCollectionTags(
+ (BasicPluralJavaType>) pluralType.getJavaTypeDescriptor(), walker.getSessionFactory()
+ );
+
+ sqlAppender.appendSql( "xmltable('/" );
+ sqlAppender.appendSql( collectionTags.rootName() );
+ sqlAppender.appendSql( '/' );
+ sqlAppender.appendSql( collectionTags.elementName() );
+ sqlAppender.appendSql( "' passing " );
+ array.accept( walker );
+ sqlAppender.appendSql( " columns" );
+ renderXmlTableColumns( sqlAppender, tupleType, walker );
+ sqlAppender.appendSql( ')' );
+ }
}
diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/UnnestFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/UnnestFunction.java
index f70e4bf6f5..563a1adec3 100644
--- a/hibernate-core/src/main/java/org/hibernate/dialect/function/array/UnnestFunction.java
+++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/array/UnnestFunction.java
@@ -162,6 +162,11 @@ public class UnnestFunction extends AbstractSqmSelfRenderingSetReturningFunction
sqlAppender.appendSql( "' passing " );
array.accept( walker );
sqlAppender.appendSql( " as \"d\" columns" );
+ renderXmlTableColumns( sqlAppender, tupleType, walker );
+ sqlAppender.appendSql( ')' );
+ }
+
+ protected void renderXmlTableColumns(SqlAppender sqlAppender, AnonymousTupleTableGroupProducer tupleType, SqlAstTranslator> walker) {
if ( tupleType.findSubPart( CollectionPart.Nature.ELEMENT.getName(), null ) == null ) {
tupleType.forEachSelectable( 0, (selectionIndex, selectableMapping) -> {
if ( selectionIndex == 0 ) {
@@ -205,8 +210,6 @@ public class UnnestFunction extends AbstractSqmSelfRenderingSetReturningFunction
}
} );
}
-
- sqlAppender.appendSql( ')' );
}
protected void renderUnnest(