HHH-18799 Add XML aggregate support for Oracle

This commit is contained in:
Christian Beikov 2024-11-20 10:08:54 +01:00
parent 6d1b9c475a
commit eeba7edf32
22 changed files with 730 additions and 162 deletions

View File

@ -53,7 +53,9 @@ import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.CheckConstraint;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UserDefinedType;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
@ -81,6 +83,7 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
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.NullType;
@ -172,6 +175,17 @@ public class OracleLegacyDialect extends Dialect {
private final OracleUserDefinedTypeExporter userDefinedTypeExporter = new OracleUserDefinedTypeExporter( this );
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
private final SequenceSupport oracleSequenceSupport = OracleSequenceSupport.getInstance(this);
private final StandardTableExporter oracleTableExporter = new StandardTableExporter( this ) {
@Override
protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
final JdbcType jdbcType = aggregateColumn.getType().getJdbcType();
if ( dialect.getVersion().isBefore( 23, 6 ) && jdbcType.isXml() ) {
// ORA-00600 when selecting XML columns that have a check constraint was fixed in 23.6
return;
}
super.applyAggregateColumnCheck( buf, aggregateColumn );
}
};
public OracleLegacyDialect() {
this( DatabaseVersion.make( 8, 0 ) );
@ -503,6 +517,8 @@ public class OracleLegacyDialect extends Dialect {
return "to_timestamp_tz(?1,'YYYY-MM-DD HH24:MI:SS.FF9 TZR')";
}
break;
case XML:
return "xmlparse(document ?1)";
}
return super.castPattern(from, to);
}
@ -1042,6 +1058,11 @@ public class OracleLegacyDialect extends Dialect {
return oracleSequenceSupport;
}
@Override
public Exporter<Table> getTableExporter() {
return oracleTableExporter;
}
@Override
public String getQuerySequencesString() {
return "select * from all_sequences";

View File

@ -11,6 +11,7 @@ import java.sql.SQLException;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
@ -31,6 +32,11 @@ public class H2JsonJdbcType extends JsonJdbcType {
super( embeddableMappingType );
}
@Override
public int getJdbcTypeCode() {
return SqlTypes.VARBINARY;
}
@Override
public String toString() {
return "H2JsonJdbcType";

View File

@ -253,6 +253,7 @@ public class OracleArrayJdbcType extends ArrayJdbcType implements SqlTypedJdbcTy
userDefinedArrayType.setArraySqlTypeCode( getDdlTypeCode() );
userDefinedArrayType.setElementTypeName( elementTypeName );
userDefinedArrayType.setElementSqlTypeCode( elementJdbcType.getDefaultSqlTypeCode() );
userDefinedArrayType.setElementDdlTypeCode( elementJdbcType.getDdlTypeCode() );
userDefinedArrayType.setArrayLength( columnSize.getArrayLength() == null ? 127 : columnSize.getArrayLength() );
}

View File

@ -47,6 +47,8 @@ import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UserDefinedType;
import org.hibernate.mapping.CheckConstraint;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -78,6 +80,7 @@ import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
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.NullType;
@ -181,6 +184,17 @@ public class OracleDialect extends Dialect {
private final OracleUserDefinedTypeExporter userDefinedTypeExporter = new OracleUserDefinedTypeExporter( this );
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
private final SequenceSupport oracleSequenceSupport = OracleSequenceSupport.getInstance(this);
private final StandardTableExporter oracleTableExporter = new StandardTableExporter( this ) {
@Override
protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
final JdbcType jdbcType = aggregateColumn.getType().getJdbcType();
if ( dialect.getVersion().isBefore( 23, 6 ) && jdbcType.isXml() ) {
// ORA-00600 when selecting XML columns that have a check constraint was fixed in 23.6
return;
}
super.applyAggregateColumnCheck( buf, aggregateColumn );
}
};
// Is it an Autonomous Database Cloud Service?
protected final boolean autonomous;
@ -589,6 +603,8 @@ public class OracleDialect extends Dialect {
return "to_timestamp_tz(?1,'YYYY-MM-DD HH24:MI:SS.FF9 TZR')";
}
break;
case XML:
return "xmlparse(document ?1)";
}
return super.castPattern(from, to);
}
@ -988,6 +1004,7 @@ public class OracleDialect extends Dialect {
typeContributions.contributeJdbcType( OracleBooleanJdbcType.INSTANCE );
}
typeContributions.contributeJdbcType( OracleXmlJdbcType.INSTANCE );
typeContributions.contributeJdbcTypeConstructor( OracleXmlArrayJdbcTypeConstructor.INSTANCE );
if ( OracleJdbcHelper.isUsable( serviceRegistry ) ) {
typeContributions.contributeJdbcType( OracleJdbcHelper.getStructJdbcType( serviceRegistry ) );
}
@ -1132,6 +1149,11 @@ public class OracleDialect extends Dialect {
return oracleSequenceSupport;
}
@Override
public Exporter<Table> getTableExporter() {
return oracleTableExporter;
}
@Override
public String getQuerySequencesString() {
return "select * from all_sequences";

View File

@ -4,6 +4,7 @@
*/
package org.hibernate.dialect;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -20,6 +21,11 @@ public class OracleJsonArrayJdbcType extends OracleJsonArrayBlobJdbcType {
super( elementJdbcType );
}
@Override
public int getDdlTypeCode() {
return SqlTypes.JSON;
}
@Override
public String toString() {
return "OracleJsonJdbcType";

View File

@ -4,8 +4,6 @@
*/
package org.hibernate.dialect;
import java.util.Locale;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.QualifiedName;
@ -16,18 +14,16 @@ import org.hibernate.tool.schema.internal.StandardUserDefinedTypeExporter;
import org.hibernate.type.SqlTypes;
import static java.sql.Types.BOOLEAN;
import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BIT;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.DATE;
import static org.hibernate.type.SqlTypes.INTEGER;
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
import static org.hibernate.type.SqlTypes.SMALLINT;
import static org.hibernate.type.SqlTypes.TABLE;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY;
@ -61,11 +57,13 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport
}
final int arrayLength = userDefinedType.getArrayLength();
final Integer elementSqlTypeCode = userDefinedType.getElementSqlTypeCode();
final Integer elementDdlTypeCode = userDefinedType.getElementDdlTypeCode();
final String jsonTypeName = metadata.getDatabase().getTypeConfiguration().getDdlTypeRegistry().getTypeName(
SqlTypes.JSON,
dialect
);
final String valueExpression = determineValueExpression( "t.value", elementSqlTypeCode, elementType );
final String valueExpression = determineValueExpression( "t.value", elementSqlTypeCode, elementDdlTypeCode, elementType );
final String jsonElementType = determineJsonElementType( elementSqlTypeCode, elementDdlTypeCode, elementType );
return new String[] {
"create or replace type " + arrayTypeName + " as varying array(" + arrayLength + ") of " + elementType,
"create or replace function " + arrayTypeName + "_cmp(a in " + arrayTypeName +
@ -266,7 +264,7 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport
"res " + arrayTypeName + ":=" + arrayTypeName + "(); begin " +
"if arr is null then return null; end if; " +
"select " + valueExpression + " bulk collect into res " +
"from json_table(arr,'$[*]' columns (value path '$')) t; " +
"from json_table(arr,'$[*]' columns (value " + jsonElementType + " path '$')) t; " +
"return res; " +
"end;"
};
@ -332,17 +330,8 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport
return dialect.getVersion().isSameOrAfter( 23 );
}
private String determineValueExpression(String expression, int elementSqlTypeCode, String elementType) {
private String determineValueExpression(String expression, int elementSqlTypeCode, Integer elementDdlTypeCode, String elementType) {
switch ( elementSqlTypeCode ) {
case BOOLEAN:
if ( elementType.toLowerCase( Locale.ROOT ).trim().startsWith( "number" ) ) {
return "decode(" + expression + ",'true',1,'false',0,null)";
}
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
return "cast(" + expression + " as " + elementType + ")";
case DATE:
return "to_date(" + expression + ",'YYYY-MM-DD')";
case TIME:
@ -355,14 +344,47 @@ public class OracleUserDefinedTypeExporter extends StandardUserDefinedTypeExport
case BINARY:
case VARBINARY:
case LONG32VARBINARY:
return "hextoraw(" + expression + ")";
case BLOB:
return "xmlcast(xmlcdata(" + expression + ") as " + elementType + ")";
case UUID:
return "hextoraw(replace(" + expression + ",'-',''))";
case BIT:
return "decode(" + expression + ",'true',1,'false',0,null)";
case BOOLEAN:
if ( SqlTypes.isNumericType( elementDdlTypeCode ) ) {
return "decode(" + expression + ",'true',1,'false',0,null)";
}
// Fall-through intended
default:
return expression;
}
}
private String determineJsonElementType(Integer elementSqlTypeCode, Integer elementDdlTypeCode, String elementType) {
switch ( elementSqlTypeCode ) {
case BINARY:
case VARBINARY:
case LONG32VARBINARY:
case BLOB:
return "clob";
case BOOLEAN:
if ( !SqlTypes.isNumericType( elementDdlTypeCode ) ) {
return elementType;
}
// Fall-through intended
case BIT:
case DATE:
case TIME:
case TIMESTAMP:
case TIMESTAMP_WITH_TIMEZONE:
case TIMESTAMP_UTC:
case UUID:
return "varchar2(4000)";
default:
return elementType;
}
}
protected String createOrReplaceConcatFunction(String arrayTypeName) {
// Since Oracle has no builtin concat function for varrays and doesn't support varargs,
// we have to create a function with a fixed amount of arguments with default that fits "most" cases.

View File

@ -0,0 +1,44 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.XmlArrayJdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
/**
* @author Christian Beikov
*/
public class OracleXmlArrayJdbcType extends XmlArrayJdbcType {
public OracleXmlArrayJdbcType(JdbcType elementJdbcType) {
super( elementJdbcType );
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
// Seems the Oracle JDBC driver doesn't support `setNull(index, Types.SQLXML)`
// but it seems that the following works fine
return new XmlArrayBinder<>( javaType, this ) {
@Override
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
st.setNull( index, Types.VARCHAR );
}
@Override
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
st.setNull( name, Types.VARCHAR );
}
};
}
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Factory for {@link OracleXmlArrayJdbcType}.
*/
public class OracleXmlArrayJdbcTypeConstructor implements JdbcTypeConstructor {
public static final OracleXmlArrayJdbcTypeConstructor INSTANCE = new OracleXmlArrayJdbcTypeConstructor();
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<?> elementType,
ColumnTypeInformation columnTypeInformation) {
return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation );
}
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
JdbcType elementType,
ColumnTypeInformation columnTypeInformation) {
return new OracleXmlArrayJdbcType( elementType );
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.XML_ARRAY;
}
}

