From 55ab573733996fe3305a39af53156e3ca48a48be Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Thu, 8 May 2025 13:36:43 -0400 Subject: [PATCH 01/19] simple DML translation --- ...ava => AbstractQueryIntegrationTests.java} | 52 ++- .../hibernate/query/{select => }/Book.java | 30 +- .../AbstractMutateQueryIntegrationTests.java | 111 ++++++ .../mutate/DeletionIntegrationTests.java | 119 ++++++ .../mutate/InsertionIntegrationTests.java | 152 +++++++ .../mutate/UpdatingIntegrationTests.java | 147 +++++++ ...ExpressionWhereClauseIntegrationTests.java | 4 +- .../SimpleSelectQueryIntegrationTests.java | 4 +- .../SortingSelectQueryIntegrationTests.java | 6 +- .../translate/AbstractMqlTranslator.java | 135 ++++++- .../translate/MongoTranslatorFactory.java | 3 +- .../translate/MutationMqlTranslator.java | 71 ++++ .../translate/NoopSqlAstTranslator.java | 371 ------------------ .../mongoast/command/AstInsertCommand.java | 12 +- .../translate/AstVisitorValueHolderTests.java | 6 +- .../command/AstInsertCommandTests.java | 15 +- 16 files changed, 811 insertions(+), 427 deletions(-) rename src/integrationTest/java/com/mongodb/hibernate/query/{select/AbstractSelectionQueryIntegrationTests.java => AbstractQueryIntegrationTests.java} (72%) rename src/integrationTest/java/com/mongodb/hibernate/query/{select => }/Book.java (62%) create mode 100644 src/integrationTest/java/com/mongodb/hibernate/query/mutate/AbstractMutateQueryIntegrationTests.java create mode 100644 src/integrationTest/java/com/mongodb/hibernate/query/mutate/DeletionIntegrationTests.java create mode 100644 src/integrationTest/java/com/mongodb/hibernate/query/mutate/InsertionIntegrationTests.java create mode 100644 src/integrationTest/java/com/mongodb/hibernate/query/mutate/UpdatingIntegrationTests.java create mode 100644 src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java delete mode 100644 src/main/java/com/mongodb/hibernate/internal/translate/NoopSqlAstTranslator.java diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/AbstractSelectionQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java similarity index 72% rename from src/integrationTest/java/com/mongodb/hibernate/query/select/AbstractSelectionQueryIntegrationTests.java rename to src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java index 0ddd2aac..d084f3aa 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/AbstractSelectionQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java @@ -14,18 +14,20 @@ * limitations under the License. */ -package com.mongodb.hibernate.query.select; +package com.mongodb.hibernate.query; import static com.mongodb.hibernate.MongoTestAssertions.assertIterableEq; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.TestCommandListener; import com.mongodb.hibernate.junit.MongoExtension; import java.util.List; import java.util.function.Consumer; import org.assertj.core.api.InstanceOfAssertFactories; import org.bson.BsonDocument; +import org.hibernate.query.MutationQuery; import org.hibernate.query.SelectionQuery; import org.hibernate.testing.orm.junit.ServiceRegistryScope; import org.hibernate.testing.orm.junit.ServiceRegistryScopeAware; @@ -36,17 +38,17 @@ @SessionFactory(exportSchema = false) @ExtendWith(MongoExtension.class) -abstract class AbstractSelectionQueryIntegrationTests implements SessionFactoryScopeAware, ServiceRegistryScopeAware { +public abstract class AbstractQueryIntegrationTests implements SessionFactoryScopeAware, ServiceRegistryScopeAware { private SessionFactoryScope sessionFactoryScope; private TestCommandListener testCommandListener; - SessionFactoryScope getSessionFactoryScope() { + protected SessionFactoryScope getSessionFactoryScope() { return sessionFactoryScope; } - TestCommandListener getTestCommandListener() { + protected TestCommandListener getTestCommandListener() { return testCommandListener; } @@ -60,7 +62,7 @@ public void injectServiceRegistryScope(ServiceRegistryScope serviceRegistryScope this.testCommandListener = serviceRegistryScope.getRegistry().requireService(TestCommandListener.class); } - void assertSelectionQuery( + protected void assertSelectionQuery( String hql, Class resultType, Consumer> queryPostProcessor, @@ -74,11 +76,12 @@ void assertSelectionQuery( resultList -> assertIterableEq(expectedResultList, resultList)); } - void assertSelectionQuery(String hql, Class resultType, String expectedMql, List expectedResultList) { + protected void assertSelectionQuery( + String hql, Class resultType, String expectedMql, List expectedResultList) { assertSelectionQuery(hql, resultType, null, expectedMql, expectedResultList); } - void assertSelectionQuery( + protected void assertSelectionQuery( String hql, Class resultType, Consumer> queryPostProcessor, @@ -97,12 +100,12 @@ void assertSelectionQuery( }); } - void assertSelectionQuery( + protected void assertSelectionQuery( String hql, Class resultType, String expectedMql, Consumer> resultListVerifier) { assertSelectionQuery(hql, resultType, null, expectedMql, resultListVerifier); } - void assertSelectQueryFailure( + protected void assertSelectQueryFailure( String hql, Class resultType, Consumer> queryPostProcessor, @@ -120,7 +123,7 @@ void assertSelectQueryFailure( .hasMessage(expectedExceptionMessage, expectedExceptionMessageParameters)); } - void assertSelectQueryFailure( + protected void assertSelectQueryFailure( String hql, Class resultType, Class expectedExceptionType, @@ -135,7 +138,7 @@ void assertSelectQueryFailure( expectedExceptionMessageParameters); } - void assertActualCommand(BsonDocument expectedCommand) { + protected void assertActualCommand(BsonDocument expectedCommand) { var capturedCommands = testCommandListener.getStartedCommands(); assertThat(capturedCommands) @@ -143,4 +146,31 @@ void assertActualCommand(BsonDocument expectedCommand) { .asInstanceOf(InstanceOfAssertFactories.MAP) .containsAllEntriesOf(expectedCommand); } + + protected void assertMutateQuery( + String hql, + Consumer queryPostProcessor, + int expectedMutatedCount, + String expectedMql, + MongoCollection collection, + Iterable expectedDocuments) { + sessionFactoryScope.inTransaction(session -> { + var mutationQuery = session.createMutationQuery(hql); + if (queryPostProcessor != null) { + queryPostProcessor.accept(mutationQuery); + } + assertThat(mutationQuery.executeUpdate()).isEqualTo(expectedMutatedCount); + assertActualCommand(BsonDocument.parse(expectedMql)); + }); + assertThat(collection.find()).containsExactlyElementsOf(expectedDocuments); + } + + protected void assertMutateQuery( + String hql, + int expectedMutatedCount, + String expectedMql, + MongoCollection collection, + Iterable expectedDocuments) { + assertMutateQuery(hql, null, expectedMutatedCount, expectedMql, collection, expectedDocuments); + } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/Book.java b/src/integrationTest/java/com/mongodb/hibernate/query/Book.java similarity index 62% rename from src/integrationTest/java/com/mongodb/hibernate/query/select/Book.java rename to src/integrationTest/java/com/mongodb/hibernate/query/Book.java index 098b145d..ab2b1427 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/Book.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/Book.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.hibernate.query.select; +package com.mongodb.hibernate.query; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -22,22 +22,28 @@ import java.math.BigDecimal; @Entity(name = "Book") -@Table(name = "books") -class Book { +@Table(name = Book.COLLECTION_NAME) +public class Book { + public static final String COLLECTION_NAME = "books"; + @Id - int id; + public int id; // TODO-HIBERNATE-48 dummy values are set for currently null value is not supported - String title = ""; - Boolean outOfStock = false; - Integer publishYear = 0; - Long isbn13 = 0L; - Double discount = 0.0; - BigDecimal price = new BigDecimal("0.0"); + public String title = ""; + public Boolean outOfStock = false; + public Integer publishYear = 0; + public Long isbn13 = 0L; + public Double discount = 0.0; + public BigDecimal price = new BigDecimal("0.0"); + + public Book() {} - Book() {} + public Book(int id, String title, Integer publishYear) { + this(id, title, publishYear, false); + } - Book(int id, String title, Integer publishYear, Boolean outOfStock) { + public Book(int id, String title, Integer publishYear, Boolean outOfStock) { this.id = id; this.title = title; this.publishYear = publishYear; diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/AbstractMutateQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutate/AbstractMutateQueryIntegrationTests.java new file mode 100644 index 00000000..b7eee97a --- /dev/null +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutate/AbstractMutateQueryIntegrationTests.java @@ -0,0 +1,111 @@ +/* + * 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.query.mutate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.cfg.JdbcSettings.DIALECT; + +import com.mongodb.hibernate.dialect.MongoDialect; +import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; +import java.util.Set; +import java.util.function.Consumer; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.MutationQuery; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.model.ast.TableMutation; +import org.hibernate.sql.model.jdbc.JdbcMutationOperation; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.Setting; + +@ServiceRegistry( + settings = { + @Setting( + name = DIALECT, + value = + "com.mongodb.hibernate.query.mutate.AbstractMutateQueryIntegrationTests$MutateTranslatorAwareDialect"), + }) +class AbstractMutateQueryIntegrationTests extends AbstractQueryIntegrationTests { + + void assertAffectedTableNames(String mutateHql, String expectedAffectedTableName) { + assertAffectedTableNames(mutateHql, null, Set.of(expectedAffectedTableName)); + } + + void assertAffectedTableNames( + String mutateHql, Consumer queryPostProcessor, String expectedAffectedTableName) { + assertAffectedTableNames(mutateHql, queryPostProcessor, Set.of(expectedAffectedTableName)); + } + + void assertAffectedTableNames( + String mutateHql, Consumer queryPostProcessor, Set expectedAffectedTableNames) { + getSessionFactoryScope().inTransaction(session -> { + var query = session.createMutationQuery(mutateHql); + if (queryPostProcessor != null) { + queryPostProcessor.accept(query); + } + query.executeUpdate(); + var mutateTranslator = + ((MutateTranslatorAwareDialect) session.getJdbcServices().getDialect()).getMutateSqlAstTranslator(); + assertThat(mutateTranslator.getAffectedTableNames()).isEqualTo(expectedAffectedTableNames); + }); + } + + public static final class MutateTranslatorAwareDialect extends Dialect { + private final Dialect delegate; + private SqlAstTranslator mutateSqlAstTranslator; + + public MutateTranslatorAwareDialect(DialectResolutionInfo info) { + super(info); + delegate = new MongoDialect(info); + } + + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new SqlAstTranslatorFactory() { + @Override + public SqlAstTranslator buildSelectTranslator( + SessionFactoryImplementor sessionFactory, SelectStatement statement) { + return delegate.getSqlAstTranslatorFactory().buildSelectTranslator(sessionFactory, statement); + } + + @Override + public SqlAstTranslator buildMutationTranslator( + SessionFactoryImplementor sessionFactory, MutationStatement statement) { + mutateSqlAstTranslator = + delegate.getSqlAstTranslatorFactory().buildMutationTranslator(sessionFactory, statement); + return mutateSqlAstTranslator; + } + + @Override + public SqlAstTranslator buildModelMutationTranslator( + TableMutation mutation, SessionFactoryImplementor sessionFactory) { + return delegate.getSqlAstTranslatorFactory().buildModelMutationTranslator(mutation, sessionFactory); + } + }; + } + + SqlAstTranslator getMutateSqlAstTranslator() { + return mutateSqlAstTranslator; + } + } +} diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/DeletionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutate/DeletionIntegrationTests.java new file mode 100644 index 00000000..b286ead9 --- /dev/null +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutate/DeletionIntegrationTests.java @@ -0,0 +1,119 @@ +/* + * 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.query.mutate; + +import com.mongodb.client.MongoCollection; +import com.mongodb.hibernate.junit.InjectMongoCollection; +import com.mongodb.hibernate.query.Book; +import java.util.List; +import org.bson.BsonDocument; +import org.hibernate.testing.orm.junit.DomainModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@DomainModel(annotatedClasses = Book.class) +class DeletionIntegrationTests extends AbstractMutateQueryIntegrationTests { + + @InjectMongoCollection(Book.COLLECTION_NAME) + private static MongoCollection mongoCollection; + + private static final List testingBooks = List.of( + new Book(1, "War and Peace", 1869), + new Book(2, "Crime and Punishment", 1866), + new Book(3, "Anna Karenina", 1877), + new Book(4, "The Brothers Karamazov", 1880), + new Book(5, "War and Peace", 2025)); + + @BeforeEach + void beforeEach() { + getSessionFactoryScope().inTransaction(session -> testingBooks.forEach(session::persist)); + getTestCommandListener().clear(); + } + + @Test + void testSimpleDeletion() { + getSessionFactoryScope() + .inTransaction( + session -> assertMutateQuery( + "delete from Book where title = :title", + q -> q.setParameter("title", "War and Peace"), + 2, + """ + { + "delete": "books", + "deletes": [ + { + "limit": 0, + "q": { + "title": { + "$eq": "War and Peace" + } + } + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( + """ + { + "_id": 2, + "title": "Crime and Punishment", + "outOfStock": false, + "publishYear": 1866, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 3, + "title": "Anna Karenina", + "outOfStock": false, + "publishYear": 1877, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 4, + "title": "The Brothers Karamazov", + "outOfStock": false, + "publishYear": 1880, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """)))); + } + + @Test + void testAffectedTableNames() { + assertAffectedTableNames( + """ + delete from Book where title = :title + """, + q -> q.setParameter("title", "War and Peace"), + "books"); + } +} diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/InsertionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutate/InsertionIntegrationTests.java new file mode 100644 index 00000000..30d42f30 --- /dev/null +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutate/InsertionIntegrationTests.java @@ -0,0 +1,152 @@ +/* + * 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.query.mutate; + +import com.mongodb.client.MongoCollection; +import com.mongodb.hibernate.junit.InjectMongoCollection; +import com.mongodb.hibernate.query.Book; +import java.util.List; +import org.bson.BsonDocument; +import org.hibernate.testing.orm.junit.DomainModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@DomainModel(annotatedClasses = Book.class) +class InsertionIntegrationTests extends AbstractMutateQueryIntegrationTests { + + @InjectMongoCollection(Book.COLLECTION_NAME) + private static MongoCollection mongoCollection; + + @BeforeEach + void beforeEach() { + getTestCommandListener().clear(); + } + + @Test + void testInsertSingleDocument() { + getSessionFactoryScope() + .inTransaction( + session -> assertMutateQuery( + "insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) values (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD)", + 1, + """ + { + "insert": "books", + "documents": [ + { + "_id": 1, + "title": "Pride & Prejudice", + "outOfStock": false, + "publishYear": 1813, + "isbn13": 9780141439518, + "discount": 0.2, + "price": {"$numberDecimal": "23.55"} + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( + """ + { + "_id": 1, + "title": "Pride & Prejudice", + "outOfStock": false, + "publishYear": 1813, + "isbn13": 9780141439518, + "discount": 0.2, + "price": {"$numberDecimal": "23.55"} + } + """)))); + } + + @Test + void testInsertMultipleDocuments() { + getSessionFactoryScope() + .inTransaction( + session -> assertMutateQuery( + """ + insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) + values + (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD), + (2, 'War & Peace', false, 1867, 9780143039990L, 0.1D, 19.99BD) + """, + 2, + """ + { + "insert": "books", + "documents": [ + { + "_id": 1, + "title": "Pride & Prejudice", + "outOfStock": false, + "publishYear": 1813, + "isbn13": 9780141439518, + "discount": 0.2, + "price": {"$numberDecimal": "23.55"} + }, + { + "_id": 2, + "title": "War & Peace", + "outOfStock": false, + "publishYear": 1867, + "isbn13": 9780143039990, + "discount": 0.1, + "price": {"$numberDecimal": "19.99"} + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( + """ + { + "_id": 1, + "title": "Pride & Prejudice", + "outOfStock": false, + "publishYear": 1813, + "isbn13": 9780141439518, + "discount": 0.2, + "price": {"$numberDecimal": "23.55"} + } + """), + BsonDocument.parse( + """ + { + "_id": 2, + "title": "War & Peace", + "outOfStock": false, + "publishYear": 1867, + "isbn13": 9780143039990, + "discount": 0.1, + "price": {"$numberDecimal": "19.99"} + } + """)))); + } + + @Test + void testAffectedTableNames() { + assertAffectedTableNames( + """ + insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) + values + (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD)""", + "books"); + } +} diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/UpdatingIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutate/UpdatingIntegrationTests.java new file mode 100644 index 00000000..5c6fa20b --- /dev/null +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutate/UpdatingIntegrationTests.java @@ -0,0 +1,147 @@ +/* + * 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.query.mutate; + +import com.mongodb.client.MongoCollection; +import com.mongodb.hibernate.junit.InjectMongoCollection; +import com.mongodb.hibernate.query.Book; +import java.util.List; +import org.bson.BsonDocument; +import org.hibernate.testing.orm.junit.DomainModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@DomainModel(annotatedClasses = Book.class) +class UpdatingIntegrationTests extends AbstractMutateQueryIntegrationTests { + + @InjectMongoCollection(Book.COLLECTION_NAME) + private static MongoCollection mongoCollection; + + private static final List testingBooks = List.of( + new Book(1, "War & Peace", 1869), + new Book(2, "Crime and Punishment", 1866), + new Book(3, "Anna Karenina", 1877), + new Book(4, "The Brothers Karamazov", 1880), + new Book(5, "War & Peace", 2025)); + + @BeforeEach + void beforeEach() { + getSessionFactoryScope().inTransaction(session -> testingBooks.forEach(session::persist)); + getTestCommandListener().clear(); + } + + @Test + void testSimpleUpdate() { + getSessionFactoryScope() + .inTransaction( + session -> assertMutateQuery( + "update Book set title = :newTitle where title = :oldTitle", + q -> q.setParameter("oldTitle", "War & Peace") + .setParameter("newTitle", "War and Peace"), + 2, + """ + { + "update": "books", + "updates": [ + { + "multi": true, + "q": { + "title": { + "$eq": "War & Peace" + } + }, + "u": { + "$set": { + "title": "War and Peace" + } + } + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( + """ + { + "_id": 1, + "title": "War and Peace", + "outOfStock": false, + "publishYear": 1869, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 2, + "title": "Crime and Punishment", + "outOfStock": false, + "publishYear": 1866, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 3, + "title": "Anna Karenina", + "outOfStock": false, + "publishYear": 1877, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 4, + "title": "The Brothers Karamazov", + "outOfStock": false, + "publishYear": 1880, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 5, + "title": "War and Peace", + "outOfStock": false, + "publishYear": 2025, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """)))); + } + + @Test + void testAffectedTableNames() { + assertAffectedTableNames( + "update Book set title = :newTitle where title = :oldTitle", + q -> q.setParameter("oldTitle", "War & Peace").setParameter("newTitle", "War and Peace"), + "books"); + } +} diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java index 3eb51677..0f0e544a 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java @@ -19,13 +19,15 @@ import static java.util.Collections.singletonList; import com.mongodb.hibernate.internal.FeatureNotSupportedException; +import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; +import com.mongodb.hibernate.query.Book; import org.hibernate.testing.orm.junit.DomainModel; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @DomainModel(annotatedClasses = Book.class) -class BooleanExpressionWhereClauseIntegrationTests extends AbstractSelectionQueryIntegrationTests { +class BooleanExpressionWhereClauseIntegrationTests extends AbstractQueryIntegrationTests { private Book bookOutOfStock; private Book bookInStock; diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/SimpleSelectQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/SimpleSelectQueryIntegrationTests.java index 5e454851..f81cf810 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/SimpleSelectQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/SimpleSelectQueryIntegrationTests.java @@ -20,6 +20,8 @@ import static org.assertj.core.api.Assertions.assertThatCode; import com.mongodb.hibernate.internal.FeatureNotSupportedException; +import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; +import com.mongodb.hibernate.query.Book; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; @@ -35,7 +37,7 @@ import org.junit.jupiter.params.provider.ValueSource; @DomainModel(annotatedClasses = {SimpleSelectQueryIntegrationTests.Contact.class, Book.class}) -class SimpleSelectQueryIntegrationTests extends AbstractSelectionQueryIntegrationTests { +class SimpleSelectQueryIntegrationTests extends AbstractQueryIntegrationTests { @Nested class QueryTests { diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java index 19379ebb..bd48ad4c 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java @@ -25,6 +25,8 @@ import static org.hibernate.query.SortDirection.ASCENDING; import com.mongodb.hibernate.internal.FeatureNotSupportedException; +import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; +import com.mongodb.hibernate.query.Book; import java.util.Arrays; import java.util.List; import org.hibernate.testing.orm.junit.DomainModel; @@ -37,7 +39,7 @@ import org.junit.jupiter.params.provider.ValueSource; @DomainModel(annotatedClasses = Book.class) -class SortingSelectQueryIntegrationTests extends AbstractSelectionQueryIntegrationTests { +class SortingSelectQueryIntegrationTests extends AbstractQueryIntegrationTests { private static final List testingBooks = List.of( new Book(1, "War and Peace", 1869, true), @@ -252,7 +254,7 @@ void testSortFieldByOrdinalReference() { @Nested @DomainModel(annotatedClasses = Book.class) @ServiceRegistry(settings = @Setting(name = DEFAULT_NULL_ORDERING, value = "first")) - class DefaultNullPrecedenceTests extends AbstractSelectionQueryIntegrationTests { + class DefaultNullPrecedenceTests extends AbstractQueryIntegrationTests { @Test void testDefaultNullPrecedenceFeatureNotSupported() { assertSelectQueryFailure( diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index 0f5d94cb..6a65657e 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -16,6 +16,7 @@ package com.mongodb.hibernate.internal.translate; +import static com.mongodb.hibernate.internal.MongoAssertions.assertFalse; import static com.mongodb.hibernate.internal.MongoAssertions.assertNotNull; import static com.mongodb.hibernate.internal.MongoAssertions.assertTrue; import static com.mongodb.hibernate.internal.MongoConstants.EXTENDED_JSON_WRITER_SETTINGS; @@ -102,8 +103,12 @@ import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.AbstractMutationStatement; +import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; +import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.cte.CteContainer; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.AggregateColumnWriteExpression; import org.hibernate.sql.ast.tree.expression.Any; @@ -148,6 +153,7 @@ import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.from.ValuesTableReference; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.ast.tree.insert.Values; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; @@ -266,7 +272,8 @@ public void visitStandardTableInsert(TableInsertStandard tableInsert) { } astVisitorValueHolder.yield( COLLECTION_MUTATION, - new AstInsertCommand(tableInsert.getMutatingTable().getTableName(), new AstDocument(astElements))); + new AstInsertCommand( + tableInsert.getMutatingTable().getTableName(), List.of(new AstDocument(astElements)))); } @Override @@ -336,10 +343,7 @@ public void visitSelectStatement(SelectStatement selectStatement) { if (!selectStatement.getQueryPart().isRoot()) { throw new FeatureNotSupportedException("Subquery not supported"); } - if (!selectStatement.getCteStatements().isEmpty() - || !selectStatement.getCteObjects().isEmpty()) { - throw new FeatureNotSupportedException("CTE not supported"); - } + checkCteContainer(selectStatement); selectStatement.getQueryPart().accept(this); } @@ -392,16 +396,9 @@ private AstProjectStage createProjectStage(SelectClause selectClause) { @Override public void visitFromClause(FromClause fromClause) { - if (fromClause.getRoots().size() != 1) { - throw new FeatureNotSupportedException(); - } + checkFromClauseSupportability(fromClause); var tableGroup = fromClause.getRoots().get(0); - - if (!(tableGroup.getModelPart() instanceof EntityPersister entityPersister) - || entityPersister.getQuerySpaces().length != 1) { - throw new FeatureNotSupportedException(); - } - + var entityPersister = (EntityPersister) tableGroup.getModelPart(); affectedTableNames.add(((String[]) entityPersister.getQuerySpaces())[0]); tableGroup.getPrimaryTableReference().accept(this); } @@ -580,17 +577,93 @@ public void visitTuple(SqlTuple sqlTuple) { @Override public void visitDeleteStatement(DeleteStatement deleteStatement) { - throw new FeatureNotSupportedException("TODO-HIBERNATE-46 https://jira.mongodb.org/browse/HIBERNATE-46"); + var pair = getCollectionAstFilterPair(deleteStatement); + astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstDeleteCommand(pair.collection, pair.filter)); } @Override public void visitUpdateStatement(UpdateStatement updateStatement) { - throw new FeatureNotSupportedException("TODO-HIBERNATE-46 https://jira.mongodb.org/browse/HIBERNATE-46"); + var collectionAndFilter = getCollectionAstFilterPair(updateStatement); + var fieldUpdates = + new ArrayList(updateStatement.getAssignments().size()); + for (var assignment : updateStatement.getAssignments()) { + var fieldReferences = assignment.getAssignable().getColumnReferences(); + if (fieldReferences.size() != 1) { + throw new FeatureNotSupportedException(); + } + var fieldPath = fieldReferences.get(0).getColumnReference().getColumnExpression(); + var assignedValue = assignment.getAssignedValue(); + + var sqlTuple = SqlTupleContainer.getSqlTuple(assignedValue); + if (sqlTuple != null) { + throw new FeatureNotSupportedException(); + } + if (!isValueExpression(assignedValue)) { + throw new FeatureNotSupportedException(); + } + var fieldValue = acceptAndYield(assignedValue, FIELD_VALUE); + fieldUpdates.add(new AstFieldUpdate(fieldPath, fieldValue)); + } + astVisitorValueHolder.yield( + COLLECTION_MUTATION, + new AstUpdateCommand(collectionAndFilter.collection, collectionAndFilter.filter, fieldUpdates)); + } + + private CollectionAstFilterPair getCollectionAstFilterPair( + AbstractUpdateOrDeleteStatement updateOrDeleteStatement) { + checkMutationStatement(updateOrDeleteStatement); + var fromClause = updateOrDeleteStatement.getFromClause(); + checkFromClauseSupportability(fromClause); + var collection = getMutationCollectionAfterRegisteredAsAffectedTable(updateOrDeleteStatement); + var filter = acceptAndYield(updateOrDeleteStatement.getRestriction(), FILTER); + return new CollectionAstFilterPair(collection, filter); } @Override - public void visitInsertStatement(InsertSelectStatement insertSelectStatement) { - throw new FeatureNotSupportedException("TODO-HIBERNATE-46 https://jira.mongodb.org/browse/HIBERNATE-46"); + public void visitInsertStatement(InsertSelectStatement insertStatement) { + checkMutationStatement(insertStatement); + if (insertStatement.getConflictClause() != null) { + throw new FeatureNotSupportedException(); + } + if (insertStatement.getSourceSelectStatement() != null) { + throw new FeatureNotSupportedException(); + } + + var collection = getMutationCollectionAfterRegisteredAsAffectedTable(insertStatement); + var fieldReferences = insertStatement.getTargetColumns(); + assertFalse(fieldReferences.isEmpty()); + + var fieldNames = new ArrayList(fieldReferences.size()); + for (var fieldReference : fieldReferences) { + fieldNames.add(fieldReference.getColumnExpression()); + } + + var valuesList = insertStatement.getValuesList(); + assertFalse(valuesList.isEmpty()); + + var documents = new ArrayList(valuesList.size()); + for (Values values : valuesList) { + assertTrue(fieldNames.size() == values.getExpressions().size()); + var astElements = new ArrayList(values.getExpressions().size()); + for (var i = 0; i < fieldReferences.size(); i++) { + var fieldName = fieldNames.get(i); + var fieldValueExpression = values.getExpressions().get(i); + if (!isValueExpression(fieldValueExpression)) { + throw new FeatureNotSupportedException(); + } + var fieldValue = acceptAndYield(fieldValueExpression, FIELD_VALUE); + astElements.add(new AstElement(fieldName, fieldValue)); + } + documents.add(new AstDocument(astElements)); + } + + astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstInsertCommand(collection, documents)); + } + + private String getMutationCollectionAfterRegisteredAsAffectedTable(MutationStatement mutationStatement) { + var collection = mutationStatement.getTargetTable().getTableExpression(); + affectedTableNames.add(collection); + return collection; } @Override @@ -977,4 +1050,30 @@ private static BsonValue toBsonValue(@Nullable Object queryLiteral) { } throw new FeatureNotSupportedException("Unsupported Java type: " + queryLiteral.getClass()); } + + private static void checkCteContainer(CteContainer cteContainer) { + if (!cteContainer.getCteStatements().isEmpty() + || !cteContainer.getCteObjects().isEmpty()) { + throw new FeatureNotSupportedException("CTE not supported"); + } + } + + private static void checkMutationStatement(AbstractMutationStatement mutationStatement) { + checkCteContainer(mutationStatement); + if (!mutationStatement.getReturningColumns().isEmpty()) { + throw new FeatureNotSupportedException(); + } + } + + private static void checkFromClauseSupportability(FromClause fromClause) { + if (fromClause.getRoots().size() != 1) { + throw new FeatureNotSupportedException(); + } + if (!(fromClause.getRoots().get(0).getModelPart() instanceof EntityPersister entityPersister) + || entityPersister.getQuerySpaces().length != 1) { + throw new FeatureNotSupportedException(); + } + } + + private record CollectionAstFilterPair(String collection, AstFilter filter) {} } diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/MongoTranslatorFactory.java b/src/main/java/com/mongodb/hibernate/internal/translate/MongoTranslatorFactory.java index 9a1a87fe..e596b89e 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/MongoTranslatorFactory.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/MongoTranslatorFactory.java @@ -36,8 +36,7 @@ public SqlAstTranslator buildSelectTranslator( @Override public SqlAstTranslator buildMutationTranslator( SessionFactoryImplementor sessionFactoryImplementor, MutationStatement mutationStatement) { - // TODO-HIBERNATE-46 https://jira.mongodb.org/browse/HIBERNATE-46 - return new NoopSqlAstTranslator<>(); + return new MutationMqlTranslator(sessionFactoryImplementor, mutationStatement); } @Override diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java new file mode 100644 index 00000000..da79c012 --- /dev/null +++ b/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.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.internal.translate; + +import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_MUTATION; +import static java.util.Collections.emptyMap; +import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst; + +import com.mongodb.hibernate.internal.FeatureNotSupportedException; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.insert.InsertStatement; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl; +import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.jspecify.annotations.Nullable; + +final class MutationMqlTranslator extends AbstractMqlTranslator { + + private final MutationStatement mutationStatement; + + MutationMqlTranslator(SessionFactoryImplementor sessionFactory, MutationStatement mutationStatement) { + super(sessionFactory); + this.mutationStatement = mutationStatement; + } + + @Override + public JdbcOperationQueryMutation translate( + @Nullable JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + + logSqlAst(mutationStatement); + + checkJdbcParameterBindingsSupportability(jdbcParameterBindings); + checkQueryOptionsSupportability(queryOptions); + + var mutationCommand = acceptAndYield(mutationStatement, COLLECTION_MUTATION); + var mql = renderMongoAstNode(mutationCommand); + var parameterBinders = getParameterBinders(); + var affectedCollectionNames = getAffectedTableNames(); + + if (mutationStatement instanceof InsertStatement) { + return new JdbcOperationQueryInsertImpl(mql, parameterBinders, affectedCollectionNames); + } else if (mutationStatement instanceof UpdateStatement) { + return new JdbcOperationQueryUpdate(mql, parameterBinders, affectedCollectionNames, emptyMap()); + } else if (mutationStatement instanceof DeleteStatement) { + return new JdbcOperationQueryDelete(mql, parameterBinders, affectedCollectionNames, emptyMap()); + } else { + throw new FeatureNotSupportedException("Unsupported mutation statement type: " + + mutationStatement.getClass().getName()); + } + } +} diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/NoopSqlAstTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/NoopSqlAstTranslator.java deleted file mode 100644 index 5eb6c2d1..00000000 --- a/src/main/java/com/mongodb/hibernate/internal/translate/NoopSqlAstTranslator.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * 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.internal.translate; - -import java.util.Set; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.persister.internal.SqlFragmentPredicate; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.sqm.tree.expression.Conversion; -import org.hibernate.sql.ast.Clause; -import org.hibernate.sql.ast.SqlAstNodeRenderingMode; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.SqlSelection; -import org.hibernate.sql.ast.tree.SqlAstNode; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.expression.AggregateColumnWriteExpression; -import org.hibernate.sql.ast.tree.expression.Any; -import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; -import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; -import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; -import org.hibernate.sql.ast.tree.expression.CastTarget; -import org.hibernate.sql.ast.tree.expression.Collation; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Distinct; -import org.hibernate.sql.ast.tree.expression.Duration; -import org.hibernate.sql.ast.tree.expression.DurationUnit; -import org.hibernate.sql.ast.tree.expression.EmbeddableTypeLiteral; -import org.hibernate.sql.ast.tree.expression.EntityTypeLiteral; -import org.hibernate.sql.ast.tree.expression.Every; -import org.hibernate.sql.ast.tree.expression.ExtractUnit; -import org.hibernate.sql.ast.tree.expression.Format; -import org.hibernate.sql.ast.tree.expression.JdbcLiteral; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.expression.ModifiedSubQueryExpression; -import org.hibernate.sql.ast.tree.expression.NestedColumnReference; -import org.hibernate.sql.ast.tree.expression.Over; -import org.hibernate.sql.ast.tree.expression.Overflow; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; -import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; -import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -import org.hibernate.sql.ast.tree.expression.Star; -import org.hibernate.sql.ast.tree.expression.Summarization; -import org.hibernate.sql.ast.tree.expression.TrimSpecification; -import org.hibernate.sql.ast.tree.expression.UnaryOperation; -import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral; -import org.hibernate.sql.ast.tree.from.FromClause; -import org.hibernate.sql.ast.tree.from.FunctionTableReference; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.QueryPartTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableGroupJoin; -import org.hibernate.sql.ast.tree.from.TableReferenceJoin; -import org.hibernate.sql.ast.tree.from.ValuesTableReference; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; -import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate; -import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; -import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; -import org.hibernate.sql.ast.tree.predicate.FilterPredicate; -import org.hibernate.sql.ast.tree.predicate.GroupedPredicate; -import org.hibernate.sql.ast.tree.predicate.InArrayPredicate; -import org.hibernate.sql.ast.tree.predicate.InListPredicate; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Junction; -import org.hibernate.sql.ast.tree.predicate.LikePredicate; -import org.hibernate.sql.ast.tree.predicate.NegatedPredicate; -import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; -import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate; -import org.hibernate.sql.ast.tree.predicate.ThruthnessPredicate; -import org.hibernate.sql.ast.tree.select.QueryGroup; -import org.hibernate.sql.ast.tree.select.QueryPart; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectClause; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.ast.tree.select.SortSpecification; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.JdbcOperation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.model.ast.ColumnWriteFragment; -import org.hibernate.sql.model.internal.OptionalTableUpdate; -import org.hibernate.sql.model.internal.TableDeleteCustomSql; -import org.hibernate.sql.model.internal.TableDeleteStandard; -import org.hibernate.sql.model.internal.TableInsertCustomSql; -import org.hibernate.sql.model.internal.TableInsertStandard; -import org.hibernate.sql.model.internal.TableUpdateCustomSql; -import org.hibernate.sql.model.internal.TableUpdateStandard; -import org.jspecify.annotations.NullUnmarked; - -@NullUnmarked -final class NoopSqlAstTranslator implements SqlAstTranslator { - - NoopSqlAstTranslator() {} - - @Override - public SessionFactoryImplementor getSessionFactory() { - return null; - } - - @Override - public void render(SqlAstNode sqlAstNode, SqlAstNodeRenderingMode renderingMode) {} - - @Override - public boolean supportsFilterClause() { - return false; - } - - @Override - public QueryPart getCurrentQueryPart() { - return null; - } - - @Override - public Stack getCurrentClauseStack() { - return null; - } - - @Override - public Set getAffectedTableNames() { - return Set.of(); - } - - @Override - public T translate(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { - return null; - } - - @Override - public void visitSelectStatement(SelectStatement statement) {} - - @Override - public void visitDeleteStatement(DeleteStatement statement) {} - - @Override - public void visitUpdateStatement(UpdateStatement statement) {} - - @Override - public void visitInsertStatement(InsertSelectStatement statement) {} - - @Override - public void visitAssignment(Assignment assignment) {} - - @Override - public void visitQueryGroup(QueryGroup queryGroup) {} - - @Override - public void visitQuerySpec(QuerySpec querySpec) {} - - @Override - public void visitSortSpecification(SortSpecification sortSpecification) {} - - @Override - public void visitOffsetFetchClause(QueryPart querySpec) {} - - @Override - public void visitSelectClause(SelectClause selectClause) {} - - @Override - public void visitSqlSelection(SqlSelection sqlSelection) {} - - @Override - public void visitFromClause(FromClause fromClause) {} - - @Override - public void visitTableGroup(TableGroup tableGroup) {} - - @Override - public void visitTableGroupJoin(TableGroupJoin tableGroupJoin) {} - - @Override - public void visitNamedTableReference(NamedTableReference tableReference) {} - - @Override - public void visitValuesTableReference(ValuesTableReference tableReference) {} - - @Override - public void visitQueryPartTableReference(QueryPartTableReference tableReference) {} - - @Override - public void visitFunctionTableReference(FunctionTableReference tableReference) {} - - @Override - public void visitTableReferenceJoin(TableReferenceJoin tableReferenceJoin) {} - - @Override - public void visitColumnReference(ColumnReference columnReference) {} - - @Override - public void visitNestedColumnReference(NestedColumnReference nestedColumnReference) {} - - @Override - public void visitAggregateColumnWriteExpression(AggregateColumnWriteExpression aggregateColumnWriteExpression) {} - - @Override - public void visitExtractUnit(ExtractUnit extractUnit) {} - - @Override - public void visitFormat(Format format) {} - - @Override - public void visitDistinct(Distinct distinct) {} - - @Override - public void visitOverflow(Overflow overflow) {} - - @Override - public void visitStar(Star star) {} - - @Override - public void visitTrimSpecification(TrimSpecification trimSpecification) {} - - @Override - public void visitCastTarget(CastTarget castTarget) {} - - @Override - public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression) {} - - @Override - public void visitCaseSearchedExpression(CaseSearchedExpression caseSearchedExpression) {} - - @Override - public void visitCaseSimpleExpression(CaseSimpleExpression caseSimpleExpression) {} - - @Override - public void visitAny(Any any) {} - - @Override - public void visitEvery(Every every) {} - - @Override - public void visitSummarization(Summarization every) {} - - @Override - public void visitOver(Over over) {} - - @Override - public void visitSelfRenderingExpression(SelfRenderingExpression expression) {} - - @Override - public void visitSqlSelectionExpression(SqlSelectionExpression expression) {} - - @Override - public void visitEntityTypeLiteral(EntityTypeLiteral expression) {} - - @Override - public void visitEmbeddableTypeLiteral(EmbeddableTypeLiteral expression) {} - - @Override - public void visitTuple(SqlTuple tuple) {} - - @Override - public void visitCollation(Collation collation) {} - - @Override - public void visitParameter(JdbcParameter jdbcParameter) {} - - @Override - public void visitJdbcLiteral(JdbcLiteral jdbcLiteral) {} - - @Override - public void visitQueryLiteral(QueryLiteral queryLiteral) {} - - @Override - public void visitUnparsedNumericLiteral(UnparsedNumericLiteral literal) {} - - @Override - public void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression) {} - - @Override - public void visitModifiedSubQueryExpression(ModifiedSubQueryExpression expression) {} - - @Override - public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) {} - - @Override - public void visitBetweenPredicate(BetweenPredicate betweenPredicate) {} - - @Override - public void visitFilterPredicate(FilterPredicate filterPredicate) {} - - @Override - public void visitFilterFragmentPredicate(FilterPredicate.FilterFragmentPredicate fragmentPredicate) {} - - @Override - public void visitSqlFragmentPredicate(SqlFragmentPredicate predicate) {} - - @Override - public void visitGroupedPredicate(GroupedPredicate groupedPredicate) {} - - @Override - public void visitInListPredicate(InListPredicate inListPredicate) {} - - @Override - public void visitInSubQueryPredicate(InSubQueryPredicate inSubQueryPredicate) {} - - @Override - public void visitInArrayPredicate(InArrayPredicate inArrayPredicate) {} - - @Override - public void visitExistsPredicate(ExistsPredicate existsPredicate) {} - - @Override - public void visitJunction(Junction junction) {} - - @Override - public void visitLikePredicate(LikePredicate likePredicate) {} - - @Override - public void visitNegatedPredicate(NegatedPredicate negatedPredicate) {} - - @Override - public void visitNullnessPredicate(NullnessPredicate nullnessPredicate) {} - - @Override - public void visitThruthnessPredicate(ThruthnessPredicate predicate) {} - - @Override - public void visitRelationalPredicate(ComparisonPredicate comparisonPredicate) {} - - @Override - public void visitSelfRenderingPredicate(SelfRenderingPredicate selfRenderingPredicate) {} - - @Override - public void visitDurationUnit(DurationUnit durationUnit) {} - - @Override - public void visitDuration(Duration duration) {} - - @Override - public void visitConversion(Conversion conversion) {} - - @Override - public void visitStandardTableInsert(TableInsertStandard tableInsert) {} - - @Override - public void visitCustomTableInsert(TableInsertCustomSql tableInsert) {} - - @Override - public void visitStandardTableDelete(TableDeleteStandard tableDelete) {} - - @Override - public void visitCustomTableDelete(TableDeleteCustomSql tableDelete) {} - - @Override - public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) {} - - @Override - public void visitOptionalTableUpdate(OptionalTableUpdate tableUpdate) {} - - @Override - public void visitCustomTableUpdate(TableUpdateCustomSql tableUpdate) {} - - @Override - public void visitColumnWriteFragment(ColumnWriteFragment columnWriteFragment) {} -} diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java index a8358991..2b6c80ee 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java @@ -16,10 +16,18 @@ package com.mongodb.hibernate.internal.translate.mongoast.command; +import static com.mongodb.hibernate.internal.MongoAssertions.assertFalse; + import com.mongodb.hibernate.internal.translate.mongoast.AstDocument; +import java.util.List; import org.bson.BsonWriter; -public record AstInsertCommand(String collection, AstDocument document) implements AstCommand { +public record AstInsertCommand(String collection, List documents) implements AstCommand { + + public AstInsertCommand { + assertFalse(documents.isEmpty()); + } + @Override public void render(BsonWriter writer) { writer.writeStartDocument(); @@ -28,7 +36,7 @@ public void render(BsonWriter writer) { writer.writeName("documents"); writer.writeStartArray(); { - document.render(writer); + documents.forEach(docuemnt -> docuemnt.render(writer)); } writer.writeEndArray(); } diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueHolderTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueHolderTests.java index b11d20aa..d8dfa49d 100644 --- a/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueHolderTests.java +++ b/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueHolderTests.java @@ -56,13 +56,11 @@ void testSimpleUsage() { void testRecursiveUsage() { Runnable tableInserter = () -> { - Runnable fieldValueYielder = () -> { - astVisitorValueHolder.yield(FIELD_VALUE, AstParameterMarker.INSTANCE); - }; + Runnable fieldValueYielder = () -> astVisitorValueHolder.yield(FIELD_VALUE, AstParameterMarker.INSTANCE); var fieldValue = astVisitorValueHolder.execute(FIELD_VALUE, fieldValueYielder); AstElement astElement = new AstElement("province", fieldValue); astVisitorValueHolder.yield( - COLLECTION_MUTATION, new AstInsertCommand("city", new AstDocument(List.of(astElement)))); + COLLECTION_MUTATION, new AstInsertCommand("city", List.of(new AstDocument(List.of(astElement))))); }; astVisitorValueHolder.execute(COLLECTION_MUTATION, tableInserter); diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommandTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommandTests.java index c26f212a..78d99307 100644 --- a/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommandTests.java +++ b/src/test/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommandTests.java @@ -33,15 +33,24 @@ class AstInsertCommandTests { void testRendering() { var collection = "books"; - var elements = List.of( + + var elements1 = List.of( new AstElement("title", new AstLiteralValue(new BsonString("War and Peace"))), new AstElement("year", new AstLiteralValue(new BsonInt32(1867))), new AstElement("_id", AstParameterMarker.INSTANCE)); - var insertCommand = new AstInsertCommand(collection, new AstDocument(elements)); + var document1 = new AstDocument(elements1); + + var elements2 = List.of( + new AstElement("title", new AstLiteralValue(new BsonString("Crime and Punishment"))), + new AstElement("year", new AstLiteralValue(new BsonInt32(1868))), + new AstElement("_id", AstParameterMarker.INSTANCE)); + var document2 = new AstDocument(elements2); + + var insertCommand = new AstInsertCommand(collection, List.of(document1, document2)); var expectedJson = """ - {"insert": "books", "documents": [{"title": "War and Peace", "year": {"$numberInt": "1867"}, "_id": {"$undefined": true}}]}\ + {"insert": "books", "documents": [{"title": "War and Peace", "year": {"$numberInt": "1867"}, "_id": {"$undefined": true}}, {"title": "Crime and Punishment", "year": {"$numberInt": "1868"}, "_id": {"$undefined": true}}]}\ """; assertRendering(expectedJson, insertCommand); } From c4deaf92df3ae62d21fe448920203e1156ae06b6 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 27 May 2025 15:35:54 -0400 Subject: [PATCH 02/19] Update src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java Co-authored-by: Jeff Yemin --- .../internal/translate/mongoast/command/AstInsertCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java index 2b6c80ee..890bb008 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/mongoast/command/AstInsertCommand.java @@ -36,7 +36,7 @@ public void render(BsonWriter writer) { writer.writeName("documents"); writer.writeStartArray(); { - documents.forEach(docuemnt -> docuemnt.render(writer)); + documents.forEach(document -> document.render(writer)); } writer.writeEndArray(); } From d71f5689f2aa625b11745025f80eb12030f0aa15 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 27 May 2025 16:29:11 -0400 Subject: [PATCH 03/19] minor improvement to AbstractMqlTranslator#visitInsertStatement() --- .../hibernate/internal/translate/AbstractMqlTranslator.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index 6a65657e..e414e252 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -153,7 +153,6 @@ import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.from.ValuesTableReference; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.insert.Values; import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; @@ -642,10 +641,10 @@ public void visitInsertStatement(InsertSelectStatement insertStatement) { assertFalse(valuesList.isEmpty()); var documents = new ArrayList(valuesList.size()); - for (Values values : valuesList) { + for (var values : valuesList) { assertTrue(fieldNames.size() == values.getExpressions().size()); var astElements = new ArrayList(values.getExpressions().size()); - for (var i = 0; i < fieldReferences.size(); i++) { + for (var i = 0; i < fieldNames.size(); i++) { var fieldName = fieldNames.get(i); var fieldValueExpression = values.getExpressions().get(i); if (!isValueExpression(fieldValueExpression)) { From 1655c57f87156b1bd0e5dec63c401749c2c3c010 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 27 May 2025 16:42:41 -0400 Subject: [PATCH 04/19] minor improvements to AbstractMqlTranslator --- .../translate/AbstractMqlTranslator.java | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index e414e252..ffd59f07 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -104,7 +104,6 @@ import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.AbstractMutationStatement; -import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.Statement; @@ -576,13 +575,23 @@ public void visitTuple(SqlTuple sqlTuple) { @Override public void visitDeleteStatement(DeleteStatement deleteStatement) { - var pair = getCollectionAstFilterPair(deleteStatement); - astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstDeleteCommand(pair.collection, pair.filter)); + checkMutationStatement(deleteStatement); + checkFromClauseSupportability(deleteStatement.getFromClause()); + + var collection = getMutationCollectionAfterRegisteredAsAffectedTable(deleteStatement); + var astFilter = acceptAndYield(deleteStatement.getRestriction(), FILTER); + + astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstDeleteCommand(collection, astFilter)); } @Override public void visitUpdateStatement(UpdateStatement updateStatement) { - var collectionAndFilter = getCollectionAstFilterPair(updateStatement); + checkMutationStatement(updateStatement); + checkFromClauseSupportability(updateStatement.getFromClause()); + + var collection = getMutationCollectionAfterRegisteredAsAffectedTable(updateStatement); + var astFilter = acceptAndYield(updateStatement.getRestriction(), FILTER); + var fieldUpdates = new ArrayList(updateStatement.getAssignments().size()); for (var assignment : updateStatement.getAssignments()) { @@ -603,19 +612,7 @@ public void visitUpdateStatement(UpdateStatement updateStatement) { var fieldValue = acceptAndYield(assignedValue, FIELD_VALUE); fieldUpdates.add(new AstFieldUpdate(fieldPath, fieldValue)); } - astVisitorValueHolder.yield( - COLLECTION_MUTATION, - new AstUpdateCommand(collectionAndFilter.collection, collectionAndFilter.filter, fieldUpdates)); - } - - private CollectionAstFilterPair getCollectionAstFilterPair( - AbstractUpdateOrDeleteStatement updateOrDeleteStatement) { - checkMutationStatement(updateOrDeleteStatement); - var fromClause = updateOrDeleteStatement.getFromClause(); - checkFromClauseSupportability(fromClause); - var collection = getMutationCollectionAfterRegisteredAsAffectedTable(updateOrDeleteStatement); - var filter = acceptAndYield(updateOrDeleteStatement.getRestriction(), FILTER); - return new CollectionAstFilterPair(collection, filter); + astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstUpdateCommand(collection, astFilter, fieldUpdates)); } @Override @@ -1073,6 +1070,4 @@ private static void checkFromClauseSupportability(FromClause fromClause) { throw new FeatureNotSupportedException(); } } - - private record CollectionAstFilterPair(String collection, AstFilter filter) {} } From 163fb45039ce779483bf4c2f637cd958e9e6b001 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 27 May 2025 17:32:11 -0400 Subject: [PATCH 05/19] improvements to naming --- .../query/AbstractQueryIntegrationTests.java | 29 +++++++------- ...bstractMutationQueryIntegrationTests.java} | 40 ++++++++++--------- .../DeletionIntegrationTests.java | 6 +-- .../InsertionIntegrationTests.java | 8 ++-- .../UpdatingIntegrationTests.java | 6 +-- .../translate/AbstractMqlTranslator.java | 21 +++++----- 6 files changed, 56 insertions(+), 54 deletions(-) rename src/integrationTest/java/com/mongodb/hibernate/query/{mutate/AbstractMutateQueryIntegrationTests.java => mutation/AbstractMutationQueryIntegrationTests.java} (70%) rename src/integrationTest/java/com/mongodb/hibernate/query/{mutate => mutation}/DeletionIntegrationTests.java (96%) rename src/integrationTest/java/com/mongodb/hibernate/query/{mutate => mutation}/InsertionIntegrationTests.java (96%) rename src/integrationTest/java/com/mongodb/hibernate/query/{mutate => mutation}/UpdatingIntegrationTests.java (97%) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java index d084f3aa..02fa3f21 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java @@ -23,7 +23,6 @@ import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.TestCommandListener; import com.mongodb.hibernate.junit.MongoExtension; -import java.util.List; import java.util.function.Consumer; import org.assertj.core.api.InstanceOfAssertFactories; import org.bson.BsonDocument; @@ -67,7 +66,7 @@ protected void assertSelectionQuery( Class resultType, Consumer> queryPostProcessor, String expectedMql, - List expectedResultList) { + Iterable expectedResultList) { assertSelectionQuery( hql, resultType, @@ -77,7 +76,7 @@ protected void assertSelectionQuery( } protected void assertSelectionQuery( - String hql, Class resultType, String expectedMql, List expectedResultList) { + String hql, Class resultType, String expectedMql, Iterable expectedResultList) { assertSelectionQuery(hql, resultType, null, expectedMql, expectedResultList); } @@ -86,7 +85,7 @@ protected void assertSelectionQuery( Class resultType, Consumer> queryPostProcessor, String expectedMql, - Consumer> resultListVerifier) { + Consumer> resultListVerifier) { sessionFactoryScope.inTransaction(session -> { var selectionQuery = session.createSelectionQuery(hql, resultType); if (queryPostProcessor != null) { @@ -101,7 +100,7 @@ protected void assertSelectionQuery( } protected void assertSelectionQuery( - String hql, Class resultType, String expectedMql, Consumer> resultListVerifier) { + String hql, Class resultType, String expectedMql, Consumer> resultListVerifier) { assertSelectionQuery(hql, resultType, null, expectedMql, resultListVerifier); } @@ -147,30 +146,30 @@ protected void assertActualCommand(BsonDocument expectedCommand) { .containsAllEntriesOf(expectedCommand); } - protected void assertMutateQuery( + protected void assertMutationQuery( String hql, Consumer queryPostProcessor, - int expectedMutatedCount, + int expectedMutationCount, String expectedMql, MongoCollection collection, - Iterable expectedDocuments) { + Iterable expectedDocuments) { sessionFactoryScope.inTransaction(session -> { - var mutationQuery = session.createMutationQuery(hql); + var query = session.createMutationQuery(hql); if (queryPostProcessor != null) { - queryPostProcessor.accept(mutationQuery); + queryPostProcessor.accept(query); } - assertThat(mutationQuery.executeUpdate()).isEqualTo(expectedMutatedCount); + assertThat(query.executeUpdate()).isEqualTo(expectedMutationCount); assertActualCommand(BsonDocument.parse(expectedMql)); }); assertThat(collection.find()).containsExactlyElementsOf(expectedDocuments); } - protected void assertMutateQuery( + protected void assertMutationQuery( String hql, - int expectedMutatedCount, + int expectedMutationCount, String expectedMql, MongoCollection collection, - Iterable expectedDocuments) { - assertMutateQuery(hql, null, expectedMutatedCount, expectedMql, collection, expectedDocuments); + Iterable expectedDocuments) { + assertMutationQuery(hql, null, expectedMutationCount, expectedMql, collection, expectedDocuments); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/AbstractMutateQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java similarity index 70% rename from src/integrationTest/java/com/mongodb/hibernate/query/mutate/AbstractMutateQueryIntegrationTests.java rename to src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java index b7eee97a..9fa5e073 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/AbstractMutateQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.hibernate.query.mutate; +package com.mongodb.hibernate.query.mutation; import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.cfg.JdbcSettings.DIALECT; @@ -43,38 +43,40 @@ @Setting( name = DIALECT, value = - "com.mongodb.hibernate.query.mutate.AbstractMutateQueryIntegrationTests$MutateTranslatorAwareDialect"), + "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslatorAwareDialect"), }) -class AbstractMutateQueryIntegrationTests extends AbstractQueryIntegrationTests { +class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { - void assertAffectedTableNames(String mutateHql, String expectedAffectedTableName) { - assertAffectedTableNames(mutateHql, null, Set.of(expectedAffectedTableName)); + void assertAffectedTableNames(String hql, String expectedAffectedTableName) { + assertAffectedTableNames(hql, null, Set.of(expectedAffectedTableName)); } void assertAffectedTableNames( - String mutateHql, Consumer queryPostProcessor, String expectedAffectedTableName) { - assertAffectedTableNames(mutateHql, queryPostProcessor, Set.of(expectedAffectedTableName)); + String hql, Consumer queryPostProcessor, String expectedAffectedTableName) { + assertAffectedTableNames(hql, queryPostProcessor, Set.of(expectedAffectedTableName)); } void assertAffectedTableNames( - String mutateHql, Consumer queryPostProcessor, Set expectedAffectedTableNames) { + String hql, Consumer queryPostProcessor, Set expectedAffectedTableNames) { getSessionFactoryScope().inTransaction(session -> { - var query = session.createMutationQuery(mutateHql); + var query = session.createMutationQuery(hql); if (queryPostProcessor != null) { queryPostProcessor.accept(query); } query.executeUpdate(); - var mutateTranslator = - ((MutateTranslatorAwareDialect) session.getJdbcServices().getDialect()).getMutateSqlAstTranslator(); - assertThat(mutateTranslator.getAffectedTableNames()).isEqualTo(expectedAffectedTableNames); + var mutationTranslator = ((MutationTranslatorAwareDialect) + session.getJdbcServices().getDialect()) + .getMutationSqlAstTranslator(); + assertThat(mutationTranslator.getAffectedTableNames()) + .containsExactlyInAnyOrderElementsOf(expectedAffectedTableNames); }); } - public static final class MutateTranslatorAwareDialect extends Dialect { + public static final class MutationTranslatorAwareDialect extends Dialect { private final Dialect delegate; - private SqlAstTranslator mutateSqlAstTranslator; + private SqlAstTranslator mutationSqlAstTranslator; - public MutateTranslatorAwareDialect(DialectResolutionInfo info) { + public MutationTranslatorAwareDialect(DialectResolutionInfo info) { super(info); delegate = new MongoDialect(info); } @@ -91,9 +93,9 @@ public SqlAstTranslator buildSelectTranslator( @Override public SqlAstTranslator buildMutationTranslator( SessionFactoryImplementor sessionFactory, MutationStatement statement) { - mutateSqlAstTranslator = + mutationSqlAstTranslator = delegate.getSqlAstTranslatorFactory().buildMutationTranslator(sessionFactory, statement); - return mutateSqlAstTranslator; + return mutationSqlAstTranslator; } @Override @@ -104,8 +106,8 @@ public SqlAstTranslator buildModelMutationT }; } - SqlAstTranslator getMutateSqlAstTranslator() { - return mutateSqlAstTranslator; + SqlAstTranslator getMutationSqlAstTranslator() { + return mutationSqlAstTranslator; } } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/DeletionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java similarity index 96% rename from src/integrationTest/java/com/mongodb/hibernate/query/mutate/DeletionIntegrationTests.java rename to src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java index b286ead9..de8cd471 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/DeletionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.hibernate.query.mutate; +package com.mongodb.hibernate.query.mutation; import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.junit.InjectMongoCollection; @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; @DomainModel(annotatedClasses = Book.class) -class DeletionIntegrationTests extends AbstractMutateQueryIntegrationTests { +class DeletionIntegrationTests extends AbstractMutationQueryIntegrationTests { @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; @@ -48,7 +48,7 @@ void beforeEach() { void testSimpleDeletion() { getSessionFactoryScope() .inTransaction( - session -> assertMutateQuery( + session -> assertMutationQuery( "delete from Book where title = :title", q -> q.setParameter("title", "War and Peace"), 2, diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/InsertionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java similarity index 96% rename from src/integrationTest/java/com/mongodb/hibernate/query/mutate/InsertionIntegrationTests.java rename to src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java index 30d42f30..9fbcb514 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/InsertionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.hibernate.query.mutate; +package com.mongodb.hibernate.query.mutation; import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.junit.InjectMongoCollection; @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; @DomainModel(annotatedClasses = Book.class) -class InsertionIntegrationTests extends AbstractMutateQueryIntegrationTests { +class InsertionIntegrationTests extends AbstractMutationQueryIntegrationTests { @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; @@ -40,7 +40,7 @@ void beforeEach() { void testInsertSingleDocument() { getSessionFactoryScope() .inTransaction( - session -> assertMutateQuery( + session -> assertMutationQuery( "insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) values (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD)", 1, """ @@ -79,7 +79,7 @@ void testInsertSingleDocument() { void testInsertMultipleDocuments() { getSessionFactoryScope() .inTransaction( - session -> assertMutateQuery( + session -> assertMutationQuery( """ insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) values diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/UpdatingIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java similarity index 97% rename from src/integrationTest/java/com/mongodb/hibernate/query/mutate/UpdatingIntegrationTests.java rename to src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java index 5c6fa20b..0d010c0d 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutate/UpdatingIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.mongodb.hibernate.query.mutate; +package com.mongodb.hibernate.query.mutation; import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.junit.InjectMongoCollection; @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; @DomainModel(annotatedClasses = Book.class) -class UpdatingIntegrationTests extends AbstractMutateQueryIntegrationTests { +class UpdatingIntegrationTests extends AbstractMutationQueryIntegrationTests { @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; @@ -48,7 +48,7 @@ void beforeEach() { void testSimpleUpdate() { getSessionFactoryScope() .inTransaction( - session -> assertMutateQuery( + session -> assertMutationQuery( "update Book set title = :newTitle where title = :oldTitle", q -> q.setParameter("oldTitle", "War & Peace") .setParameter("newTitle", "War and Peace"), diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index ffd59f07..335ed48b 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -341,7 +341,7 @@ public void visitSelectStatement(SelectStatement selectStatement) { if (!selectStatement.getQueryPart().isRoot()) { throw new FeatureNotSupportedException("Subquery not supported"); } - checkCteContainer(selectStatement); + checkCteContainerSupportability(selectStatement); selectStatement.getQueryPart().accept(this); } @@ -575,7 +575,7 @@ public void visitTuple(SqlTuple sqlTuple) { @Override public void visitDeleteStatement(DeleteStatement deleteStatement) { - checkMutationStatement(deleteStatement); + checkMutationStatementSupportability(deleteStatement); checkFromClauseSupportability(deleteStatement.getFromClause()); var collection = getMutationCollectionAfterRegisteredAsAffectedTable(deleteStatement); @@ -586,7 +586,7 @@ public void visitDeleteStatement(DeleteStatement deleteStatement) { @Override public void visitUpdateStatement(UpdateStatement updateStatement) { - checkMutationStatement(updateStatement); + checkMutationStatementSupportability(updateStatement); checkFromClauseSupportability(updateStatement.getFromClause()); var collection = getMutationCollectionAfterRegisteredAsAffectedTable(updateStatement); @@ -617,7 +617,7 @@ public void visitUpdateStatement(UpdateStatement updateStatement) { @Override public void visitInsertStatement(InsertSelectStatement insertStatement) { - checkMutationStatement(insertStatement); + checkMutationStatementSupportability(insertStatement); if (insertStatement.getConflictClause() != null) { throw new FeatureNotSupportedException(); } @@ -639,11 +639,12 @@ public void visitInsertStatement(InsertSelectStatement insertStatement) { var documents = new ArrayList(valuesList.size()); for (var values : valuesList) { - assertTrue(fieldNames.size() == values.getExpressions().size()); - var astElements = new ArrayList(values.getExpressions().size()); + var fieldValueExpressions = values.getExpressions(); + assertTrue(fieldNames.size() == fieldValueExpressions.size()); + var astElements = new ArrayList(fieldValueExpressions.size()); for (var i = 0; i < fieldNames.size(); i++) { var fieldName = fieldNames.get(i); - var fieldValueExpression = values.getExpressions().get(i); + var fieldValueExpression = fieldValueExpressions.get(i); if (!isValueExpression(fieldValueExpression)) { throw new FeatureNotSupportedException(); } @@ -1047,15 +1048,15 @@ private static BsonValue toBsonValue(@Nullable Object queryLiteral) { throw new FeatureNotSupportedException("Unsupported Java type: " + queryLiteral.getClass()); } - private static void checkCteContainer(CteContainer cteContainer) { + private static void checkCteContainerSupportability(CteContainer cteContainer) { if (!cteContainer.getCteStatements().isEmpty() || !cteContainer.getCteObjects().isEmpty()) { throw new FeatureNotSupportedException("CTE not supported"); } } - private static void checkMutationStatement(AbstractMutationStatement mutationStatement) { - checkCteContainer(mutationStatement); + private static void checkMutationStatementSupportability(AbstractMutationStatement mutationStatement) { + checkCteContainerSupportability(mutationStatement); if (!mutationStatement.getReturningColumns().isEmpty()) { throw new FeatureNotSupportedException(); } From 6558ab18e1528c450e01fcab07dccc0d4a8fc2ca Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Wed, 28 May 2025 11:43:53 -0400 Subject: [PATCH 06/19] some improvements --- .../query/AbstractQueryIntegrationTests.java | 16 +- .../com/mongodb/hibernate/query/Book.java | 8 +- ...AbstractMutationQueryIntegrationTests.java | 47 +-- .../mutation/DeletionIntegrationTests.java | 211 ++++++++----- .../mutation/InsertionIntegrationTests.java | 190 ++++++------ .../mutation/UpdatingIntegrationTests.java | 278 ++++++++++++------ ...ExpressionWhereClauseIntegrationTests.java | 31 +- .../translate/AbstractMqlTranslator.java | 69 ++--- .../translate/MutationMqlTranslator.java | 17 +- 9 files changed, 505 insertions(+), 362 deletions(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java index 02fa3f21..b1b876c4 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java @@ -85,7 +85,7 @@ protected void assertSelectionQuery( Class resultType, Consumer> queryPostProcessor, String expectedMql, - Consumer> resultListVerifier) { + Consumer> resultListVerifier) { sessionFactoryScope.inTransaction(session -> { var selectionQuery = session.createSelectionQuery(hql, resultType); if (queryPostProcessor != null) { @@ -100,7 +100,7 @@ protected void assertSelectionQuery( } protected void assertSelectionQuery( - String hql, Class resultType, String expectedMql, Consumer> resultListVerifier) { + String hql, Class resultType, String expectedMql, Consumer> resultListVerifier) { assertSelectionQuery(hql, resultType, null, expectedMql, resultListVerifier); } @@ -158,18 +158,10 @@ protected void assertMutationQuery( if (queryPostProcessor != null) { queryPostProcessor.accept(query); } - assertThat(query.executeUpdate()).isEqualTo(expectedMutationCount); + var mutationCount = query.executeUpdate(); assertActualCommand(BsonDocument.parse(expectedMql)); + assertThat(mutationCount).isEqualTo(expectedMutationCount); }); assertThat(collection.find()).containsExactlyElementsOf(expectedDocuments); } - - protected void assertMutationQuery( - String hql, - int expectedMutationCount, - String expectedMql, - MongoCollection collection, - Iterable expectedDocuments) { - assertMutationQuery(hql, null, expectedMutationCount, expectedMql, collection, expectedDocuments); - } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/Book.java b/src/integrationTest/java/com/mongodb/hibernate/query/Book.java index ab2b1427..8a1e0fa8 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/Book.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/Book.java @@ -22,9 +22,9 @@ import java.math.BigDecimal; @Entity(name = "Book") -@Table(name = Book.COLLECTION_NAME) +@Table(name = Book.COLLECTION) public class Book { - public static final String COLLECTION_NAME = "books"; + public static final String COLLECTION = "books"; @Id public int id; @@ -39,10 +39,6 @@ public class Book { public Book() {} - public Book(int id, String title, Integer publishYear) { - this(id, title, publishYear, false); - } - public Book(int id, String title, Integer publishYear, Boolean outOfStock) { this.id = id; this.title = title; diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java index 9fa5e073..8461ee3b 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java @@ -21,12 +21,9 @@ import com.mongodb.hibernate.dialect.MongoDialect; import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; -import java.util.Set; -import java.util.function.Consumer; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.query.MutationQuery; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.MutationStatement; @@ -39,37 +36,21 @@ import org.hibernate.testing.orm.junit.Setting; @ServiceRegistry( - settings = { - @Setting( - name = DIALECT, - value = - "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslatorAwareDialect"), - }) -class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { + settings = + @Setting( + name = DIALECT, + value = + "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslatorAwareDialect")) +public class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { - void assertAffectedTableNames(String hql, String expectedAffectedTableName) { - assertAffectedTableNames(hql, null, Set.of(expectedAffectedTableName)); - } - - void assertAffectedTableNames( - String hql, Consumer queryPostProcessor, String expectedAffectedTableName) { - assertAffectedTableNames(hql, queryPostProcessor, Set.of(expectedAffectedTableName)); - } - - void assertAffectedTableNames( - String hql, Consumer queryPostProcessor, Set expectedAffectedTableNames) { - getSessionFactoryScope().inTransaction(session -> { - var query = session.createMutationQuery(hql); - if (queryPostProcessor != null) { - queryPostProcessor.accept(query); - } - query.executeUpdate(); - var mutationTranslator = ((MutationTranslatorAwareDialect) - session.getJdbcServices().getDialect()) - .getMutationSqlAstTranslator(); - assertThat(mutationTranslator.getAffectedTableNames()) - .containsExactlyInAnyOrderElementsOf(expectedAffectedTableNames); - }); + protected void assertExpectedAffectedCollections(String... expectedAffectedfCollections) { + assertThat(((MutationTranslatorAwareDialect) getSessionFactoryScope() + .getSessionFactory() + .getJdbcServices() + .getDialect()) + .getMutationSqlAstTranslator() + .getAffectedTableNames()) + .containsExactlyInAnyOrder(expectedAffectedfCollections); } public static final class MutationTranslatorAwareDialect extends Dialect { diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java index de8cd471..fba94527 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java @@ -28,15 +28,15 @@ @DomainModel(annotatedClasses = Book.class) class DeletionIntegrationTests extends AbstractMutationQueryIntegrationTests { - @InjectMongoCollection(Book.COLLECTION_NAME) + @InjectMongoCollection(Book.COLLECTION) private static MongoCollection mongoCollection; private static final List testingBooks = List.of( - new Book(1, "War and Peace", 1869), - new Book(2, "Crime and Punishment", 1866), - new Book(3, "Anna Karenina", 1877), - new Book(4, "The Brothers Karamazov", 1880), - new Book(5, "War and Peace", 2025)); + new Book(1, "War and Peace", 1869, true), + new Book(2, "Crime and Punishment", 1866, false), + new Book(3, "Anna Karenina", 1877, false), + new Book(4, "The Brothers Karamazov", 1880, false), + new Book(5, "War and Peace", 2025, false)); @BeforeEach void beforeEach() { @@ -45,75 +45,150 @@ void beforeEach() { } @Test - void testSimpleDeletion() { - getSessionFactoryScope() - .inTransaction( - session -> assertMutationQuery( - "delete from Book where title = :title", - q -> q.setParameter("title", "War and Peace"), - 2, + void testDeletionWithNonZeroMutationCount() { + assertMutationQuery( + "delete from Book where title = :title", + q -> q.setParameter("title", "War and Peace"), + 2, + """ + { + "delete": "books", + "deletes": [ + { + "limit": 0, + "q": { + "title": { + "$eq": "War and Peace" + } + } + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( """ { - "delete": "books", - "deletes": [ - { - "limit": 0, - "q": { - "title": { - "$eq": "War and Peace" - } - } - } - ] + "_id": 2, + "title": "Crime and Punishment", + "outOfStock": false, + "publishYear": 1866, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} } - """, - mongoCollection, - List.of( - BsonDocument.parse( - """ - { - "_id": 2, - "title": "Crime and Punishment", - "outOfStock": false, - "publishYear": 1866, - "isbn13": {"$numberLong": "0"}, - "discount": {"$numberDouble": "0"}, - "price": {"$numberDecimal": "0.0"} - } - """), - BsonDocument.parse( - """ - { - "_id": 3, - "title": "Anna Karenina", - "outOfStock": false, - "publishYear": 1877, - "isbn13": {"$numberLong": "0"}, - "discount": {"$numberDouble": "0"}, - "price": {"$numberDecimal": "0.0"} - } - """), - BsonDocument.parse( - """ - { - "_id": 4, - "title": "The Brothers Karamazov", - "outOfStock": false, - "publishYear": 1880, - "isbn13": {"$numberLong": "0"}, - "discount": {"$numberDouble": "0"}, - "price": {"$numberDecimal": "0.0"} - } - """)))); + """), + BsonDocument.parse( + """ + { + "_id": 3, + "title": "Anna Karenina", + "outOfStock": false, + "publishYear": 1877, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 4, + "title": "The Brothers Karamazov", + "outOfStock": false, + "publishYear": 1880, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """))); + assertExpectedAffectedCollections(Book.COLLECTION); } @Test - void testAffectedTableNames() { - assertAffectedTableNames( + void testDeletionWithZeroMutationCount() { + assertMutationQuery( + "delete from Book where publishYear < :year", + q -> q.setParameter("year", 1850), + 0, """ - delete from Book where title = :title + { + "delete": "books", + "deletes": [ + { + "limit": 0, + "q": { + "publishYear": { + "$lt": 1850 + } + } + } + ] + } """, - q -> q.setParameter("title", "War and Peace"), - "books"); + mongoCollection, + List.of( + BsonDocument.parse( + """ + { + "_id": 1, + "title": "War and Peace", + "outOfStock": true, + "publishYear": 1869, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 2, + "title": "Crime and Punishment", + "outOfStock": false, + "publishYear": 1866, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 3, + "title": "Anna Karenina", + "outOfStock": false, + "publishYear": 1877, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 4, + "title": "The Brothers Karamazov", + "outOfStock": false, + "publishYear": 1880, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 5, + "title": "War and Peace", + "outOfStock": false, + "publishYear": 2025, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """))); + assertExpectedAffectedCollections(Book.COLLECTION); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java index 9fbcb514..2a9e76a2 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java @@ -28,7 +28,7 @@ @DomainModel(annotatedClasses = Book.class) class InsertionIntegrationTests extends AbstractMutationQueryIntegrationTests { - @InjectMongoCollection(Book.COLLECTION_NAME) + @InjectMongoCollection(Book.COLLECTION) private static MongoCollection mongoCollection; @BeforeEach @@ -38,115 +38,105 @@ void beforeEach() { @Test void testInsertSingleDocument() { - getSessionFactoryScope() - .inTransaction( - session -> assertMutationQuery( - "insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) values (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD)", - 1, + assertMutationQuery( + "insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) values (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD)", + null, + 1, + """ + { + "insert": "books", + "documents": [ + { + "_id": 1, + "title": "Pride & Prejudice", + "outOfStock": false, + "publishYear": 1813, + "isbn13": 9780141439518, + "discount": 0.2, + "price": {"$numberDecimal": "23.55"} + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( """ { - "insert": "books", - "documents": [ - { - "_id": 1, - "title": "Pride & Prejudice", - "outOfStock": false, - "publishYear": 1813, - "isbn13": 9780141439518, - "discount": 0.2, - "price": {"$numberDecimal": "23.55"} - } - ] + "_id": 1, + "title": "Pride & Prejudice", + "outOfStock": false, + "publishYear": 1813, + "isbn13": 9780141439518, + "discount": 0.2, + "price": {"$numberDecimal": "23.55"} } - """, - mongoCollection, - List.of( - BsonDocument.parse( - """ - { - "_id": 1, - "title": "Pride & Prejudice", - "outOfStock": false, - "publishYear": 1813, - "isbn13": 9780141439518, - "discount": 0.2, - "price": {"$numberDecimal": "23.55"} - } - """)))); + """))); + assertExpectedAffectedCollections(Book.COLLECTION); } @Test void testInsertMultipleDocuments() { - getSessionFactoryScope() - .inTransaction( - session -> assertMutationQuery( + assertMutationQuery( + """ + insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) + values + (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD), + (2, 'War & Peace', false, 1867, 9780143039990L, 0.1D, 19.99BD) + """, + null, + 2, + """ + { + "insert": "books", + "documents": [ + { + "_id": 1, + "title": "Pride & Prejudice", + "outOfStock": false, + "publishYear": 1813, + "isbn13": 9780141439518, + "discount": 0.2, + "price": {"$numberDecimal": "23.55"} + }, + { + "_id": 2, + "title": "War & Peace", + "outOfStock": false, + "publishYear": 1867, + "isbn13": 9780143039990, + "discount": 0.1, + "price": {"$numberDecimal": "19.99"} + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( """ - insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) - values - (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD), - (2, 'War & Peace', false, 1867, 9780143039990L, 0.1D, 19.99BD) - """, - 2, + { + "_id": 1, + "title": "Pride & Prejudice", + "outOfStock": false, + "publishYear": 1813, + "isbn13": 9780141439518, + "discount": 0.2, + "price": {"$numberDecimal": "23.55"} + } + """), + BsonDocument.parse( """ { - "insert": "books", - "documents": [ - { - "_id": 1, - "title": "Pride & Prejudice", - "outOfStock": false, - "publishYear": 1813, - "isbn13": 9780141439518, - "discount": 0.2, - "price": {"$numberDecimal": "23.55"} - }, - { - "_id": 2, - "title": "War & Peace", - "outOfStock": false, - "publishYear": 1867, - "isbn13": 9780143039990, - "discount": 0.1, - "price": {"$numberDecimal": "19.99"} - } - ] + "_id": 2, + "title": "War & Peace", + "outOfStock": false, + "publishYear": 1867, + "isbn13": 9780143039990, + "discount": 0.1, + "price": {"$numberDecimal": "19.99"} } - """, - mongoCollection, - List.of( - BsonDocument.parse( - """ - { - "_id": 1, - "title": "Pride & Prejudice", - "outOfStock": false, - "publishYear": 1813, - "isbn13": 9780141439518, - "discount": 0.2, - "price": {"$numberDecimal": "23.55"} - } - """), - BsonDocument.parse( - """ - { - "_id": 2, - "title": "War & Peace", - "outOfStock": false, - "publishYear": 1867, - "isbn13": 9780143039990, - "discount": 0.1, - "price": {"$numberDecimal": "19.99"} - } - """)))); - } - - @Test - void testAffectedTableNames() { - assertAffectedTableNames( - """ - insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) - values - (1, 'Pride & Prejudice', false, 1813, 9780141439518L, 0.2D, 23.55BD)""", - "books"); + """))); + assertExpectedAffectedCollections(Book.COLLECTION); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java index 0d010c0d..de23046f 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java @@ -28,15 +28,15 @@ @DomainModel(annotatedClasses = Book.class) class UpdatingIntegrationTests extends AbstractMutationQueryIntegrationTests { - @InjectMongoCollection(Book.COLLECTION_NAME) + @InjectMongoCollection(Book.COLLECTION) private static MongoCollection mongoCollection; private static final List testingBooks = List.of( - new Book(1, "War & Peace", 1869), - new Book(2, "Crime and Punishment", 1866), - new Book(3, "Anna Karenina", 1877), - new Book(4, "The Brothers Karamazov", 1880), - new Book(5, "War & Peace", 2025)); + new Book(1, "War & Peace", 1869, true), + new Book(2, "Crime and Punishment", 1866, false), + new Book(3, "Anna Karenina", 1877, false), + new Book(4, "The Brothers Karamazov", 1880, false), + new Book(5, "War & Peace", 2025, false)); @BeforeEach void beforeEach() { @@ -45,103 +45,185 @@ void beforeEach() { } @Test - void testSimpleUpdate() { - getSessionFactoryScope() - .inTransaction( - session -> assertMutationQuery( - "update Book set title = :newTitle where title = :oldTitle", - q -> q.setParameter("oldTitle", "War & Peace") - .setParameter("newTitle", "War and Peace"), - 2, + void testUpdateWithNonZeroMutationCount() { + assertMutationQuery( + "update Book set title = :newTitle, outOfStock = false where title = :oldTitle", + q -> q.setParameter("oldTitle", "War & Peace").setParameter("newTitle", "War and Peace"), + 2, + """ + { + "update": "books", + "updates": [ + { + "multi": true, + "q": { + "title": { + "$eq": "War & Peace" + } + }, + "u": { + "$set": { + "title": "War and Peace", + "outOfStock": false + } + } + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( + """ + { + "_id": 1, + "title": "War and Peace", + "outOfStock": false, + "publishYear": 1869, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( """ { - "update": "books", - "updates": [ - { - "multi": true, - "q": { - "title": { - "$eq": "War & Peace" - } - }, - "u": { - "$set": { - "title": "War and Peace" - } - } - } - ] + "_id": 2, + "title": "Crime and Punishment", + "outOfStock": false, + "publishYear": 1866, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} } - """, - mongoCollection, - List.of( - BsonDocument.parse( - """ - { - "_id": 1, - "title": "War and Peace", - "outOfStock": false, - "publishYear": 1869, - "isbn13": {"$numberLong": "0"}, - "discount": {"$numberDouble": "0"}, - "price": {"$numberDecimal": "0.0"} - } - """), - BsonDocument.parse( - """ - { - "_id": 2, - "title": "Crime and Punishment", - "outOfStock": false, - "publishYear": 1866, - "isbn13": {"$numberLong": "0"}, - "discount": {"$numberDouble": "0"}, - "price": {"$numberDecimal": "0.0"} - } - """), - BsonDocument.parse( - """ - { - "_id": 3, - "title": "Anna Karenina", - "outOfStock": false, - "publishYear": 1877, - "isbn13": {"$numberLong": "0"}, - "discount": {"$numberDouble": "0"}, - "price": {"$numberDecimal": "0.0"} - } - """), - BsonDocument.parse( - """ - { - "_id": 4, - "title": "The Brothers Karamazov", - "outOfStock": false, - "publishYear": 1880, - "isbn13": {"$numberLong": "0"}, - "discount": {"$numberDouble": "0"}, - "price": {"$numberDecimal": "0.0"} - } - """), - BsonDocument.parse( - """ - { - "_id": 5, - "title": "War and Peace", - "outOfStock": false, - "publishYear": 2025, - "isbn13": {"$numberLong": "0"}, - "discount": {"$numberDouble": "0"}, - "price": {"$numberDecimal": "0.0"} - } - """)))); + """), + BsonDocument.parse( + """ + { + "_id": 3, + "title": "Anna Karenina", + "outOfStock": false, + "publishYear": 1877, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 4, + "title": "The Brothers Karamazov", + "outOfStock": false, + "publishYear": 1880, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 5, + "title": "War and Peace", + "outOfStock": false, + "publishYear": 2025, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """))); + assertExpectedAffectedCollections(Book.COLLECTION); } @Test - void testAffectedTableNames() { - assertAffectedTableNames( - "update Book set title = :newTitle where title = :oldTitle", - q -> q.setParameter("oldTitle", "War & Peace").setParameter("newTitle", "War and Peace"), - "books"); + void testUpdateWithZeroMutationCount() { + assertMutationQuery( + "update Book set outOfStock = false where publishYear < :year", + q -> q.setParameter("year", 1850), + 0, + """ + { + "update": "books", + "updates": [ + { + "multi": true, + "q": { + "publishYear": { + "$lt": 1850 + } + }, + "u": { + "$set": { + "outOfStock": false + } + } + } + ] + } + """, + mongoCollection, + List.of( + BsonDocument.parse( + """ + { + "_id": 1, + "title": "War & Peace", + "outOfStock": true, + "publishYear": 1869, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 2, + "title": "Crime and Punishment", + "outOfStock": false, + "publishYear": 1866, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 3, + "title": "Anna Karenina", + "outOfStock": false, + "publishYear": 1877, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 4, + "title": "The Brothers Karamazov", + "outOfStock": false, + "publishYear": 1880, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """), + BsonDocument.parse( + """ + { + "_id": 5, + "title": "War & Peace", + "outOfStock": false, + "publishYear": 2025, + "isbn13": {"$numberLong": "0"}, + "discount": {"$numberDouble": "0"}, + "price": {"$numberDecimal": "0.0"} + } + """))); + assertExpectedAffectedCollections(Book.COLLECTION); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java index 0f0e544a..d7682b90 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java @@ -56,15 +56,38 @@ void testBooleanFieldPathExpression(boolean negated) { assertSelectionQuery( "from Book where" + (negated ? " not " : " ") + "outOfStock", Book.class, - "{'aggregate': 'books', 'pipeline': [{'$match': {'outOfStock': {'$eq': " - + (negated ? "false" : "true") - + "}}}, {'$project': {'_id': true, 'discount': true, 'isbn13': true, 'outOfStock': true, 'price': true, 'publishYear': true, 'title': true}}]}", + """ + { + "aggregate": "books", + "pipeline": [ + { + "$match": { + "outOfStock": { + "$eq": %s + } + } + }, + { + "$project": { + "_id": true, + "discount": true, + "isbn13": true, + "outOfStock": true, + "price": true, + "publishYear": true, + "title": true + } + } + ] + } + """ + .formatted(negated ? "false" : "true"), negated ? singletonList(bookInStock) : singletonList(bookOutOfStock)); } @ParameterizedTest @ValueSource(booleans = {true, false}) - void testNonFieldPathExpressionNotSupported(final boolean booleanLiteral) { + void testNonFieldPathExpressionNotSupported(boolean booleanLiteral) { assertSelectQueryFailure( "from Book where " + booleanLiteral, Book.class, diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index 0672f25e..30f4c73c 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -97,7 +97,7 @@ import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.AbstractMutationStatement; -import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.cte.CteContainer; @@ -572,43 +572,42 @@ public void visitTuple(SqlTuple sqlTuple) { @Override public void visitDeleteStatement(DeleteStatement deleteStatement) { checkMutationStatementSupportability(deleteStatement); - checkFromClauseSupportability(deleteStatement.getFromClause()); - - var collection = getMutationCollectionAfterRegisteredAsAffectedTable(deleteStatement); - var astFilter = acceptAndYield(deleteStatement.getRestriction(), FILTER); - - astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstDeleteCommand(collection, astFilter)); + var collectionAndAstFilter = getCollectionAndFilter(deleteStatement); + affectedTableNames.add(collectionAndAstFilter.collection); + astVisitorValueHolder.yield( + COLLECTION_MUTATION, + new AstDeleteCommand(collectionAndAstFilter.collection, collectionAndAstFilter.filter)); } @Override public void visitUpdateStatement(UpdateStatement updateStatement) { checkMutationStatementSupportability(updateStatement); - checkFromClauseSupportability(updateStatement.getFromClause()); - - var collection = getMutationCollectionAfterRegisteredAsAffectedTable(updateStatement); - var astFilter = acceptAndYield(updateStatement.getRestriction(), FILTER); + var collectionAndAstFilter = getCollectionAndFilter(updateStatement); + affectedTableNames.add(collectionAndAstFilter.collection); - var fieldUpdates = - new ArrayList(updateStatement.getAssignments().size()); - for (var assignment : updateStatement.getAssignments()) { + var assignments = updateStatement.getAssignments(); + var fieldUpdates = new ArrayList(assignments.size()); + for (var assignment : assignments) { var fieldReferences = assignment.getAssignable().getColumnReferences(); - if (fieldReferences.size() != 1) { - throw new FeatureNotSupportedException(); - } - var fieldPath = fieldReferences.get(0).getColumnReference().getColumnExpression(); - var assignedValue = assignment.getAssignedValue(); + assertTrue(fieldReferences.size() == 1); - var sqlTuple = SqlTupleContainer.getSqlTuple(assignedValue); - if (sqlTuple != null) { - throw new FeatureNotSupportedException(); - } + var fieldPath = acceptAndYield(fieldReferences.get(0), FIELD_PATH); + var assignedValue = assignment.getAssignedValue(); if (!isValueExpression(assignedValue)) { throw new FeatureNotSupportedException(); } var fieldValue = acceptAndYield(assignedValue, FIELD_VALUE); fieldUpdates.add(new AstFieldUpdate(fieldPath, fieldValue)); } - astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstUpdateCommand(collection, astFilter, fieldUpdates)); + astVisitorValueHolder.yield( + COLLECTION_MUTATION, + new AstUpdateCommand(collectionAndAstFilter.collection, collectionAndAstFilter.filter, fieldUpdates)); + } + + private CollectionAndFilter getCollectionAndFilter(AbstractUpdateOrDeleteStatement updateOrDeleteStatement) { + var collection = updateOrDeleteStatement.getTargetTable().getTableExpression(); + var astFilter = acceptAndYield(updateOrDeleteStatement.getRestriction(), FILTER); + return new CollectionAndFilter(collection, astFilter); } @Override @@ -621,7 +620,9 @@ public void visitInsertStatement(InsertSelectStatement insertStatement) { throw new FeatureNotSupportedException(); } - var collection = getMutationCollectionAfterRegisteredAsAffectedTable(insertStatement); + var collection = insertStatement.getTargetTable().getTableExpression(); + affectedTableNames.add(collection); + var fieldReferences = insertStatement.getTargetColumns(); assertFalse(fieldReferences.isEmpty()); @@ -653,12 +654,6 @@ public void visitInsertStatement(InsertSelectStatement insertStatement) { astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstInsertCommand(collection, documents)); } - private String getMutationCollectionAfterRegisteredAsAffectedTable(MutationStatement mutationStatement) { - var collection = mutationStatement.getTargetTable().getTableExpression(); - affectedTableNames.add(collection); - return collection; - } - @Override public void visitAssignment(Assignment assignment) { throw new FeatureNotSupportedException(); @@ -1039,15 +1034,21 @@ private static void checkMutationStatementSupportability(AbstractMutationStateme if (!mutationStatement.getReturningColumns().isEmpty()) { throw new FeatureNotSupportedException(); } + if (mutationStatement instanceof AbstractUpdateOrDeleteStatement updateOrDeleteStatement) { + checkFromClauseSupportability(updateOrDeleteStatement.getFromClause()); + } } private static void checkFromClauseSupportability(FromClause fromClause) { if (fromClause.getRoots().size() != 1) { - throw new FeatureNotSupportedException(); + throw new FeatureNotSupportedException("Only single root from clause is supported"); } - if (!(fromClause.getRoots().get(0).getModelPart() instanceof EntityPersister entityPersister) + var root = fromClause.getRoots().get(0); + if (!(root.getModelPart() instanceof EntityPersister entityPersister) || entityPersister.getQuerySpaces().length != 1) { - throw new FeatureNotSupportedException(); + throw new FeatureNotSupportedException("Only single table from clause is supported"); } } + + private record CollectionAndFilter(String collection, AstFilter filter) {} } diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java index da79c012..a6ad5253 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java @@ -16,11 +16,12 @@ package com.mongodb.hibernate.internal.translate; +import static com.mongodb.hibernate.internal.MongoAssertions.fail; import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_MUTATION; +import static java.lang.String.format; import static java.util.Collections.emptyMap; import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst; -import com.mongodb.hibernate.internal.FeatureNotSupportedException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.tree.MutationStatement; @@ -55,17 +56,19 @@ public JdbcOperationQueryMutation translate( var mutationCommand = acceptAndYield(mutationStatement, COLLECTION_MUTATION); var mql = renderMongoAstNode(mutationCommand); var parameterBinders = getParameterBinders(); - var affectedCollectionNames = getAffectedTableNames(); + var affectedCollections = getAffectedTableNames(); + // switch to Switch Pattern Matching when JDK is upgraded to 21+ if (mutationStatement instanceof InsertStatement) { - return new JdbcOperationQueryInsertImpl(mql, parameterBinders, affectedCollectionNames); + return new JdbcOperationQueryInsertImpl(mql, parameterBinders, affectedCollections); } else if (mutationStatement instanceof UpdateStatement) { - return new JdbcOperationQueryUpdate(mql, parameterBinders, affectedCollectionNames, emptyMap()); + return new JdbcOperationQueryUpdate(mql, parameterBinders, affectedCollections, emptyMap()); } else if (mutationStatement instanceof DeleteStatement) { - return new JdbcOperationQueryDelete(mql, parameterBinders, affectedCollectionNames, emptyMap()); + return new JdbcOperationQueryDelete(mql, parameterBinders, affectedCollections, emptyMap()); } else { - throw new FeatureNotSupportedException("Unsupported mutation statement type: " - + mutationStatement.getClass().getName()); + throw fail(format( + "Unexpected mutation statement type: %s", + mutationStatement.getClass().getName())); } } } From 164f4335fb4ab5e25a9f133a2e1d16bd160dfb3a Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Wed, 11 Jun 2025 16:41:22 -0400 Subject: [PATCH 07/19] validate affectedTableNames via translation result as opposed to translator --- ...AbstractMutationQueryIntegrationTests.java | 33 +++++++++++-------- .../translate/SelectMqlTranslatorTests.java | 2 +- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java index 8461ee3b..1d985a32 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java @@ -18,6 +18,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.cfg.JdbcSettings.DIALECT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; import com.mongodb.hibernate.dialect.MongoDialect; import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; @@ -28,36 +31,37 @@ import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.AbstractJdbcOperationQuery; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.model.ast.TableMutation; import org.hibernate.sql.model.jdbc.JdbcMutationOperation; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.Setting; +import org.mockito.stubbing.Answer; @ServiceRegistry( settings = @Setting( name = DIALECT, value = - "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslatorAwareDialect")) + "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslateResultAwareDialect")) public class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { protected void assertExpectedAffectedCollections(String... expectedAffectedfCollections) { - assertThat(((MutationTranslatorAwareDialect) getSessionFactoryScope() + assertThat(((MutationTranslateResultAwareDialect) getSessionFactoryScope() .getSessionFactory() .getJdbcServices() .getDialect()) - .getMutationSqlAstTranslator() - .getAffectedTableNames()) + .capturedTranslateResult.getAffectedTableNames()) .containsExactlyInAnyOrder(expectedAffectedfCollections); } - public static final class MutationTranslatorAwareDialect extends Dialect { + public static final class MutationTranslateResultAwareDialect extends Dialect { private final Dialect delegate; - private SqlAstTranslator mutationSqlAstTranslator; + private AbstractJdbcOperationQuery capturedTranslateResult; - public MutationTranslatorAwareDialect(DialectResolutionInfo info) { + public MutationTranslateResultAwareDialect(DialectResolutionInfo info) { super(info); delegate = new MongoDialect(info); } @@ -74,9 +78,16 @@ public SqlAstTranslator buildSelectTranslator( @Override public SqlAstTranslator buildMutationTranslator( SessionFactoryImplementor sessionFactory, MutationStatement statement) { - mutationSqlAstTranslator = + var originalTranslator = delegate.getSqlAstTranslatorFactory().buildMutationTranslator(sessionFactory, statement); - return mutationSqlAstTranslator; + var translatorSpy = spy(originalTranslator); + doAnswer((Answer) invocation -> { + capturedTranslateResult = (AbstractJdbcOperationQuery) invocation.callRealMethod(); + return capturedTranslateResult; + }) + .when(translatorSpy) + .translate(any(), any()); + return translatorSpy; } @Override @@ -86,9 +97,5 @@ public SqlAstTranslator buildModelMutationT } }; } - - SqlAstTranslator getMutationSqlAstTranslator() { - return mutationSqlAstTranslator; - } } } diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslatorTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslatorTests.java index 3e70c220..7975491e 100644 --- a/src/test/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslatorTests.java +++ b/src/test/java/com/mongodb/hibernate/internal/translate/SelectMqlTranslatorTests.java @@ -88,7 +88,7 @@ void testAffectedTableNames( var translator = new SelectMqlTranslator(sessionFactory, selectFromTableName); - translator.translate(null, QueryOptions.NONE); + translator.translate(null, QueryOptions.NONE).getAffectedTableNames(); assertThat(translator.getAffectedTableNames()).containsExactly(tableName); } From ed8eb065fb750a6da88f39bae163bac299616bb6 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:18:09 -0400 Subject: [PATCH 08/19] Update src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java Co-authored-by: Viacheslav Babanin --- .../query/mutation/AbstractMutationQueryIntegrationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java index 1d985a32..d8efaf02 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java @@ -46,7 +46,7 @@ name = DIALECT, value = "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslateResultAwareDialect")) -public class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { +public abstract class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { protected void assertExpectedAffectedCollections(String... expectedAffectedfCollections) { assertThat(((MutationTranslateResultAwareDialect) getSessionFactoryScope() From 3cd06f73c227121c699a926b5a1b8ba2af79e37c Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:22:23 -0400 Subject: [PATCH 09/19] Update src/integrationTest/java/com/mongodb/hibernate/query/Book.java Co-authored-by: Viacheslav Babanin --- src/integrationTest/java/com/mongodb/hibernate/query/Book.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/Book.java b/src/integrationTest/java/com/mongodb/hibernate/query/Book.java index 8a1e0fa8..506b1835 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/Book.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/Book.java @@ -24,7 +24,7 @@ @Entity(name = "Book") @Table(name = Book.COLLECTION) public class Book { - public static final String COLLECTION = "books"; + public static final String COLLECTION_NAME = "books"; @Id public int id; From 29b740fe9432dd0fc23e032980524476e1fa8eb6 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:29:13 -0400 Subject: [PATCH 10/19] fix compiling issues after the collection name constant in Book was changed in last commit --- .../java/com/mongodb/hibernate/query/Book.java | 2 +- .../hibernate/query/mutation/DeletionIntegrationTests.java | 6 +++--- .../hibernate/query/mutation/InsertionIntegrationTests.java | 6 +++--- .../hibernate/query/mutation/UpdatingIntegrationTests.java | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/Book.java b/src/integrationTest/java/com/mongodb/hibernate/query/Book.java index 506b1835..e4b9b3f8 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/Book.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/Book.java @@ -22,7 +22,7 @@ import java.math.BigDecimal; @Entity(name = "Book") -@Table(name = Book.COLLECTION) +@Table(name = Book.COLLECTION_NAME) public class Book { public static final String COLLECTION_NAME = "books"; diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java index fba94527..d9480e8c 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java @@ -28,7 +28,7 @@ @DomainModel(annotatedClasses = Book.class) class DeletionIntegrationTests extends AbstractMutationQueryIntegrationTests { - @InjectMongoCollection(Book.COLLECTION) + @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; private static final List testingBooks = List.of( @@ -103,7 +103,7 @@ void testDeletionWithNonZeroMutationCount() { "price": {"$numberDecimal": "0.0"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION); + assertExpectedAffectedCollections(Book.COLLECTION_NAME); } @Test @@ -189,6 +189,6 @@ void testDeletionWithZeroMutationCount() { "price": {"$numberDecimal": "0.0"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION); + assertExpectedAffectedCollections(Book.COLLECTION_NAME); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java index 2a9e76a2..cb12757f 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java @@ -28,7 +28,7 @@ @DomainModel(annotatedClasses = Book.class) class InsertionIntegrationTests extends AbstractMutationQueryIntegrationTests { - @InjectMongoCollection(Book.COLLECTION) + @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; @BeforeEach @@ -72,7 +72,7 @@ void testInsertSingleDocument() { "price": {"$numberDecimal": "23.55"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION); + assertExpectedAffectedCollections(Book.COLLECTION_NAME); } @Test @@ -137,6 +137,6 @@ insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) "price": {"$numberDecimal": "19.99"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION); + assertExpectedAffectedCollections(Book.COLLECTION_NAME); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java index de23046f..91e3160c 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java @@ -28,7 +28,7 @@ @DomainModel(annotatedClasses = Book.class) class UpdatingIntegrationTests extends AbstractMutationQueryIntegrationTests { - @InjectMongoCollection(Book.COLLECTION) + @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; private static final List testingBooks = List.of( @@ -133,7 +133,7 @@ void testUpdateWithNonZeroMutationCount() { "price": {"$numberDecimal": "0.0"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION); + assertExpectedAffectedCollections(Book.COLLECTION_NAME); } @Test @@ -224,6 +224,6 @@ void testUpdateWithZeroMutationCount() { "price": {"$numberDecimal": "0.0"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION); + assertExpectedAffectedCollections(Book.COLLECTION_NAME); } } From 8e11fa98827b6a99929896e79eaf26b6e987025a Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:35:42 -0400 Subject: [PATCH 11/19] Update src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java Co-authored-by: Viacheslav Babanin --- .../query/mutation/AbstractMutationQueryIntegrationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java index d8efaf02..b664ffa9 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java @@ -48,7 +48,7 @@ "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslateResultAwareDialect")) public abstract class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { - protected void assertExpectedAffectedCollections(String... expectedAffectedfCollections) { + protected void assertExpectedAffectedCollections(String... expectedAffectedCollections) { assertThat(((MutationTranslateResultAwareDialect) getSessionFactoryScope() .getSessionFactory() .getJdbcServices() From 41b1f1078e54736b97eea60a7329637d0d446070 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:40:46 -0400 Subject: [PATCH 12/19] Update src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java Co-authored-by: Viacheslav Babanin --- .../query/mutation/AbstractMutationQueryIntegrationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java index b664ffa9..9a2eacc7 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java @@ -57,7 +57,7 @@ protected void assertExpectedAffectedCollections(String... expectedAffectedColle .containsExactlyInAnyOrder(expectedAffectedfCollections); } - public static final class MutationTranslateResultAwareDialect extends Dialect { + protected static final class MutationTranslateResultAwareDialect extends Dialect { private final Dialect delegate; private AbstractJdbcOperationQuery capturedTranslateResult; From 5217cc93c2a6c651edca024a02380bf4a2285e02 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:43:56 -0400 Subject: [PATCH 13/19] fix compiling issue since last suggestion commit; rename `assertExpectedAffectedCollections` to `assertAffectedCollections` --- .../query/mutation/AbstractMutationQueryIntegrationTests.java | 4 ++-- .../hibernate/query/mutation/DeletionIntegrationTests.java | 4 ++-- .../hibernate/query/mutation/InsertionIntegrationTests.java | 4 ++-- .../hibernate/query/mutation/UpdatingIntegrationTests.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java index 9a2eacc7..93d398b9 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java @@ -48,13 +48,13 @@ "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslateResultAwareDialect")) public abstract class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { - protected void assertExpectedAffectedCollections(String... expectedAffectedCollections) { + protected void assertAffectedCollections(String... expectedAffectedCollections) { assertThat(((MutationTranslateResultAwareDialect) getSessionFactoryScope() .getSessionFactory() .getJdbcServices() .getDialect()) .capturedTranslateResult.getAffectedTableNames()) - .containsExactlyInAnyOrder(expectedAffectedfCollections); + .containsExactlyInAnyOrder(expectedAffectedCollections); } protected static final class MutationTranslateResultAwareDialect extends Dialect { diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java index d9480e8c..66f4e5b3 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java @@ -103,7 +103,7 @@ void testDeletionWithNonZeroMutationCount() { "price": {"$numberDecimal": "0.0"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION_NAME); + assertAffectedCollections(Book.COLLECTION_NAME); } @Test @@ -189,6 +189,6 @@ void testDeletionWithZeroMutationCount() { "price": {"$numberDecimal": "0.0"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION_NAME); + assertAffectedCollections(Book.COLLECTION_NAME); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java index cb12757f..6d738622 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java @@ -72,7 +72,7 @@ void testInsertSingleDocument() { "price": {"$numberDecimal": "23.55"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION_NAME); + assertAffectedCollections(Book.COLLECTION_NAME); } @Test @@ -137,6 +137,6 @@ insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) "price": {"$numberDecimal": "19.99"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION_NAME); + assertAffectedCollections(Book.COLLECTION_NAME); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java index 91e3160c..323699ab 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java @@ -133,7 +133,7 @@ void testUpdateWithNonZeroMutationCount() { "price": {"$numberDecimal": "0.0"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION_NAME); + assertAffectedCollections(Book.COLLECTION_NAME); } @Test @@ -224,6 +224,6 @@ void testUpdateWithZeroMutationCount() { "price": {"$numberDecimal": "0.0"} } """))); - assertExpectedAffectedCollections(Book.COLLECTION_NAME); + assertAffectedCollections(Book.COLLECTION_NAME); } } From f6bd3531cc8088cf734d7c3297b07ec2b255c13a Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:47:33 -0400 Subject: [PATCH 14/19] add description for FeatureNotSupportedException for returning columns from mutation statement --- .../hibernate/internal/translate/AbstractMqlTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index 30f4c73c..8e50850b 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -1032,7 +1032,7 @@ private static void checkCteContainerSupportability(CteContainer cteContainer) { private static void checkMutationStatementSupportability(AbstractMutationStatement mutationStatement) { checkCteContainerSupportability(mutationStatement); if (!mutationStatement.getReturningColumns().isEmpty()) { - throw new FeatureNotSupportedException(); + throw new FeatureNotSupportedException("Returning columns from mutation statements not supported"); } if (mutationStatement instanceof AbstractUpdateOrDeleteStatement updateOrDeleteStatement) { checkFromClauseSupportability(updateOrDeleteStatement.getFromClause()); From 36a7d267185614819d0983f051e2530d70679e65 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:55:53 -0400 Subject: [PATCH 15/19] add FeatureNotSupportedException when table joining is found --- .../hibernate/internal/translate/AbstractMqlTranslator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index 8e50850b..9379aa7b 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -1044,6 +1044,9 @@ private static void checkFromClauseSupportability(FromClause fromClause) { throw new FeatureNotSupportedException("Only single root from clause is supported"); } var root = fromClause.getRoots().get(0); + if (root.hasRealJoins()) { + throw new FeatureNotSupportedException("TODO-HIBERNATE-65 https://jira.mongodb.org/browse/HIBERNATE-65"); + } if (!(root.getModelPart() instanceof EntityPersister entityPersister) || entityPersister.getQuerySpaces().length != 1) { throw new FeatureNotSupportedException("Only single table from clause is supported"); From 221ced33e28a8b65017f64089301b769db771aec Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 24 Jun 2025 09:59:14 -0400 Subject: [PATCH 16/19] add descriptive messages to unsupported features in insertion statement --- .../hibernate/internal/translate/AbstractMqlTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index 9379aa7b..dc382237 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -614,10 +614,10 @@ private CollectionAndFilter getCollectionAndFilter(AbstractUpdateOrDeleteStateme public void visitInsertStatement(InsertSelectStatement insertStatement) { checkMutationStatementSupportability(insertStatement); if (insertStatement.getConflictClause() != null) { - throw new FeatureNotSupportedException(); + throw new FeatureNotSupportedException("Conflict clause in insert statement not supported"); } if (insertStatement.getSourceSelectStatement() != null) { - throw new FeatureNotSupportedException(); + throw new FeatureNotSupportedException("Insertion statement with source selection not supported"); } var collection = insertStatement.getTargetTable().getTableExpression(); From 826df65ce8cda132a1ecd3abdbdede27e1f8b8f3 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Wed, 25 Jun 2025 09:55:50 -0400 Subject: [PATCH 17/19] add disabled testing case to track unique constraint violation exception is thrown in InsertionIntegrationTests --- .../query/AbstractQueryIntegrationTests.java | 17 +++++++++++++++++ .../mutation/InsertionIntegrationTests.java | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java index b1b876c4..f01a1a59 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java @@ -164,4 +164,21 @@ protected void assertMutationQuery( }); assertThat(collection.find()).containsExactlyElementsOf(expectedDocuments); } + + protected void assertMutationQueryFailure( + String hql, + Consumer queryPostProcessor, + Class expectedExceptionType, + String expectedExceptionMessage, + Object... expectedExceptionMessageParameters) { + sessionFactoryScope.inTransaction(session -> assertThatThrownBy(() -> { + var query = session.createMutationQuery(hql); + if (queryPostProcessor != null) { + queryPostProcessor.accept(query); + } + query.executeUpdate(); + }) + .isInstanceOf(expectedExceptionType) + .hasMessage(expectedExceptionMessage, expectedExceptionMessageParameters)); + } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java index 6d738622..2d3a6c3c 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java @@ -21,8 +21,10 @@ import com.mongodb.hibernate.query.Book; import java.util.List; 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) @@ -139,4 +141,18 @@ insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) """))); assertAffectedCollections(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"); + } } From 6eb657824b3f08dd142cdf3c7592833b42a065ed Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Mon, 30 Jun 2025 09:23:27 -0400 Subject: [PATCH 18/19] add affectedTableNames generic validation covering both new DML and the old selection queries testing caeses --- .../query/AbstractQueryIntegrationTests.java | 115 ++++++++++++++++-- ...AbstractMutationQueryIntegrationTests.java | 101 --------------- .../mutation/DeletionIntegrationTests.java | 12 +- .../mutation/InsertionIntegrationTests.java | 12 +- .../mutation/UpdatingIntegrationTests.java | 14 ++- ...ExpressionWhereClauseIntegrationTests.java | 6 +- .../SimpleSelectQueryIntegrationTests.java | 70 +++++++---- .../SortingSelectQueryIntegrationTests.java | 27 ++-- 8 files changed, 198 insertions(+), 159 deletions(-) delete mode 100644 src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java index f01a1a59..955a6737 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/AbstractQueryIntegrationTests.java @@ -19,23 +19,51 @@ import static com.mongodb.hibernate.MongoTestAssertions.assertIterableEq; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hibernate.cfg.JdbcSettings.DIALECT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.TestCommandListener; +import com.mongodb.hibernate.dialect.MongoDialect; import com.mongodb.hibernate.junit.MongoExtension; +import java.util.Set; import java.util.function.Consumer; import org.assertj.core.api.InstanceOfAssertFactories; import org.bson.BsonDocument; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.MutationQuery; import org.hibernate.query.SelectionQuery; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.AbstractJdbcOperationQuery; +import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.model.ast.TableMutation; +import org.hibernate.sql.model.jdbc.JdbcMutationOperation; +import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.ServiceRegistryScope; import org.hibernate.testing.orm.junit.ServiceRegistryScopeAware; 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.extension.ExtendWith; +import org.mockito.stubbing.Answer; @SessionFactory(exportSchema = false) +@ServiceRegistry( + settings = + @Setting( + name = DIALECT, + value = + "com.mongodb.hibernate.query.AbstractQueryIntegrationTests$TranslateResultAwareDialect")) @ExtendWith(MongoExtension.class) public abstract class AbstractQueryIntegrationTests implements SessionFactoryScopeAware, ServiceRegistryScopeAware { @@ -66,18 +94,24 @@ protected void assertSelectionQuery( Class resultType, Consumer> queryPostProcessor, String expectedMql, - Iterable expectedResultList) { + Iterable expectedResultList, + Set expectedAffectedCollections) { assertSelectionQuery( hql, resultType, queryPostProcessor, expectedMql, - resultList -> assertIterableEq(expectedResultList, resultList)); + resultList -> assertIterableEq(expectedResultList, resultList), + expectedAffectedCollections); } protected void assertSelectionQuery( - String hql, Class resultType, String expectedMql, Iterable expectedResultList) { - assertSelectionQuery(hql, resultType, null, expectedMql, expectedResultList); + String hql, + Class resultType, + String expectedMql, + Iterable expectedResultList, + Set expectedAffectedCollections) { + assertSelectionQuery(hql, resultType, null, expectedMql, expectedResultList, expectedAffectedCollections); } protected void assertSelectionQuery( @@ -85,7 +119,8 @@ protected void assertSelectionQuery( Class resultType, Consumer> queryPostProcessor, String expectedMql, - Consumer> resultListVerifier) { + Consumer> resultListVerifier, + Set expectedAffectedCollections) { sessionFactoryScope.inTransaction(session -> { var selectionQuery = session.createSelectionQuery(hql, resultType); if (queryPostProcessor != null) { @@ -96,12 +131,18 @@ protected void assertSelectionQuery( assertActualCommand(BsonDocument.parse(expectedMql)); resultListVerifier.accept(resultList); + + assertAffectedCollections(expectedAffectedCollections); }); } protected void assertSelectionQuery( - String hql, Class resultType, String expectedMql, Consumer> resultListVerifier) { - assertSelectionQuery(hql, resultType, null, expectedMql, resultListVerifier); + String hql, + Class resultType, + String expectedMql, + Consumer> resultListVerifier, + Set expectedAffectedCollections) { + assertSelectionQuery(hql, resultType, null, expectedMql, resultListVerifier, expectedAffectedCollections); } protected void assertSelectQueryFailure( @@ -152,7 +193,8 @@ protected void assertMutationQuery( int expectedMutationCount, String expectedMql, MongoCollection collection, - Iterable expectedDocuments) { + Iterable expectedDocuments, + Set expectedAffectedCollections) { sessionFactoryScope.inTransaction(session -> { var query = session.createMutationQuery(hql); if (queryPostProcessor != null) { @@ -163,6 +205,7 @@ protected void assertMutationQuery( assertThat(mutationCount).isEqualTo(expectedMutationCount); }); assertThat(collection.find()).containsExactlyElementsOf(expectedDocuments); + assertAffectedCollections(expectedAffectedCollections); } protected void assertMutationQueryFailure( @@ -181,4 +224,60 @@ protected void assertMutationQueryFailure( .isInstanceOf(expectedExceptionType) .hasMessage(expectedExceptionMessage, expectedExceptionMessageParameters)); } + + private void assertAffectedCollections(Set expectedAffectedCollections) { + assertThat(((TranslateResultAwareDialect) getSessionFactoryScope() + .getSessionFactory() + .getJdbcServices() + .getDialect()) + .capturedTranslateResult.getAffectedTableNames()) + .containsExactlyInAnyOrderElementsOf(expectedAffectedCollections); + } + + protected static final class TranslateResultAwareDialect extends Dialect { + private final Dialect delegate; + private AbstractJdbcOperationQuery capturedTranslateResult; + + public TranslateResultAwareDialect(DialectResolutionInfo info) { + super(info); + delegate = new MongoDialect(info); + } + + @Override + public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { + return new SqlAstTranslatorFactory() { + @Override + public SqlAstTranslator buildSelectTranslator( + SessionFactoryImplementor sessionFactory, SelectStatement statement) { + return createCapturingTranslator( + delegate.getSqlAstTranslatorFactory().buildSelectTranslator(sessionFactory, statement)); + } + + @Override + public SqlAstTranslator buildMutationTranslator( + SessionFactoryImplementor sessionFactory, MutationStatement statement) { + return createCapturingTranslator( + delegate.getSqlAstTranslatorFactory().buildMutationTranslator(sessionFactory, statement)); + } + + @Override + public SqlAstTranslator buildModelMutationTranslator( + TableMutation mutation, SessionFactoryImplementor sessionFactory) { + return delegate.getSqlAstTranslatorFactory().buildModelMutationTranslator(mutation, sessionFactory); + } + + private SqlAstTranslator createCapturingTranslator( + SqlAstTranslator originalTranslator) { + var translatorSpy = spy(originalTranslator); + doAnswer((Answer) invocation -> { + capturedTranslateResult = (AbstractJdbcOperationQuery) invocation.callRealMethod(); + return capturedTranslateResult; + }) + .when(translatorSpy) + .translate(any(), any()); + return translatorSpy; + } + }; + } + } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java deleted file mode 100644 index 93d398b9..00000000 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/AbstractMutationQueryIntegrationTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.query.mutation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hibernate.cfg.JdbcSettings.DIALECT; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.spy; - -import com.mongodb.hibernate.dialect.MongoDialect; -import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.spi.AbstractJdbcOperationQuery; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; -import org.hibernate.sql.model.ast.TableMutation; -import org.hibernate.sql.model.jdbc.JdbcMutationOperation; -import org.hibernate.testing.orm.junit.ServiceRegistry; -import org.hibernate.testing.orm.junit.Setting; -import org.mockito.stubbing.Answer; - -@ServiceRegistry( - settings = - @Setting( - name = DIALECT, - value = - "com.mongodb.hibernate.query.mutation.AbstractMutationQueryIntegrationTests$MutationTranslateResultAwareDialect")) -public abstract class AbstractMutationQueryIntegrationTests extends AbstractQueryIntegrationTests { - - protected void assertAffectedCollections(String... expectedAffectedCollections) { - assertThat(((MutationTranslateResultAwareDialect) getSessionFactoryScope() - .getSessionFactory() - .getJdbcServices() - .getDialect()) - .capturedTranslateResult.getAffectedTableNames()) - .containsExactlyInAnyOrder(expectedAffectedCollections); - } - - protected static final class MutationTranslateResultAwareDialect extends Dialect { - private final Dialect delegate; - private AbstractJdbcOperationQuery capturedTranslateResult; - - public MutationTranslateResultAwareDialect(DialectResolutionInfo info) { - super(info); - delegate = new MongoDialect(info); - } - - @Override - public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { - return new SqlAstTranslatorFactory() { - @Override - public SqlAstTranslator buildSelectTranslator( - SessionFactoryImplementor sessionFactory, SelectStatement statement) { - return delegate.getSqlAstTranslatorFactory().buildSelectTranslator(sessionFactory, statement); - } - - @Override - public SqlAstTranslator buildMutationTranslator( - SessionFactoryImplementor sessionFactory, MutationStatement statement) { - var originalTranslator = - delegate.getSqlAstTranslatorFactory().buildMutationTranslator(sessionFactory, statement); - var translatorSpy = spy(originalTranslator); - doAnswer((Answer) invocation -> { - capturedTranslateResult = (AbstractJdbcOperationQuery) invocation.callRealMethod(); - return capturedTranslateResult; - }) - .when(translatorSpy) - .translate(any(), any()); - return translatorSpy; - } - - @Override - public SqlAstTranslator buildModelMutationTranslator( - TableMutation mutation, SessionFactoryImplementor sessionFactory) { - return delegate.getSqlAstTranslatorFactory().buildModelMutationTranslator(mutation, sessionFactory); - } - }; - } - } -} diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java index 66f4e5b3..906966a5 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/DeletionIntegrationTests.java @@ -18,15 +18,17 @@ import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.junit.InjectMongoCollection; +import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; import com.mongodb.hibernate.query.Book; import java.util.List; +import java.util.Set; import org.bson.BsonDocument; import org.hibernate.testing.orm.junit.DomainModel; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @DomainModel(annotatedClasses = Book.class) -class DeletionIntegrationTests extends AbstractMutationQueryIntegrationTests { +class DeletionIntegrationTests extends AbstractQueryIntegrationTests { @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; @@ -102,8 +104,8 @@ void testDeletionWithNonZeroMutationCount() { "discount": {"$numberDouble": "0"}, "price": {"$numberDecimal": "0.0"} } - """))); - assertAffectedCollections(Book.COLLECTION_NAME); + """)), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -188,7 +190,7 @@ void testDeletionWithZeroMutationCount() { "discount": {"$numberDouble": "0"}, "price": {"$numberDecimal": "0.0"} } - """))); - assertAffectedCollections(Book.COLLECTION_NAME); + """)), + Set.of(Book.COLLECTION_NAME)); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java index 2d3a6c3c..8ce0a724 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/InsertionIntegrationTests.java @@ -18,8 +18,10 @@ import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.junit.InjectMongoCollection; +import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; import com.mongodb.hibernate.query.Book; import java.util.List; +import java.util.Set; import org.bson.BsonDocument; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.testing.orm.junit.DomainModel; @@ -28,7 +30,7 @@ import org.junit.jupiter.api.Test; @DomainModel(annotatedClasses = Book.class) -class InsertionIntegrationTests extends AbstractMutationQueryIntegrationTests { +class InsertionIntegrationTests extends AbstractQueryIntegrationTests { @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; @@ -73,8 +75,8 @@ void testInsertSingleDocument() { "discount": 0.2, "price": {"$numberDecimal": "23.55"} } - """))); - assertAffectedCollections(Book.COLLECTION_NAME); + """)), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -138,8 +140,8 @@ insert into Book (id, title, outOfStock, publishYear, isbn13, discount, price) "discount": 0.1, "price": {"$numberDecimal": "19.99"} } - """))); - assertAffectedCollections(Book.COLLECTION_NAME); + """)), + Set.of(Book.COLLECTION_NAME)); } @Test diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java index 323699ab..b1a4d44b 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/mutation/UpdatingIntegrationTests.java @@ -18,15 +18,17 @@ import com.mongodb.client.MongoCollection; import com.mongodb.hibernate.junit.InjectMongoCollection; +import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; import com.mongodb.hibernate.query.Book; import java.util.List; +import java.util.Set; import org.bson.BsonDocument; import org.hibernate.testing.orm.junit.DomainModel; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @DomainModel(annotatedClasses = Book.class) -class UpdatingIntegrationTests extends AbstractMutationQueryIntegrationTests { +class UpdatingIntegrationTests extends AbstractQueryIntegrationTests { @InjectMongoCollection(Book.COLLECTION_NAME) private static MongoCollection mongoCollection; @@ -39,7 +41,7 @@ class UpdatingIntegrationTests extends AbstractMutationQueryIntegrationTests { new Book(5, "War & Peace", 2025, false)); @BeforeEach - void beforeEach() { + protected void beforeEach() { getSessionFactoryScope().inTransaction(session -> testingBooks.forEach(session::persist)); getTestCommandListener().clear(); } @@ -132,8 +134,8 @@ void testUpdateWithNonZeroMutationCount() { "discount": {"$numberDouble": "0"}, "price": {"$numberDecimal": "0.0"} } - """))); - assertAffectedCollections(Book.COLLECTION_NAME); + """)), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -223,7 +225,7 @@ void testUpdateWithZeroMutationCount() { "discount": {"$numberDouble": "0"}, "price": {"$numberDecimal": "0.0"} } - """))); - assertAffectedCollections(Book.COLLECTION_NAME); + """)), + Set.of(Book.COLLECTION_NAME)); } } diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java index d7682b90..1e42d6cf 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/BooleanExpressionWhereClauseIntegrationTests.java @@ -21,6 +21,7 @@ import com.mongodb.hibernate.internal.FeatureNotSupportedException; import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; import com.mongodb.hibernate.query.Book; +import java.util.Set; import org.hibernate.testing.orm.junit.DomainModel; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; @@ -33,7 +34,7 @@ class BooleanExpressionWhereClauseIntegrationTests extends AbstractQueryIntegrat private Book bookInStock; @BeforeEach - void beforeEach() { + protected void beforeEach() { bookOutOfStock = new Book(); bookOutOfStock.id = 1; bookOutOfStock.outOfStock = true; @@ -82,7 +83,8 @@ void testBooleanFieldPathExpression(boolean negated) { } """ .formatted(negated ? "false" : "true"), - negated ? singletonList(bookInStock) : singletonList(bookOutOfStock)); + negated ? singletonList(bookInStock) : singletonList(bookOutOfStock), + Set.of(Book.COLLECTION_NAME)); } @ParameterizedTest diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/SimpleSelectQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/SimpleSelectQueryIntegrationTests.java index f81cf810..ab098c6c 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/SimpleSelectQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/SimpleSelectQueryIntegrationTests.java @@ -16,7 +16,6 @@ package com.mongodb.hibernate.query.select; -import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThatCode; import com.mongodb.hibernate.internal.FeatureNotSupportedException; @@ -28,6 +27,7 @@ import java.math.BigDecimal; import java.util.Arrays; import java.util.List; +import java.util.Set; import org.hibernate.query.SemanticException; import org.hibernate.testing.orm.junit.DomainModel; import org.junit.jupiter.api.BeforeEach; @@ -92,7 +92,8 @@ void testComparisonByEq(boolean fieldAsLhs) { } ] }""", - getTestingContacts(1, 5)); + getTestingContacts(1, 5), + Set.of(Contact.COLLECTION_NAME)); } @ParameterizedTest @@ -123,7 +124,8 @@ void testComparisonByNe(boolean fieldAsLhs) { } ] }""", - getTestingContacts(2, 3, 4)); + getTestingContacts(2, 3, 4), + Set.of(Contact.COLLECTION_NAME)); } @ParameterizedTest @@ -154,7 +156,8 @@ void testComparisonByLt(boolean fieldAsLhs) { } ] }""", - getTestingContacts(1, 3, 5)); + getTestingContacts(1, 3, 5), + Set.of(Contact.COLLECTION_NAME)); } @ParameterizedTest @@ -185,7 +188,8 @@ void testComparisonByLte(boolean fieldAsLhs) { } ] }""", - getTestingContacts(1, 2, 3, 5)); + getTestingContacts(1, 2, 3, 5), + Set.of(Contact.COLLECTION_NAME)); } @ParameterizedTest @@ -216,7 +220,8 @@ void testComparisonByGt(boolean fieldAsLhs) { } ] }""", - getTestingContacts(2, 4, 5)); + getTestingContacts(2, 4, 5), + Set.of(Contact.COLLECTION_NAME)); } @ParameterizedTest @@ -247,7 +252,8 @@ void testComparisonByGte(boolean fieldAsLhs) { } ] }""", - getTestingContacts(1, 2, 4, 5)); + getTestingContacts(1, 2, 4, 5), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -286,7 +292,8 @@ void testAndFilter() { } ] }""", - getTestingContacts(2, 4)); + getTestingContacts(2, 4), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -325,7 +332,8 @@ void testOrFilter() { } ] }""", - getTestingContacts(2, 3, 4, 5)); + getTestingContacts(2, 3, 4, 5), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -367,7 +375,8 @@ void testSingleNegation() { } ] }""", - getTestingContacts(2, 4)); + getTestingContacts(2, 4), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -411,7 +420,8 @@ void testSingleNegationWithAnd() { } ] }""", - getTestingContacts(1, 2, 3, 4)); + getTestingContacts(1, 2, 3, 4), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -455,7 +465,8 @@ void testSingleNegationWithOr() { } ] }""", - getTestingContacts(3)); + getTestingContacts(3), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -510,7 +521,8 @@ void testSingleNegationWithAndOr() { } ] }""", - getTestingContacts(2, 4)); + getTestingContacts(2, 4), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -556,7 +568,8 @@ void testDoubleNegation() { } ] }""", - getTestingContacts(5)); + getTestingContacts(5), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -584,7 +597,8 @@ void testProjectWithoutAlias() { } ] }""", - List.of(new Object[] {"Mary", 35}, new Object[] {"Dylan", 7}, new Object[] {"Lucy", 78})); + List.of(new Object[] {"Mary", 35}, new Object[] {"Dylan", 7}, new Object[] {"Lucy", 78}), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -612,7 +626,8 @@ void testProjectUsingAlias() { } ] }""", - List.of(new Object[] {"Mary", 35}, new Object[] {"Dylan", 7}, new Object[] {"Lucy", 78})); + List.of(new Object[] {"Mary", 35}, new Object[] {"Dylan", 7}, new Object[] {"Lucy", 78}), + Set.of(Contact.COLLECTION_NAME)); } @Test @@ -743,7 +758,8 @@ void testBoolean() { } ] }""", - singletonList(testingBook)); + List.of(testingBook), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -775,7 +791,8 @@ void testInteger() { } ] }""", - singletonList(testingBook)); + List.of(testingBook), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -807,7 +824,8 @@ void testLong() { } ] }""", - singletonList(testingBook)); + List.of(testingBook), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -839,7 +857,8 @@ void testDouble() { } ] }""", - singletonList(testingBook)); + List.of(testingBook), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -871,7 +890,8 @@ void testString() { } ] }""", - singletonList(testingBook)); + List.of(testingBook), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -905,13 +925,17 @@ void testBigDecimal() { } ] }""", - singletonList(testingBook)); + List.of(testingBook), + Set.of(Book.COLLECTION_NAME)); } } @Entity(name = "Contact") - @Table(name = "contacts") + @Table(name = Contact.COLLECTION_NAME) static class Contact { + + static final String COLLECTION_NAME = "contacts"; + @Id int id; diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java index bd48ad4c..75dfd9a1 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/SortingSelectQueryIntegrationTests.java @@ -29,6 +29,7 @@ import com.mongodb.hibernate.query.Book; import java.util.Arrays; import java.util.List; +import java.util.Set; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.Setting; @@ -58,7 +59,7 @@ private static List getBooksByIds(int... ids) { } @BeforeEach - void beforeEach() { + protected void beforeEach() { getSessionFactoryScope().inTransaction(session -> testingBooks.forEach(session::persist)); getTestCommandListener().clear(); } @@ -93,7 +94,8 @@ void testOrderBySingleFieldWithoutTies(String sortDirection) { } """ .formatted(sortDirection.equals("ASC") ? 1 : -1), - sortDirection.equals("ASC") ? getBooksByIds(2, 1, 3, 4, 5) : getBooksByIds(5, 4, 3, 1, 2)); + sortDirection.equals("ASC") ? getBooksByIds(2, 1, 3, 4, 5) : getBooksByIds(5, 4, 3, 1, 2), + Set.of(Book.COLLECTION_NAME)); } @ParameterizedTest @@ -134,7 +136,8 @@ void testOrderBySingleFieldWithTies(String sortDirection) { : resultList -> assertThat(resultList) .satisfiesAnyOf( list -> assertIterableEq(getBooksByIds(1, 5, 4, 2, 3), list), - list -> assertIterableEq(getBooksByIds(5, 1, 4, 2, 3), list))); + list -> assertIterableEq(getBooksByIds(5, 1, 4, 2, 3), list)), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -173,7 +176,8 @@ void testOrderByMultipleFieldsWithoutTies() { } ] }""", - getBooksByIds(3, 2, 4, 5)); + getBooksByIds(3, 2, 4, 5), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -185,7 +189,8 @@ void testOrderByMultipleFieldsWithTies() { resultList -> assertThat(resultList) .satisfiesAnyOf( list -> assertIterableEq(getBooksByIds(3, 2, 4, 1, 5), list), - list -> assertIterableEq(getBooksByIds(3, 2, 4, 5, 1), list))); + list -> assertIterableEq(getBooksByIds(3, 2, 4, 5, 1), list)), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -217,7 +222,8 @@ void testSortFieldByAlias() { new Object[] {"The Brothers Karamazov", 1880}, new Object[] {"Anna Karenina", 1877}, new Object[] {"War and Peace", 1869}, - new Object[] {"Crime and Punishment", 1866})); + new Object[] {"Crime and Punishment", 1866}), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -248,7 +254,8 @@ void testSortFieldByOrdinalReference() { new Object[] {"Crime and Punishment", 1866}, new Object[] {"The Brothers Karamazov", 1880}, new Object[] {"War and Peace", 2025}, - new Object[] {"War and Peace", 1869})); + new Object[] {"War and Peace", 1869}), + Set.of(Book.COLLECTION_NAME)); } @Nested @@ -334,7 +341,8 @@ void testOrderBySimpleTuple() { ] } """, - getBooksByIds(2, 1, 3, 4, 5)); + getBooksByIds(2, 1, 3, 4, 5), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -367,7 +375,8 @@ void testOrderByNestedTuple() { ] } """, - getBooksByIds(5, 1, 4, 2, 3)); + getBooksByIds(5, 1, 4, 2, 3), + Set.of(Book.COLLECTION_NAME)); } } } From 9c35cf5d31985241622b0c44ab09b8c2e42c56e4 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Wed, 2 Jul 2025 18:09:04 -0400 Subject: [PATCH 19/19] resolve conflict with latest main branch --- ...imitOffsetFetchClauseIntegrationTests.java | 37 ++++++++---- .../translate/AbstractMqlTranslator.java | 33 +++++++---- .../translate/AstVisitorValueDescriptor.java | 4 +- .../translate/ModelMutationMqlTranslator.java | 4 +- .../translate/MutationMqlTranslator.java | 56 ++++++++++++------- .../AstVisitorValueDescriptorTests.java | 2 +- .../translate/AstVisitorValueHolderTests.java | 10 ++-- 7 files changed, 94 insertions(+), 52 deletions(-) diff --git a/src/integrationTest/java/com/mongodb/hibernate/query/select/LimitOffsetFetchClauseIntegrationTests.java b/src/integrationTest/java/com/mongodb/hibernate/query/select/LimitOffsetFetchClauseIntegrationTests.java index e8263e4a..ed8fd76e 100644 --- a/src/integrationTest/java/com/mongodb/hibernate/query/select/LimitOffsetFetchClauseIntegrationTests.java +++ b/src/integrationTest/java/com/mongodb/hibernate/query/select/LimitOffsetFetchClauseIntegrationTests.java @@ -26,8 +26,11 @@ import com.mongodb.hibernate.dialect.MongoDialect; import com.mongodb.hibernate.internal.FeatureNotSupportedException; import com.mongodb.hibernate.internal.MongoConstants; +import com.mongodb.hibernate.query.AbstractQueryIntegrationTests; +import com.mongodb.hibernate.query.Book; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.bson.BsonDocument; import org.hibernate.Session; @@ -56,7 +59,7 @@ import org.junit.jupiter.params.provider.ValueSource; @DomainModel(annotatedClasses = Book.class) -class LimitOffsetFetchClauseIntegrationTests extends AbstractSelectionQueryIntegrationTests { +class LimitOffsetFetchClauseIntegrationTests extends AbstractQueryIntegrationTests { private static final List testingBooks = List.of( new Book(0, "Nostromo", 1904, true), @@ -122,7 +125,8 @@ void testHqlLimitClauseOnly(boolean useLiteralParameter) { } """ .formatted(5), - getBooksByIds(0, 1, 2, 3, 4)); + getBooksByIds(0, 1, 2, 3, 4), + Set.of(Book.COLLECTION_NAME)); } @ParameterizedTest @@ -159,7 +163,8 @@ void testHqlOffsetClauseOnly(boolean useLiteralParameter) { } """ .formatted(7), - getBooksByIds(7, 8, 9)); + getBooksByIds(7, 8, 9), + Set.of(Book.COLLECTION_NAME)); } @ParameterizedTest @@ -203,7 +208,8 @@ void testHqlLimitAndOffsetClauses(boolean useLiteralParameters) { } """ .formatted(3, 2), - getBooksByIds(3, 4)); + getBooksByIds(3, 4), + Set.of(Book.COLLECTION_NAME)); } @ParameterizedTest @@ -244,7 +250,8 @@ void testHqlFetchClauseOnly(String fetchClause) { } """ .formatted(5), - getBooksByIds(0, 1, 2, 3, 4)); + getBooksByIds(0, 1, 2, 3, 4), + Set.of(Book.COLLECTION_NAME)); } } @@ -286,7 +293,8 @@ void testQueryOptionsSetFirstResultOnly() { } """ .formatted(6), - getBooksByIds(6, 7, 8, 9)); + getBooksByIds(6, 7, 8, 9), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -322,7 +330,8 @@ void testQueryOptionsSetMaxResultOnly() { } """ .formatted(3), - getBooksByIds(0, 1, 2)); + getBooksByIds(0, 1, 2), + Set.of(Book.COLLECTION_NAME)); } @Test @@ -361,7 +370,8 @@ void testQueryOptionsSetFirstResultAndMaxResults() { } """ .formatted(2, 3), - getBooksByIds(2, 3, 4)); + getBooksByIds(2, 3, 4), + Set.of(Book.COLLECTION_NAME)); } } @@ -407,7 +417,8 @@ void testFirstResultConflictingOnly() { .setParameter("offset", 0) .setFirstResult(firstResult), expectedMqlTemplate.formatted("{\"$skip\": " + firstResult + "}"), - expectedBooks); + expectedBooks, + Set.of(Book.COLLECTION_NAME)); } @Test @@ -423,7 +434,8 @@ void testMaxResultsConflictingOnly() { .setParameter("offset", 0) .setMaxResults(maxResults), expectedMqlTemplate.formatted("{\"$limit\": " + maxResults + "}"), - expectedBooks); + expectedBooks, + Set.of(Book.COLLECTION_NAME)); } @Test @@ -442,7 +454,8 @@ void testBothFirstResultAndMaxResultsConflicting() { .setMaxResults(maxResults), expectedMqlTemplate.formatted( "{\"$skip\": " + firstResult + "}," + "{\"$limit\": " + maxResults + "}"), - expectedBooks); + expectedBooks, + Set.of(Book.COLLECTION_NAME)); } } } @@ -482,7 +495,7 @@ void testUnsupportedFetchClauseType(FetchClauseType fetchClauseType) { value = "com.mongodb.hibernate.query.select.LimitOffsetFetchClauseIntegrationTests$TranslatingCacheTestingDialect"), }) - class QueryPlanCacheTests extends AbstractSelectionQueryIntegrationTests { + class QueryPlanCacheTests extends AbstractQueryIntegrationTests { private static final String HQL = "from Book order by id"; private static final String expectedMqlTemplate = diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java index 2578af05..93bc78b1 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java @@ -26,6 +26,7 @@ import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_NAME; import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.FIELD_PATH; import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.FILTER; +import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MODEL_MUTATION_RESULT; import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MUTATION_RESULT; import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.PROJECT_STAGE_SPECIFICATIONS; import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.SELECT_RESULT; @@ -270,10 +271,10 @@ public void visitStandardTableInsert(TableInsertStandard tableInsert) { astElements.add(new AstElement(fieldName, fieldValue)); } astVisitorValueHolder.yield( - MUTATION_RESULT, + MODEL_MUTATION_RESULT, ModelMutationMqlTranslator.Result.create( new AstInsertCommand( - tableInsert.getMutatingTable().getTableName(), new AstDocument(astElements)), + tableInsert.getMutatingTable().getTableName(), List.of(new AstDocument(astElements))), parameterBinders)); } @@ -292,7 +293,7 @@ public void visitStandardTableDelete(TableDeleteStandard tableDelete) { } var keyFilter = getKeyFilter(tableDelete); astVisitorValueHolder.yield( - MUTATION_RESULT, + MODEL_MUTATION_RESULT, ModelMutationMqlTranslator.Result.create( new AstDeleteCommand(tableDelete.getMutatingTable().getTableName(), keyFilter), parameterBinders)); @@ -314,7 +315,7 @@ public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) { updates.add(new AstFieldUpdate(fieldName, fieldValue)); } astVisitorValueHolder.yield( - MUTATION_RESULT, + MODEL_MUTATION_RESULT, ModelMutationMqlTranslator.Result.create( new AstUpdateCommand(tableUpdate.getMutatingTable().getTableName(), keyFilter, updates), parameterBinders)); @@ -664,8 +665,11 @@ public void visitDeleteStatement(DeleteStatement deleteStatement) { var collectionAndAstFilter = getCollectionAndFilter(deleteStatement); affectedTableNames.add(collectionAndAstFilter.collection); astVisitorValueHolder.yield( - COLLECTION_MUTATION, - new AstDeleteCommand(collectionAndAstFilter.collection, collectionAndAstFilter.filter)); + MUTATION_RESULT, + new MutationMqlTranslator.Result( + new AstDeleteCommand(collectionAndAstFilter.collection, collectionAndAstFilter.filter), + parameterBinders, + affectedTableNames)); } @Override @@ -685,12 +689,16 @@ public void visitUpdateStatement(UpdateStatement updateStatement) { if (!isValueExpression(assignedValue)) { throw new FeatureNotSupportedException(); } - var fieldValue = acceptAndYield(assignedValue, FIELD_VALUE); + var fieldValue = acceptAndYield(assignedValue, VALUE); fieldUpdates.add(new AstFieldUpdate(fieldPath, fieldValue)); } astVisitorValueHolder.yield( - COLLECTION_MUTATION, - new AstUpdateCommand(collectionAndAstFilter.collection, collectionAndAstFilter.filter, fieldUpdates)); + MUTATION_RESULT, + new MutationMqlTranslator.Result( + new AstUpdateCommand( + collectionAndAstFilter.collection, collectionAndAstFilter.filter, fieldUpdates), + parameterBinders, + affectedTableNames)); } private CollectionAndFilter getCollectionAndFilter(AbstractUpdateOrDeleteStatement updateOrDeleteStatement) { @@ -734,13 +742,16 @@ public void visitInsertStatement(InsertSelectStatement insertStatement) { if (!isValueExpression(fieldValueExpression)) { throw new FeatureNotSupportedException(); } - var fieldValue = acceptAndYield(fieldValueExpression, FIELD_VALUE); + var fieldValue = acceptAndYield(fieldValueExpression, VALUE); astElements.add(new AstElement(fieldName, fieldValue)); } documents.add(new AstDocument(astElements)); } - astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstInsertCommand(collection, documents)); + astVisitorValueHolder.yield( + MUTATION_RESULT, + new MutationMqlTranslator.Result( + new AstInsertCommand(collection, documents), parameterBinders, affectedTableNames)); } @Override diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptor.java b/src/main/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptor.java index ad844f63..2214636e 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptor.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptor.java @@ -33,10 +33,12 @@ @SuppressWarnings("UnusedTypeParameter") final class AstVisitorValueDescriptor { - static final AstVisitorValueDescriptor MUTATION_RESULT = + static final AstVisitorValueDescriptor MODEL_MUTATION_RESULT = new AstVisitorValueDescriptor<>(); static final AstVisitorValueDescriptor SELECT_RESULT = new AstVisitorValueDescriptor<>(); + static final AstVisitorValueDescriptor MUTATION_RESULT = + new AstVisitorValueDescriptor<>(); static final AstVisitorValueDescriptor COLLECTION_NAME = new AstVisitorValueDescriptor<>(); diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/ModelMutationMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/ModelMutationMqlTranslator.java index 90b75c40..71b56ec6 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/ModelMutationMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/ModelMutationMqlTranslator.java @@ -18,7 +18,7 @@ import static com.mongodb.hibernate.internal.MongoAssertions.assertNotNull; import static com.mongodb.hibernate.internal.MongoAssertions.assertNull; -import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MUTATION_RESULT; +import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MODEL_MUTATION_RESULT; import static java.util.Collections.emptyList; import com.mongodb.hibernate.internal.translate.mongoast.command.AstCommand; @@ -50,7 +50,7 @@ public O translate(@Nullable JdbcParameterBindings jdbcParameterBindings, QueryO if ((TableMutation) tableMutation instanceof TableUpdateNoSet) { result = Result.empty(); } else { - result = acceptAndYield(tableMutation, MUTATION_RESULT); + result = acceptAndYield(tableMutation, MODEL_MUTATION_RESULT); } return result.createJdbcMutationOperation(tableMutation); } diff --git a/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java b/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java index a6ad5253..58de052e 100644 --- a/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java +++ b/src/main/java/com/mongodb/hibernate/internal/translate/MutationMqlTranslator.java @@ -17,21 +17,25 @@ package com.mongodb.hibernate.internal.translate; import static com.mongodb.hibernate.internal.MongoAssertions.fail; -import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.COLLECTION_MUTATION; +import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MUTATION_RESULT; import static java.lang.String.format; import static java.util.Collections.emptyMap; import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst; +import com.mongodb.hibernate.internal.translate.mongoast.command.AstCommand; +import com.mongodb.hibernate.internal.translate.mongoast.command.AstDeleteCommand; +import com.mongodb.hibernate.internal.translate.mongoast.command.AstInsertCommand; +import com.mongodb.hibernate.internal.translate.mongoast.command.AstUpdateCommand; +import java.util.List; +import java.util.Set; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.insert.InsertStatement; -import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl; import org.hibernate.sql.exec.spi.JdbcOperationQueryDelete; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.jspecify.annotations.Nullable; @@ -51,24 +55,36 @@ public JdbcOperationQueryMutation translate( logSqlAst(mutationStatement); checkJdbcParameterBindingsSupportability(jdbcParameterBindings); - checkQueryOptionsSupportability(queryOptions); + applyQueryOptions(queryOptions); - var mutationCommand = acceptAndYield(mutationStatement, COLLECTION_MUTATION); - var mql = renderMongoAstNode(mutationCommand); - var parameterBinders = getParameterBinders(); - var affectedCollections = getAffectedTableNames(); + var result = acceptAndYield(mutationStatement, MUTATION_RESULT); + return result.createJdbcOperationQueryMutation(); + } + + static final class Result { + private final AstCommand command; + private final List parameterBinders; + private final Set affectedTableNames; + + Result(AstCommand command, List parameterBinders, Set affectedTableNames) { + this.command = command; + this.parameterBinders = parameterBinders; + this.affectedTableNames = affectedTableNames; + } - // switch to Switch Pattern Matching when JDK is upgraded to 21+ - if (mutationStatement instanceof InsertStatement) { - return new JdbcOperationQueryInsertImpl(mql, parameterBinders, affectedCollections); - } else if (mutationStatement instanceof UpdateStatement) { - return new JdbcOperationQueryUpdate(mql, parameterBinders, affectedCollections, emptyMap()); - } else if (mutationStatement instanceof DeleteStatement) { - return new JdbcOperationQueryDelete(mql, parameterBinders, affectedCollections, emptyMap()); - } else { - throw fail(format( - "Unexpected mutation statement type: %s", - mutationStatement.getClass().getName())); + private JdbcOperationQueryMutation createJdbcOperationQueryMutation() { + var mql = renderMongoAstNode(command); + if (command instanceof AstInsertCommand) { + return new JdbcOperationQueryInsertImpl(mql, parameterBinders, affectedTableNames); + } else if (command instanceof AstUpdateCommand) { + return new JdbcOperationQueryUpdate(mql, parameterBinders, affectedTableNames, emptyMap()); + } else if (command instanceof AstDeleteCommand) { + return new JdbcOperationQueryDelete(mql, parameterBinders, affectedTableNames, emptyMap()); + } else { + throw fail(format( + "Unexpected mutation command type: %s", + command.getClass().getName())); + } } } } diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptorTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptorTests.java index f75244fe..c6427554 100644 --- a/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptorTests.java +++ b/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueDescriptorTests.java @@ -24,6 +24,6 @@ class AstVisitorValueDescriptorTests { @Test void testToString() { - assertEquals("MUTATION_RESULT", AstVisitorValueDescriptor.MUTATION_RESULT.toString()); + assertEquals("MODEL_MUTATION_RESULT", AstVisitorValueDescriptor.MODEL_MUTATION_RESULT.toString()); } } diff --git a/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueHolderTests.java b/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueHolderTests.java index c9865e63..c8475f10 100644 --- a/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueHolderTests.java +++ b/src/test/java/com/mongodb/hibernate/internal/translate/AstVisitorValueHolderTests.java @@ -16,7 +16,7 @@ package com.mongodb.hibernate.internal.translate; -import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MUTATION_RESULT; +import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.MODEL_MUTATION_RESULT; import static com.mongodb.hibernate.internal.translate.AstVisitorValueDescriptor.VALUE; import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertSame; @@ -63,12 +63,12 @@ void testRecursiveUsage() { var fieldValue = astVisitorValueHolder.execute(VALUE, fieldValueYielder); AstElement astElement = new AstElement("province", fieldValue); astVisitorValueHolder.yield( - MUTATION_RESULT, + MODEL_MUTATION_RESULT, ModelMutationMqlTranslator.Result.create( - new AstInsertCommand("city", new AstDocument(List.of(astElement))), emptyList())); + new AstInsertCommand("city", List.of(new AstDocument(List.of(astElement)))), emptyList())); }; - astVisitorValueHolder.execute(MUTATION_RESULT, tableInserter); + astVisitorValueHolder.execute(MODEL_MUTATION_RESULT, tableInserter); } @Test @@ -90,7 +90,7 @@ void testHolderExpectingDifferentDescriptor() { Runnable valueYielder = () -> astVisitorValueHolder.yield(VALUE, new AstLiteralValue(new BsonString("some_value"))); - assertThrows(Error.class, () -> astVisitorValueHolder.execute(MUTATION_RESULT, valueYielder)); + assertThrows(Error.class, () -> astVisitorValueHolder.execute(MODEL_MUTATION_RESULT, valueYielder)); } @Test