From 1d36b12bc41dd2449f85c1a32c68a89e4015c2d6 Mon Sep 17 00:00:00 2001 From: Valentin Kovalenko Date: Mon, 8 Sep 2025 21:08:59 -0600 Subject: [PATCH] Explicitly forbid `StatelessSession.upsert` HIBERNATE-97 --- .../hibernate/UpsertIntegrationTests.java | 71 +++++++++++++++++++ .../hibernate/dialect/MongoDialect.java | 26 +++++++ 2 files changed, 97 insertions(+) create mode 100644 src/integrationTest/java/com/mongodb/hibernate/UpsertIntegrationTests.java diff --git a/src/integrationTest/java/com/mongodb/hibernate/UpsertIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/UpsertIntegrationTests.java new file mode 100644 index 00000000..4934e727 --- /dev/null +++ b/src/integrationTest/java/com/mongodb/hibernate/UpsertIntegrationTests.java @@ -0,0 +1,71 @@ +/* + * 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.hibernate.internal.FeatureNotSupportedException; +import com.mongodb.hibernate.junit.MongoExtension; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SessionFactoryScopeAware; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@SessionFactory(exportSchema = false) +@DomainModel(annotatedClasses = {UpsertIntegrationTests.Item.class}) +@ExtendWith(MongoExtension.class) +class UpsertIntegrationTests implements SessionFactoryScopeAware { + private SessionFactoryScope sessionFactoryScope; + + @Override + public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) { + this.sessionFactoryScope = sessionFactoryScope; + } + + @Test + void unsupported() { + var item = new Item(1, 1); + sessionFactoryScope.inStatelessTransaction(session -> + assertThatThrownBy(() -> session.upsert(item)).isInstanceOf(FeatureNotSupportedException.class)); + } + + @Entity + @Table(name = Item.COLLECTION_NAME) + static class Item { + static final String COLLECTION_NAME = "items"; + + @Id + int id; + /** + * {@link org.hibernate.StatelessSession#upsert(Object)} results in no commands issued to the DBMS if {@link Id} + * is the only persistent attribute, which must be a bug. + */ + int v; + + Item() {} + + Item(int id, int v) { + this.id = id; + v = 1; + } + } +} diff --git a/src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java b/src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java index 9758693d..12ea1286 100644 --- a/src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java +++ b/src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java @@ -19,6 +19,7 @@ import static com.mongodb.hibernate.internal.MongoConstants.MONGO_DBMS_NAME; import static java.lang.String.format; +import com.mongodb.hibernate.internal.FeatureNotSupportedException; import com.mongodb.hibernate.internal.dialect.function.array.MongoArrayConstructorFunction; import com.mongodb.hibernate.internal.dialect.function.array.MongoArrayContainsFunction; import com.mongodb.hibernate.internal.dialect.function.array.MongoArrayIncludesFunction; @@ -35,8 +36,16 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.persister.entity.mutation.EntityMutationTarget; import org.hibernate.service.ServiceRegistry; import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.model.MutationOperation; +import org.hibernate.sql.model.ValuesAnalysis; +import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; import org.jspecify.annotations.Nullable; @@ -237,4 +246,21 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionRegistry.register("array_includes", new MongoArrayIncludesFunction(false, typeConfiguration)); functionRegistry.register("array_includes_nullable", new MongoArrayIncludesFunction(true, typeConfiguration)); } + + @Override + public MutationOperation createOptionalTableUpdateOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + return new OptionalTableUpdateOperation(mutationTarget, optionalTableUpdate, factory) { + @Override + public void performMutation( + JdbcValueBindings jdbcValueBindings, + ValuesAnalysis valuesAnalysis, + SharedSessionContractImplementor session) { + throw new FeatureNotSupportedException( + "TODO-HIBERNATE-94 https://jira.mongodb.org/browse/HIBERNATE-94"); + } + }; + } }