diff --git a/src/main/java/liquibase/ext/mongodb/statement/FindOneAndUpdateStatement.java b/src/main/java/liquibase/ext/mongodb/statement/FindOneAndUpdateStatement.java index 4888455d..aeb0070c 100644 --- a/src/main/java/liquibase/ext/mongodb/statement/FindOneAndUpdateStatement.java +++ b/src/main/java/liquibase/ext/mongodb/statement/FindOneAndUpdateStatement.java @@ -20,10 +20,7 @@ * #L% */ -import com.mongodb.client.MongoCollection; -import com.mongodb.client.model.FindOneAndUpdateOptions; import liquibase.ext.mongodb.database.MongoLiquibaseDatabase; -import liquibase.nosql.statement.NoSqlExecuteStatement; import liquibase.nosql.statement.NoSqlUpdateStatement; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -31,56 +28,57 @@ import org.bson.conversions.Bson; import static java.util.Objects.isNull; -import static java.util.Optional.ofNullable; -import static liquibase.ext.mongodb.statement.AbstractRunCommandStatement.SHELL_DB_PREFIX; +import static java.util.Objects.nonNull; +import static liquibase.ext.mongodb.statement.BsonUtils.toCommand; +/** + * Finds and updates a single document via the database runCommand method + * NOTE: This does not return the original document, + * instead returns 1 if a document was updated, else 0 + * + * For a list of supported options see the reference page: + * @see findAndModify + * + */ @Getter @EqualsAndHashCode(callSuper = true) -public class FindOneAndUpdateStatement extends AbstractCollectionStatement - implements NoSqlExecuteStatement, NoSqlUpdateStatement { +public class FindOneAndUpdateStatement extends AbstractRunCommandStatement + implements NoSqlUpdateStatement { - public static final String COMMAND_NAME = "updateLastTag"; - - private final Bson filter; - private final Bson document; - private final Bson sort; + public static final String RUN_COMMAND_NAME = "findAndModify"; + public static final String QUERY = "query"; + public static final String UPDATE = "update"; + public static final String SORT = "sort"; + public static final String VALUE = "value"; public FindOneAndUpdateStatement(final String collectionName, final Bson filter, final Bson document, final Bson sort) { - super(collectionName); - this.filter = filter; - this.document = document; - this.sort = sort; + this(collectionName, combine(filter, document, sort)); } - @Override - public String getCommandName() { - return COMMAND_NAME; + public FindOneAndUpdateStatement(final String collectionName, Document options) { + super(toCommand(RUN_COMMAND_NAME, collectionName, options)); } @Override - public String toJs() { - return - SHELL_DB_PREFIX + - getCollectionName() + - "." + - getCommandName() + - "(" + - ofNullable(filter).map(Bson::toString).orElse(null) + - ", " + - ofNullable(document).map(Bson::toString).orElse(null) + - ", " + - ofNullable(sort).map(Bson::toString).orElse(null) + - ");"; + public String getRunCommandName() { + return RUN_COMMAND_NAME; } - @Override - public void execute(final MongoLiquibaseDatabase database) { - update(database); + private static Document combine(final Bson filter, final Bson document, final Bson sort) { + final Document combined = new Document(QUERY, filter); + if(nonNull(document)) { combined.put(UPDATE, document); } + if(nonNull(sort)) { combined.put(SORT, sort); } + return combined; } + /** + * Executes the findAndModify operation + * @param database the database to run against + * @return 1 if a document was modified else 0 + */ @Override public int update(final MongoLiquibaseDatabase database) { - final MongoCollection collection = database.getMongoDatabase().getCollection(getCollectionName()); - return isNull(collection.findOneAndUpdate(filter, document, new FindOneAndUpdateOptions().sort(sort))) ? 0 : 1; + Document response = super.run(database); + return isNull(response.get(VALUE)) ? 0 : 1; } } diff --git a/src/test/java/liquibase/ext/mongodb/statement/FindOneAndUpdateStatementIT.java b/src/test/java/liquibase/ext/mongodb/statement/FindOneAndUpdateStatementIT.java new file mode 100644 index 00000000..16fedecb --- /dev/null +++ b/src/test/java/liquibase/ext/mongodb/statement/FindOneAndUpdateStatementIT.java @@ -0,0 +1,117 @@ +package liquibase.ext.mongodb.statement; + +/*- + * #%L + * Liquibase MongoDB Extension + * %% + * Copyright (C) 2021 Mastercard + * %% + * 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. + * #L% + */ + +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCursor; +import liquibase.ext.AbstractMongoIntegrationTest; +import org.bson.Document; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static liquibase.ext.mongodb.TestUtils.COLLECTION_NAME_1; +import static org.assertj.core.api.Assertions.assertThat; + +class FindOneAndUpdateStatementIT extends AbstractMongoIntegrationTest { + + private final Document first = new Document("name", "first"); + private final Document second = new Document("name", "second"); + private final Document modified = new Document("name", "modified"); + private final Document update = new Document("$set", modified); + private final Document sort = new Document("name", -1); + private final Document emptyDocument = new Document(); + + private String collectionName; + + @BeforeEach + public void createCollectionName() { + collectionName = COLLECTION_NAME_1 + System.nanoTime(); + } + + @Test + public void testUpdateWhenNoDocumentFound() { + int updated = new FindOneAndUpdateStatement(collectionName, emptyDocument, update, emptyDocument) + .update(database); + assertThat(updated).isEqualTo(0); + } + + @Test + public void testUpdateWhenDocumentFound() { + + new InsertOneStatement(collectionName, first).execute(database); + + int updated = new FindOneAndUpdateStatement(collectionName, emptyDocument, update, emptyDocument) + .update(database); + assertThat(updated).isEqualTo(1); + + final FindIterable docs = mongoDatabase.getCollection(collectionName).find(); + assertThat(docs).hasSize(1); + assertThat(docs.iterator().next()) + .containsEntry("name", "modified"); + } + + @Test + public void testUpdateWithMatchingFilter() { + + new InsertOneStatement(collectionName, first).execute(database); + new InsertOneStatement(collectionName, second).execute(database); + + int updated = new FindOneAndUpdateStatement(collectionName, second, update, emptyDocument) + .update(database); + assertThat(updated).isEqualTo(1); + + final FindIterable docs = mongoDatabase.getCollection(collectionName).find(modified); + assertThat(docs).hasSize(1); + assertThat(docs.iterator().next()) + .containsEntry("name", "modified"); + } + + @Test + public void testUpdateWhenDocumentFoundWithSort() { + + new InsertOneStatement(collectionName, first).execute(database); + new InsertOneStatement(collectionName, second).execute(database); + + int updated = new FindOneAndUpdateStatement(collectionName, emptyDocument, update, sort) + .update(database); + assertThat(updated).isEqualTo(1); + + final FindIterable docs = mongoDatabase.getCollection(collectionName).find() + .sort(new Document("name",1)); + assertThat(docs).hasSize(2); + MongoCursor iterator = docs.iterator(); + assertThat(iterator.next()) + .containsEntry("name", "first"); + assertThat(iterator.next()) + .containsEntry("name", "modified"); + } + + @Test + void toStringJs() { + final FindOneAndUpdateStatement statement = new FindOneAndUpdateStatement(COLLECTION_NAME_1, first, modified, sort); + assertThat(statement.toJs()) + .isEqualTo(statement.toString()) + .isEqualTo("db.runCommand({\"findAndModify\": \"collectionName\", " + + "\"query\": {\"name\": \"first\"}, " + + "\"update\": {\"name\": \"modified\"}, " + + "\"sort\": {\"name\": -1}});"); + } +}