Skip to content

Commit ccca670

Browse files
NathanQingyangXukatcharovstIncMale
authored
basic update translation (#59)
* basic updating translation * exclude 'merge' out of this PR scope * revert back naming changing for BasicCrudTests * Update src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java Co-authored-by: Maxim Katcharov <[email protected]> * Update src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java Co-authored-by: Valentin Kovalenko <[email protected]> * add update testing case to test `@DynamicUpdate` * addresses code review comments * changes as per code review comments * further code changes as per comments --------- Co-authored-by: Maxim Katcharov <[email protected]> Co-authored-by: Valentin Kovalenko <[email protected]>
1 parent 1e814df commit ccca670

File tree

8 files changed

+256
-106
lines changed

8 files changed

+256
-106
lines changed

src/integrationTest/java/com/mongodb/hibernate/BasicCrudIntegrationTests.java

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.ArrayList;
3232
import java.util.List;
3333
import org.bson.BsonDocument;
34+
import org.hibernate.annotations.DynamicUpdate;
3435
import org.hibernate.testing.orm.junit.DomainModel;
3536
import org.hibernate.testing.orm.junit.SessionFactory;
3637
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@@ -41,7 +42,10 @@
4142

4243
@SessionFactory(exportSchema = false)
4344
@DomainModel(
44-
annotatedClasses = {BasicCrudIntegrationTests.Book.class, BasicCrudIntegrationTests.BookWithEmbeddedField.class
45+
annotatedClasses = {
46+
BasicCrudIntegrationTests.Book.class,
47+
BasicCrudIntegrationTests.BookWithEmbeddedField.class,
48+
BasicCrudIntegrationTests.BookDynamicallyUpdated.class
4549
})
4650
@ExtendWith(MongoExtension.class)
4751
class BasicCrudIntegrationTests implements SessionFactoryScopeAware {
@@ -151,17 +155,64 @@ void testSimpleDeletion() {
151155
}
152156
}
153157

154-
private List<BsonDocument> getCollectionDocuments() {
158+
@Nested
159+
class UpdateTests {
160+
161+
@Test
162+
void testSimpleUpdate() {
163+
sessionFactoryScope.inTransaction(session -> {
164+
var book = new Book();
165+
book.id = 1;
166+
book.title = "War and Peace";
167+
book.author = "Leo Tolstoy";
168+
book.publishYear = 1867;
169+
session.persist(book);
170+
session.flush();
171+
172+
book.title = "Resurrection";
173+
book.publishYear = 1899;
174+
});
175+
176+
assertCollectionContainsOnly(
177+
BsonDocument.parse(
178+
"""
179+
{"_id": 1, "author": "Leo Tolstoy", "publishYear": 1899, "title": "Resurrection"}\
180+
"""));
181+
}
182+
183+
@Test
184+
void testDynamicUpdate() {
185+
sessionFactoryScope.inTransaction(session -> {
186+
var book = new BookDynamicallyUpdated();
187+
book.id = 1;
188+
book.title = "War and Peace";
189+
book.author = "Leo Tolstoy";
190+
book.publishYear = 1899;
191+
session.persist(book);
192+
session.flush();
193+
194+
book.publishYear = 1867;
195+
});
196+
197+
assertCollectionContainsOnly(
198+
BsonDocument.parse(
199+
"""
200+
{"_id": 1, "author": "Leo Tolstoy", "publishYear": 1867, "title": "War and Peace"}\
201+
"""));
202+
}
203+
}
204+
205+
private static List<BsonDocument> getCollectionDocuments() {
155206
var documents = new ArrayList<BsonDocument>();
156207
collection.find().sort(Sorts.ascending("_id")).into(documents);
157208
return documents;
158209
}
159210

160-
private void assertCollectionContainsOnly(BsonDocument expectedDoc) {
211+
private static void assertCollectionContainsOnly(BsonDocument expectedDoc) {
161212
assertThat(getCollectionDocuments()).asInstanceOf(LIST).singleElement().isEqualTo(expectedDoc);
162213
}
163214

164-
@Entity(name = "Book")
215+
@Entity
165216
@Table(name = "books")
166217
static class Book {
167218
@Id
@@ -175,7 +226,22 @@ static class Book {
175226
int publishYear;
176227
}
177228

178-
@Entity(name = "BookWithEmbeddedField")
229+
@Entity
230+
@Table(name = "books")
231+
@DynamicUpdate
232+
static class BookDynamicallyUpdated {
233+
@Id
234+
@Column(name = "_id")
235+
int id;
236+
237+
String title;
238+
239+
String author;
240+
241+
int publishYear;
242+
}
243+
244+
@Entity
179245
@Table(name = "books")
180246
static class BookWithEmbeddedField {
181247
@Id

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

Lines changed: 44 additions & 14 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;
@@ -117,6 +120,8 @@
117120
import org.hibernate.sql.ast.tree.update.UpdateStatement;
118121
import org.hibernate.sql.exec.spi.JdbcOperation;
119122
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
123+
import org.hibernate.sql.model.MutationOperation;
124+
import org.hibernate.sql.model.ast.AbstractRestrictedTableMutation;
120125
import org.hibernate.sql.model.ast.ColumnWriteFragment;
121126
import org.hibernate.sql.model.internal.OptionalTableUpdate;
122127
import org.hibernate.sql.model.internal.TableDeleteCustomSql;
@@ -200,10 +205,13 @@ <R extends AstNode> R acceptAndYield(SqlAstNode node, AstVisitorValueDescriptor<
200205
}
201206

202207
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
203-
// Table Mutation: insertion
208+
// Table Mutation: insert
204209

205210
@Override
206211
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
212+
if (tableInsert.getNumberOfReturningColumns() > 0) {
213+
throw new FeatureNotSupportedException();
214+
}
207215
var tableName = tableInsert.getTableName();
208216
var astElements = new ArrayList<AstElement>(tableInsert.getNumberOfValueBindings());
209217
for (var columnValueBinding : tableInsert.getValueBindings()) {
@@ -229,30 +237,57 @@ public void visitColumnWriteFragment(ColumnWriteFragment columnWriteFragment) {
229237
}
230238

231239
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
232-
// Table Mutation: deletion
240+
// Table Mutation: delete
233241

234242
@Override
235243
public void visitStandardTableDelete(TableDeleteStandard tableDelete) {
236244
if (tableDelete.getWhereFragment() != null) {
237245
throw new FeatureNotSupportedException();
238246
}
247+
var keyFilter = getKeyFilter(tableDelete);
248+
astVisitorValueHolder.yield(
249+
COLLECTION_MUTATION,
250+
new AstDeleteCommand(tableDelete.getMutatingTable().getTableName(), keyFilter));
251+
}
252+
253+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
254+
// Table Mutation: update
255+
256+
@Override
257+
public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) {
258+
if (tableUpdate.getNumberOfReturningColumns() > 0) {
259+
throw new FeatureNotSupportedException();
260+
}
261+
if (tableUpdate.getWhereFragment() != null) {
262+
throw new FeatureNotSupportedException();
263+
}
264+
var keyFilter = getKeyFilter(tableUpdate);
265+
var updates = new ArrayList<AstFieldUpdate>(tableUpdate.getNumberOfValueBindings());
266+
for (var valueBinding : tableUpdate.getValueBindings()) {
267+
var columnExpression = valueBinding.getColumnReference().getColumnExpression();
268+
var astValue = acceptAndYield(valueBinding.getValueExpression(), FIELD_VALUE);
269+
updates.add(new AstFieldUpdate(columnExpression, astValue));
270+
}
271+
astVisitorValueHolder.yield(
272+
COLLECTION_MUTATION,
273+
new AstUpdateCommand(tableUpdate.getMutatingTable().getTableName(), keyFilter, updates));
274+
}
239275

240-
if (tableDelete.getNumberOfOptimisticLockBindings() > 0) {
276+
private AstFilter getKeyFilter(AbstractRestrictedTableMutation<? extends MutationOperation> tableMutation) {
277+
if (tableMutation.getNumberOfOptimisticLockBindings() > 0) {
241278
throw new FeatureNotSupportedException("TODO-HIBERNATE-51 https://jira.mongodb.org/browse/HIBERNATE-51");
242279
}
243280

244-
if (tableDelete.getNumberOfKeyBindings() > 1) {
281+
if (tableMutation.getNumberOfKeyBindings() > 1) {
245282
throw new FeatureNotSupportedException("MongoDB doesn't support '_id' spanning multiple columns");
246283
}
247-
assertTrue(tableDelete.getNumberOfKeyBindings() == 1);
248-
var keyBinding = tableDelete.getKeyBindings().get(0);
284+
assertTrue(tableMutation.getNumberOfKeyBindings() == 1);
285+
var keyBinding = tableMutation.getKeyBindings().get(0);
249286

250-
var tableName = tableDelete.getMutatingTable().getTableName();
251287
var astFilterFieldPath =
252288
new AstFilterFieldPath(keyBinding.getColumnReference().getColumnExpression());
253289
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));
290+
return new AstFieldOperationFilter(astFilterFieldPath, new AstComparisonFilterOperation(EQ, astValue));
256291
}
257292

258293
@Override
@@ -606,11 +641,6 @@ public void visitCustomTableDelete(TableDeleteCustomSql tableDeleteCustomSql) {
606641
throw new FeatureNotSupportedException();
607642
}
608643

609-
@Override
610-
public void visitStandardTableUpdate(TableUpdateStandard tableUpdateStandard) {
611-
throw new FeatureNotSupportedException("TODO-HIBERNATE-19 https://jira.mongodb.org/browse/HIBERNATE-19");
612-
}
613-
614644
@Override
615645
public void visitOptionalTableUpdate(OptionalTableUpdate optionalTableUpdate) {
616646
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)