Skip to content

Commit 6bb7138

Browse files
basic updating translation
1 parent d7d68b6 commit 6bb7138

File tree

8 files changed

+233
-110
lines changed

8 files changed

+233
-110
lines changed

src/integrationTest/java/com/mongodb/hibernate/BasicCrudTests.java renamed to src/integrationTest/java/com/mongodb/hibernate/BasicCRUDTests.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.ArrayList;
3333
import java.util.List;
3434
import org.bson.BsonDocument;
35+
import org.hibernate.testing.jdbc.SQLStatementInspector;
3536
import org.hibernate.testing.orm.junit.DomainModel;
3637
import org.hibernate.testing.orm.junit.SessionFactory;
3738
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@@ -41,10 +42,12 @@
4142
import org.junit.jupiter.api.BeforeEach;
4243
import org.junit.jupiter.api.Nested;
4344
import org.junit.jupiter.api.Test;
45+
import org.junit.jupiter.params.ParameterizedTest;
46+
import org.junit.jupiter.params.provider.ValueSource;
4447

45-
@SessionFactory(exportSchema = false)
46-
@DomainModel(annotatedClasses = {BasicCrudTests.Book.class, BasicCrudTests.BookWithEmbeddedField.class})
47-
class BasicCrudTests implements SessionFactoryScopeAware {
48+
@SessionFactory(exportSchema = false, useCollectingStatementInspector = true)
49+
@DomainModel(annotatedClasses = {BasicCRUDTests.Book.class, BasicCRUDTests.BookWithEmbeddedField.class})
50+
class BasicCRUDTests implements SessionFactoryScopeAware {
4851

4952
@AutoClose
5053
private MongoClient mongoClient;
@@ -143,7 +146,6 @@ void testEntityWithEmbeddedFieldInsertion() {
143146

144147
@Nested
145148
class DeleteTests {
146-
147149
@Test
148150
void testSimpleDeletion() {
149151

@@ -170,6 +172,46 @@ void testSimpleDeletion() {
170172
}
171173
}
172174

175+
@Nested
176+
class UpdateTests {
177+
178+
@ParameterizedTest(name = "merge: {0}")
179+
@ValueSource(booleans = {true, false})
180+
void testSimpleUpdate(boolean merge) {
181+
var statementInspector = sessionFactoryScope.getStatementInspector(SQLStatementInspector.class);
182+
statementInspector.clear();
183+
sessionFactoryScope.inTransaction(session -> {
184+
var book = new Book();
185+
book.id = 1;
186+
book.title = "War and Peace";
187+
book.author = "Leo Tolstoy";
188+
book.publishYear = 1867;
189+
session.persist(book);
190+
session.flush();
191+
192+
book.title = "Insurrection";
193+
book.publishYear = 1899;
194+
if (merge) {
195+
session.merge(book);
196+
}
197+
});
198+
199+
assertCollectionContainsOnly(
200+
BsonDocument.parse(
201+
"""
202+
{"_id": 1, "author": "Leo Tolstoy", "publishYear": 1899, "title": "Insurrection"}\
203+
"""));
204+
assertThat(statementInspector.getSqlQueries())
205+
.containsExactly(
206+
"""
207+
{"insert": "books", "documents": [{"author": {"$undefined": true}, "publishYear": {"$undefined": true}, "title": {"$undefined": true}, "_id": {"$undefined": true}}]}\
208+
""",
209+
"""
210+
{"update": "books", "updates": [{"q": {"_id": {"$eq": {"$undefined": true}}}, "u": {"$set": {"author": {"$undefined": true}, "publishYear": {"$undefined": true}, "title": {"$undefined": true}}}, "multi": true}]}\
211+
""");
212+
}
213+
}
214+
173215
private List<BsonDocument> getCollectionDocuments() {
174216
var documents = new ArrayList<BsonDocument>();
175217
collection.find().sort(Sorts.ascending("_id")).into(documents);

src/main/java/com/mongodb/hibernate/internal/translate/AbstractMqlTranslator.java

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
import com.mongodb.hibernate.internal.service.StandardServiceRegistryScopedState;
2727
import com.mongodb.hibernate.internal.translate.mongoast.AstDocument;
2828
import com.mongodb.hibernate.internal.translate.mongoast.AstElement;
29+
import com.mongodb.hibernate.internal.translate.mongoast.AstFieldUpdate;
2930
import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
3031
import com.mongodb.hibernate.internal.translate.mongoast.AstParameterMarker;
3132
import com.mongodb.hibernate.internal.translate.mongoast.command.AstDeleteCommand;
3233
import com.mongodb.hibernate.internal.translate.mongoast.command.AstInsertCommand;
34+
import com.mongodb.hibernate.internal.translate.mongoast.command.AstUpdateCommand;
3335
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstComparisonFilterOperation;
3436
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFieldOperationFilter;
37+
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilter;
3538
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilterFieldPath;
3639
import java.io.IOException;
3740
import java.io.StringWriter;
@@ -118,6 +121,7 @@
118121
import org.hibernate.sql.exec.spi.JdbcOperation;
119122
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
120123
import org.hibernate.sql.model.ast.ColumnWriteFragment;
124+
import org.hibernate.sql.model.ast.RestrictedTableMutation;
121125
import org.hibernate.sql.model.internal.OptionalTableUpdate;
122126
import org.hibernate.sql.model.internal.TableDeleteCustomSql;
123127
import org.hibernate.sql.model.internal.TableDeleteStandard;
@@ -200,7 +204,7 @@ <R extends AstNode> R acceptAndYield(SqlAstNode node, AstVisitorValueDescriptor<
200204
}
201205

202206
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
203-
// Table Mutation: insertion
207+
// Table Mutation: insert
204208

205209
@Override
206210
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
@@ -229,30 +233,63 @@ public void visitColumnWriteFragment(ColumnWriteFragment columnWriteFragment) {
229233
}
230234

231235
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
232-
// Table Mutation: deletion
236+
// Table Mutation: delete
233237

234238
@Override
235239
public void visitStandardTableDelete(TableDeleteStandard tableDelete) {
236240
if (tableDelete.getWhereFragment() != null) {
237241
throw new FeatureNotSupportedException();
238242
}
243+
var keyFilter = getKeyFilter(tableDelete);
244+
astVisitorValueHolder.yield(
245+
COLLECTION_MUTATION,
246+
new AstDeleteCommand(tableDelete.getMutatingTable().getTableName(), keyFilter));
247+
}
248+
249+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
250+
// Table Mutation: update
251+
252+
@Override
253+
public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) {
254+
if (tableUpdate.getWhereFragment() != null) {
255+
throw new FeatureNotSupportedException();
256+
}
257+
doVisitTableUpdate(tableUpdate);
258+
}
259+
260+
@Override
261+
public void visitOptionalTableUpdate(OptionalTableUpdate tableUpdate) {
262+
doVisitTableUpdate(tableUpdate);
263+
}
264+
265+
private void doVisitTableUpdate(RestrictedTableMutation<?> tableUpdate) {
266+
var keyFilter = getKeyFilter(tableUpdate);
267+
var updates = new ArrayList<AstFieldUpdate>();
268+
tableUpdate.forEachValueBinding((position, valueBinding) -> {
269+
var columnExpression = valueBinding.getColumnReference().getColumnExpression();
270+
var astValue = acceptAndYield(valueBinding.getValueExpression(), FIELD_VALUE);
271+
updates.add(new AstFieldUpdate(columnExpression, astValue));
272+
});
273+
astVisitorValueHolder.yield(
274+
COLLECTION_MUTATION,
275+
new AstUpdateCommand(tableUpdate.getMutatingTable().getTableName(), keyFilter, updates));
276+
}
239277

240-
if (tableDelete.getNumberOfOptimisticLockBindings() > 0) {
278+
private AstFilter getKeyFilter(RestrictedTableMutation<?> tableMutation) {
279+
if (tableMutation.getNumberOfOptimisticLockBindings() > 0) {
241280
throw new FeatureNotSupportedException("TODO-HIBERNATE-51 https://jira.mongodb.org/browse/HIBERNATE-51");
242281
}
243282

244-
if (tableDelete.getNumberOfKeyBindings() > 1) {
283+
if (tableMutation.getNumberOfKeyBindings() > 1) {
245284
throw new FeatureNotSupportedException("MongoDB doesn't support '_id' spanning multiple columns");
246285
}
247-
assertTrue(tableDelete.getNumberOfKeyBindings() == 1);
248-
var keyBinding = tableDelete.getKeyBindings().get(0);
286+
assertTrue(tableMutation.getNumberOfKeyBindings() == 1);
287+
var keyBinding = tableMutation.getKeyBindings().get(0);
249288

250-
var tableName = tableDelete.getMutatingTable().getTableName();
251289
var astFilterFieldPath =
252290
new AstFilterFieldPath(keyBinding.getColumnReference().getColumnExpression());
253291
var astValue = acceptAndYield(keyBinding.getValueExpression(), FIELD_VALUE);
254-
var keyFilter = new AstFieldOperationFilter(astFilterFieldPath, new AstComparisonFilterOperation(EQ, astValue));
255-
astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstDeleteCommand(tableName, keyFilter));
292+
return new AstFieldOperationFilter(astFilterFieldPath, new AstComparisonFilterOperation(EQ, astValue));
256293
}
257294

258295
@Override
@@ -606,16 +643,6 @@ public void visitCustomTableDelete(TableDeleteCustomSql tableDeleteCustomSql) {
606643
throw new FeatureNotSupportedException();
607644
}
608645

609-
@Override
610-
public void visitStandardTableUpdate(TableUpdateStandard tableUpdateStandard) {
611-
throw new FeatureNotSupportedException("TODO-HIBERNATE-19 https://jira.mongodb.org/browse/HIBERNATE-19");
612-
}
613-
614-
@Override
615-
public void visitOptionalTableUpdate(OptionalTableUpdate optionalTableUpdate) {
616-
throw new FeatureNotSupportedException();
617-
}
618-
619646
@Override
620647
public void visitCustomTableUpdate(TableUpdateCustomSql tableUpdateCustomSql) {
621648
throw new FeatureNotSupportedException();

src/main/java/com/mongodb/hibernate/internal/translate/NoopJdbcMutationOperation.java

Lines changed: 0 additions & 74 deletions
This file was deleted.

src/main/java/com/mongodb/hibernate/internal/translate/TableMutationMqlTranslator.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import org.hibernate.engine.spi.SessionFactoryImplementor;
2323
import org.hibernate.query.spi.QueryOptions;
2424
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
25-
import org.hibernate.sql.model.ast.TableDelete;
26-
import org.hibernate.sql.model.ast.TableInsert;
2725
import org.hibernate.sql.model.ast.TableMutation;
2826
import org.hibernate.sql.model.jdbc.JdbcMutationOperation;
2927
import org.jspecify.annotations.Nullable;
@@ -38,20 +36,10 @@ final class TableMutationMqlTranslator<O extends JdbcMutationOperation> extends
3836
}
3937

4038
@Override
41-
@SuppressWarnings("unchecked")
4239
public O translate(@Nullable JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) {
4340
assertNull(jdbcParameterBindings);
4441
// QueryOptions class is not applicable to table mutation so a dummy value is always passed in
4542

46-
if (tableMutation instanceof TableInsert || tableMutation instanceof TableDelete) {
47-
return translateTableMutation();
48-
} else {
49-
// TODO-HIBERNATE-19 https://jira.mongodb.org/browse/HIBERNATE-19
50-
return (O) new NoopJdbcMutationOperation();
51-
}
52-
}
53-
54-
private O translateTableMutation() {
5543
var rootAstNode = acceptAndYield(tableMutation, COLLECTION_MUTATION);
5644
return tableMutation.createMutationOperation(renderMongoAstNode(rootAstNode), getParameterBinders());
5745
}

src/main/java/com/mongodb/hibernate/internal/translate/mongoast/AstDocument.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.util.List;
2020
import org.bson.BsonWriter;
2121

22-
public record AstDocument(List<? extends AstElement> elements) implements AstValue {
22+
public record AstDocument(List<AstElement> elements) implements AstValue {
2323
@Override
2424
public void render(BsonWriter writer) {
2525
writer.writeStartDocument();
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2025-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.hibernate.internal.translate.mongoast;
18+
19+
import org.bson.BsonWriter;
20+
21+
public record AstFieldUpdate(String name, AstValue value) implements AstNode {
22+
@Override
23+
public void render(BsonWriter writer) {
24+
writer.writeName(name);
25+
value.render(writer);
26+
}
27+
}

0 commit comments

Comments
 (0)