Skip to content

Commit

Permalink
[hibernate#1732] Fix to support array Types
Browse files Browse the repository at this point in the history
  • Loading branch information
blafond authored and DavideD committed Dec 13, 2023
1 parent 1195039 commit f0a667a
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive.adaptor.impl;

import java.sql.Array;
import java.sql.ResultSet;
import java.util.Map;

import org.hibernate.type.descriptor.jdbc.JdbcType;

public class ArrayAdaptor implements Array {

private final String baseTypeName;
private final int jdbcTypeCode;
private Object[] objects;

public ArrayAdaptor(JdbcType elementJdbcType, Object[] objects) {
this( elementJdbcType.getFriendlyName(), elementJdbcType.getJdbcTypeCode(), objects );
}

public ArrayAdaptor(String baseTypeName, Object[] objects) {
this( baseTypeName, 0, objects );
}

private ArrayAdaptor(String baseTypeName, int jdbcTypeCode, Object[] objects) {
this.baseTypeName = baseTypeName;
this.jdbcTypeCode = jdbcTypeCode;
this.objects = objects;
}

@Override
public String getBaseTypeName() {
return baseTypeName;
}

@Override
public int getBaseType() {
return jdbcTypeCode;
}

@Override
public Object getArray() {
return objects;
}

@Override
public Object getArray(Map<String, Class<?>> map) {
throw new UnsupportedOperationException( "array of maps is not yet supported" );
}

@Override
public Object getArray(long index, int count) {
throw new UnsupportedOperationException( "array of maps is not yet supported" );
}

@Override
public Object getArray(long index, int count, Map<String, Class<?>> map) {
throw new UnsupportedOperationException( "array of maps is not yet supported" );
}

@Override
public ResultSet getResultSet() {
throw new UnsupportedOperationException( "array of maps is not yet supported" );
}

@Override
public ResultSet getResultSet(Map<String, Class<?>> map) {
throw new UnsupportedOperationException( "array of maps is not yet supported" );
}

@Override
public ResultSet getResultSet(long index, int count) {
throw new UnsupportedOperationException( "array of maps is not yet supported" );
}

@Override
public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) {
throw new UnsupportedOperationException( "array of maps is not yet supported" );
}

@Override
public void free() {
objects = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public void setString(int parameterIndex, String x) {

@Override
public void setBytes(int parameterIndex, byte[] x) {
put( parameterIndex, Buffer.buffer(x) );
put( parameterIndex, Buffer.buffer( x ) );
}

@Override
Expand Down Expand Up @@ -245,7 +245,12 @@ public void setClob(int parameterIndex, Clob x) {

@Override
public void setArray(int parameterIndex, Array x) {
put( parameterIndex, x );
try {
put( parameterIndex, x.getArray() );
}
catch (SQLException e) {
throw new AssertionFailure( "This shouldn't have happened" );
}
}

@Override
Expand All @@ -260,7 +265,7 @@ public void setDate(int parameterIndex, Date x, Calendar cal) {

@Override
public void setNull(int parameterIndex, int sqlType, String typeName) {
throw new UnsupportedOperationException();
put( parameterIndex, new JdbcNull( sqlType ) );
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import org.hibernate.engine.jdbc.BlobProxy;
import org.hibernate.engine.jdbc.ClobProxy;
import org.hibernate.type.descriptor.jdbc.JdbcType;

import io.vertx.core.buffer.Buffer;
import io.vertx.sqlclient.Row;
Expand Down Expand Up @@ -748,6 +749,15 @@ public Array getArray(int columnIndex) {
throw new UnsupportedOperationException();
}

public Array getArray(int columnIndex, JdbcType elementJdbcType) {
Object[] objects = (Object[]) row.getValue( columnIndex - 1 );
wasNull = objects == null;
if ( objects == null ) {
return null;
}
return new ArrayAdaptor( elementJdbcType, objects );
}

@Override
public Object getObject(String columnLabel, Map<String, Class<?>> map) {
throw new UnsupportedOperationException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor;
import org.hibernate.reactive.type.descriptor.jdbc.ReactiveArrayJdbcType;
import org.hibernate.reactive.type.descriptor.jdbc.ReactiveArrayJdbcTypeConstructor;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.BasicTypeRegistry;
Expand All @@ -45,7 +45,6 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.ObjectJdbcType;
import org.hibernate.type.descriptor.jdbc.ReactiveArrayJdbcTypeConstructor;
import org.hibernate.type.descriptor.jdbc.TimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
Expand Down Expand Up @@ -83,7 +82,6 @@ private void registerReactiveChanges(TypeContributions typeContributions, Servic
javaTypeRegistry.addDescriptor( JsonObjectJavaType.INSTANCE );

JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry();
jdbcTypeRegistry.addDescriptor( ReactiveArrayJdbcType.INSTANCE );
jdbcTypeRegistry.addTypeConstructor( ReactiveArrayJdbcTypeConstructor.INSTANCE );

if ( dialect instanceof MySQLDialect || dialect instanceof DB2Dialect || dialect instanceof OracleDialect ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@

import java.lang.reflect.Array;
import java.sql.CallableStatement;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

import org.hibernate.HibernateException;
import org.hibernate.reactive.adaptor.impl.ArrayAdaptor;
import org.hibernate.reactive.adaptor.impl.ResultSetAdaptor;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
Expand All @@ -24,19 +31,21 @@
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.ObjectJdbcType;
import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterArray;
import org.hibernate.type.spi.TypeConfiguration;



/**
* {@link java.sql.Connection} has a method {@link java.sql.Connection#createArrayOf(String, Object[])}, but we don't have
* it in Vert.x SQL Client.
* <p>
* Plus, the Vert.x SQL client accept arrays as parameters.
* </p>
*
* @see org.hibernate.type.descriptor.jdbc.ArrayJdbcType
*/
public class ReactiveArrayJdbcType implements JdbcType {

public static final ReactiveArrayJdbcType INSTANCE = new ReactiveArrayJdbcType( ObjectJdbcType.INSTANCE );
public class ReactiveArrayJdbcType implements JdbcType {

private final JdbcType elementJdbcType;

Expand Down Expand Up @@ -88,20 +97,64 @@ public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
st.setObject( index, value );

ArrayAdaptor arrayObject = getArrayObject( value, options );
st.setArray( index, arrayObject );
}

@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options){
try {
st.setObject( name, value );
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) {
throw new UnsupportedOperationException();
}

private ArrayAdaptor getArrayObject(X value, WrapperOptions options) {
final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration();
ReactiveArrayJdbcType jdbcType = (ReactiveArrayJdbcType) getJdbcType();
final JdbcType elementJdbcType = jdbcType.getElementJdbcType();
final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry()
.getDescriptor( elementJdbcType.getDefaultSqlTypeCode() );
final Class<?> elementJdbcJavaTypeClass = elementJdbcJavaTypeClass(
options,
elementJdbcType,
underlyingJdbcType,
typeConfiguration
);
//noinspection unchecked
final Class<Object[]> arrayClass = (Class<Object[]>) Array.newInstance( elementJdbcJavaTypeClass, 0 )
.getClass();
final Object[] objects = getJavaType().unwrap( value, arrayClass, options );
return new ArrayAdaptor( elementJdbcType, objects );
}

private Class<?> elementJdbcJavaTypeClass(
WrapperOptions options,
JdbcType elementJdbcType,
JdbcType underlyingJdbcType,
TypeConfiguration typeConfiguration) {
final Class<?> preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( options );
final Class<?> elementJdbcJavaTypeClass;
if ( preferredJavaTypeClass == null ) {
elementJdbcJavaTypeClass = underlyingJdbcType
.getJdbcRecommendedJavaTypeMapping( null, null, typeConfiguration )
.getJavaTypeClass();
}
catch (SQLException ex) {
throw new HibernateException(
"JDBC driver does not support named parameters for setArray. Use positional.",
ex
);
else {
elementJdbcJavaTypeClass = preferredJavaTypeClass;
}
return convertTypes( elementJdbcJavaTypeClass );
}

private Class<?> convertTypes(Class<?> elementJdbcJavaTypeClass) {
if ( Timestamp.class.equals( elementJdbcJavaTypeClass ) ) {
return LocalDateTime.class;
}
if ( Date.class.equals( elementJdbcJavaTypeClass ) ) {
return LocalDate.class;
}
if ( Time.class.equals( elementJdbcJavaTypeClass ) ) {
return LocalTime.class;
}
return elementJdbcJavaTypeClass;
}
};
}
Expand All @@ -110,13 +163,19 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions
public <X> ValueExtractor<X> getExtractor(final JavaType<X> javaTypeDescriptor) {
return new BasicExtractor<>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getArray( paramIndex ), options );
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) {
return javaTypeDescriptor.wrap(
( (ResultSetAdaptor) rs ).getArray( paramIndex, elementJdbcType ),
options
);
}

@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getArray( index ), options );
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) {
return javaTypeDescriptor.wrap(
( (ResultSetAdaptor) statement ).getArray( index, elementJdbcType ),
options
);
}

@Override
Expand All @@ -132,6 +191,10 @@ public String getFriendlyName() {
return "ARRAY";
}

public JdbcType getElementJdbcType() {
return elementJdbcType;
}

@Override
public String toString() {
return ReactiveArrayJdbcType.class.getSimpleName() + "(" + getFriendlyName() + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,40 @@
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.type.descriptor.jdbc;
package org.hibernate.reactive.type.descriptor.jdbc;

import java.sql.Types;

import org.hibernate.dialect.Dialect;
import org.hibernate.reactive.type.descriptor.jdbc.ReactiveArrayJdbcType;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
import org.hibernate.type.spi.TypeConfiguration;

import static org.hibernate.dialect.DialectDelegateWrapper.extractRealDialect;

/**
* Factory for {@link ReactiveArrayJdbcType}.
*/
public class ReactiveArrayJdbcTypeConstructor implements JdbcTypeConstructor {
public static final ReactiveArrayJdbcTypeConstructor INSTANCE = new ReactiveArrayJdbcTypeConstructor();

@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<?> elementType,
ColumnTypeInformation columnTypeInformation) {
Dialect realDialect = extractRealDialect( dialect );
if ( realDialect instanceof OracleDialect ) {
String typeName = columnTypeInformation == null ? null : columnTypeInformation.getTypeName();
if ( typeName == null || typeName.isBlank() ) {
typeName = ReactiveOracleArrayJdbcType.getTypeName( elementType.getJavaTypeDescriptor(), dialect );
}
return new ReactiveOracleArrayJdbcType( elementType.getJdbcType(), typeName );
}
return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation );
}

Expand All @@ -33,6 +46,14 @@ public JdbcType resolveType(
Dialect dialect,
JdbcType elementType,
ColumnTypeInformation columnTypeInformation) {
Dialect realDialect = extractRealDialect( dialect );
if ( realDialect instanceof OracleDialect ) {
// a bit wrong, since columnTypeInformation.getTypeName() is typically null!
return new ReactiveOracleArrayJdbcType(
elementType,
columnTypeInformation == null ? null : columnTypeInformation.getTypeName()
);
}
return new ReactiveArrayJdbcType( elementType );
}

Expand Down
Loading

0 comments on commit f0a667a

Please sign in to comment.