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}});");
+ }
+}