Skip to content

Support for MySQL and MSSQL #1535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ org.gradle.java.installations.auto-download=false
#db = MSSQL

# Enable the SonatypeOS maven repository (mainly for Vert.x snapshots) when present (value ignored)
#enableSonatypeOpenSourceSnapshotsRep = true
enableSonatypeOpenSourceSnapshotsRep = true

# Enable the JBoss Snapshot maven repository (mainly for Hibernate ORM snapshots) when present (value ignored)
#enableJBossSnapshotsRep = true
Expand All @@ -38,7 +38,7 @@ org.gradle.java.installations.auto-download=false
enableMavenLocalRepo = true

# Override default Hibernate ORM version
#hibernateOrmVersion = 6.2.0-SNAPSHOT
hibernateOrmVersion = 6.2.0-SNAPSHOT

# If set to true, skip Hibernate ORM version parsing (default is true, if set to null)
# this is required when using intervals or weird versions or the build will fail
Expand Down
5 changes: 4 additions & 1 deletion hibernate-reactive-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@ dependencies {
testRuntimeOnly "org.postgresql:postgresql:42.5.0"

// JDBC driver for Testcontainers with MS SQL Server
testRuntimeOnly "com.microsoft.sqlserver:mssql-jdbc:10.2.1.jre11"
testRuntimeOnly "com.microsoft.sqlserver:mssql-jdbc:12.2.0.jre11"

// JDBC driver for Testcontainers with MariaDB Server
testRuntimeOnly "org.mariadb.jdbc:mariadb-java-client:3.0.7";

// JDBC driver for Testcontainers with MYSQL Server
testRuntimeOnly "com.mysql:mysql-connector-j:8.0.32";

// JDBC driver for Db2 server, for testing
testRuntimeOnly "com.ibm.db2:jcc:11.5.8.0"

// EHCache
testRuntimeOnly ("org.ehcache:ehcache:3.10.0-alpha0") {
capabilities {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.Arrays;
import java.util.Calendar;
import java.util.function.Function;

/**
* Collects parameter bindings from Hibernate core code
Expand Down Expand Up @@ -168,7 +171,16 @@ public void setTimestamp(int parameterIndex, Timestamp x) {

@Override
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) {
put( parameterIndex, x.toInstant().atZone( cal.getTimeZone().toZoneId() ).toLocalDateTime() );
setTimestamp( parameterIndex, x, cal, ZonedDateTime::toOffsetDateTime );
}

// Sometimes we need a different approach depending on the database
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal, Function<ZonedDateTime, Temporal> converter) {
put( parameterIndex, converter.apply( x.toInstant().atZone( cal.getTimeZone().toZoneId() ) ) );
}

public void setTimestamp(String name, Timestamp x, Calendar cal, Function<ZonedDateTime, Temporal> converter) {
throw new UnsupportedOperationException();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.hibernate.LockMode;
import org.hibernate.NonUniqueObjectException;
import org.hibernate.action.internal.AbstractEntityInsertAction;
import org.hibernate.action.internal.EntityIdentityInsertAction;
import org.hibernate.engine.internal.CascadePoint;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.EntityEntry;
Expand Down Expand Up @@ -131,7 +132,6 @@ protected CompletionStage<Void> reactiveSaveWithGeneratedId(
.thenCompose( generatedId -> performSaveWithId( entity, context, source, persister, generator, generatedId ) );
}

// FIXME: I think this should be a reactive type
final Object generatedId = ( (BeforeExecutionGenerator) generator ).generate( source, entity, null, INSERT );
return performSaveWithId( entity, context, source, persister, generator, generatedId );
}
Expand Down Expand Up @@ -282,68 +282,69 @@ protected CompletionStage<Void> reactivePerformSaveOrReplicate(
);

return cascadeBeforeSave( source, persister, entity, context )
.thenCompose( v -> {
// We have to do this after cascadeBeforeSave completes,
// since it could result in generation of parent ids,
// which we will need as foreign keys in the insert

Object[] values = persister.getPropertyValuesToInsert( entity, getMergeMap( context ), source );
Type[] types = persister.getPropertyTypes();

boolean substitute = substituteValuesIfNecessary( entity, id, values, persister, source );

if ( persister.hasCollections() ) {
boolean substituteBecauseOfCollections = visitCollectionsBeforeSave( entity, id, values, types, source );
substitute = substitute || substituteBecauseOfCollections;
}

if ( substitute ) {
persister.setValues( entity, values );
}

TypeHelper.deepCopy(
values,
types,
persister.getPropertyUpdateability(),
values,
source
);

CompletionStage<AbstractEntityInsertAction> insert = addInsertAction(
values, id, entity, persister, useIdentityColumn, source, shouldDelayIdentityInserts
);

EntityEntry newEntry = persistenceContext.getEntry( entity );

if ( newEntry != original ) {
EntityEntryExtraState extraState = newEntry.getExtraState( EntityEntryExtraState.class );
if ( extraState == null ) {
newEntry.addExtraState( original.getExtraState( EntityEntryExtraState.class ) );
.thenCompose( v -> addInsertAction(
// We have to do this after cascadeBeforeSave completes,
// since it could result in generation of parent ids,
// which we will need as foreign keys in the insert
cloneAndSubstituteValues( entity, persister, context, source, id ),
id,
entity,
persister,
useIdentityColumn,
source,
shouldDelayIdentityInserts
) )
.thenCompose( insert -> cascadeAfterSave( source, persister, entity, context )
.thenAccept( unused -> {
final Object finalId = handleGeneratedId( useIdentityColumn, id, insert );
EntityEntry newEntry = persistenceContext.getEntry( entity );

if ( newEntry != original ) {
EntityEntryExtraState extraState = newEntry.getExtraState( EntityEntryExtraState.class );
if ( extraState == null ) {
newEntry.addExtraState( original.getExtraState( EntityEntryExtraState.class ) );
}
}
}

return insert;
} )
.thenCompose( vv -> cascadeAfterSave( source, persister, entity, context ) );

// .thenAccept( v -> {
// postpone initializing id in case the insert has non-nullable transient dependencies
// that are not resolved until cascadeAfterSave() is executed

// Object newId = id;
// if ( useIdentityColumn && insert.isEarlyInsert() ) {
// if ( !EntityIdentityInsertAction.class.isInstance( insert ) ) {
// throw new IllegalStateException(
// "Insert should be using an identity column, but action is of unexpected type: " +
// insert.getClass().getName()
// );
// }
// newId = ( (EntityIdentityInsertAction) insert ).getGeneratedId();
//
// insert.handleNaturalIdPostSaveNotifications( newId );
// }
// return newId;
// } );
} )
);
}

private static Object handleGeneratedId(boolean useIdentityColumn, Object id, AbstractEntityInsertAction insert) {
if ( useIdentityColumn && insert.isEarlyInsert() ) {
if ( insert instanceof EntityIdentityInsertAction ) {
Object generatedId = ( (EntityIdentityInsertAction) insert ).getGeneratedId();
insert.handleNaturalIdPostSaveNotifications( generatedId );
return generatedId;
}
throw new IllegalStateException(
"Insert should be using an identity column, but action is of unexpected type: " + insert.getClass()
.getName() );
}

return id;
}

private Object[] cloneAndSubstituteValues(Object entity, EntityPersister persister, C context, EventSource source, Object id) {
Object[] values = persister.getPropertyValuesToInsert( entity, getMergeMap(context), source );
Type[] types = persister.getPropertyTypes();

boolean substitute = substituteValuesIfNecessary( entity, id, values, persister, source );
if ( persister.hasCollections() ) {
substitute = visitCollectionsBeforeSave( entity, id, values, types, source ) || substitute;
}

if ( substitute ) {
persister.setValues( entity, values );
}

TypeHelper.deepCopy(
values,
types,
persister.getPropertyUpdateability(),
values,
source
);
return values;
}

protected Map<Object,Object> getMergeMap(C anything) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.spi.JdbcServices;
Expand Down Expand Up @@ -54,6 +55,23 @@ private static String createInsert(PreparedStatementDetails insertStatementDetai
if ( instanceOf( dialect, PostgreSQLDialect.class ) ) {
return insertStatementDetails.getSqlString() + " returning " + identifierColumnName;
}
if ( instanceOf( dialect, SQLServerDialect.class ) ) {
String sql = insertStatementDetails.getSqlString();
int index = sql.lastIndexOf( " returning " + identifierColumnName );
// FIXME: this is a hack for HHH-16365
if ( index > -1 ) {
sql = sql.substring( 0, index );
}
if ( sql.endsWith( "default values" ) ) {
index = sql.indexOf( "default values" );
sql = sql.substring( 0, index );
sql = sql + "output inserted." + identifierColumnName + " default values";
}
else {
sql = sql.replace( ") values (", ") output inserted." + identifierColumnName + " values (" );
}
return sql;
}
return insertStatementDetails.getSqlString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.loader.ast.internal.LoaderSelectBuilder;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.reactive.metamodel.mapping.internal.ReactivePluralAttributeMapping;
import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor;
import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
Expand All @@ -47,11 +49,13 @@ public class ReactiveCollectionLoaderSingleKey implements ReactiveCollectionLoad
private final List<JdbcParameter> jdbcParameters;

public ReactiveCollectionLoaderSingleKey(PluralAttributeMapping attributeMapping, LoadQueryInfluencers influencers, SessionFactoryImplementor sessionFactory) {
this.attributeMapping = attributeMapping;
this.keyJdbcCount = attributeMapping.getKeyDescriptor().getJdbcTypeCount();
// PluralAttributeMappingImpl is the only implementation available at the moment in ORM
final PluralAttributeMapping reactivePluralAttributeMapping = new ReactivePluralAttributeMapping( (PluralAttributeMappingImpl) attributeMapping );
this.attributeMapping = reactivePluralAttributeMapping;
this.keyJdbcCount = reactivePluralAttributeMapping.getKeyDescriptor().getJdbcTypeCount();
this.jdbcParameters = new ArrayList<>();
this.sqlAst = LoaderSelectBuilder.createSelect(
attributeMapping,
reactivePluralAttributeMapping,
null,
attributeMapping.getKeyDescriptor(),
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@



import java.sql.SQLWarning;

import jakarta.persistence.PersistenceException;

import org.hibernate.HibernateException;
Expand Down Expand Up @@ -276,4 +278,8 @@ public interface Log extends BasicLogger {
@LogMessage(level = WARN)
@Message(id = 447, value= "Explicit use of UPGRADE_SKIPLOCKED in lock() calls is not recommended; use normal UPGRADE locking instead")
void explicitSkipLockedLockCombo();

@LogMessage(level = WARN)
@Message(id = 448, value = "Warnings creating temp table : %s")
void warningsCreatingTempTable(SQLWarning warning);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* 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 org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.internal.OneToManyCollectionPart;
import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.entity.EntityFetch;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl;

public class ReactiveOneToManyCollectionPart extends OneToManyCollectionPart {

public ReactiveOneToManyCollectionPart(OneToManyCollectionPart delegate) {
super( delegate );
}

@Override
public EntityFetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
String resultVariable,
DomainResultCreationState creationState) {
EntityFetch entityFetch = super.generateFetch(
fetchParent,
fetchablePath,
fetchTiming,
selected,
resultVariable,
creationState
);
if ( entityFetch instanceof EntityFetchJoinedImpl ) {
return new ReactiveEntityFetchJoinedImpl( (EntityFetchJoinedImpl) entityFetch );
}
return entityFetch;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* 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 org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl;
import org.hibernate.reactive.sql.results.graph.collection.internal.ReactiveCollectionDomainResult;
import org.hibernate.reactive.sql.results.graph.collection.internal.ReactiveEagerCollectionFetch;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;

public class ReactivePluralAttributeMapping extends PluralAttributeMappingImpl implements PluralAttributeMapping {

public ReactivePluralAttributeMapping(PluralAttributeMappingImpl original) {
super( original );
}

@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
final TableGroup collectionTableGroup = creationState.getSqlAstCreationState()
.getFromClauseAccess()
.getTableGroup( navigablePath );

assert collectionTableGroup != null;

// This is only used for collection initialization where we know the owner is available, so we mark it as visited
// which will cause bidirectional to-one associations to be treated as such and avoid a join
creationState.registerVisitedAssociationKey( getKeyDescriptor().getAssociationKey() );

return new ReactiveCollectionDomainResult<>( navigablePath, this, resultVariable, tableGroup, creationState );
}

@Override
protected Fetch buildEagerCollectionFetch(
NavigablePath fetchedPath,
PluralAttributeMapping fetchedAttribute,
TableGroup collectionTableGroup,
FetchParent fetchParent,
DomainResultCreationState creationState) {
return new ReactiveEagerCollectionFetch(
fetchedPath,
fetchedAttribute,
collectionTableGroup,
fetchParent,
creationState
);
}
}
Loading