Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4b65eb2
Introduce batch support.
vbabanin Sep 11, 2025
5b17d58
Add tests and refactor MongoStatements.
vbabanin Oct 2, 2025
3f1ebcb
Merge branch 'main' into HIBERNATE-40
vbabanin Oct 2, 2025
ded2bb7
Rename tests.
vbabanin Oct 2, 2025
92d9c41
Rename variables.
vbabanin Oct 2, 2025
b78cb77
Rename methods, add tests.
vbabanin Oct 2, 2025
a7f7792
Revert to private.
vbabanin Oct 2, 2025
7372d56
Add JDBC exception handling.
vbabanin Oct 7, 2025
330de0f
Apply spotless.
vbabanin Oct 7, 2025
5c81496
Apply spotless.
vbabanin Oct 7, 2025
e4d7afd
Revert SqlTransientException handling.
vbabanin Oct 8, 2025
183aafc
Remove SQlConsumer.
vbabanin Oct 8, 2025
9128c9e
Convert SQLException's to Hibernate ORM exception's.
vbabanin Oct 8, 2025
35dbc91
Remove public modifier.
vbabanin Oct 9, 2025
6fe48b4
Remove disabled tests.
vbabanin Oct 17, 2025
9577603
Fix issues.
vbabanin Oct 21, 2025
76acbc5
Update src/integrationTest/java/com/mongodb/hibernate/jdbc/MongoPrepa…
vbabanin Oct 25, 2025
39df511
Update src/integrationTest/java/com/mongodb/hibernate/jdbc/MongoPrepa…
vbabanin Oct 25, 2025
11a2b07
Update src/integrationTest/java/com/mongodb/hibernate/jdbc/MongoPrepa…
vbabanin Oct 25, 2025
510bc2d
Update src/integrationTest/java/com/mongodb/hibernate/jdbc/MongoPrepa…
vbabanin Oct 27, 2025
6faa9cb
Fix batch update count reporting.
vbabanin Oct 28, 2025
b27ef0e
Merge branch 'main' into HIBERNATE-40
vbabanin Oct 28, 2025
7c8a702
Make NULL_SQL_STATE private.
vbabanin Oct 28, 2025
51103be
Remove final in parameters.
vbabanin Oct 28, 2025
06b1ee0
Merge branch 'HIBERNATE-40' into HIBERNATE-95
vbabanin Oct 28, 2025
bbade89
Move failpoint to beforeEach and afterEach.
vbabanin Oct 28, 2025
0cda4bc
Add afterAll.
vbabanin Oct 28, 2025
ad94e8f
Update src/integrationTest/java/com/mongodb/hibernate/junit/MongoExte…
vbabanin Oct 29, 2025
17af7e8
Use JdbcException for all cases.
vbabanin Oct 29, 2025
c7a67dc
Update src/integrationTest/java/com/mongodb/hibernate/exception/Excep…
vbabanin Oct 30, 2025
9d0b258
Move tests to .*hibernate root package.
vbabanin Oct 30, 2025
2057907
Merge branch 'main' into HIBERNATE-95
vbabanin Oct 30, 2025
1a22030
Spotless apply.
vbabanin Oct 30, 2025
8414f45
Merge branch 'main' into HIBERNATE-95
vbabanin Oct 30, 2025
95d3448
Spotless apply.
vbabanin Oct 30, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright 2025-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.mongodb.MongoException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.hibernate.dialect.MongoDialect;
import com.mongodb.hibernate.junit.InjectMongoClient;
import com.mongodb.hibernate.junit.InjectMongoCollection;
import com.mongodb.hibernate.junit.MongoExtension;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.sql.SQLException;
import org.bson.BsonDocument;
import org.hibernate.JDBCException;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

