HHH-18803 Add XML aggregate support for DB2
This commit is contained in:
parent
d39aa6d162
commit
1077b6f0a9
|
@ -63,7 +63,9 @@ 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.Column;
|
||||
import org.hibernate.mapping.Table;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||
import org.hibernate.procedure.internal.DB2CallableStatementSupport;
|
||||
|
@ -87,6 +89,8 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
|
|||
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorDB2DatabaseImpl;
|
||||
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
|
||||
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
|
||||
import org.hibernate.tool.schema.internal.StandardTableExporter;
|
||||
import org.hibernate.tool.schema.spi.Exporter;
|
||||
import org.hibernate.type.JavaObjectType;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
|
@ -96,6 +100,7 @@ import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
|
|||
import org.hibernate.type.descriptor.jdbc.CharJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.LocalDateJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.LocalDateTimeJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.LocalTimeJdbcType;
|
||||
|
@ -154,6 +159,17 @@ public class DB2LegacyDialect extends Dialect {
|
|||
? LegacyDB2LimitHandler.INSTANCE
|
||||
: DB2LimitHandler.INSTANCE;
|
||||
private final UniqueDelegate uniqueDelegate = createUniqueDelegate();
|
||||
private final StandardTableExporter db2TableExporter = new StandardTableExporter( this ) {
|
||||
@Override
|
||||
protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
|
||||
final JdbcType jdbcType = aggregateColumn.getType().getJdbcType();
|
||||
if ( jdbcType.isLob() || jdbcType.isXml() ) {
|
||||
// LOB or XML columns can't have check constraints
|
||||
return;
|
||||
}
|
||||
super.applyAggregateColumnCheck( buf, aggregateColumn );
|
||||
}
|
||||
};
|
||||
|
||||
public DB2LegacyDialect() {
|
||||
this( DatabaseVersion.make( 9, 0 ) );
|
||||
|
@ -189,6 +205,11 @@ public class DB2LegacyDialect extends Dialect {
|
|||
return this.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exporter<Table> getTableExporter() {
|
||||
return this.db2TableExporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultStatementBatchSize() {
|
||||
return 0;
|
||||
|
|
|
@ -53,7 +53,9 @@ import org.hibernate.exception.LockTimeoutException;
|
|||
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
|
||||
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
|
||||
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
|
||||
import org.hibernate.mapping.AggregateColumn;
|
||||
import org.hibernate.mapping.Column;
|
||||
import org.hibernate.mapping.Table;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||
import org.hibernate.procedure.internal.DB2CallableStatementSupport;
|
||||
|
@ -77,6 +79,8 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
|
|||
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorDB2DatabaseImpl;
|
||||
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
|
||||
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
|
||||
import org.hibernate.tool.schema.internal.StandardTableExporter;
|
||||
import org.hibernate.tool.schema.spi.Exporter;
|
||||
import org.hibernate.type.JavaObjectType;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
|
@ -84,6 +88,7 @@ import org.hibernate.type.descriptor.ValueExtractor;
|
|||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
|
||||
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.LocalDateJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.LocalDateTimeJdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.LocalTimeJdbcType;
|
||||
|
@ -146,6 +151,17 @@ public class DB2Dialect extends Dialect {
|
|||
? LegacyDB2LimitHandler.INSTANCE
|
||||
: DB2LimitHandler.INSTANCE;
|
||||
private final UniqueDelegate uniqueDelegate = createUniqueDelegate();
|
||||
private final StandardTableExporter db2TableExporter = new StandardTableExporter( this ) {
|
||||
@Override
|
||||
protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
|
||||
final JdbcType jdbcType = aggregateColumn.getType().getJdbcType();
|
||||
if ( jdbcType.isLob() || jdbcType.isXml() ) {
|
||||
// LOB or XML columns can't have check constraints
|
||||
return;
|
||||
}
|
||||
super.applyAggregateColumnCheck( buf, aggregateColumn );
|
||||
}
|
||||
};
|
||||
|
||||
public DB2Dialect() {
|
||||
this( MINIMUM_VERSION );
|
||||
|
@ -171,6 +187,11 @@ public class DB2Dialect extends Dialect {
|
|||
return this.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exporter<Table> getTableExporter() {
|
||||
return this.db2TableExporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultStatementBatchSize() {
|
||||
return 0;
|
||||
|
|
|
@ -45,6 +45,7 @@ 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.TIME;
|
||||
import static org.hibernate.type.SqlTypes.TIMESTAMP;
|
||||
|
@ -52,6 +53,7 @@ import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
|
|||
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
|
||||
import static org.hibernate.type.SqlTypes.UUID;
|
||||
import static org.hibernate.type.SqlTypes.VARBINARY;
|
||||
import static org.hibernate.type.SqlTypes.XML_ARRAY;
|
||||
|
||||
public class DB2AggregateSupport extends AggregateSupportImpl {
|
||||
|
||||
|
@ -59,6 +61,9 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
public static final AggregateSupport JSON_INSTANCE = new DB2AggregateSupport( true );
|
||||
private static final String JSON_QUERY_START = "json_query(";
|
||||
private static final String JSON_QUERY_JSON_END = "')";
|
||||
private static final String XML_EXTRACT_START = "xmlelement(name \"" + XmlHelper.ROOT_TAG + "\",xmlquery(";
|
||||
private static final String XML_EXTRACT_SEPARATOR = "/*' passing ";
|
||||
private static final String XML_EXTRACT_END = " as \"d\"))";
|
||||
|
||||
private final boolean jsonSupport;
|
||||
|
||||
|
@ -134,12 +139,91 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
"json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ")"
|
||||
);
|
||||
}
|
||||
case SQLXML:
|
||||
case XML_ARRAY:
|
||||
switch ( column.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode() ) {
|
||||
case BOOLEAN:
|
||||
if ( SqlTypes.isNumericType( column.getJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
|
||||
return template.replace(
|
||||
placeholder,
|
||||
"decode(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression ) + ") as varchar(5)),'true',1,'false',0)"
|
||||
);
|
||||
}
|
||||
else {
|
||||
return template.replace(
|
||||
placeholder,
|
||||
"decode(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression ) + ") as varchar(5)),'true',true,'false',false)"
|
||||
);
|
||||
}
|
||||
case BINARY:
|
||||
case VARBINARY:
|
||||
case LONG32VARBINARY:
|
||||
case BLOB:
|
||||
// We encode binary data as hex, so we have to decode here
|
||||
return template.replace(
|
||||
placeholder,
|
||||
"hextoraw(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression ) + ") as clob))"
|
||||
);
|
||||
case TIMESTAMP_WITH_TIMEZONE:
|
||||
case TIMESTAMP_UTC:
|
||||
return template.replace(
|
||||
placeholder,
|
||||
"cast(trim(trailing 'Z' from xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression ) + ") as varchar(35))) as " + column.getColumnDefinition() + ")"
|
||||
);
|
||||
case SQLXML:
|
||||
return template.replace(
|
||||
placeholder,
|
||||
XML_EXTRACT_START + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/*" ) + "))"
|
||||
);
|
||||
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\",xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/*" ) + "))"
|
||||
);
|
||||
}
|
||||
case UUID:
|
||||
if ( SqlTypes.isBinaryType( column.getJdbcMapping().getJdbcType().getDdlTypeCode() ) ) {
|
||||
return template.replace(
|
||||
placeholder,
|
||||
"hextoraw(replace(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression ) + ") as varchar(36)),'-',''))"
|
||||
);
|
||||
}
|
||||
// Fall-through intended
|
||||
default:
|
||||
return template.replace(
|
||||
placeholder,
|
||||
"xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression ) + ") as " + column.getColumnDefinition() + ")"
|
||||
);
|
||||
}
|
||||
case STRUCT:
|
||||
return template.replace( placeholder, aggregateParentReadExpression + ".." + columnExpression );
|
||||
}
|
||||
throw new IllegalArgumentException( "Unsupported aggregate SQL type: " + aggregateColumnTypeCode );
|
||||
}
|
||||
|
||||
private static String xmlExtractArguments(String aggregateParentReadExpression, String xpathFragment) {
|
||||
final String extractArguments;
|
||||
final 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() - 2 );
|
||||
extractArguments = sb.toString();
|
||||
}
|
||||
else {
|
||||
extractArguments = "'$d/" + XmlHelper.ROOT_TAG + "/" + xpathFragment + "' passing " + aggregateParentReadExpression + " as \"d\"";
|
||||
}
|
||||
return extractArguments;
|
||||
}
|
||||
|
||||
private static String jsonCustomWriteExpression(String customWriteExpression, JdbcMapping jdbcMapping) {
|
||||
final int sqlTypeCode = jdbcMapping.getJdbcType().getDefaultSqlTypeCode();
|
||||
switch ( sqlTypeCode ) {
|
||||
|
@ -167,6 +251,33 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
}
|
||||
}
|
||||
|
||||
private static String xmlCustomWriteExpression(String customWriteExpression, JdbcMapping jdbcMapping) {
|
||||
final int sqlTypeCode = jdbcMapping.getJdbcType().getDefaultSqlTypeCode();
|
||||
switch ( sqlTypeCode ) {
|
||||
case BINARY:
|
||||
case VARBINARY:
|
||||
case LONG32VARBINARY:
|
||||
case BLOB:
|
||||
// We encode binary data as hex
|
||||
return "hex(" + customWriteExpression + ")";
|
||||
case UUID:
|
||||
return "regexp_replace(lower(hex(" + customWriteExpression + ")),'^(.{8})(.{4})(.{4})(.{4})(.{12})$','$1-$2-$3-$4-$5')";
|
||||
// case ARRAY:
|
||||
// case XML_ARRAY:
|
||||
// return "(" + customWriteExpression + ") format json";
|
||||
case BOOLEAN:
|
||||
return "decode(" + customWriteExpression + ",true,'true',false,'false')";
|
||||
case TIME:
|
||||
return "varchar_format(timestamp('1970-01-01'," + customWriteExpression + "),'HH24:MI:SS')";
|
||||
case TIMESTAMP:
|
||||
return "replace(varchar_format(" + customWriteExpression + ",'YYYY-MM-DD HH24:MI:SS.FF9'),' ','T')";
|
||||
case TIMESTAMP_UTC:
|
||||
return "replace(varchar_format(" + customWriteExpression + ",'YYYY-MM-DD HH24:MI:SS.FF9'),' ','T')||'Z'";
|
||||
default:
|
||||
return customWriteExpression;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String aggregateComponentAssignmentExpression(
|
||||
String aggregateParentAssignmentExpression,
|
||||
|
@ -181,6 +292,9 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
return aggregateParentAssignmentExpression;
|
||||
}
|
||||
break;
|
||||
case SQLXML:
|
||||
case XML_ARRAY:
|
||||
return aggregateParentAssignmentExpression;
|
||||
case STRUCT:
|
||||
return aggregateParentAssignmentExpression + ".." + columnExpression;
|
||||
}
|
||||
|
@ -201,6 +315,9 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
return null;
|
||||
}
|
||||
break;
|
||||
case SQLXML:
|
||||
case XML_ARRAY:
|
||||
return null;
|
||||
case STRUCT:
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
appendStructCustomWriteExpression( aggregateColumn, aggregatedColumns, sb );
|
||||
|
@ -240,6 +357,9 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
else if ( aggregateColumnSqlTypeCode == JSON ) {
|
||||
return columnSqlTypeCode == ARRAY ? JSON_ARRAY : columnSqlTypeCode;
|
||||
}
|
||||
else if ( aggregateColumnSqlTypeCode == SQLXML ) {
|
||||
return columnSqlTypeCode == ARRAY ? XML_ARRAY : columnSqlTypeCode;
|
||||
}
|
||||
else {
|
||||
return columnSqlTypeCode;
|
||||
}
|
||||
|
@ -247,7 +367,7 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
|
||||
@Override
|
||||
public boolean requiresAggregateCustomWriteExpressionRenderer(int aggregateSqlTypeCode) {
|
||||
return aggregateSqlTypeCode == STRUCT || aggregateSqlTypeCode == JSON;
|
||||
return aggregateSqlTypeCode == STRUCT || aggregateSqlTypeCode == JSON || aggregateSqlTypeCode == SQLXML;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -259,28 +379,17 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
switch ( aggregateSqlTypeCode ) {
|
||||
case JSON:
|
||||
if ( jsonSupport ) {
|
||||
return jsonAggregateColumnWriter( aggregateColumn, columnsToUpdate );
|
||||
return new RootJsonWriteExpression( aggregateColumn, columnsToUpdate );
|
||||
}
|
||||
break;
|
||||
case SQLXML:
|
||||
return new RootXmlWriteExpression( aggregateColumn, columnsToUpdate );
|
||||
case STRUCT:
|
||||
return structAggregateColumnWriter( aggregateColumn, columnsToUpdate, typeConfiguration );
|
||||
return new RootStructWriteExpression( aggregateColumn, columnsToUpdate, typeConfiguration );
|
||||
}
|
||||
throw new IllegalArgumentException( "Unsupported aggregate SQL type: " + aggregateSqlTypeCode );
|
||||
}
|
||||
|
||||
private WriteExpressionRenderer jsonAggregateColumnWriter(
|
||||
SelectableMapping aggregateColumn,
|
||||
SelectableMapping[] columns) {
|
||||
return new RootJsonWriteExpression( aggregateColumn, columns );
|
||||
}
|
||||
|
||||
private WriteExpressionRenderer structAggregateColumnWriter(
|
||||
SelectableMapping aggregateColumn,
|
||||
SelectableMapping[] columns,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
return new RootStructWriteExpression( aggregateColumn, columns, typeConfiguration );
|
||||
}
|
||||
|
||||
private static String determineTypeName(SelectableMapping column, TypeConfiguration typeConfiguration) {
|
||||
final String typeName;
|
||||
if ( column.getColumnDefinition() == null ) {
|
||||
|
@ -470,21 +579,23 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
final String columnType = aggregateColumn.getTypeName();
|
||||
final boolean legacyXmlFormatEnabled = aggregateColumn.getValue().getBuildingContext().getBuildingOptions()
|
||||
.isXmlFormatMapperLegacyFormatEnabled();
|
||||
// The serialize and deserialize functions, as well as the transform are for supporting struct types in native queries and functions
|
||||
var list = new ArrayList<AuxiliaryDatabaseObject>( 3 );
|
||||
var serializerSb = new StringBuilder();
|
||||
var deserializerSb = new StringBuilder();
|
||||
serializerSb.append( "create function " ).append( columnType ).append( "_serializer(v " ).append( columnType ).append( ") returns xml language sql " )
|
||||
.append( "return xmlelement(name \"").append( XmlHelper.ROOT_TAG ).append( "\"" );
|
||||
appendSerializer( aggregatedColumns, serializerSb, "v.." );
|
||||
appendSerializer( aggregatedColumns, serializerSb, "v..", legacyXmlFormatEnabled );
|
||||
serializerSb.append( ')' );
|
||||
|
||||
deserializerSb.append( "create function " ).append( columnType ).append( "_deserializer(v xml) returns " ).append( columnType ).append( " language sql " )
|
||||
.append( "return select " ).append( columnType ).append( "()" );
|
||||
appendDeserializerConstructor( aggregatedColumns, deserializerSb, "" );
|
||||
appendDeserializerConstructor( aggregatedColumns, deserializerSb, "", legacyXmlFormatEnabled );
|
||||
deserializerSb.append( " from xmltable('$" ).append( XmlHelper.ROOT_TAG ).append( "' passing v as \"" )
|
||||
.append( XmlHelper.ROOT_TAG ).append( "\" columns" );
|
||||
appendDeserializerColumns( aggregatedColumns, deserializerSb, ' ', "" );
|
||||
appendDeserializerColumns( aggregatedColumns, deserializerSb, ' ', "", legacyXmlFormatEnabled );
|
||||
deserializerSb.append( ") as t" );
|
||||
list.add(
|
||||
new NamedAuxiliaryDatabaseObject(
|
||||
|
@ -516,7 +627,7 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
return list;
|
||||
}
|
||||
|
||||
private static void appendSerializer(List<Column> aggregatedColumns, StringBuilder serializerSb, String prefix) {
|
||||
private static void appendSerializer(List<Column> aggregatedColumns, StringBuilder serializerSb, String prefix, boolean legacyXmlFormatEnabled) {
|
||||
char sep;
|
||||
if ( aggregatedColumns.size() > 1 ) {
|
||||
serializerSb.append( ",xmlconcat" );
|
||||
|
@ -533,12 +644,26 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
appendSerializer(
|
||||
aggregateColumn.getComponent().getAggregatedColumns(),
|
||||
serializerSb,
|
||||
prefix + udtColumn.getName() + ".."
|
||||
prefix + udtColumn.getName() + "..",
|
||||
legacyXmlFormatEnabled
|
||||
);
|
||||
}
|
||||
else if ( needsVarcharForBitDataCast( udtColumn.getSqlType() ) ) {
|
||||
serializerSb.append( ",cast(" ).append( prefix ).append( udtColumn.getName() ).append( " as varchar(" )
|
||||
.append( udtColumn.getColumnSize( null, null ).getLength() ).append( ") for bit data)" );
|
||||
if ( legacyXmlFormatEnabled ) {
|
||||
serializerSb.append( ",cast(" ).append( prefix ).append( udtColumn.getName() ).append( " as " );
|
||||
final long binaryLength = udtColumn.getColumnSize( null, null ).getLength();
|
||||
// Legacy is Base64 encoded which is 4/3 bigger
|
||||
final long varcharLength = ( binaryLength << 2 ) / 3;
|
||||
if ( varcharLength < 32_672L ) {
|
||||
serializerSb.append( "varchar(" ).append( varcharLength ).append( ") for bit data)" );
|
||||
}
|
||||
else {
|
||||
serializerSb.append( "clob)" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
serializerSb.append( ",hex(" ).append( prefix ).append( udtColumn.getName() ).append( ")" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
serializerSb.append( ',' ).append( prefix ).append( udtColumn.getName() );
|
||||
|
@ -554,7 +679,8 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
private static void appendDeserializerConstructor(
|
||||
List<Column> aggregatedColumns,
|
||||
StringBuilder deserializerSb,
|
||||
String prefix) {
|
||||
String prefix,
|
||||
boolean legacyXmlFormatEnabled) {
|
||||
for ( Column udtColumn : aggregatedColumns ) {
|
||||
deserializerSb.append( ".." ).append( udtColumn.getName() ).append( '(' );
|
||||
if ( udtColumn.getSqlTypeCode() == STRUCT ) {
|
||||
|
@ -563,13 +689,20 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
appendDeserializerConstructor(
|
||||
aggregateColumn.getComponent().getAggregatedColumns(),
|
||||
deserializerSb,
|
||||
udtColumn.getName() + "_"
|
||||
udtColumn.getName() + "_",
|
||||
legacyXmlFormatEnabled
|
||||
);
|
||||
deserializerSb.append( ')' );
|
||||
}
|
||||
else if ( needsVarcharForBitDataCast( udtColumn.getSqlType() ) ) {
|
||||
deserializerSb.append( "cast(t." ).append( prefix ).append( udtColumn.getName() ).append( " as " )
|
||||
.append( udtColumn.getSqlType() ).append( "))" );
|
||||
if ( legacyXmlFormatEnabled ) {
|
||||
deserializerSb.append( "cast(t." ).append( prefix ).append( udtColumn.getName() ).append( " as " )
|
||||
.append( udtColumn.getSqlType() ).append( "))" );
|
||||
}
|
||||
else {
|
||||
deserializerSb.append( "cast(hextoraw(t." ).append( prefix ).append( udtColumn.getName() ).append( ") as " )
|
||||
.append( udtColumn.getSqlType() ).append( "))" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
deserializerSb.append( "t." ).append( prefix ).append( udtColumn.getName() ).append( ')' );
|
||||
|
@ -581,7 +714,8 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
List<Column> aggregatedColumns,
|
||||
StringBuilder deserializerSb,
|
||||
char sep,
|
||||
String prefix) {
|
||||
String prefix,
|
||||
boolean legacyXmlFormatEnabled) {
|
||||
for ( Column udtColumn : aggregatedColumns ) {
|
||||
if ( udtColumn.getSqlTypeCode() == STRUCT ) {
|
||||
final AggregateColumn aggregateColumn = (AggregateColumn) udtColumn;
|
||||
|
@ -589,15 +723,29 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
aggregateColumn.getComponent().getAggregatedColumns(),
|
||||
deserializerSb,
|
||||
sep,
|
||||
udtColumn.getName() + "_"
|
||||
udtColumn.getName() + "_",
|
||||
legacyXmlFormatEnabled
|
||||
);
|
||||
}
|
||||
else {
|
||||
deserializerSb.append( sep );
|
||||
deserializerSb.append( prefix ).append( udtColumn.getName() ).append( ' ' );
|
||||
if ( needsVarcharForBitDataCast( udtColumn.getSqlType() ) ) {
|
||||
deserializerSb.append( "varchar(" )
|
||||
.append( udtColumn.getColumnSize( null, null ).getLength() ).append( ") for bit data" );
|
||||
final long binaryLength = udtColumn.getColumnSize( null, null ).getLength();
|
||||
final long varcharLength;
|
||||
if ( legacyXmlFormatEnabled ) {
|
||||
// Legacy is Base64 encoded which is 4/3 bigger
|
||||
varcharLength = ( binaryLength << 2 ) / 3;
|
||||
}
|
||||
else {
|
||||
varcharLength = binaryLength << 1;
|
||||
}
|
||||
if ( varcharLength < 32_672L ) {
|
||||
deserializerSb.append( "varchar(" ).append( varcharLength ).append( ") for bit data" );
|
||||
}
|
||||
else {
|
||||
deserializerSb.append( "clob" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
deserializerSb.append( udtColumn.getSqlType() );
|
||||
|
@ -659,7 +807,7 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
if ( jsonWriteExpression == null ) {
|
||||
subExpressions.put(
|
||||
selectableMapping.getSelectableName(),
|
||||
new PassThroughExpression( selectableMapping )
|
||||
new PassThroughJsonWriteExpression( selectableMapping )
|
||||
);
|
||||
}
|
||||
else if ( jsonWriteExpression instanceof AggregateJsonWriteExpression writeExpression ) {
|
||||
|
@ -760,11 +908,11 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
}
|
||||
}
|
||||
|
||||
private static class PassThroughExpression implements JsonWriteExpression {
|
||||
private static class PassThroughJsonWriteExpression implements JsonWriteExpression {
|
||||
|
||||
private final SelectableMapping selectableMapping;
|
||||
|
||||
PassThroughExpression(SelectableMapping selectableMapping) {
|
||||
PassThroughJsonWriteExpression(SelectableMapping selectableMapping) {
|
||||
this.selectableMapping = selectableMapping;
|
||||
}
|
||||
|
||||
|
@ -781,4 +929,158 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
|
|||
}
|
||||
}
|
||||
|
||||
private static class RootXmlWriteExpression implements WriteExpressionRenderer {
|
||||
private final SelectableMapping aggregateColumn;
|
||||
private final SelectableMapping[] columns;
|
||||
|
||||
RootXmlWriteExpression(SelectableMapping aggregateColumn, SelectableMapping[] columns) {
|
||||
this.aggregateColumn = aggregateColumn;
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
SqlAstTranslator<?> translator,
|
||||
AggregateColumnWriteExpression aggregateColumnWriteExpression,
|
||||
String qualifier) {
|
||||
sqlAppender.append( "xmldocument(xmlquery('transform copy $d-out:=if(empty($d-in)) then <" );
|
||||
sqlAppender.append( XmlHelper.ROOT_TAG );
|
||||
sqlAppender.append( "/> else $d-in/" );
|
||||
sqlAppender.append( XmlHelper.ROOT_TAG );
|
||||
sqlAppender.append( " modify " );
|
||||
|
||||
char separator = '(';
|
||||
for ( SelectableMapping column : columns ) {
|
||||
final SelectablePath selectablePath = column.getSelectablePath();
|
||||
final String tagXPath = columnXPath( selectablePath );
|
||||
final String columnVariable = columnVariable( selectablePath );
|
||||
sqlAppender.append( separator );
|
||||
sqlAppender.append( "if(empty($" );
|
||||
sqlAppender.append( columnVariable );
|
||||
sqlAppender.append( ")) then do delete $d-out" );
|
||||
sqlAppender.append( tagXPath );
|
||||
sqlAppender.append( " else if(empty($d-out" );
|
||||
sqlAppender.append( tagXPath );
|
||||
sqlAppender.append( ")) then" );
|
||||
|
||||
SelectablePath parentPath = selectablePath.getParent();
|
||||
assert parentPath != null;
|
||||
renderParentInserts( sqlAppender, parentPath, "{$" + columnVariable + "}" );
|
||||
|
||||
sqlAppender.append( " do insert $" );
|
||||
sqlAppender.append( columnVariable );
|
||||
sqlAppender.append( " into $d-out" );
|
||||
sqlAppender.append( columnXPath( selectablePath.getParent() ) );
|
||||
sqlAppender.append( " else do replace $d-out" );
|
||||
sqlAppender.append( tagXPath );
|
||||
sqlAppender.append( " with $" );
|
||||
sqlAppender.append( columnVariable );
|
||||
separator = ',';
|
||||
}
|
||||
|
||||
sqlAppender.append( ") return <" );
|
||||
sqlAppender.append( XmlHelper.ROOT_TAG );
|
||||
sqlAppender.append( ">{$d-out/*}</" );
|
||||
sqlAppender.append( XmlHelper.ROOT_TAG );
|
||||
sqlAppender.append( ">' passing " );
|
||||
if ( qualifier != null && !qualifier.isBlank() ) {
|
||||
sqlAppender.append( qualifier );
|
||||
sqlAppender.append( '.' );
|
||||
}
|
||||
sqlAppender.append( aggregateColumn.getSelectionExpression() );
|
||||
sqlAppender.append( " as \"d-in\"" );
|
||||
|
||||
for ( SelectableMapping column : columns ) {
|
||||
sqlAppender.append( ",xmlelement(name " );
|
||||
sqlAppender.appendDoubleQuoteEscapedString( column.getSelectableName() );
|
||||
sqlAppender.append( ',' );
|
||||
appendColumn(
|
||||
sqlAppender,
|
||||
column,
|
||||
xmlCustomWriteExpression( column.getCustomWriteExpression(), column.getJdbcMapping() ),
|
||||
translator,
|
||||
aggregateColumnWriteExpression
|
||||
);
|
||||
sqlAppender.append( " option null on null) as " );
|
||||
sqlAppender.appendDoubleQuoteEscapedString( columnVariable( column.getSelectablePath() ) );
|
||||
}
|
||||
|
||||
sqlAppender.append( "))" );
|
||||
}
|
||||
|
||||
private void renderParentInserts(SqlAppender sqlAppender, SelectablePath parentPath, String parentContent) {
|
||||
if ( !parentPath.isRoot() ) {
|
||||
final String newParentContent = "<" + parentPath.getSelectableName() + ">" + parentContent + "</" + parentPath.getSelectableName() + ">";
|
||||
final SelectablePath grandParentPath = parentPath.getParent();
|
||||
assert grandParentPath != null;
|
||||
|
||||
sqlAppender.append( " if(empty($d-out" );
|
||||
sqlAppender.append( columnXPath( parentPath ) );
|
||||
sqlAppender.append( ")) then" );
|
||||
renderParentInserts( sqlAppender, grandParentPath, newParentContent );
|
||||
sqlAppender.append( " do insert " );
|
||||
sqlAppender.append( newParentContent );
|
||||
sqlAppender.append( " into $d-out" );
|
||||
sqlAppender.append( columnXPath( grandParentPath ) );
|
||||
sqlAppender.append( " else" );
|
||||
}
|
||||
}
|
||||
|
||||
private String columnXPath(SelectablePath selectablePath) {
|
||||
final SelectablePath[] parts = selectablePath.getParts();
|
||||
final StringBuilder xpath = new StringBuilder();
|
||||
for ( int i = 1; i < parts.length; i++ ) {
|
||||
xpath.append( '/' );
|
||||
xpath.append( parts[i].getSelectableName() );
|
||||
}
|
||||
return xpath.toString();
|
||||
}
|
||||
|
||||
private String columnVariable(SelectablePath selectablePath) {
|
||||
final SelectablePath[] parts = selectablePath.getParts();
|
||||
final StringBuilder variable = new StringBuilder();
|
||||
for ( int i = 1; i < parts.length; i++ ) {
|
||||
variable.append( parts[i].getSelectableName() );
|
||||
variable.append( '-' );
|
||||
}
|
||||
variable.append( "in" );
|
||||
return variable.toString();
|
||||
}
|
||||
|
||||
private void appendColumn(
|
||||
SqlAppender sb,
|
||||
SelectableMapping selectableMapping,
|
||||
String customWriteExpression,
|
||||
SqlAstTranslator<?> translator,
|
||||
AggregateColumnWriteExpression expression) {
|
||||
final String customWriteExpressionStart;
|
||||
final String customWriteExpressionEnd;
|
||||
if ( customWriteExpression.equals( "?" ) ) {
|
||||
customWriteExpressionStart = "";
|
||||
customWriteExpressionEnd = "";
|
||||
}
|
||||
else {
|
||||
final String[] parts = StringHelper.split( "?", customWriteExpression );
|
||||
assert parts.length == 2;
|
||||
customWriteExpressionStart = parts[0];
|
||||
customWriteExpressionEnd = parts[1];
|
||||
}
|
||||
|
||||
final boolean isArray = selectableMapping.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode() == XML_ARRAY;
|
||||
if ( isArray ) {
|
||||
sb.append( "xmlquery('$d/*/*' passing " );
|
||||
}
|
||||
sb.append( customWriteExpressionStart );
|
||||
// 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( customWriteExpressionEnd );
|
||||
if ( isArray ) {
|
||||
sb.append( " as \"d\")" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -207,7 +207,7 @@ public class StandardTableExporter implements Exporter<Table> {
|
|||
return false;
|
||||
}
|
||||
|
||||
private void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
|
||||
protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
|
||||
final AggregateSupport aggregateSupport = dialect.getAggregateSupport();
|
||||
final int checkStart = buf.length();
|
||||
buf.append( ", check (" );
|
||||
|
|
|
@ -304,6 +304,18 @@ public class NestedXmlEmbeddableTest extends BaseSessionFactoryFunctionalTest {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlComponentUpdate.class)
|
||||
public void testUpdateAggregateMemberOnNestedNull() {
|
||||
sessionFactoryScope().inTransaction(
|
||||
entityManager -> {
|
||||
entityManager.createMutationQuery( "update XmlHolder b set b.theXml.simpleEmbeddable.doubleNested = null" ).executeUpdate();
|
||||
entityManager.createMutationQuery( "update XmlHolder b set b.theXml.simpleEmbeddable.doubleNested.theNested.theLeaf.stringField = 'Abc'" ).executeUpdate();
|
||||
assertEquals( "Abc", entityManager.find( XmlHolder.class, 1L ).getTheXml().simpleEmbeddable.doubleNested.theNested.theLeaf.stringField );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsXmlComponentUpdate.class)
|
||||
public void testUpdateMultipleAggregateMembers() {
|
||||
|
|
Loading…
Reference in New Issue