Skip to content

Commit eeba7ed

Browse files
committed
HHH-18799 Add XML aggregate support for Oracle
1 parent 6d1b9c4 commit eeba7ed

22 files changed

+730
-162
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
5454
import org.hibernate.internal.util.JdbcExceptionHelper;
5555
import org.hibernate.internal.util.StringHelper;
56+
import org.hibernate.mapping.AggregateColumn;
5657
import org.hibernate.mapping.CheckConstraint;
58+
import org.hibernate.mapping.Table;
5759
import org.hibernate.mapping.UserDefinedType;
5860
import org.hibernate.metamodel.mapping.EntityMappingType;
5961
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
@@ -81,6 +83,7 @@
8183
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl;
8284
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
8385
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
86+
import org.hibernate.tool.schema.internal.StandardTableExporter;
8487
import org.hibernate.tool.schema.spi.Exporter;
8588
import org.hibernate.type.JavaObjectType;
8689
import org.hibernate.type.NullType;
@@ -172,6 +175,17 @@ public class OracleLegacyDialect extends Dialect {
172175
private final OracleUserDefinedTypeExporter userDefinedTypeExporter = new OracleUserDefinedTypeExporter( this );
173176
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
174177
private final SequenceSupport oracleSequenceSupport = OracleSequenceSupport.getInstance(this);
178+
private final StandardTableExporter oracleTableExporter = new StandardTableExporter( this ) {
179+
@Override
180+
protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
181+
final JdbcType jdbcType = aggregateColumn.getType().getJdbcType();
182+
if ( dialect.getVersion().isBefore( 23, 6 ) && jdbcType.isXml() ) {
183+
// ORA-00600 when selecting XML columns that have a check constraint was fixed in 23.6
184+
return;
185+
}
186+
super.applyAggregateColumnCheck( buf, aggregateColumn );
187+
}
188+
};
175189

176190
public OracleLegacyDialect() {
177191
this( DatabaseVersion.make( 8, 0 ) );
@@ -503,6 +517,8 @@ public String castPattern(CastType from, CastType to) {
503517
return "to_timestamp_tz(?1,'YYYY-MM-DD HH24:MI:SS.FF9 TZR')";
504518
}
505519
break;
520+
case XML:
521+
return "xmlparse(document ?1)";
506522
}
507523
return super.castPattern(from, to);
508524
}
@@ -1042,6 +1058,11 @@ public SequenceSupport getSequenceSupport() {
10421058
return oracleSequenceSupport;
10431059
}
10441060