/** @see MongoDialect#buildSQLExceptionConversionDelegate() */
@SessionFactory(exportSchema = false)
@DomainModel(annotatedClasses = {ExceptionHandlingIntegrationTest.Item.class})
@ExtendWith(MongoExtension.class)
class ExceptionHandlingIntegrationTest implements SessionFactoryScopeAware {
private static final String COLLECTION_NAME = "items";
private static final String EXCEPTION_MESSAGE_FAILED_TO_EXECUTE_OPERATION = "Failed to execute operation";
private static final String EXCEPTION_MESSAGE_TIMEOUT = "Timeout while waiting for operation to complete";

@InjectMongoClient
private static MongoClient mongoClient;

@InjectMongoCollection(COLLECTION_NAME)
private static MongoCollection<BsonDocument> mongoCollection;

SessionFactoryScope sessionFactoryScope;

@Override
public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) {
this.sessionFactoryScope = sessionFactoryScope;
}

@ParameterizedTest
@ValueSource(ints = {1000, 1200})
void testGenericExceptionThrown(int errorCode) {
configureFailPointErrorCode(errorCode);
assertThatThrownBy(() -> {
sessionFactoryScope.inTransaction(session -> {
session.persist(new Item(1));
});
})
.isInstanceOf(JDBCException.class)
.hasMessageContaining(EXCEPTION_MESSAGE_FAILED_TO_EXECUTE_OPERATION)
.cause()
.isInstanceOf(SQLException.class)
.hasRootCauseInstanceOf(MongoException.class);
}

@Test
void testTimeoutExceptionThrown() {
configureFailPointErrorCode(50);
assertThatThrownBy(() -> {
sessionFactoryScope.inTransaction(session -> {
session.persist(new Item(1));
});
})
.isInstanceOf(JDBCException.class)
.hasMessageContaining(EXCEPTION_MESSAGE_TIMEOUT)
.cause()
.isInstanceOf(SQLException.class)
.hasRootCauseInstanceOf(MongoException.class);
}

@Nested
@ServiceRegistry(settings = @Setting(name = AvailableSettings.STATEMENT_BATCH_SIZE, value = "2"))
@ExtendWith(MongoExtension.class)
@SessionFactory(exportSchema = false)
@DomainModel(annotatedClasses = {ExceptionHandlingIntegrationTest.Item.class})
class Batch implements SessionFactoryScopeAware {
SessionFactoryScope sessionFactoryScope;

@Override
public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) {
this.sessionFactoryScope = sessionFactoryScope;
}

@ParameterizedTest
@ValueSource(ints = {1000, 1200})
void testGenericExceptionThrown(int errorCode) {
configureFailPointErrorCode(errorCode);
assertThatThrownBy(() -> {
sessionFactoryScope.inTransaction(session -> {
session.persist(new Item(1));
session.persist(new Item(2));
});
})
.isInstanceOf(JDBCException.class)
.hasMessageContaining(EXCEPTION_MESSAGE_FAILED_TO_EXECUTE_OPERATION)
.cause()
.isExactlyInstanceOf(SQLException.class)
.hasRootCauseInstanceOf(MongoException.class);
}

@Test
void testTimeoutExceptionThrown() {
configureFailPointErrorCode(50);
assertThatThrownBy(() -> {
sessionFactoryScope.inTransaction(session -> {
session.persist(new Item(1));
session.persist(new Item(2));
});
})
.isInstanceOf(JDBCException.class)
.hasMessageContaining(EXCEPTION_MESSAGE_TIMEOUT)
.cause()
.isExactlyInstanceOf(SQLException.class)
.hasRootCauseInstanceOf(MongoException.class);
}
}

private static void configureFailPointErrorCode(int errorCode) {
BsonDocument failPointCommand = BsonDocument.parse(
"""
{
configureFailPoint: "failCommand",
mode: { times: 1 },
data: {
failCommands: ["insert"],
errorCode: %d
errorLabels: ["TransientTransactionError"]
}
}
"""
.formatted(errorCode));
mongoClient.getDatabase("admin").runCommand(failPointCommand);
}

