Skip to content

Commit

Permalink
Issue liquibase#90 Convert FindOneAndUpdateStatement to use runCommand
Browse files Browse the repository at this point in the history
  • Loading branch information
jsonking committed Mar 6, 2021
1 parent cd36861 commit a261e1f
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,67 +20,65 @@
* #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;
import org.bson.Document;
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 <a href="https://docs.mongodb.com/manual/reference/command/findAndModify//">findAndModify</a>
*
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public class FindOneAndUpdateStatement extends AbstractCollectionStatement
implements NoSqlExecuteStatement<MongoLiquibaseDatabase>, NoSqlUpdateStatement<MongoLiquibaseDatabase> {
public class FindOneAndUpdateStatement extends AbstractRunCommandStatement
implements NoSqlUpdateStatement<MongoLiquibaseDatabase> {

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<Document> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Document> 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<Document> 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<Document> docs = mongoDatabase.getCollection(collectionName).find()
.sort(new Document("name",1));
assertThat(docs).hasSize(2);
MongoCursor<Document> 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}});");
}
}

0 comments on commit a261e1f

Please sign in to comment.