1061+
@Override
1062+
public Exporter<Table> getTableExporter() {
1063+
return oracleTableExporter;
1064+
}
1065+
10451066
@Override
10461067
public String getQuerySequencesString() {
10471068
return "select * from all_sequences";

hibernate-core/src/main/java/org/hibernate/dialect/H2JsonJdbcType.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
1313
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
14+
import org.hibernate.type.SqlTypes;
1415
import org.hibernate.type.descriptor.ValueBinder;
1516
import org.hibernate.type.descriptor.WrapperOptions;
1617
import org.hibernate.type.descriptor.java.JavaType;
@@ -31,6 +32,11 @@ protected H2JsonJdbcType(EmbeddableMappingType embeddableMappingType) {
3132
super( embeddableMappingType );
3233
}
3334

35+
@Override
36+
public int getJdbcTypeCode() {
37+
return SqlTypes.VARBINARY;
38+
}
39+
3440
@Override
3541
public String toString() {
3642
return "H2JsonJdbcType";

hibernate-core/src/main/java/org/hibernate/dialect/OracleArrayJdbcType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ private void createUserDefinedArrayType(
253253
userDefinedArrayType.setArraySqlTypeCode( getDdlTypeCode() );
254254
userDefinedArrayType.setElementTypeName( elementTypeName );
255255
userDefinedArrayType.setElementSqlTypeCode( elementJdbcType.getDefaultSqlTypeCode() );
256+
userDefinedArrayType.setElementDdlTypeCode( elementJdbcType.getDdlTypeCode() );
256257
userDefinedArrayType.setArrayLength( columnSize.getArrayLength() == null ? 127 : columnSize.getArrayLength() );
257258
}
258259

hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor;
4848
import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
4949
import org.hibernate.internal.util.config.ConfigurationHelper;
50+
import org.hibernate.mapping.AggregateColumn;
51+
import org.hibernate.mapping.Table;
5052
import org.hibernate.mapping.UserDefinedType;
5153
import org.hibernate.mapping.CheckConstraint;
5254
import org.hibernate.metamodel.mapping.EntityMappingType;
@@ -78,6 +80,7 @@
7880
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorOracleDatabaseImpl;
7981
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
8082
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
83+
import org.hibernate.tool.schema.internal.StandardTableExporter;
8184
import org.hibernate.tool.schema.spi.Exporter;
8285
import org.hibernate.type.JavaObjectType;
8386
import org.hibernate.type.NullType;
@@ -181,6 +184,17 @@ public class OracleDialect extends Dialect {
181184
private final OracleUserDefinedTypeExporter userDefinedTypeExporter = new OracleUserDefinedTypeExporter( this );
182185
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
183186
private final SequenceSupport oracleSequenceSupport = OracleSequenceSupport.getInstance(this);
187+
private final StandardTableExporter oracleTableExporter = new StandardTableExporter( this ) {
188+
@Override
189+
protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggregateColumn) {
190+
final JdbcType jdbcType = aggregateColumn.getType().getJdbcType();
191+
if ( dialect.getVersion().isBefore( 23, 6 ) && jdbcType.isXml() ) {
192+
// ORA-00600 when selecting XML columns that have a check constraint was fixed in 23.6
193+
return;
194+
}
195+
super.applyAggregateColumnCheck( buf, aggregateColumn );
196+
}
197+
};
184198

185199
// Is it an Autonomous Database Cloud Service?
186200
protected final boolean autonomous;
@@ -589,6 +603,8 @@ public String castPattern(CastType from, CastType to) {
589603
return "to_timestamp_tz(?1,'YYYY-MM-DD HH24:MI:SS.FF9 TZR')";
590604
}
591605
break;
606+
case XML:
607+
return "xmlparse(document ?1)";
592608
}
593609
return super.castPattern(from, to);
594610
}
@@ -988,6 +1004,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry
9881004
typeContributions.contributeJdbcType( OracleBooleanJdbcType.INSTANCE );
9891005
}
9901006
typeContributions.contributeJdbcType( OracleXmlJdbcType.INSTANCE );
1007+
typeContributions.contributeJdbcTypeConstructor( OracleXmlArrayJdbcTypeConstructor.INSTANCE );
9911008
if ( OracleJdbcHelper.isUsable( serviceRegistry ) ) {
9921009
typeContributions.contributeJdbcType( OracleJdbcHelper.getStructJdbcType( serviceRegistry ) );
9931010
}
@@ -1132,6 +1149,11 @@ public SequenceSupport getSequenceSupport() {
11321149
return oracleSequenceSupport;
11331150
}
11341151

