HHH-18800 Add XML aggregate support for PostgreSQL
This commit is contained in:
parent
eeba7edf32
commit
57142a86dd
|
@ -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<Table> getTableExporter() {
|
||||
return postgresqlTableExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true}, but only because we can "batch" truncate
|
||||
*/
|
||||
|
|
|
@ -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<Table> getTableExporter() {
|
||||
return postgresqlTableExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true}, but only because we can "batch" truncate
|
||||
*/
|
||||
|
|
|
@ -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<String, XmlWriteExpression> 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<String, XmlWriteExpression> 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 <Collection> 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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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( ')' );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue