Skip to content

basic update translation #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.List;
import org.bson.BsonDocument;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
Expand All @@ -41,7 +42,10 @@

@SessionFactory(exportSchema = false)
@DomainModel(
annotatedClasses = {BasicCrudIntegrationTests.Book.class, BasicCrudIntegrationTests.BookWithEmbeddedField.class
annotatedClasses = {
BasicCrudIntegrationTests.Book.class,
BasicCrudIntegrationTests.BookWithEmbeddedField.class,
BasicCrudIntegrationTests.BookDynamicallyUpdated.class
})
@ExtendWith(MongoExtension.class)
class BasicCrudIntegrationTests implements SessionFactoryScopeAware {
Expand Down Expand Up @@ -151,17 +155,64 @@ void testSimpleDeletion() {
}
}

private List<BsonDocument> getCollectionDocuments() {
@Nested
class UpdateTests {

@Test
void testSimpleUpdate() {
sessionFactoryScope.inTransaction(session -> {
var book = new Book();
book.id = 1;
book.title = "War and Peace";
book.author = "Leo Tolstoy";
book.publishYear = 1867;
session.persist(book);
session.flush();

book.title = "Resurrection";
book.publishYear = 1899;
});

assertCollectionContainsOnly(
BsonDocument.parse(
"""
{"_id": 1, "author": "Leo Tolstoy", "publishYear": 1899, "title": "Resurrection"}\
"""));
}

@Test
void testDynamicUpdate() {
sessionFactoryScope.inTransaction(session -> {
var book = new BookDynamicallyUpdated();
book.id = 1;
book.title = "War and Peace";
book.author = "Leo Tolstoy";
book.publishYear = 1899;
session.persist(book);
session.flush();

book.publishYear = 1867;
});

assertCollectionContainsOnly(
BsonDocument.parse(
"""
{"_id": 1, "author": "Leo Tolstoy", "publishYear": 1867, "title": "War and Peace"}\
"""));
}
}

private static List<BsonDocument> getCollectionDocuments() {
var documents = new ArrayList<BsonDocument>();
collection.find().sort(Sorts.ascending("_id")).into(documents);
return documents;
}

private void assertCollectionContainsOnly(BsonDocument expectedDoc) {
private static void assertCollectionContainsOnly(BsonDocument expectedDoc) {
assertThat(getCollectionDocuments()).asInstanceOf(LIST).singleElement().isEqualTo(expectedDoc);
}

@Entity(name = "Book")
@Entity
@Table(name = "books")
static class Book {
@Id
Expand All @@ -175,7 +226,22 @@ static class Book {
int publishYear;
}

@Entity(name = "BookWithEmbeddedField")
@Entity
@Table(name = "books")
@DynamicUpdate
static class BookDynamicallyUpdated {
@Id
@Column(name = "_id")
int id;

String title;

String author;

int publishYear;
}

@Entity
@Table(name = "books")
static class BookWithEmbeddedField {
@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
import com.mongodb.hibernate.internal.service.StandardServiceRegistryScopedState;
import com.mongodb.hibernate.internal.translate.mongoast.AstDocument;
import com.mongodb.hibernate.internal.translate.mongoast.AstElement;
import com.mongodb.hibernate.internal.translate.mongoast.AstFieldUpdate;
import com.mongodb.hibernate.internal.translate.mongoast.AstNode;
import com.mongodb.hibernate.internal.translate.mongoast.AstParameterMarker;
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 com.mongodb.hibernate.internal.translate.mongoast.filter.AstComparisonFilterOperation;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFieldOperationFilter;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilter;
import com.mongodb.hibernate.internal.translate.mongoast.filter.AstFilterFieldPath;
import java.io.IOException;
import java.io.StringWriter;
Expand Down Expand Up @@ -117,6 +120,8 @@
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.ast.AbstractRestrictedTableMutation;
import org.hibernate.sql.model.ast.ColumnWriteFragment;
import org.hibernate.sql.model.internal.OptionalTableUpdate;
import org.hibernate.sql.model.internal.TableDeleteCustomSql;
Expand Down Expand Up @@ -200,10 +205,13 @@ <R extends AstNode> R acceptAndYield(SqlAstNode node, AstVisitorValueDescriptor<
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Table Mutation: insertion
// Table Mutation: insert

@Override
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
if (tableInsert.getNumberOfReturningColumns() > 0) {
throw new FeatureNotSupportedException();
}
var tableName = tableInsert.getTableName();
var astElements = new ArrayList<AstElement>(tableInsert.getNumberOfValueBindings());
for (var columnValueBinding : tableInsert.getValueBindings()) {
Expand All @@ -229,30 +237,57 @@ public void visitColumnWriteFragment(ColumnWriteFragment columnWriteFragment) {
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Table Mutation: deletion
// Table Mutation: delete

@Override
public void visitStandardTableDelete(TableDeleteStandard tableDelete) {
if (tableDelete.getWhereFragment() != null) {
throw new FeatureNotSupportedException();
}
var keyFilter = getKeyFilter(tableDelete);
astVisitorValueHolder.yield(
COLLECTION_MUTATION,
new AstDeleteCommand(tableDelete.getMutatingTable().getTableName(), keyFilter));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Table Mutation: update

@Override
public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) {
if (tableUpdate.getNumberOfReturningColumns() > 0) {
throw new FeatureNotSupportedException();
}
if (tableUpdate.getWhereFragment() != null) {
throw new FeatureNotSupportedException();
}
var keyFilter = getKeyFilter(tableUpdate);
var updates = new ArrayList<AstFieldUpdate>(tableUpdate.getNumberOfValueBindings());
for (var valueBinding : tableUpdate.getValueBindings()) {
var columnExpression = valueBinding.getColumnReference().getColumnExpression();
var astValue = acceptAndYield(valueBinding.getValueExpression(), FIELD_VALUE);
updates.add(new AstFieldUpdate(columnExpression, astValue));
}
astVisitorValueHolder.yield(
COLLECTION_MUTATION,
new AstUpdateCommand(tableUpdate.getMutatingTable().getTableName(), keyFilter, updates));
}

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

if (tableDelete.getNumberOfKeyBindings() > 1) {
if (tableMutation.getNumberOfKeyBindings() > 1) {
throw new FeatureNotSupportedException("MongoDB doesn't support '_id' spanning multiple columns");
}
assertTrue(tableDelete.getNumberOfKeyBindings() == 1);
var keyBinding = tableDelete.getKeyBindings().get(0);
assertTrue(tableMutation.getNumberOfKeyBindings() == 1);
var keyBinding = tableMutation.getKeyBindings().get(0);

var tableName = tableDelete.getMutatingTable().getTableName();
var astFilterFieldPath =
new AstFilterFieldPath(keyBinding.getColumnReference().getColumnExpression());
var astValue = acceptAndYield(keyBinding.getValueExpression(), FIELD_VALUE);
var keyFilter = new AstFieldOperationFilter(astFilterFieldPath, new AstComparisonFilterOperation(EQ, astValue));
astVisitorValueHolder.yield(COLLECTION_MUTATION, new AstDeleteCommand(tableName, keyFilter));
return new AstFieldOperationFilter(astFilterFieldPath, new AstComparisonFilterOperation(EQ, astValue));
}

@Override
Expand Down Expand Up @@ -606,11 +641,6 @@ public void visitCustomTableDelete(TableDeleteCustomSql tableDeleteCustomSql) {
throw new FeatureNotSupportedException();
}

@Override
public void visitStandardTableUpdate(TableUpdateStandard tableUpdateStandard) {
throw new FeatureNotSupportedException("TODO-HIBERNATE-19 https://jira.mongodb.org/browse/HIBERNATE-19");
}

@Override
public void visitOptionalTableUpdate(OptionalTableUpdate optionalTableUpdate) {
throw new FeatureNotSupportedException();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.model.ast.TableDelete;
import org.hibernate.sql.model.ast.TableInsert;
import org.hibernate.sql.model.ast.TableMutation;
import org.hibernate.sql.model.jdbc.JdbcMutationOperation;
import org.jspecify.annotations.Nullable;
Expand All @@ -38,20 +36,10 @@ final class TableMutationMqlTranslator<O extends JdbcMutationOperation> extends
}

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

if (tableMutation instanceof TableInsert || tableMutation instanceof TableDelete) {
return translateTableMutation();
} else {
// TODO-HIBERNATE-19 https://jira.mongodb.org/browse/HIBERNATE-19
return (O) new NoopJdbcMutationOperation();
}
}

private O translateTableMutation() {
var rootAstNode = acceptAndYield(tableMutation, COLLECTION_MUTATION);
return tableMutation.createMutationOperation(renderMongoAstNode(rootAstNode), getParameterBinders());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import java.util.List;
import org.bson.BsonWriter;

public record AstDocument(List<? extends AstElement> elements) implements AstValue {
public record AstDocument(List<AstElement> elements) implements AstValue {
@Override
public void render(BsonWriter writer) {
writer.writeStartDocument();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.mongoast;

import org.bson.BsonWriter;

public record AstFieldUpdate(String name, AstValue value) implements AstNode {
@Override
public void render(BsonWriter writer) {
writer.writeName(name);
value.render(writer);
}
}
Loading