1152+
@Override
1153+
public Exporter<Table> getTableExporter() {
1154+
return oracleTableExporter;
1155+
}
1156+
11351157
@Override
11361158
public String getQuerySequencesString() {
11371159
return "select * from all_sequences";

hibernate-core/src/main/java/org/hibernate/dialect/OracleJsonArrayJdbcType.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
package org.hibernate.dialect;
66

7+
import org.hibernate.type.SqlTypes;
78
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
89
import org.hibernate.type.descriptor.java.JavaType;
910
import org.hibernate.type.descriptor.jdbc.JdbcType;
@@ -20,6 +21,11 @@ public OracleJsonArrayJdbcType(JdbcType elementJdbcType) {
2021
super( elementJdbcType );
2122
}
2223

24+
@Override
25+
public int getDdlTypeCode() {
26+
return SqlTypes.JSON;
27+
}
28+
2329
@Override
2430
public String toString() {
2531
return "OracleJsonJdbcType";

hibernate-core/src/main/java/org/hibernate/dialect/OracleUserDefinedTypeExporter.java

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
*/
55
package org.hibernate.dialect;
66

7-
import java.util.Locale;
8-
97
import org.hibernate.boot.Metadata;
108
import org.hibernate.boot.model.naming.Identifier;
119
import org.hibernate.boot.model.relational.QualifiedName;
@@ -16,18 +14,16 @@
1614
import org.hibernate.type.SqlTypes;
1715

1816
import static java.sql.Types.BOOLEAN;
19-
import static org.hibernate.type.SqlTypes.BIGINT;
2017
import static org.hibernate.type.SqlTypes.BINARY;
18+
import static org.hibernate.type.SqlTypes.BIT;
19+
import static org.hibernate.type.SqlTypes.BLOB;
2120
import static org.hibernate.type.SqlTypes.DATE;
22-
import static org.hibernate.type.SqlTypes.INTEGER;
2321
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
24-
import static org.hibernate.type.SqlTypes.SMALLINT;
2522
import static org.hibernate.type.SqlTypes.TABLE;
2623
import static org.hibernate.type.SqlTypes.TIME;
2724
import static org.hibernate.type.SqlTypes.TIMESTAMP;
2825
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
2926
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
30-
import static org.hibernate.type.SqlTypes.TINYINT;
3127
import static org.hibernate.type.SqlTypes.UUID;
3228
import static org.hibernate.type.SqlTypes.VARBINARY;
3329

@@ -61,11 +57,13 @@ public String[] getSqlCreateStrings(
6157
}
6258
final int arrayLength = userDefinedType.getArrayLength();
6359
final Integer elementSqlTypeCode = userDefinedType.getElementSqlTypeCode();
60+
final Integer elementDdlTypeCode = userDefinedType.getElementDdlTypeCode();
6461
final String jsonTypeName = metadata.getDatabase().getTypeConfiguration().getDdlTypeRegistry().getTypeName(
6562
SqlTypes.JSON,
6663
dialect
6764
);
68-
final String valueExpression = determineValueExpression( "t.value", elementSqlTypeCode, elementType );
65+
final String valueExpression = determineValueExpression( "t.value", elementSqlTypeCode, elementDdlTypeCode, elementType );
66+
final String jsonElementType = determineJsonElementType( elementSqlTypeCode, elementDdlTypeCode, elementType );
6967
return new String[] {
7068
"create or replace type " + arrayTypeName + " as varying array(" + arrayLength + ") of " + elementType,
7169
"create or replace function " + arrayTypeName + "_cmp(a in " + arrayTypeName +
@@ -266,7 +264,7 @@ public String[] getSqlCreateStrings(
266264
"res " + arrayTypeName + ":=" + arrayTypeName + "(); begin " +
267265
"if arr is null then return null; end if; " +
268266
"select " + valueExpression + " bulk collect into res " +
269-
"from json_table(arr,'$[*]' columns (value path '$')) t; " +
267+
"from json_table(arr,'$[*]' columns (value " + jsonElementType + " path '$')) t; " +
270268
"return res; " +
271269
"end;"
272270
};
@@ -332,17 +330,8 @@ private boolean supportsIfExistsBeforeFunctionName() {
332330
return dialect.getVersion().isSameOrAfter( 23 );
333331
}
334332

335-
private String determineValueExpression(String expression, int elementSqlTypeCode, String elementType) {
333+
private String determineValueExpression(String expression, int elementSqlTypeCode, Integer elementDdlTypeCode, String elementType) {
336334
switch ( elementSqlTypeCode ) {
337-
case BOOLEAN:
338-
if ( elementType.toLowerCase( Locale.ROOT ).trim().startsWith( "number" ) ) {
339-
return "decode(" + expression + ",'true',1,'false',0,null)";
340-
}
341-
case TINYINT:
342-
case SMALLINT:
343-
case INTEGER:
344-
case BIGINT:
345-
return "cast(" + expression + " as " + elementType + ")";
346335
case DATE:
347336
return "to_date(" + expression + ",'YYYY-MM-DD')";
348337
case TIME:
@@ -355,14 +344,47 @@ private String determineValueExpression(String expression, int elementSqlTypeCod
355344
case BINARY:
356345
case VARBINARY:
357346
case LONG32VARBINARY:
358-
return "hextoraw(" + expression + ")";
347+
case BLOB:
348+
return "xmlcast(xmlcdata(" + expression + ") as " + elementType + ")";
359349
case UUID:
360350
return "hextoraw(replace(" + expression + ",'-',''))";
351+
case BIT:
352+
return "decode(" + expression + ",'true',1,'false',0,null)";
353+
case BOOLEAN:
354+
if ( SqlTypes.isNumericType( elementDdlTypeCode ) ) {
355+
return "decode(" + expression + ",'true',1,'false',0,null)";
356+
}
357+
// Fall-through intended
361358
default:
362359
return expression;
363360
}
364361
}
365362

363+
private String determineJsonElementType(Integer elementSqlTypeCode, Integer elementDdlTypeCode, String elementType) {
364+
switch ( elementSqlTypeCode ) {
365+
case BINARY:
366+
case VARBINARY:
367+
case LONG32VARBINARY:
368+
case BLOB:
369+
return "clob";
370+
case BOOLEAN:
371+
if ( !SqlTypes.isNumericType( elementDdlTypeCode ) ) {
372+
return elementType;
373+
}
374+
// Fall-through intended
375+
case BIT:
376+
case DATE:
377+
case TIME:
378+
case TIMESTAMP:
379+
case TIMESTAMP_WITH_TIMEZONE:
380+
case TIMESTAMP_UTC:
381+
case UUID:
382+
return "varchar2(4000)";
383+
default:
384+
return elementType;
385+
}
386+
}
387+
366388
protected String createOrReplaceConcatFunction(String arrayTypeName) {
367389
// Since Oracle has no builtin concat function for varrays and doesn't support varargs,
368390
// we have to create a function with a fixed amount of arguments with default that fits "most" cases.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.dialect;
6+
7+
import org.hibernate.type.descriptor.ValueBinder;
8+
import org.hibernate.type.descriptor.WrapperOptions;
9+
import org.hibernate.type.descriptor.java.JavaType;
10+
import org.hibernate.type.descriptor.jdbc.JdbcType;
11+
import org.hibernate.type.descriptor.jdbc.XmlArrayJdbcType;
12+
13+
import java.sql.CallableStatement;
14+
import java.sql.PreparedStatement;
15+
import java.sql.SQLException;
16+
import java.sql.Types;
17+
18+
/**
19+
* @author Christian Beikov
20+
*/
21+
public class OracleXmlArrayJdbcType extends XmlArrayJdbcType {
22+
23+
public OracleXmlArrayJdbcType(JdbcType elementJdbcType) {
24+
super( elementJdbcType );
25+
}
26+
27+
@Override
28+
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
29+
// Seems the Oracle JDBC driver doesn't support `setNull(index, Types.SQLXML)`
30+
// but it seems that the following works fine
31+
return new XmlArrayBinder<>( javaType, this ) {
32+
@Override
33+
protected void doBindNull(PreparedStatement st, int index, WrapperOptions options) throws SQLException {
34+
st.setNull( index, Types.VARCHAR );
35+
}
36+
37+
@Override
38+
protected void doBindNull(CallableStatement st, String name, WrapperOptions options) throws SQLException {
39+
st.setNull( name, Types.VARCHAR );
40+
}
41+
};
42+
}
43+
44+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.dialect;
6+
7+
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
8+
import org.hibernate.type.BasicType;
9+
import org.hibernate.type.SqlTypes;
10+
import org.hibernate.type.descriptor.jdbc.JdbcType;
11+
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
12+
import org.hibernate.type.spi.TypeConfiguration;
13+
14+
/**
15+
* Factory for {@link OracleXmlArrayJdbcType}.
16+
*/
17+
public class OracleXmlArrayJdbcTypeConstructor implements JdbcTypeConstructor {
18+
19+
public static final OracleXmlArrayJdbcTypeConstructor INSTANCE = new OracleXmlArrayJdbcTypeConstructor();
20+
21+
@Override
22+
public JdbcType resolveType(
23+
TypeConfiguration typeConfiguration,
24+
Dialect dialect,
25+
BasicType<?> elementType,
26+
ColumnTypeInformation columnTypeInformation) {
27+
return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation );
28+
}
29+
30+
@Override
31+
public JdbcType resolveType(
32+
TypeConfiguration typeConfiguration,
33+
Dialect dialect,
34+
JdbcType elementType,
35+
ColumnTypeInformation columnTypeInformation) {
36+
return new OracleXmlArrayJdbcType( elementType );
37+
}
38+
39+
@Override
40+
public int getDefaultSqlTypeCode() {
41+
return SqlTypes.XML_ARRAY;
42+
}
43+
}

0 commit comments

Comments
 (0)