View File

@ -91,6 +91,14 @@ public class XmlHelper {
sb.append( '&' );
i += 4;
}
else if ( i + 5 < end
&& string.charAt( i + 2 ) == 'p'
&& string.charAt( i + 3 ) == 'o'
&& string.charAt( i + 4 ) == 's'
&& string.charAt( i + 5 ) == ';' ) {
sb.append( '\'' );
i += 5;
}
break OUTER;
case 'g':
if ( string.charAt( i + 2 ) == 't' && string.charAt( i + 3 ) == ';' ) {
@ -274,7 +282,14 @@ public class XmlHelper {
if ( NULL_TAG.equals( string ) ) {
return null;
}
if ( !string.startsWith( START_TAG ) || !string.endsWith( END_TAG ) ) {
int contentEnd = string.length() - 1;
while ( contentEnd >= 0 ) {
if ( !Character.isWhitespace( string.charAt( contentEnd ) ) ) {
break;
}
contentEnd--;
}
if ( !string.startsWith( START_TAG ) || !string.regionMatches( contentEnd - END_TAG.length()+ 1, END_TAG, 0, END_TAG.length() ) ) {
throw new IllegalArgumentException( "XML not properly formatted: " + string );
}
int end;
@ -289,7 +304,7 @@ public class XmlHelper {
array = new Object[embeddableMappingType.getJdbcValueCount() + ( embeddableMappingType.isPolymorphic() ? 1 : 0 )];
end = fromString( embeddableMappingType, string, returnEmbeddable, options, array, START_TAG.length() );
}
assert end + END_TAG.length() == string.length();
assert end + END_TAG.length() == contentEnd + 1;
if ( returnEmbeddable ) {
final StructAttributeValues attributeValues = StructHelper.getAttributeValues( embeddableMappingType, array, options );

View File

@ -13,9 +13,14 @@ import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.Column;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.SqlTypedMapping;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.JSON;
import static org.hibernate.type.SqlTypes.JSON_ARRAY;
import static org.hibernate.type.SqlTypes.SQLXML;
import static org.hibernate.type.SqlTypes.XML_ARRAY;
public class AggregateSupportImpl implements AggregateSupport {
public static final AggregateSupport INSTANCE = new AggregateSupportImpl();
@ -85,7 +90,8 @@ public class AggregateSupportImpl implements AggregateSupport {
@Override
public int aggregateComponentSqlTypeCode(int aggregateColumnSqlTypeCode, int columnSqlTypeCode) {
return switch (aggregateColumnSqlTypeCode) {
case SqlTypes.JSON -> columnSqlTypeCode == SqlTypes.ARRAY ? SqlTypes.JSON_ARRAY : columnSqlTypeCode;
case JSON -> columnSqlTypeCode == ARRAY ? JSON_ARRAY : columnSqlTypeCode;
case SQLXML -> columnSqlTypeCode == ARRAY ? XML_ARRAY : columnSqlTypeCode;
default -> columnSqlTypeCode;
};
}

View File

@ -308,7 +308,7 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
// We need to know what array this is STRUCT_ARRAY/JSON_ARRAY/XML_ARRAY,
// which we can easily get from the type code of the aggregate column
final int sqlTypeCode = aggregateColumn.getType().getJdbcType().getDefaultSqlTypeCode();
switch ( sqlTypeCode == SqlTypes.ARRAY ? aggregateColumn.getTypeCode() : sqlTypeCode ) {
switch ( sqlTypeCode == ARRAY ? aggregateColumn.getTypeCode() : sqlTypeCode ) {
case JSON:
case JSON_ARRAY:
if ( jsonSupport ) {
@ -350,19 +350,13 @@ public class DB2AggregateSupport extends AggregateSupportImpl {
@Override
public int aggregateComponentSqlTypeCode(int aggregateColumnSqlTypeCode, int columnSqlTypeCode) {
if ( aggregateColumnSqlTypeCode == STRUCT ) {
return switch (aggregateColumnSqlTypeCode) {
// DB2 doesn't support booleans in structs
return columnSqlTypeCode == BOOLEAN ? SMALLINT : columnSqlTypeCode;
}
else if ( aggregateColumnSqlTypeCode == JSON ) {
return columnSqlTypeCode == ARRAY ? JSON_ARRAY : columnSqlTypeCode;
}
else if ( aggregateColumnSqlTypeCode == SQLXML ) {
return columnSqlTypeCode == ARRAY ? XML_ARRAY : columnSqlTypeCode;
}
else {
return columnSqlTypeCode;
}
case STRUCT -> columnSqlTypeCode == BOOLEAN ? SMALLINT : columnSqlTypeCode;
case JSON -> columnSqlTypeCode == ARRAY ? JSON_ARRAY : columnSqlTypeCode;
case SQLXML -> columnSqlTypeCode == ARRAY ? XML_ARRAY : columnSqlTypeCode;
default -> columnSqlTypeCode;
};
}
@Override

View File

@ -27,7 +27,6 @@ import java.util.Map;
import static org.hibernate.dialect.function.json.HANAJsonValueFunction.jsonValueReturningType;
import static org.hibernate.dialect.function.xml.HANAXmlTableFunction.xmlValueReturningType;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB;
@ -284,19 +283,6 @@ public class HANAAggregateSupport extends AggregateSupportImpl {
throw new IllegalArgumentException( "Unsupported aggregate SQL type: " + aggregateColumn.getTypeCode() );
}
@Override
public int aggregateComponentSqlTypeCode(int aggregateColumnSqlTypeCode, int columnSqlTypeCode) {
if ( aggregateColumnSqlTypeCode == JSON ) {
return columnSqlTypeCode == ARRAY ? JSON_ARRAY : columnSqlTypeCode;
}
if ( aggregateColumnSqlTypeCode == SQLXML ) {
return columnSqlTypeCode == ARRAY ? XML_ARRAY : columnSqlTypeCode;
}
else {
return columnSqlTypeCode;
}
}
@Override
public boolean requiresAggregateCustomWriteExpressionRenderer(int aggregateSqlTypeCode) {
return aggregateSqlTypeCode == JSON || aggregateSqlTypeCode == SQLXML;

View File

@ -4,17 +4,13 @@
*/
package org.hibernate.dialect.aggregate;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleArrayJdbcType;
import org.hibernate.dialect.XmlHelper;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.AggregateColumn;
@ -27,45 +23,29 @@ import org.hibernate.metamodel.mapping.SelectablePath;
import org.hibernate.metamodel.mapping.SqlTypedMapping;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.StructJdbcType;
import org.hibernate.type.descriptor.sql.DdlType;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
import org.hibernate.type.internal.TypeConfigurationWrapperOptions;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BIT;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.BOOLEAN;
import static org.hibernate.type.SqlTypes.CLOB;
import static org.hibernate.type.SqlTypes.DATE;
import static org.hibernate.type.SqlTypes.INTEGER;
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.NCLOB;
import static org.hibernate.type.SqlTypes.SMALLINT;
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.TABLE;
import static org.hibernate.type.SqlTypes.TIME;
import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
import static org.hibernate.type.SqlTypes.TINYINT;
import static org.hibernate.type.SqlTypes.UUID;
import static org.hibernate.type.SqlTypes.VARBINARY;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.hibernate.type.SqlTypes.*;
public class OracleAggregateSupport extends AggregateSupportImpl {
@ -79,6 +59,12 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
private static final String JSON_QUERY_START = "json_query(";
private static final String JSON_QUERY_JSON_END = "' returning json)";
private static final String JSON_QUERY_BLOB_END = "' returning blob)";
private static final String XML_EXTRACT_START = "xmlelement(\"" + XmlHelper.ROOT_TAG + "\",xmlquery(";
private static final String XML_EXTRACT_SEPARATOR = "/*' passing ";
private static final String XML_EXTRACT_END = " returning content))";
private static final String XML_QUERY_START = "xmlquery(";
private static final String XML_QUERY_SEPARATOR = "' passing ";
private static final String XML_QUERY_END = " returning content)";
private final boolean checkConstraintSupport;
private final JsonSupport jsonSupport;
@ -141,22 +127,24 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
}
switch ( column.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode() ) {
case BIT:
case BOOLEAN:
//noinspection unchecked
final JdbcLiteralFormatter<Boolean> jdbcLiteralFormatter = (JdbcLiteralFormatter<Boolean>) column.getJdbcMapping().getJdbcType()
.getJdbcLiteralFormatter( column.getJdbcMapping().getMappedJavaType() );
final Dialect dialect = typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect();
final WrapperOptions wrapperOptions = new TypeConfigurationWrapperOptions( typeConfiguration );
final String trueLiteral = jdbcLiteralFormatter.toJdbcLiteral( true, dialect, wrapperOptions );
final String falseLiteral = jdbcLiteralFormatter.toJdbcLiteral( false, dialect, wrapperOptions );
return template.replace(
placeholder,
"decode(json_value(" + parentPartExpression + columnExpression + "'),'true',1,'false',0,null)"
"decode(json_value(" + parentPartExpression + columnExpression + "'),'true'," + trueLiteral + ",'false'," + falseLiteral + ",null)"
);
case BOOLEAN:
if ( column.getColumnDefinition().toLowerCase( Locale.ROOT ).trim().startsWith( "number" ) ) {
return template.replace(
placeholder,
"decode(json_value(" + parentPartExpression + columnExpression + "'),'true',1,'false',0,null)"
);
}
// Fall-through intended
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case CLOB:
case NCLOB:
return template.replace(
placeholder,
"json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')'
@ -182,26 +170,29 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
placeholder,
"to_timestamp_tz(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9TZH:TZM')"
);
case BINARY:
case VARBINARY:
case LONG32VARBINARY:
// We encode binary data as hex, so we have to decode here
return template.replace(
placeholder,
"hextoraw(json_value(" + parentPartExpression + columnExpression + "'))"
);
case UUID:
return template.replace(
placeholder,
"hextoraw(replace(json_value(" + parentPartExpression + columnExpression + "'),'-',''))"
);
case CLOB:
case NCLOB:
case BINARY:
case VARBINARY:
case LONG32VARBINARY:
// We encode binary data as hex, so we have to decode here
if ( determineLength( column ) * 2 < 4000L ) {
return template.replace(
placeholder,
"hextoraw(json_value(" + parentPartExpression + columnExpression + "'))"
);
}
// Fall-through intended
case BLOB:
// We encode binary data as hex, so we have to decode here
return template.replace(
placeholder,
"(select * from json_table(" + aggregateParentReadExpression + ",'$' columns (" + columnExpression + " " + column.getColumnDefinition() + " path '$." + columnExpression + "')))"
// returning binary data is not yet implemented in the json functions,
// so use the xml implementation
"xmlcast(xmlcdata(json_value(" + parentPartExpression + columnExpression + "' returning clob))) as " + column.getColumnDefinition() + ')'
);
case ARRAY:
final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) column.getJdbcMapping();
@ -242,6 +233,83 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
case NONE:
throw new UnsupportedOperationException( "The Oracle version doesn't support JSON aggregates!" );
}
case SQLXML:
case XML_ARRAY:
switch ( column.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode() ) {
case BIT:
case BOOLEAN:
//noinspection unchecked
final JdbcLiteralFormatter<Boolean> jdbcLiteralFormatter = (JdbcLiteralFormatter<Boolean>) column.getJdbcMapping().getJdbcType()
.getJdbcLiteralFormatter( column.getJdbcMapping().getMappedJavaType() );
final Dialect dialect = typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect();
final WrapperOptions wrapperOptions = new TypeConfigurationWrapperOptions( typeConfiguration );
final String trueLiteral = jdbcLiteralFormatter.toJdbcLiteral( true, dialect, wrapperOptions );
final String falseLiteral = jdbcLiteralFormatter.toJdbcLiteral( false, dialect, wrapperOptions );
return template.replace(
placeholder,
"decode(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/text()" ) + ") as varchar(5)),'true'," + trueLiteral + ",'false'," + falseLiteral + ",null)"
);
case FLOAT:
case REAL:
case DOUBLE:
// Since cast is the only way to do optional exponential form parsing, we have to use that.
// Unfortunately, the parsing is nationalized, so we need to replace the standard decimal separator dot with the nationalized one first
return template.replace(
placeholder,
"cast(replace(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/text()" ) + ") as varchar2(255)),'.',substr(to_char(0.1),1,1)) as " + column.getColumnDefinition() + ")"
);
case DATE:
return template.replace(
placeholder,
"to_date(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/text()" ) + ") as varchar(35)),'YYYY-MM-DD')"
);
case TIME:
return template.replace(
placeholder,
"to_timestamp(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/text()" ) + ") as varchar(35)),'hh24:mi:ss')"
);
case TIMESTAMP:
return template.replace(
placeholder,
"to_timestamp(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/text()" ) + ") as varchar(35)),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9')"
);
case TIMESTAMP_WITH_TIMEZONE:
case TIMESTAMP_UTC:
return template.replace(
placeholder,
"to_timestamp_tz(xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/text()" ) + ") as varchar(35)),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9TZH:TZM')"
);
case ARRAY:
throw new UnsupportedOperationException( "Transforming XML_ARRAY to native arrays is not supported on Oracle!" );
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(\"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 + "/text()" ) + ") as varchar(36)),'-',''))"
);
}
// Fall-through intended
default:
return template.replace(
placeholder,
"xmlcast(xmlquery(" + xmlExtractArguments( aggregateParentReadExpression, columnExpression + "/text()" ) + ") as " + column.getColumnDefinition() + ")"
);
}
case STRUCT:
case STRUCT_ARRAY:
case STRUCT_TABLE:
@ -250,6 +318,58 @@ public class OracleAggregateSupport 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() - 2 );
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() - 1 );
extractArguments = sb.toString();
}
else {
extractArguments = "'/" + XmlHelper.ROOT_TAG + "/" + xpathFragment + "' passing " + aggregateParentReadExpression + " returning content";
}
return extractArguments;
}
private static long determineLength(SqlTypedMapping column) {
final Long length = column.getLength();
if ( length != null ) {
return length;
}
else {
final String columnDefinition = column.getColumnDefinition();
assert columnDefinition != null;
final int parenthesisIndex = columnDefinition.indexOf( '(' );
if ( parenthesisIndex != -1 ) {
int end;
for ( end = parenthesisIndex + 1; end < columnDefinition.length(); end++ ) {
if ( !Character.isDigit( columnDefinition.charAt( end ) ) ) {
break;
}
}
return Long.parseLong( columnDefinition.substring( parenthesisIndex + 1, end ) );
}
// Default to the max varchar length
return 4000L;
}
}
@Override
public String aggregateComponentAssignmentExpression(
String aggregateParentAssignmentExpression,
@ -259,7 +379,9 @@ public class OracleAggregateSupport 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:
@ -307,10 +429,14 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
case BIT:
return "decode(" + customWriteExpression + ",1,'true',0,'false',null)";
case BOOLEAN:
final String sqlTypeName = AbstractSqlAstTranslator.getSqlTypeName( column, typeConfiguration );
if ( sqlTypeName.toLowerCase( Locale.ROOT ).trim().startsWith( "number" ) ) {
return "decode(" + customWriteExpression + ",1,'true',0,'false',null)";
}
//noinspection unchecked
final JdbcLiteralFormatter<Boolean> jdbcLiteralFormatter = (JdbcLiteralFormatter<Boolean>) jdbcMapping.getJdbcType()
.getJdbcLiteralFormatter( jdbcMapping.getMappedJavaType() );
final Dialect dialect = typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect();
final WrapperOptions wrapperOptions = new TypeConfigurationWrapperOptions( typeConfiguration );
final String trueLiteral = jdbcLiteralFormatter.toJdbcLiteral( true, dialect, wrapperOptions );
final String falseLiteral = jdbcLiteralFormatter.toJdbcLiteral( false, dialect, wrapperOptions );
return "decode(" + customWriteExpression + "," + trueLiteral + ",'true'," + falseLiteral + ",'false')";
// Fall-through intended
default:
return customWriteExpression;
@ -319,6 +445,34 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
throw new IllegalStateException( "JSON not supported!" );
}
private static String xmlCustomWriteExpression(String customWriteExpression, JdbcMapping jdbcMapping, TypeConfiguration typeConfiguration) {
final int sqlTypeCode = jdbcMapping.getJdbcType().getDefaultSqlTypeCode();
switch ( sqlTypeCode ) {
case UUID:
return "regexp_replace(lower(rawtohex(" + customWriteExpression + ")),'^(.{8})(.{4})(.{4})(.{4})(.{12})$','\\1-\\2-\\3-\\4-\\5')";
// case ARRAY:
// case XML_ARRAY:
// return "(" + customWriteExpression + ") format json";
case BOOLEAN:
//noinspection unchecked
final JdbcLiteralFormatter<Boolean> jdbcLiteralFormatter = (JdbcLiteralFormatter<Boolean>) jdbcMapping.getJdbcType()
.getJdbcLiteralFormatter( jdbcMapping.getMappedJavaType() );
final Dialect dialect = typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect();
final WrapperOptions wrapperOptions = new TypeConfigurationWrapperOptions( typeConfiguration );
final String trueLiteral = jdbcLiteralFormatter.toJdbcLiteral( true, dialect, wrapperOptions );
final String falseLiteral = jdbcLiteralFormatter.toJdbcLiteral( false, dialect, wrapperOptions );
return "decode(" + customWriteExpression + "," + trueLiteral + ",'true'," + falseLiteral + ",'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;
}
}
private static String determineElementTypeName(
Size castTargetSize,
BasicPluralType<?, ?> pluralType,
@ -337,7 +491,7 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
@Override
public boolean requiresAggregateCustomWriteExpressionRenderer(int aggregateSqlTypeCode) {
return aggregateSqlTypeCode == JSON;
return aggregateSqlTypeCode == JSON || aggregateSqlTypeCode == SQLXML;
}
@Override
@ -348,7 +502,9 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
final int aggregateSqlTypeCode = aggregateColumn.getJdbcMapping().getJdbcType().getDefaultSqlTypeCode();
switch ( aggregateSqlTypeCode ) {
case JSON:
return jsonAggregateColumnWriter( aggregateColumn, columnsToUpdate, typeConfiguration );
return new RootJsonWriteExpression( aggregateColumn, columnsToUpdate, this, typeConfiguration );
case SQLXML:
return new RootXmlWriteExpression( aggregateColumn, columnsToUpdate, typeConfiguration );
}
throw new IllegalArgumentException( "Unsupported aggregate SQL type: " + aggregateSqlTypeCode );
}
@ -381,6 +537,7 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
}
arrayType.setElementTypeName( elementJdbcType.getStructTypeName() );
arrayType.setElementSqlTypeCode( elementJdbcType.getDefaultSqlTypeCode() );
arrayType.setElementDdlTypeCode( elementJdbcType.getDdlTypeCode() );
}
return super.aggregateAuxiliaryDatabaseObjects(
namespace,
@ -416,13 +573,6 @@ public class OracleAggregateSupport extends AggregateSupportImpl {
NONE;
}
private WriteExpressionRenderer jsonAggregateColumnWriter(
SelectableMapping aggregateColumn,
SelectableMapping[] columns,
TypeConfiguration typeConfiguration) {
return new RootJsonWriteExpression( aggregateColumn, columns, this, typeConfiguration );
}
interface JsonWriteExpression {
void append(
SqlAppender sb,
@ -615,4 +765,198 @@ public class OracleAggregateSupport 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, TypeConfiguration typeConfiguration) {
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(), typeConfiguration )
)
);
}
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(" );
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 = "xmlquery(" + xmlExtractArguments( path, entry.getKey() ) + ")";
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, TypeConfiguration typeConfiguration) {
super( aggregateColumn, aggregateColumn.getColumnDefinition() );
path = aggregateColumn.getSelectionExpression();
initializeSubExpressions( aggregateColumn, columns, typeConfiguration );
}
@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, "xmlquery('/" + getTagName() + "' passing " + basePath + " returning content)", 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(" );
sb.appendDoubleQuoteEscapedString( selectableMapping.getSelectableName() );
sb.append( ',' );
if ( isArray ) {
// Remove the <Collection> tag to wrap the value into the selectable specific tag
sb.append( "xmlquery('/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( " returning content)" );
}
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( "xmlquery(" );
sb.append( xmlExtractArguments( path, selectableMapping.getSelectableName() ) );
sb.append( ")" );
}
}
}

