From d051b7a4cb570a7b3ed324d52c41c6dd27771489 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 28 Nov 2024 12:14:53 +0100 Subject: [PATCH] Fix --- .../impl/PreparedStatementAdaptor.java | 20 +- .../adaptor/impl/ResultSetAdaptor.java | 9 +- .../ReactiveOracleSqlAstTranslator.java | 43 --- .../hibernate/reactive/logging/impl/Log.java | 6 + .../ReactiveRuntimeModelCreationContext.java | 128 ++++++++ .../impl/ReactiveAbstractEntityPersister.java | 167 ++++++---- ...ReactiveJoinedSubclassEntityPersister.java | 5 +- .../ReactiveSingleTableEntityPersister.java | 5 +- .../ReactiveUnionSubclassEntityPersister.java | 5 +- .../pool/impl/SqlClientConnection.java | 3 +- .../impl/ReactiveTypeContributor.java | 3 + .../jdbc/ReactiveXmlArrayJdbcType.java | 63 ++++ .../ReactiveXmlArrayJdbcTypeConstructor.java | 39 +++ .../descriptor/jdbc/ReactiveXmlJdbcType.java | 69 +++++ .../reactive/containers/DB2Database.java | 10 +- .../schema/ColumnTypesMappingTest.java | 18 +- .../reactive/types/JavaTypesArrayTest.java | 292 ++++++++---------- 17 files changed, 602 insertions(+), 283 deletions(-) delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcTypeConstructor.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java index 8ff274857..066ec9de6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/PreparedStatementAdaptor.java @@ -5,11 +5,6 @@ */ package org.hibernate.reactive.adaptor.impl; -import io.vertx.core.buffer.Buffer; - -import org.hibernate.AssertionFailure; -import org.hibernate.HibernateException; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -39,12 +34,23 @@ import java.util.Calendar; import java.util.function.Function; +import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; +import org.hibernate.reactive.logging.impl.Log; + +import io.vertx.core.buffer.Buffer; + +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + /** * Collects parameter bindings from Hibernate core code * that expects a JDBC {@link PreparedStatement}. */ public class PreparedStatementAdaptor implements PreparedStatement { + private static final Log LOG = make( Log.class, lookup() ); + @FunctionalInterface public interface Binder { void bind(PreparedStatement statement) throws SQLException; @@ -315,7 +321,7 @@ public void setNClob(int parameterIndex, Reader reader, long length) { @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) { - throw new UnsupportedOperationException(); + throw LOG.unsupportedXmlType(); } @Override @@ -540,7 +546,7 @@ public int[] executeBatch() { @Override public Connection getConnection() { - throw new UnsupportedOperationException(); + throw LOG.unexpectedConnectionRequest(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java index f8954e184..b75133699 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java @@ -38,6 +38,7 @@ import org.hibernate.engine.jdbc.proxy.BlobProxy; import org.hibernate.engine.jdbc.proxy.ClobProxy; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.type.descriptor.jdbc.JdbcType; import io.vertx.core.buffer.Buffer; @@ -48,8 +49,10 @@ import io.vertx.sqlclient.desc.ColumnDescriptor; import io.vertx.sqlclient.impl.RowBase; +import static java.lang.invoke.MethodHandles.lookup; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; /** * An adaptor that allows Hibernate core code which expects a JDBC @@ -57,6 +60,8 @@ */ public class ResultSetAdaptor implements ResultSet { + private static final Log LOG = make( Log.class, lookup() ); + private final Iterator iterator; private final List columnDescriptors; @@ -866,12 +871,12 @@ public NClob getNClob(String columnLabel) { @Override public SQLXML getSQLXML(int columnIndex) { - throw new UnsupportedOperationException(); + throw LOG.unsupportedXmlType(); } @Override public SQLXML getSQLXML(String columnLabel) { - throw new UnsupportedOperationException(); + throw LOG.unsupportedXmlType(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java deleted file mode 100644 index 0773ab033..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java +++ /dev/null @@ -1,43 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.dialect; - -import org.hibernate.dialect.OracleSqlAstTranslator; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.persister.entity.mutation.EntityTableMapping; -import org.hibernate.reactive.sql.model.ReactiveDeleteOrUpsertOperation; -import org.hibernate.sql.ast.tree.Statement; -import org.hibernate.sql.exec.spi.JdbcOperation; -import org.hibernate.sql.model.MutationOperation; -import org.hibernate.sql.model.internal.OptionalTableUpdate; -import org.hibernate.sql.model.jdbc.UpsertOperation; - -public class ReactiveOracleSqlAstTranslator extends OracleSqlAstTranslator { - public ReactiveOracleSqlAstTranslator( - SessionFactoryImplementor sessionFactory, - Statement statement) { - super( sessionFactory, statement ); - } - - @Override - public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) { - renderUpsertStatement( optionalTableUpdate ); - - final UpsertOperation upsertOperation = new UpsertOperation( - optionalTableUpdate.getMutatingTable().getTableMapping(), - optionalTableUpdate.getMutationTarget(), - getSql(), - getParameterBinders() - ); - - return new ReactiveDeleteOrUpsertOperation( - optionalTableUpdate.getMutationTarget(), - (EntityTableMapping) optionalTableUpdate.getMutatingTable().getTableMapping(), - upsertOperation, - optionalTableUpdate - ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java index 213b23d02..9c6c126cc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java @@ -258,6 +258,12 @@ public interface Log extends BasicLogger { @Message(id = 80, value = "No results were returned by the query (you can try running it with '.executeUpdate()'): %1$s") HibernateException noResultException(String sql); + @Message(id = 81, value = "The Vert.x SQL client doesn't support the SQL XML data type. If it's the mapping of an array, you can also try setting the property `hibernate.type.preferred_array_jdbc_type`") + HibernateException unsupportedXmlType(); + + @Message(id = 83, value = "Unexpected request of a non reactive connection") + HibernateException unexpectedConnectionRequest(); + // Same method that exists in CoreMessageLogger @LogMessage(level = WARN) @Message(id = 104, value = "firstResult/maxResults specified with collection fetch; applying in memory!" ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java new file mode 100644 index 000000000..876312b47 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java @@ -0,0 +1,128 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.metamodel.mapping.internal; + +import java.util.Map; + +import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.generator.Generator; +import org.hibernate.mapping.GeneratorSettings; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.sqm.function.SqmFunctionRegistry; +import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; +import org.hibernate.type.spi.TypeConfiguration; + +public class ReactiveRuntimeModelCreationContext implements RuntimeModelCreationContext { + + private final RuntimeModelCreationContext delegate; + + public ReactiveRuntimeModelCreationContext(RuntimeModelCreationContext delegate) { + this.delegate = delegate; + } + + @Override + public EntityMetamodel createEntityMetamodel(PersistentClass persistentClass, EntityPersister persister) { + return new ReactiveEntityMetamodel( persistentClass, persister, delegate ); + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + return delegate.getSessionFactory(); + } + + @Override + public BootstrapContext getBootstrapContext() { + return delegate.getBootstrapContext(); + } + + @Override + public MetadataImplementor getBootModel() { + return delegate.getBootModel(); + } + + @Override + public MappingMetamodelImplementor getDomainModel() { + return delegate.getDomainModel(); + } + + @Override + public TypeConfiguration getTypeConfiguration() { + return delegate.getTypeConfiguration(); + } + + @Override + public JavaTypeRegistry getJavaTypeRegistry() { + return delegate.getJavaTypeRegistry(); + } + + @Override + public MetadataImplementor getMetadata() { + return delegate.getMetadata(); + } + + @Override + public SqmFunctionRegistry getFunctionRegistry() { + return delegate.getFunctionRegistry(); + } + + @Override + public Map getSettings() { + return delegate.getSettings(); + } + + @Override + public Dialect getDialect() { + return delegate.getDialect(); + } + + @Override + public CacheImplementor getCache() { + return delegate.getCache(); + } + + @Override + public SessionFactoryOptions getSessionFactoryOptions() { + return delegate.getSessionFactoryOptions(); + } + + @Override + public JdbcServices getJdbcServices() { + return delegate.getJdbcServices(); + } + + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return delegate.getSqlStringGenerationContext(); + } + + @Override + public ServiceRegistry getServiceRegistry() { + return delegate.getServiceRegistry(); + } + + @Override + public Map getGenerators() { + return delegate.getGenerators(); + } + + @Override + public GeneratorSettings getGeneratorSettings() { + return delegate.getGeneratorSettings(); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java index 400cf467f..22ccdaf13 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java @@ -7,7 +7,12 @@ import java.sql.ResultSet; import java.sql.SQLException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletionStage; import org.hibernate.AssertionFailure; @@ -18,11 +23,21 @@ import org.hibernate.MappingException; import org.hibernate.StaleObjectStateException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.bytecode.enhance.spi.interceptor.*; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.ManagedTypeHelper; -import org.hibernate.engine.spi.*; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; import org.hibernate.generator.OnExecutionGenerator; @@ -32,11 +47,14 @@ import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.spi.NaturalIdLoader; import org.hibernate.mapping.PersistentClass; -import org.hibernate.metamodel.mapping.*; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.AttributeMappingsList; +import org.hibernate.metamodel.mapping.EntityVersionMapping; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.NaturalIdMapping; +import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; -import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.AbstractEntityPersister; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; import org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; @@ -48,12 +66,10 @@ import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; -import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel; import org.hibernate.sql.SimpleSelect; import org.hibernate.sql.Update; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.BasicType; import jakarta.persistence.metamodel.Attribute; @@ -80,7 +96,7 @@ * three flavors of {@link ReactiveEntityPersister}. Therefore, this * interface is defined as a mixin. This design avoid duplicating * the code in this class in the three different subclasses. - * + *

* Concrete implementations of this interface _must_ also extend * {@code AbstractEntityPersister} or one of its concrete * subclasses. @@ -92,17 +108,6 @@ */ public interface ReactiveAbstractEntityPersister extends ReactiveEntityPersister { - class ReactiveEntityMetamodelFactory extends AbstractEntityPersister.EntityMetamodelFactory { - - @Override - public EntityMetamodel createEntityMetamodel( - PersistentClass persistentClass, - EntityPersister persister, - RuntimeModelCreationContext creationContext) { - return new ReactiveEntityMetamodel( persistentClass, persister, creationContext ); - } - } - default Parameters parameters() { return Parameters.instance( getFactory().getJdbcServices().getDialect() ); } @@ -151,7 +156,7 @@ default String generateSelectLockString(LockOptions lockOptions) { default String generateUpdateLockString(LockOptions lockOptions) { final SessionFactoryImplementor factory = getFactory(); final Update update = new Update( factory ); - update.setTableName( getEntityMappingType().getMappedTableDetails().getTableName() ); + update.setTableName( getEntityMappingType().getMappedTableDetails().getTableName() ); update.addAssignment( getVersionMapping().getVersionAttribute().getAttributeName() ); update.addRestriction( getIdentifierPropertyName() ); update.addRestriction( getVersionMapping().getVersionAttribute().getAttributeName() ); @@ -176,17 +181,17 @@ default CompletionStage reactiveLock( String sql; boolean writeLock; - switch (lockMode) { + switch ( lockMode ) { // 0) noop case NONE: return voidFuture(); // 1) select ... for share case PESSIMISTIC_READ: - // 2) select ... for update + // 2) select ... for update case PESSIMISTIC_WRITE: - // 3) select ... for nowait + // 3) select ... for nowait case UPGRADE_NOWAIT: - // 4) select ... for update skip locked + // 4) select ... for update skip locked case UPGRADE_SKIPLOCKED: // TODO: introduce separate support for PESSIMISTIC_READ // the current implementation puts the version number in @@ -205,14 +210,14 @@ default CompletionStage reactiveLock( // locks obtained in the before completion phase case OPTIMISTIC: case OPTIMISTIC_FORCE_INCREMENT: - throw new AssertionFailure("optimistic lock mode is not supported here"); - // 7) READ and WRITE are obtained implicitly by - // other operations + throw new AssertionFailure( "optimistic lock mode is not supported here" ); + // 7) READ and WRITE are obtained implicitly by + // other operations case READ: case WRITE: - throw new AssertionFailure("implicit lock mode is not supported here"); + throw new AssertionFailure( "implicit lock mode is not supported here" ); default: - throw new AssertionFailure("illegal lock mode"); + throw new AssertionFailure( "illegal lock mode" ); } Object[] arguments = PreparedStatementAdaptor.bind( statement -> { @@ -234,10 +239,22 @@ default CompletionStage reactiveLock( throw new StaleObjectStateException( getEntityName(), id ); } } ) - .whenComplete( (r, e) -> logSqlException( e, () -> "could not lock: " + infoString( this, id, getFactory() ), sql ) ); + .whenComplete( (r, e) -> logSqlException( + e, + () -> "could not lock: " + infoString( + this, + id, + getFactory() + ), + sql + ) ); } - private CompletionStage writeLock(SharedSessionContractImplementor session, String sql, boolean writeLock, Object[] arguments) { + private CompletionStage writeLock( + SharedSessionContractImplementor session, + String sql, + boolean writeLock, + Object[] arguments) { return writeLock ? getReactiveConnection( session ).update( sql, arguments ).thenApply( affected -> affected > 0 ) : getReactiveConnection( session ).select( sql, arguments ).thenApply( Iterator::hasNext ); @@ -252,7 +269,12 @@ private CompletionStage writeLock(SharedSessionContractImplementor sess /** * @see AbstractEntityPersister#forceVersionIncrement(Object, Object, SharedSessionContractImplementor) */ - default Object nextVersionForLock(LockMode lockMode, Object id, Object currentVersion, Object entity, SharedSessionContractImplementor session) { + default Object nextVersionForLock( + LockMode lockMode, + Object id, + Object currentVersion, + Object entity, + SharedSessionContractImplementor session) { if ( lockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) { if ( !isVersioned() ) { throw new AssertionFailure( "cannot force version increment on non-versioned entity" ); @@ -261,13 +283,19 @@ default Object nextVersionForLock(LockMode lockMode, Object id, Object currentVe final EntityVersionMapping versionMapping = getVersionMapping(); BasicType versionType = getVersionType(); final Object nextVersion = getVersionJavaType() - .next( currentVersion, versionMapping.getLength(), versionMapping.getPrecision(), versionMapping.getScale(), session ); + .next( + currentVersion, + versionMapping.getLength(), + versionMapping.getPrecision(), + versionMapping.getScale(), + session + ); Log LOG = make( Log.class, lookup() ); if ( LOG.isTraceEnabled() ) { LOG.trace( "Forcing version increment [" + infoString( this, id, getFactory() ) + "; " - + versionType.toLoggableString( currentVersion, getFactory() ) + " -> " - + versionType.toLoggableString( nextVersion, getFactory() ) + "]" ); + + versionType.toLoggableString( currentVersion, getFactory() ) + " -> " + + versionType.toLoggableString( nextVersion, getFactory() ) + "]" ); } session.getPersistenceContextInternal().getEntry( entity ).forceLocked( entity, nextVersion ); @@ -345,7 +373,7 @@ else if ( result instanceof PersistentCollection ) { // collection. That's inconsistent with what happens // for other lazy fields, so let's set the field here final String[] propertyNames = getPropertyNames(); - for ( int index=0; index collection = (PersistentCollection) result; return collection.wasInitialized() ? completedFuture( (T) collection ) - : ((ReactiveSession) session).reactiveInitializeCollection( collection, false ) - .thenApply( v -> (T) result ); + : ( (ReactiveSession) session ).reactiveInitializeCollection( collection, false ) + .thenApply( v -> (T) result ); } else { return completedFuture( (T) result ); @@ -380,7 +408,8 @@ default CompletionStage reactiveInitializeLazyPropertiesFromDatastore( throw new AssertionFailure( "no lazy properties" ); } - final PersistentAttributeInterceptor interceptor = ManagedTypeHelper.asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor(); + final PersistentAttributeInterceptor interceptor = ManagedTypeHelper.asPersistentAttributeInterceptable( entity ) + .$$_hibernate_getInterceptor(); if ( interceptor == null ) { throw new AssertionFailure( "Expecting bytecode interceptor to be non-null" ); } @@ -425,7 +454,7 @@ default CompletionStage initLazyProperty( Object[] values) { // Load all the lazy properties that are in the same fetch group CompletionStage resultStage = nullFuture(); int i = 0; - for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor: fetchGroupAttributeDescriptors ) { + for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) { if ( initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() ) ) { // Already initialized if ( fetchGroupAttributeDescriptor.getName().equals( fieldName ) ) { @@ -434,7 +463,7 @@ default CompletionStage initLazyProperty( continue; } - final Object selectedValue = values[i++]; + final Object selectedValue = values[i++]; if ( selectedValue instanceof CompletionStage ) { // This happens with a lazy one-to-one (bytecode enhancement enabled) CompletionStage selectedValueStage = (CompletionStage) selectedValue; @@ -457,7 +486,13 @@ default CompletionStage initLazyProperty( ); } else { - final boolean set = initializeLazyProperty( fieldName, entity, entry, fetchGroupAttributeDescriptor.getLazyIndex(), selectedValue ); + final boolean set = initializeLazyProperty( + fieldName, + entity, + entry, + fetchGroupAttributeDescriptor.getLazyIndex(), + selectedValue + ); if ( set ) { resultStage = completedFuture( selectedValue ); interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() ); @@ -478,7 +513,7 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( final BytecodeEnhancementMetadata enhancementMetadata = getEntityPersister().getBytecodeEnhancementMetadata(); final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); - if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor) { + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { final EnhancementAsProxyLazinessInterceptor proxyInterceptor = (EnhancementAsProxyLazinessInterceptor) currentInterceptor; @@ -507,7 +542,7 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) ? getPropertyValue( entity, nameOfAttributeBeingAccessed ) : ( (LazyPropertyInitializer) this ) - .initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ) + .initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ) ); } } ); @@ -542,7 +577,12 @@ private CompletionStage loadFromDatabaseOrCache( ); } - boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue); + boolean initializeLazyProperty( + String fieldName, + Object entity, + EntityEntry entry, + int lazyIndex, + Object selectedValue); Object initializeLazyProperty(String fieldName, Object entity, SharedSessionContractImplementor session); @@ -561,7 +601,9 @@ default NaturalIdMapping generateNaturalIdMapping( assert naturalIdAttributeIndexes.length > 0; if ( naturalIdAttributeIndexes.length == 1 ) { - final String propertyName = getEntityPersister().getAttributeMappings().get(naturalIdAttributeIndexes[0]).getAttributeName(); + final String propertyName = getEntityPersister().getAttributeMappings() + .get( naturalIdAttributeIndexes[0] ) + .getAttributeName(); final AttributeMapping attributeMapping = findAttributeMapping( propertyName ); final SingularAttributeMapping singularAttributeMapping = (SingularAttributeMapping) attributeMapping; return new ReactiveSimpleNaturalIdMapping( singularAttributeMapping, this, creationProcess ); @@ -600,19 +642,24 @@ default NaturalIdLoader getNaturalIdLoader() { /** * @see AbstractEntityPersister#getLazyLoadPlanByFetchGroup() */ - default Map getLazyLoadPlanByFetchGroup(String[] subclassPropertyNameClosure ) { + default Map getLazyLoadPlanByFetchGroup(String[] subclassPropertyNameClosure) { final BytecodeEnhancementMetadata metadata = delegate().getEntityPersister().getBytecodeEnhancementMetadata(); return metadata.isEnhancedForLazyLoading() && metadata.getLazyAttributesMetadata().hasLazyAttributes() ? createLazyLoadPlanByFetchGroup( metadata, subclassPropertyNameClosure ) : emptyMap(); } - default Map createLazyLoadPlanByFetchGroup(BytecodeEnhancementMetadata metadata, String[] subclassPropertyNameClosure ) { + default Map createLazyLoadPlanByFetchGroup( + BytecodeEnhancementMetadata metadata, + String[] subclassPropertyNameClosure) { final Map result = new HashMap<>(); final LazyAttributesMetadata attributesMetadata = metadata.getLazyAttributesMetadata(); for ( String groupName : attributesMetadata.getFetchGroupNames() ) { final ReactiveSingleIdArrayLoadPlan loadPlan = - createLazyLoadPlan( attributesMetadata.getFetchGroupAttributeDescriptors( groupName), subclassPropertyNameClosure ); + createLazyLoadPlan( + attributesMetadata.getFetchGroupAttributeDescriptors( groupName ), + subclassPropertyNameClosure + ); if ( loadPlan != null ) { result.put( groupName, loadPlan ); } @@ -620,14 +667,19 @@ default Map createLazyLoadPlanByFetchGrou return result; } - default ReactiveSingleIdArrayLoadPlan createLazyLoadPlan(List fetchGroupAttributeDescriptors, String[] subclassPropertyNameClosure ) { + default ReactiveSingleIdArrayLoadPlan createLazyLoadPlan( + List fetchGroupAttributeDescriptors, + String[] subclassPropertyNameClosure) { final List partsToSelect = new ArrayList<>( fetchGroupAttributeDescriptors.size() ); for ( LazyAttributeDescriptor lazyAttributeDescriptor : fetchGroupAttributeDescriptors ) { // all this only really needs to consider properties // of this class, not its subclasses, but since we // are reusing code used for sequential selects, we // use the subclass closure - partsToSelect.add( getAttributeMapping( getSubclassPropertyIndex( lazyAttributeDescriptor.getName(), subclassPropertyNameClosure ) ) ); + partsToSelect.add( getAttributeMapping( getSubclassPropertyIndex( + lazyAttributeDescriptor.getName(), + subclassPropertyNameClosure + ) ) ); } if ( partsToSelect.isEmpty() ) { @@ -649,11 +701,18 @@ default ReactiveSingleIdArrayLoadPlan createLazyLoadPlan(List T convertException(T rows, String sql, Throwable sqlException) { if ( sqlException == null ) { return rows; } - if ( sqlException instanceof DatabaseException ) { - DatabaseException de = (DatabaseException) sqlException; + if ( sqlException instanceof DatabaseException de ) { sqlException = sqlExceptionHelper .convert( new SQLException( de.getMessage(), de.getSqlState(), de.getErrorCode() ), "error executing SQL statement", sql ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java index 4b66306ba..ef048feac 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveTypeContributor.java @@ -30,6 +30,7 @@ import org.hibernate.reactive.type.descriptor.jdbc.ReactiveArrayJdbcTypeConstructor; import org.hibernate.reactive.type.descriptor.jdbc.ReactiveJsonArrayJdbcTypeConstructor; import org.hibernate.reactive.type.descriptor.jdbc.ReactiveJsonJdbcType; +import org.hibernate.reactive.type.descriptor.jdbc.ReactiveXmlArrayJdbcTypeConstructor; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.AbstractSingleColumnStandardBasicType; import org.hibernate.type.BasicTypeRegistry; @@ -87,8 +88,10 @@ private void registerReactiveChanges(TypeContributions typeContributions, Servic JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry(); jdbcTypeRegistry.addTypeConstructor( ReactiveArrayJdbcTypeConstructor.INSTANCE ); + jdbcTypeRegistry.addTypeConstructor( ReactiveXmlArrayJdbcTypeConstructor.INSTANCE ); jdbcTypeRegistry.addDescriptor( SqlTypes.JSON, ReactiveJsonJdbcType.INSTANCE ); + if ( !(dialect instanceof MariaDBDialect) && dialect instanceof MySQLDialect ) { // The two vert.x clients behave differently in this case jdbcTypeRegistry.addTypeConstructor( ReactiveJsonArrayJdbcTypeConstructor.INSTANCE ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java new file mode 100644 index 000000000..306e619b8 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcType.java @@ -0,0 +1,63 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.type.descriptor.jdbc; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.SQLXML; + +import org.hibernate.dialect.XmlHelper; +import org.hibernate.reactive.logging.impl.Log; +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.BasicBinder; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.XmlArrayJdbcType; + +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + +/** + * @see org.hibernate.type.descriptor.jdbc.XmlArrayJdbcType + */ +public class ReactiveXmlArrayJdbcType extends XmlArrayJdbcType { + + public static final ReactiveXmlArrayJdbcType INSTANCE = new ReactiveXmlArrayJdbcType( null ); + + private static final Log LOG = make( Log.class, lookup() ); + + public ReactiveXmlArrayJdbcType(JdbcType elementJdbcType) { + super( elementJdbcType ); + } + + @Override + protected X fromString(String string, JavaType javaType, WrapperOptions options) throws SQLException { + if ( string == null ) { + return null; + } + if ( javaType.getJavaType() == SQLXML.class ) { + throw LOG.unsupportedXmlType(); + } + return XmlHelper.arrayFromString( javaType, this, string, options ); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) { + throw LOG.unsupportedXmlType(); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) { + throw LOG.unsupportedXmlType(); + } + }; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcTypeConstructor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcTypeConstructor.java new file mode 100644 index 000000000..1fa8999cb --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlArrayJdbcTypeConstructor.java @@ -0,0 +1,39 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.type.descriptor.jdbc; + +import org.hibernate.dialect.Dialect; +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.XmlArrayJdbcTypeConstructor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * @see XmlArrayJdbcTypeConstructor + * @see ReactiveArrayJdbcType + */ +public class ReactiveXmlArrayJdbcTypeConstructor extends XmlArrayJdbcTypeConstructor { + public static final ReactiveXmlArrayJdbcTypeConstructor INSTANCE = new ReactiveXmlArrayJdbcTypeConstructor(); + + @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 ReactiveXmlArrayJdbcType( elementType ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java new file mode 100644 index 000000000..1362e3a20 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveXmlJdbcType.java @@ -0,0 +1,69 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.type.descriptor.jdbc; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.SQLXML; + +import org.hibernate.dialect.XmlHelper; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.reactive.logging.impl.Log; +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.BasicBinder; +import org.hibernate.type.descriptor.jdbc.XmlJdbcType; + +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + +/** + * @see XmlJdbcType + */ +public class ReactiveXmlJdbcType extends XmlJdbcType { + + private static final Log LOG = make( Log.class, lookup() ); + + public static final ReactiveXmlJdbcType INSTANCE = new ReactiveXmlJdbcType( null ); + + protected ReactiveXmlJdbcType(EmbeddableMappingType embeddableMappingType) { + super( embeddableMappingType ); + } + + @Override + protected X fromString(String string, JavaType javaType, WrapperOptions options) throws SQLException { + if ( getEmbeddableMappingType() != null ) { + return XmlHelper.fromString( + getEmbeddableMappingType(), + string, + javaType.getJavaTypeClass() != Object[].class, + options + ); + } + if ( javaType.getJavaType() == SQLXML.class ) { + throw LOG.unsupportedXmlType(); + } + return options.getSessionFactory().getFastSessionServices().getXmlFormatMapper() + .fromString( string, javaType, options ); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) { + throw LOG.unsupportedXmlType(); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) { + throw LOG.unsupportedXmlType(); + } + }; + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java index edb1f88d5..b259b1c02 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java @@ -73,11 +73,11 @@ class DB2Database implements TestableDatabase { expectedDBTypeForClass.put( Character.class, "CHARACTER" ); expectedDBTypeForClass.put( char.class, "CHARACTER" ); expectedDBTypeForClass.put( String.class, "VARCHAR" ); - expectedDBTypeForClass.put( String[].class, "VARBINARY" ); - expectedDBTypeForClass.put( Long[].class, "VARBINARY" ); - expectedDBTypeForClass.put( BigDecimal[].class, "VARBINARY" ); - expectedDBTypeForClass.put( BigInteger[].class, "VARBINARY" ); - expectedDBTypeForClass.put( Boolean[].class, "VARBINARY" ); + expectedDBTypeForClass.put( String[].class, "XML" ); + expectedDBTypeForClass.put( Long[].class, "XML" ); + expectedDBTypeForClass.put( BigDecimal[].class, "XML" ); + expectedDBTypeForClass.put( BigInteger[].class, "XML" ); + expectedDBTypeForClass.put( Boolean[].class, "XML" ); }} /** diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/ColumnTypesMappingTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/ColumnTypesMappingTest.java index 134a749fb..892db5e1e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/ColumnTypesMappingTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/ColumnTypesMappingTest.java @@ -20,6 +20,7 @@ import java.util.Date; import java.util.List; import java.util.TimeZone; +import java.util.concurrent.CompletionStage; import org.hibernate.reactive.BaseReactiveTest; @@ -37,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.expectedDatatype; import static org.hibernate.reactive.containers.DatabaseConfiguration.getDatatypeQuery; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * Check that each property is mapped as the expected type in the database. @@ -49,12 +51,21 @@ protected Collection> annotatedEntities() { return List.of( BasicTypesTestEntity.class ); } + @Override + public CompletionStage deleteEntities(Class... entities) { + // Skip delete step. + // We don't insert any value, so there's nothing to delete + // Avoid having extra stuff in the log that's not relevant to the test + return voidFuture(); + } + private void testDatatype(VertxTestContext context, String columnName, Class type) { - test( context, openSession() - .thenCompose( s -> s + test( context, getSessionFactory() + .withTransaction( s -> s .createNativeQuery( getDatatypeQuery( BasicTypesTestEntity.TABLE_NAME, columnName ), String.class ) .getSingleResult() - .thenAccept( typeOnTheDb -> assertThat( toString( typeOnTheDb ) ).isEqualTo( expectedDatatype( type ) ) ) ) + .thenAccept( typeOnTheDb -> assertThat( toString( typeOnTheDb ) ).isEqualTo( expectedDatatype( type ) ) ) + ) ); } @@ -230,5 +241,4 @@ public void testSerializableType(VertxTestContext context) { public void testInstantType(VertxTestContext context) { testDatatype( context, "instant", Instant.class ); } - } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JavaTypesArrayTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JavaTypesArrayTest.java index 030c599ab..69f5ad23f 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JavaTypesArrayTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JavaTypesArrayTest.java @@ -13,12 +13,14 @@ import java.time.Month; import java.util.Calendar; import java.util.Date; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Predicate; import org.hibernate.AssertionFailure; +import org.hibernate.HibernateException; import org.hibernate.annotations.Array; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; @@ -40,10 +42,12 @@ import static java.lang.Boolean.TRUE; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MYSQL; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE; import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -83,16 +87,24 @@ protected Set> annotatedEntities() { } private void testField(VertxTestContext context, Basic original, Consumer consumer) { - test( - context, getSessionFactory() - .withTransaction( s -> s.persist( original ) ) - .thenCompose( v -> getSessionFactory().withSession( s -> s - .find( Basic.class, original.id ) - .thenAccept( found -> { - assertNotNull( found ); - consumer.accept( found ); - } ) ) ) - ); + if ( List.of( DB2, ORACLE ).contains( dbType() ) ) { + test( context, assertThrown( HibernateException.class, getSessionFactory() + .withTransaction( s -> s.persist( original ) ) ) + .thenAccept( e -> assertThat( e.getMessage() ).startsWith( "HR000081" ) ) + ); + } + else { + test( + context, getSessionFactory() + .withTransaction( s -> s.persist( original ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .find( Basic.class, original.id ) + .thenAccept( found -> { + assertNotNull( found ); + consumer.accept( found ); + } ) ) ) + ); + } } @Test @@ -101,12 +113,10 @@ public void testStringArrayType(VertxTestContext context) { String[] dataArray = {"Hello world!", "Hello earth"}; basic.stringArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.stringArray ); - validateArrayColumn( "stringArray", null, 255 ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "stringArray", null, 255 ); + assertArrayEquals( dataArray, found.stringArray ); + } ); } @Test @@ -115,12 +125,10 @@ public void testStringArrayTypeWithArrayAnnotation(VertxTestContext context) { String[] dataArray = {"Hello world!", "Hello earth"}; basic.stringArrayWithArrayAnnotation = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.stringArrayWithArrayAnnotation ); - validateArrayColumn( "stringArrayWithArrayAnnotation", 5, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "stringArrayWithArrayAnnotation", 5, null ); + assertArrayEquals( dataArray, found.stringArrayWithArrayAnnotation ); + } ); } @Test @@ -129,12 +137,10 @@ public void testStringArrayTypeWithColumnAnnotation(VertxTestContext context) { String[] dataArray = {"Hello world!", "Hello earth"}; basic.stringArrayWithColumnAnnotation = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.stringArrayWithColumnAnnotation ); - validateArrayColumn( "stringArrayWithColumnAnnotation", null, 200 ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "stringArrayWithColumnAnnotation", null, 200 ); + assertArrayEquals( dataArray, found.stringArrayWithColumnAnnotation ); + } ); } @Test @@ -143,12 +149,10 @@ public void testStringArrayTypeWithBothAnnotations(VertxTestContext context) { String[] dataArray = {"Hello world!", "Hello earth"}; basic.stringArrayWithBothAnnotations = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.stringArrayWithBothAnnotations ); - validateArrayColumn( "stringArrayWithBothAnnotations", 5, 200 ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "stringArrayWithBothAnnotations", 5, 200 ); + assertArrayEquals( dataArray, found.stringArrayWithBothAnnotations ); + } ); } @Test @@ -157,8 +161,10 @@ public void testBooleanArrayType(VertxTestContext context) { Boolean[] dataArray = {TRUE, FALSE, null, TRUE}; basic.booleanArray = dataArray; - testField( context, basic, found -> assertArrayEquals( dataArray, found.booleanArray ) ); - validateArrayColumn( "booleanArray", null, null ); + testField( context, basic, found -> { + validateArrayColumn( "booleanArray", null, null ); + assertArrayEquals( dataArray, found.booleanArray ); + } ); } @Test @@ -167,12 +173,10 @@ public void testPrimitiveBooleanArrayType(VertxTestContext context) { boolean[] dataArray = {true, false, true}; basic.primitiveBooleanArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveBooleanArray ); + testField( context, basic, found -> { validateArrayColumn( "primitiveBooleanArray", null, null ); - } - ); + assertArrayEquals( dataArray, found.primitiveBooleanArray ); + } ); } @Test @@ -181,12 +185,10 @@ public void testIntegerArrayType(VertxTestContext context) { Integer[] dataArray = {null, Integer.MIN_VALUE, 2, Integer.MAX_VALUE}; basic.integerArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.integerArray ); - validateArrayColumn( "integerArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "integerArray", null, null ); + assertArrayEquals( dataArray, found.integerArray ); + } ); } @Test @@ -195,12 +197,10 @@ public void testPrimitiveIntegerArrayType(VertxTestContext context) { int[] dataArray = {1, 2, 3}; basic.primitiveIntegerArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveIntegerArray ); - validateArrayColumn( "primitiveIntegerArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "primitiveIntegerArray", null, null ); + assertArrayEquals( dataArray, found.primitiveIntegerArray ); + } ); } @Test @@ -209,12 +209,10 @@ public void testLongArrayType(VertxTestContext context) { Long[] dataArray = {Long.MIN_VALUE, Long.MAX_VALUE, 3L, null}; basic.longArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.longArray ); - validateArrayColumn( "longArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "longArray", null, null ); + assertArrayEquals( dataArray, found.longArray ); + } ); } @Test @@ -223,12 +221,10 @@ public void testPrimitiveLongArrayType(VertxTestContext context) { long[] dataArray = {Long.MIN_VALUE, Long.MAX_VALUE, 3L}; basic.primitiveLongArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveLongArray ); - validateArrayColumn( "primitiveLongArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "primitiveLongArray", null, null ); + assertArrayEquals( dataArray, found.primitiveLongArray ); + } ); } @Test @@ -237,12 +233,10 @@ public void testFloatArrayType(VertxTestContext context) { Float[] dataArray = {12.562f, null, 13.562f}; basic.floatArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.floatArray ); - validateArrayColumn( "floatArray", null, null ); - } - ); + testField( context, basic, found -> { + assertArrayEquals( dataArray, found.floatArray ); + validateArrayColumn( "floatArray", null, null ); + } ); } @Test @@ -251,12 +245,10 @@ public void testPrimitiveFloatArrayType(VertxTestContext context) { float[] dataArray = {12.562f, 13.562f}; basic.primitiveFloatArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveFloatArray ); - validateArrayColumn( "primitiveFloatArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "primitiveFloatArray", null, null ); + assertArrayEquals( dataArray, found.primitiveFloatArray ); + } ); } @Test @@ -265,12 +257,10 @@ public void testDoubleArrayType(VertxTestContext context) { Double[] dataArray = {12.562d, null, 13.562d}; basic.doubleArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.doubleArray ); - validateArrayColumn( "doubleArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "doubleArray", null, null ); + assertArrayEquals( dataArray, found.doubleArray ); + } ); } @Test @@ -279,12 +269,10 @@ public void testPrimitiveDoubleArrayType(VertxTestContext context) { double[] dataArray = {12.562d, 13.562d}; basic.primitiveDoubleArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveDoubleArray ); - validateArrayColumn( "primitiveDoubleArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "primitiveDoubleArray", null, null ); + assertArrayEquals( dataArray, found.primitiveDoubleArray ); + } ); } @Test @@ -297,12 +285,10 @@ public void testUUIDArrayType(VertxTestContext context) { }; basic.uuidArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.uuidArray ); - validateArrayColumn( "uuidArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "uuidArray", null, null ); + assertArrayEquals( dataArray, found.uuidArray ); + } ); } @Test @@ -311,12 +297,10 @@ public void testEnumArrayType(VertxTestContext context) { AnEnum[] dataArray = {AnEnum.FOURTH, AnEnum.FIRST, AnEnum.THIRD}; basic.enumArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.enumArray ); - validateArrayColumn( "enumArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "enumArray", null, null ); + assertArrayEquals( dataArray, found.enumArray ); + } ); } @Test @@ -325,12 +309,10 @@ public void testShortArrayType(VertxTestContext context) { Short[] dataArray = {512, 112, null, 0}; basic.shortArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.shortArray ); - validateArrayColumn( "shortArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "shortArray", null, null ); + assertArrayEquals( dataArray, found.shortArray ); + } ); } @Test @@ -339,12 +321,10 @@ public void testPrimitiveShortArrayType(VertxTestContext context) { short[] dataArray = {500, 32, -1}; basic.primitiveShortArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.primitiveShortArray ); - validateArrayColumn( "primitiveShortArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "primitiveShortArray", null, null ); + assertArrayEquals( dataArray, found.primitiveShortArray ); + } ); } @Test @@ -358,12 +338,10 @@ public void testLocalDateArrayType(VertxTestContext context) { }; basic.localDateArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.localDateArray ); - validateArrayColumn( "localDateArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "localDateArray", null, null ); + assertArrayEquals( dataArray, found.localDateArray ); + } ); } @Test @@ -373,12 +351,10 @@ public void testDateArrayType(VertxTestContext context) { Date[] dataArray = {Calendar.getInstance().getTime(), Calendar.getInstance().getTime()}; basic.dateArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.dateArray ); - validateArrayColumn( "dateArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "dateArray", null, null ); + assertArrayEquals( dataArray, found.dateArray ); + } ); } @Test @@ -392,12 +368,10 @@ public void testLocalTimeArrayType(VertxTestContext context) { }; basic.localTimeArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.localTimeArray ); - validateArrayColumn( "localTimeArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "localTimeArray", null, null ); + assertArrayEquals( dataArray, found.localTimeArray ); + } ); } @Test @@ -416,12 +390,10 @@ public void testLocalDateTimeArrayType(VertxTestContext context) { }; basic.localDateTimeArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.localDateTimeArray ); - validateArrayColumn( "localDateTimeArray", null, null ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "localDateTimeArray", null, null ); + assertArrayEquals( dataArray, found.localDateTimeArray ); + } ); } @Test @@ -430,12 +402,10 @@ public void testBigIntegerArrayType(VertxTestContext context) { BigInteger[] dataArray = {BigInteger.TEN, BigInteger.ZERO}; basic.bigIntegerArray = dataArray; - testField( - context, basic, found -> { - assertArrayEquals( dataArray, found.bigIntegerArray ); - validateArrayColumn( "bigIntegerArray", null, 5000 ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "bigIntegerArray", null, 5000 ); + assertArrayEquals( dataArray, found.bigIntegerArray ); + } ); } @Test @@ -444,14 +414,12 @@ public void testBigDecimalArrayType(VertxTestContext context) { BigDecimal[] dataArray = {BigDecimal.valueOf( 123384967L ), BigDecimal.ZERO}; basic.bigDecimalArray = dataArray; - testField( - context, basic, found -> { - assertEquals( dataArray.length, found.bigDecimalArray.length ); - assertEquals( 0, dataArray[0].compareTo( found.bigDecimalArray[0] ) ); - assertEquals( 0, dataArray[1].compareTo( found.bigDecimalArray[1] ) ); - validateArrayColumn( "bigDecimalArray", null, 5000 ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "bigDecimalArray", null, 5000 ); + assertEquals( dataArray.length, found.bigDecimalArray.length ); + assertEquals( 0, dataArray[0].compareTo( found.bigDecimalArray[0] ) ); + assertEquals( 0, dataArray[1].compareTo( found.bigDecimalArray[1] ) ); + } ); } @Test @@ -460,14 +428,12 @@ public void testBigDecimalArrayTypeWithArrayAnnotation(VertxTestContext context) BigDecimal[] dataArray = {BigDecimal.valueOf( 123384967L ), BigDecimal.ZERO}; basic.bigDecimalArrayWithArrayAnnotation = dataArray; - testField( - context, basic, found -> { - assertEquals( dataArray.length, found.bigDecimalArrayWithArrayAnnotation.length ); - assertEquals( 0, dataArray[0].compareTo( found.bigDecimalArrayWithArrayAnnotation[0] ) ); - assertEquals( 0, dataArray[1].compareTo( found.bigDecimalArrayWithArrayAnnotation[1] ) ); - validateArrayColumn( "bigDecimalArrayWithArrayAnnotation", 5, 5000 ); - } - ); + testField( context, basic, found -> { + validateArrayColumn( "bigDecimalArrayWithArrayAnnotation", 5, 5000 ); + assertEquals( dataArray.length, found.bigDecimalArrayWithArrayAnnotation.length ); + assertEquals( 0, dataArray[0].compareTo( found.bigDecimalArrayWithArrayAnnotation[0] ) ); + assertEquals( 0, dataArray[1].compareTo( found.bigDecimalArrayWithArrayAnnotation[1] ) ); + } ); } @@ -489,8 +455,9 @@ private static Predicate arrayColumnPredicate( case MARIA: return arrayAsJsonPredicate( columnName ); case SQLSERVER: - case DB2: return arrayAsVarbinaryPredicate( columnName, columnLength ); + case DB2: + return arrayAsXmlPredicate( columnName ); default: throw new AssertionFailure( "Unexpected database: " + dbType() ); } @@ -527,6 +494,11 @@ private static Predicate arrayAsJsonPredicate(String columnName) { return s -> s.contains( columnName + " json" ); } + private static Predicate arrayAsXmlPredicate(String columnName) { + // Example of correct query definition: columnName xml + return s -> s.contains( columnName + " xml" ); + } + private static Predicate arrayAsVarbinaryPredicate(String columnName, Integer columnLength) { StringBuilder regexBuilder = new StringBuilder(); // Example of correct query definition: columnName varbinary(255)