Skip to content

Commit

Permalink
[hibernate#1768] verify correct upsert sql queries for each DB
Browse files Browse the repository at this point in the history
  • Loading branch information
blafond authored and DavideD committed Nov 29, 2023
1 parent 4c72c94 commit 5d626d0
Showing 1 changed file with 78 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,62 @@
import java.util.List;
import java.util.Objects;

import org.hibernate.reactive.testing.DBSelectionExtension;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.reactive.testing.SqlStatementTracker;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.vertx.junit5.Timeout;
import io.vertx.junit5.VertxTestContext;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.assertj.core.api.Condition;

import static java.util.concurrent.TimeUnit.MINUTES;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.COCKROACHDB;
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.testing.DBSelectionExtension.skipTestsFor;
import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType;

/**
* Same as Hibernate ORM org.hibernate.orm.test.stateless.UpsertTest
* <p>
* These tests are in a separate class because we need to skip the execution on some databases,
* but once this has been resolved, they could be in {@link ReactiveStatelessSessionTest}.
* These tests are in a separate class because we need to skip the execution on some databases,
* but once this has been resolved, they could be in {@link ReactiveStatelessSessionTest}.
* </p>
*/
@Timeout(value = 10, timeUnit = MINUTES)
public class UpsertTest extends BaseReactiveTest {

/**
* Something is missing in HR to make it work for these databases.
*/
@RegisterExtension
public DBSelectionExtension dbSelection = skipTestsFor( COCKROACHDB, DB2, MARIA, MYSQL, ORACLE );
private static SqlStatementTracker sqlTracker;

// A condition to check that entities are persisted using a merge operator when the database actually supports it.
private final static Condition<String> IS_USING_MERGE = new Condition<>(
s -> s.toLowerCase().startsWith( "merge into" ),
"insertions or updates without using the merge operator"
);

@Override
protected Configuration constructConfiguration() {
Configuration configuration = super.constructConfiguration();
sqlTracker = new SqlStatementTracker( UpsertTest::filter, configuration.getProperties() );
return configuration;
}

@Override
protected void addServices(StandardServiceRegistryBuilder builder) {
sqlTracker.registerService( builder );
}

private static boolean filter(String s) {
String[] accepted = {"insert ", "update ", "merge "};
for ( String valid : accepted ) {
if ( s.toLowerCase().startsWith( valid ) ) {
return true;
}
}
return false;
}

@Override
protected Collection<Class<?>> annotatedEntities() {
Expand All @@ -55,19 +76,26 @@ public void testMutinyUpsert(VertxTestContext context) {
test( context, getMutinySessionFactory().withStatelessTransaction( ss -> ss
.upsert( new Record( 123L, "hello earth" ) )
.call( () -> ss.upsert( new Record( 456L, "hello mars" ) ) )
.invoke( this::assertQueries )
)
.call( v -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
.invoke( results -> assertThat( results ).containsExactly(
new Record( 123L, "hello earth" ),
new Record( 456L, "hello mars" )
) )
.createSelectionQuery( "from Record order by id", Record.class )
.getResultList() )
.invoke( results -> {
assertThat( results ).containsExactly(
new Record( 123L, "hello earth" ),
new Record( 456L, "hello mars" )
);
} )
)
.call( () -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
.upsert( new Record( 123L, "goodbye earth" ) )
) )
.call( v -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
.invoke( this::assertQueries )
.call( v -> getMutinySessionFactory()
.withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class )
.getResultList() )
.invoke( results -> assertThat( results ).containsExactly(
new Record( 123L, "goodbye earth" ),
new Record( 456L, "hello mars" )
Expand All @@ -81,6 +109,7 @@ public void testMutinyUpsertWithEntityName(VertxTestContext context) {
test( context, getMutinySessionFactory().withStatelessTransaction( ss -> ss
.upsert( Record.class.getName(), new Record( 123L, "hello earth" ) )
.call( () -> ss.upsert( Record.class.getName(), new Record( 456L, "hello mars" ) ) )
.invoke( this::assertQueries )
)
.call( v -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
Expand All @@ -92,6 +121,7 @@ public void testMutinyUpsertWithEntityName(VertxTestContext context) {
.call( () -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
.upsert( Record.class.getName(), new Record( 123L, "goodbye earth" ) )
) )
.invoke( this::assertQueries )
.call( v -> getMutinySessionFactory().withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
.invoke( results -> assertThat( results ).containsExactly(
Expand All @@ -108,6 +138,7 @@ public void testStageUpsert(VertxTestContext context) {
.upsert( new Record( 123L, "hello earth" ) )
.thenCompose( v -> ss.upsert( new Record( 456L, "hello mars" ) ) )
)
.thenAccept( v -> this.assertQueries() )
.thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
.thenAccept( results -> assertThat( results ).containsExactly(
Expand All @@ -118,6 +149,7 @@ public void testStageUpsert(VertxTestContext context) {
.thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
.upsert( new Record( 123L, "goodbye earth" ) )
) )
.thenAccept( v -> this.assertQueries() )
.thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
.thenAccept( results -> assertThat( results ).containsExactly(
Expand All @@ -134,6 +166,7 @@ public void testStageUpsertWithEntityName(VertxTestContext context) {
.upsert( Record.class.getName(), new Record( 123L, "hello earth" ) )
.thenCompose( v -> ss.upsert( Record.class.getName(), new Record( 456L, "hello mars" ) ) )
)
.thenAccept( v -> this.assertQueries() )
.thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
.thenAccept( results -> assertThat( results ).containsExactly(
Expand All @@ -144,6 +177,7 @@ public void testStageUpsertWithEntityName(VertxTestContext context) {
.thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
.upsert( Record.class.getName(), new Record( 123L, "goodbye earth" ) )
) )
.thenAccept( v -> this.assertQueries() )
.thenCompose( v -> getSessionFactory().withStatelessTransaction( ss -> ss
.createSelectionQuery( "from Record order by id", Record.class ).getResultList() )
.thenAccept( results -> assertThat( results ).containsExactly(
Expand All @@ -154,6 +188,28 @@ public void testStageUpsertWithEntityName(VertxTestContext context) {
);
}

private void assertQueries() {
if ( hasMergeOperator() ) {
assertThat( sqlTracker.getLoggedQueries() ).have( IS_USING_MERGE );
}
else {
// This might be overkill, but it's still helpful in case more databases are going to support
// the merge operator, and we need to update the documentation or warn people about it.
assertThat( sqlTracker.getLoggedQueries() ).doNotHave( IS_USING_MERGE );
}
}

private boolean hasMergeOperator() {
switch ( dbType() ) {
case SQLSERVER:
case ORACLE:
case POSTGRESQL:
return true;
default:
return false;
}
}

@Entity(name = "Record")
@Table(name = "Record")
public static class Record {
Expand Down

0 comments on commit 5d626d0

Please sign in to comment.