@Entity
@Table(name = COLLECTION_NAME)
static class Item {
@Id
int id;

Item() {}

Item(int id) {
this.id = id;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.Map;
import org.bson.BsonDocument;
import org.hibernate.cfg.Configuration;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -37,8 +38,23 @@
/**
* Assumes that all tests that use this {@linkplain ExtendWith#value() extension} run <a
* href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution">sequentially</a>.
*
* <p>The {@linkplain MongoConfigurator#databaseName(String) database} is dropped
* {@linkplain MongoExtension#beforeEach(ExtensionContext) before each} test and
* {@linkplain MongoExtension#afterAll(ExtensionContext) after all} tests. The fail points are disabled
* {@linkplain MongoExtension#beforeEach(ExtensionContext) before each} test and
* {@linkplain MongoExtension#afterAll(ExtensionContext) after all} tests.
*/
public final class MongoExtension implements BeforeAllCallback, BeforeEachCallback {
public final class MongoExtension implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback {

/** Disables {@code failCommand} fail point. */
private static final BsonDocument DISABLE_FAIL_POINT_COMMAND = BsonDocument.parse(
"""
{
"configureFailPoint": "failCommand",
"mode": "off"
}
""");

private static final State STATE = State.create();

Expand All @@ -61,22 +77,31 @@ public void beforeAll(ExtensionContext context) throws Exception {
}
}

@Override
public void afterAll(ExtensionContext context) {
disableFailPoint();
STATE.mongoDatabase().drop();
}

/**
* {@linkplain MongoDatabase#drop() Drops} the {@link MongoConfigurator#databaseName(String) database}, thus
* dropping all {@linkplain InjectMongoCollection collections}.
*/
@Override
public void beforeEach(ExtensionContext context) {
STATE.mongoDatabase().drop();
@SuppressWarnings("try")
public void beforeEach(ExtensionContext context) throws Exception {
try (AutoCloseable disableFilPoint = MongoExtension::disableFailPoint;
AutoCloseable dropDatabase = () -> STATE.mongoDatabase().drop()) {}
}

private record State(MongoClient mongoClient, MongoDatabase mongoDatabase) {
private record State(MongoClient mongoClient, MongoDatabase mongoDatabase, MongoDatabase adminDatabase) {
static State create() {
@SuppressWarnings("unchecked")
var hibernateProperties = (Map<String, Object>) (Map<?, Object>) new Configuration().getProperties();
var mongoConfig = new MongoConfigurationBuilder(hibernateProperties).build();
var mongoClient = MongoClients.create(mongoConfig.mongoClientSettings());
var state = new State(mongoClient, mongoClient.getDatabase(mongoConfig.databaseName()));
var state = new State(
mongoClient, mongoClient.getDatabase(mongoConfig.databaseName()), mongoClient.getDatabase("admin"));
Runtime.getRuntime().addShutdownHook(new Thread(state::close));
return state;
}
Expand All @@ -85,4 +110,8 @@ private void close() {
mongoClient.close();
}
}

private static void disableFailPoint() {
STATE.adminDatabase().runCommand(DISABLE_FAIL_POINT_COMMAND);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@
import java.util.List;
import java.util.Set;
import org.bson.BsonDocument;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.testing.orm.junit.DomainModel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@DomainModel(annotatedClasses = Book.class)
Expand Down Expand Up @@ -178,18 +176,4 @@ insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price)
""")),
Set.of(Book.COLLECTION_NAME));
}

@Test
@Disabled("TODO-HIBERNATE-95 https://jira.mongodb.org/browse/HIBERNATE-95 enable this test")
void testConstraintViolationExceptionIsThrown() {
var hql =
"""
insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price)
values
(:id, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD),
(:id, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD)
""";
assertMutationQueryFailure(
hql, query -> query.setParameter("id", 1), ConstraintViolationException.class, "to be decided");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.Calendar;
import java.util.Collection;
import org.bson.types.ObjectId;
import org.hibernate.JDBCException;
import org.hibernate.annotations.Struct;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
Expand All @@ -50,6 +51,7 @@
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
Expand Down Expand Up @@ -369,4 +371,9 @@ public void performMutation(
public void appendDatetimeFormat(SqlAppender appender, String format) {
throw new FeatureNotSupportedException("TODO-HIBERNATE-88 https://jira.mongodb.org/browse/HIBERNATE-88");
}

@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, exceptionMessage, mql) -> new JDBCException(exceptionMessage, sqlException, mql);
}
}