View File

@ -279,19 +279,6 @@ public class SQLServerAggregateSupport extends AggregateSupportImpl {
}
}
@Override
public int aggregateComponentSqlTypeCode(int aggregateColumnSqlTypeCode, int columnSqlTypeCode) {
if ( aggregateColumnSqlTypeCode == JSON ) {
return columnSqlTypeCode == ARRAY ? JSON_ARRAY : columnSqlTypeCode;
}
else if ( aggregateColumnSqlTypeCode == SQLXML ) {
return columnSqlTypeCode == ARRAY ? XML_ARRAY : columnSqlTypeCode;
}
else {
return columnSqlTypeCode;
}
}
@Override
public boolean requiresAggregateCustomWriteExpressionRenderer(int aggregateSqlTypeCode) {
return aggregateSqlTypeCode == JSON || aggregateSqlTypeCode == SQLXML;

View File

@ -26,7 +26,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.hibernate.type.SqlTypes.ARRAY;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.BOOLEAN;
@ -280,16 +279,6 @@ public class SybaseASEAggregateSupport extends AggregateSupportImpl {
throw new IllegalArgumentException( "Unsupported aggregate SQL type: " + aggregateColumn.getTypeCode() );
}
@Override
public int aggregateComponentSqlTypeCode(int aggregateColumnSqlTypeCode, int columnSqlTypeCode) {
if ( aggregateColumnSqlTypeCode == SQLXML ) {
return columnSqlTypeCode == ARRAY ? XML_ARRAY : columnSqlTypeCode;
}
else {
return columnSqlTypeCode;
}
}
@Override
public boolean requiresAggregateCustomWriteExpressionRenderer(int aggregateSqlTypeCode) {
return aggregateSqlTypeCode == SQLXML;

View File

@ -17,6 +17,7 @@ public class UserDefinedArrayType extends AbstractUserDefinedType {
private Integer arraySqlTypeCode;
private String elementTypeName;
private Integer elementSqlTypeCode;
private Integer elementDdlTypeCode;
private Integer arrayLength;
public UserDefinedArrayType(String contributor, Namespace namespace, Identifier physicalTypeName) {
@ -47,6 +48,14 @@ public class UserDefinedArrayType extends AbstractUserDefinedType {
this.elementSqlTypeCode = elementSqlTypeCode;
}
public Integer getElementDdlTypeCode() {
return elementDdlTypeCode;
}
public void setElementDdlTypeCode(Integer elementDdlTypeCode) {
this.elementDdlTypeCode = elementDdlTypeCode;
}
public Integer getArrayLength() {
return arrayLength;
}

View File

@ -35,6 +35,7 @@ public enum CastType {
DATE, TIME, TIMESTAMP,
OFFSET_TIMESTAMP, ZONE_TIMESTAMP,
JSON,
XML,
NULL,
OTHER;

View File

@ -350,6 +350,9 @@ public interface JdbcType extends Serializable {
case JSON:
case JSON_ARRAY:
return CastType.JSON;
case SQLXML:
case XML_ARRAY:
return CastType.XML;
case NULL:
return CastType.NULL;
default:

View File

@ -34,6 +34,11 @@ public class OracleJsonArrayBlobJdbcType extends JsonArrayJdbcType {
return SqlTypes.BLOB;
}
@Override
public int getDdlTypeCode() {
return SqlTypes.BLOB;
}
@Override
public String toString() {
return "JsonArrayBlobJdbcType";

View File

@ -87,25 +87,7 @@ public class XmlArrayJdbcType extends ArrayJdbcType {
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
final String xml = ( (XmlArrayJdbcType ) getJdbcType() ).toString( value, getJavaType(), options );
SQLXML sqlxml = st.getConnection().createSQLXML();
sqlxml.setString( xml );
st.setSQLXML( index, sqlxml );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final String xml = ( (XmlArrayJdbcType ) getJdbcType() ).toString( value, getJavaType(), options );
SQLXML sqlxml = st.getConnection().createSQLXML();
sqlxml.setString( xml );
st.setSQLXML( name, sqlxml );
}
};
return new XmlArrayBinder<>( javaType, this );
}
@Override
@ -139,4 +121,28 @@ public class XmlArrayJdbcType extends ArrayJdbcType {
};
}
protected static class XmlArrayBinder<X> extends BasicBinder<X> {
public XmlArrayBinder(JavaType<X> javaType, XmlArrayJdbcType jdbcType) {
super( javaType, jdbcType );
}
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
final String xml = ( (XmlArrayJdbcType) getJdbcType() ).toString( value, getJavaType(), options );
SQLXML sqlxml = st.getConnection().createSQLXML();
sqlxml.setString( xml );
st.setSQLXML( index, sqlxml );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final String xml = ( (XmlArrayJdbcType ) getJdbcType() ).toString( value, getJavaType(), options );
SQLXML sqlxml = st.getConnection().createSQLXML();
sqlxml.setString( xml );
st.setSQLXML( name, sqlxml );
}
}
}

View File

@ -0,0 +1,58 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.type.internal;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.spi.TypeConfiguration;
import java.util.TimeZone;
public class TypeConfigurationWrapperOptions implements WrapperOptions {
private final TypeConfiguration typeConfiguration;
public TypeConfigurationWrapperOptions(TypeConfiguration typeConfiguration) {
this.typeConfiguration = typeConfiguration;
}
@Override
public Dialect getDialect() {
return typeConfiguration.getCurrentBaseSqlTypeIndicators().getDialect();
}
@Override
public SharedSessionContractImplementor getSession() {
return typeConfiguration.getSessionFactory().getWrapperOptions().getSession();
}
@Override
public SessionFactoryImplementor getSessionFactory() {
return typeConfiguration.getSessionFactory();
}
@Override
public boolean useStreamForLobBinding() {
return typeConfiguration.getSessionFactory().getWrapperOptions().useStreamForLobBinding();
}
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return typeConfiguration.getCurrentBaseSqlTypeIndicators().getPreferredSqlTypeCodeForBoolean();
}
@Override
public LobCreator getLobCreator() {
return typeConfiguration.getSessionFactory().getWrapperOptions().getLobCreator();
}
@Override
public TimeZone getJdbcTimeZone() {
return typeConfiguration.getSessionFactory().getWrapperOptions().getJdbcTimeZone();
}
}

View File

@ -108,10 +108,10 @@ public class XmlEmbeddableTest extends BaseSessionFactoryFunctionalTest {
public void testFetchNull() {
sessionFactoryScope().inSession(
entityManager -> {
List<XmlHolder> XmlHolders = entityManager.createQuery( "from XmlHolder b where b.id = 2", XmlHolder.class ).getResultList();
assertEquals( 1, XmlHolders.size() );
assertEquals( 2L, XmlHolders.get( 0 ).getId() );
EmbeddableAggregate.assertEquals( EmbeddableAggregate.createAggregate2(), XmlHolders.get( 0 ).getAggregate() );
List<XmlHolder> xmlHolders = entityManager.createQuery( "from XmlHolder b where b.id = 2", XmlHolder.class ).getResultList();
assertEquals( 1, xmlHolders.size() );
assertEquals( 2L, xmlHolders.get( 0 ).getId() );
EmbeddableAggregate.assertEquals( EmbeddableAggregate.createAggregate2(), xmlHolders.get( 0 ).getAggregate() );
}
);
}