Skip to content
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

Issue 90: Convert CreateIndex to execute as a runCommand #95

Merged
merged 1 commit into from
Feb 7, 2021
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 @@ -34,7 +34,7 @@
@DatabaseChange(name = "createCollection",
description = "Create collection. Supports all options available: " +
"https://docs.mongodb.com/manual/reference/method/db.createCollection/#db.createCollection\n" +
"https://docs.mongodb.com/manual/reference/method/db.runCommand/#db.runCommand",
"https://docs.mongodb.com/manual/reference/command/create/",
priority = ChangeMetaData.PRIORITY_DEFAULT, appliesTo = "collection")
@NoArgsConstructor
@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@

@DatabaseChange(name = "createIndex",
description = "Creates index for collection" +
"https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#db.collection.createIndex",
"https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#db.collection.createIndex\n" +
"https://docs.mongodb.com/manual/reference/command/createIndexes/",
priority = ChangeMetaData.PRIORITY_DATABASE, appliesTo = "collection")
@NoArgsConstructor
@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public abstract class AbstractRunCommandStatement extends AbstractMongoStatement

public static final String COMMAND_NAME = "runCommand";
public static final String SHELL_DB_PREFIX = "db.";
public static final String OK = "ok";
public static final String WRITE_ERRORS = "writeErrors";

@Getter
protected final Document command;
Expand Down Expand Up @@ -66,8 +68,11 @@ public Document run(final MongoLiquibaseDatabase database) {
* Check the response and throw an appropriate exception if the command was not successful
*/
protected void checkResponse(final Document responseDocument) throws MongoException {
final List<Document> writeErrors = responseDocument.getList(BsonUtils.WRITE_ERRORS, Document.class);
if (nonNull(writeErrors) && !writeErrors.isEmpty()) {
final Double ok = responseDocument.getDouble(OK);
final List<Document> writeErrors = responseDocument.getList(WRITE_ERRORS, Document.class);

if (nonNull(ok) && !ok.equals(1.0d)
|| nonNull(writeErrors) && !writeErrors.isEmpty()) {
throw new MongoException("Command failed. The full response is " + responseDocument.toJson());
}
}
Expand Down
26 changes: 4 additions & 22 deletions src/main/java/liquibase/ext/mongodb/statement/BsonUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import com.mongodb.DBRefCodecProvider;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.model.IndexOptions;
import lombok.NoArgsConstructor;
import org.bson.Document;
import org.bson.UuidRepresentation;
Expand All @@ -37,7 +36,6 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static java.util.Objects.nonNull;
import static java.util.Optional.ofNullable;
Expand All @@ -48,9 +46,6 @@
@NoArgsConstructor(access = PRIVATE)
public final class BsonUtils {

public static final String WRITE_ERRORS = "writeErrors";
public static final String DOCUMENTS = "documents";

public static final DocumentCodec DOCUMENT_CODEC =
new DocumentCodec(fromProviders(
new UuidCodecProvider(UuidRepresentation.STANDARD),
Expand All @@ -59,6 +54,8 @@ public final class BsonUtils {
new DocumentCodecProvider(),
new DBRefCodecProvider()));

public static final String ITEMS = "items";

public static CodecRegistry uuidCodecRegistry() {
return CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)),
Expand All @@ -77,28 +74,13 @@ public static Document orEmptyDocument(final String json) {
public static List<Document> orEmptyList(final String json) {
return (
ofNullable(trimToNull(json))
.map(jn -> "{ items: " + jn + "}")
.map(jn -> "{ " + ITEMS + ": " + jn + "}")
.map(s -> Document.parse(s, DOCUMENT_CODEC))
.map(d -> d.getList("items", Document.class, new ArrayList<>()))
.map(d -> d.getList(ITEMS, Document.class, new ArrayList<>()))
.orElseGet(ArrayList::new)
);
}

public static IndexOptions orEmptyIndexOptions(final Document options) {
//TODO: add POJO codec
final IndexOptions indexOptions = new IndexOptions();
if (options.containsKey("unique") && options.getBoolean("unique")) {
indexOptions.unique(true);
}
if (options.containsKey("name")) {
indexOptions.name(options.getString("name"));
}
if (options.containsKey("expireAfterSeconds")) {
indexOptions.expireAfter(options.getLong("expireAfterSeconds"), TimeUnit.SECONDS);
}
return indexOptions;
}

public static String toJson(final Document document) {
return ofNullable(document).map(Document::toJson).orElse(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,52 +26,46 @@
import lombok.Getter;
import org.bson.Document;

import static java.util.Optional.ofNullable;
import static liquibase.ext.mongodb.statement.AbstractRunCommandStatement.SHELL_DB_PREFIX;
import static java.util.Collections.singletonList;
import static java.util.Objects.nonNull;
import static liquibase.ext.mongodb.statement.BsonUtils.orEmptyDocument;
import static liquibase.ext.mongodb.statement.BsonUtils.toCommand;

/**
* Creates a index via the database runCommand method
* For a list of supported options see the reference page:
* @see <a href="https://docs.mongodb.com/manual/reference/command/createIndexes//">createIndexes</a>
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public class CreateIndexStatement extends AbstractCollectionStatement
public class CreateIndexStatement extends AbstractRunCommandStatement
implements NoSqlExecuteStatement<MongoLiquibaseDatabase> {

public static final String COMMAND_NAME = "createIndex";
public static final String RUN_COMMAND_NAME = "createIndexes";
private static final String KEY = "key";
private static final String INDEXES = "indexes";

private final Document keys;
private final Document options;
@Override
public String getRunCommandName() {
return RUN_COMMAND_NAME;
}

public CreateIndexStatement(final String collectionName, final Document keys, final Document options) {
super(collectionName);
this.keys = keys;
this.options = options;
super(toCommand(RUN_COMMAND_NAME, collectionName, combine(keys, options)));
}

public CreateIndexStatement(final String collectionName, final String keys, final String options) {
this(collectionName, orEmptyDocument(keys), orEmptyDocument(options));
}

@Override
public String getCommandName() {
return COMMAND_NAME;
}
private static Document combine(final Document key, final Document options) {

@Override
public String toJs() {
return
SHELL_DB_PREFIX
+ getCollectionName()
+ ". "
+ getCommandName()
+ "("
+ ofNullable(keys).map(Document::toJson).orElse(null)
+ ", "
+ ofNullable(options).map(Document::toJson).orElse(null)
+ ");";
}
final Document indexDocument = new Document(KEY, key);
if (nonNull(options)) {
indexDocument.putAll(options);
}

@Override
public void execute(final MongoLiquibaseDatabase database) {
getMongoDatabase(database).getCollection(collectionName).createIndex(keys, BsonUtils.orEmptyIndexOptions(options));
return new Document(INDEXES, singletonList(indexDocument));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,27 @@
public class InsertManyStatement extends AbstractRunCommandStatement {

public static final String RUN_COMMAND_NAME = "insert";
public static final String DOCUMENTS = "documents";

private final List<Document> documents;
private final Document options;
@Override
public String getRunCommandName() {
return RUN_COMMAND_NAME;
}

public InsertManyStatement(final String collectionName, final String documents, final String options) {
this(collectionName, new ArrayList<>(orEmptyList(documents)), orEmptyDocument(options));
}

public InsertManyStatement(final String collectionName, final List<Document> documents, final Document options) {
super(BsonUtils.toCommand(RUN_COMMAND_NAME, collectionName, combine(documents, options)));
this.documents = documents;
this.options = options;
}

private static Document combine(final List<Document> documents, final Document options) {
final Document combined = new Document(BsonUtils.DOCUMENTS, documents);
final Document combined = new Document(DOCUMENTS, documents);
if (nonNull(options)) {
combined.putAll(options);
}
return combined;
}

@Override
public String getRunCommandName() {
return RUN_COMMAND_NAME;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
/**
* Inserts a document via the database runCommand method
* For a list of supported options see the reference page:
* https://docs.mongodb.com/manual/reference/command/insert/
* @see <a href="https://docs.mongodb.com/manual/reference/command/insert/">Insert</a>
*/
@Getter
@EqualsAndHashCode(callSuper = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@

<xsd:sequence>
<xsd:element name="keys" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<xsd:element name="options" type="xsd:string" minOccurs="0" maxOccurs="1"/>
<xsd:element name="options" type="xsd:string" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>

<xsd:attribute name="collectionName" type="xsd:string" use="required"/>
Expand Down
12 changes: 0 additions & 12 deletions src/test/java/liquibase/ext/mongodb/TestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,4 @@ public static List<ChangeSet> getChangesets(final String changeSetPath, final Mo
parser.parse(changeSetPath, new ChangeLogParameters(database), resourceAccessor);
return changeLog.getChangeSets();
}

/**
* Helper to format and convert a string containing single quotes to one with double quotes.
* Use this to declare readable strings in tests
* @param singleQuoted a string containing single quotes
* @param args format args
* @return original string formatted with double quotes
*/
public static final String formatDoubleQuoted(final String singleQuoted, final String... args) {
String formatted = String.format(singleQuoted, args);
return formatted.replaceAll("'","\"");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.junit.jupiter.api.Test;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static java.util.stream.Collectors.toList;
import static liquibase.ext.mongodb.statement.BsonUtils.DOCUMENT_CODEC;
Expand Down Expand Up @@ -76,10 +75,4 @@ void uuidParseTest() {
.isEqualTo(uuid1);
}

@Test
void orEmptyIndexOptionsTest() {
assertThat(BsonUtils.orEmptyIndexOptions(
BsonUtils.orEmptyDocument("{expireAfterSeconds: NumberLong(\"60\")}")).getExpireAfter(TimeUnit.SECONDS))
.isEqualTo(60L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@

import static liquibase.ext.mongodb.TestUtils.COLLECTION_NAME_1;
import static liquibase.ext.mongodb.TestUtils.EMPTY_OPTION;
import static liquibase.ext.mongodb.TestUtils.formatDoubleQuoted;
import static org.assertj.core.api.Assertions.assertThat;


class CreateCollectionStatementTest {

// Some of the extra options that create collection supports
private static final String CREATE_OPTIONS = "'capped': true, 'size': 100, 'max': 200";
private static final String CREATE_OPTIONS = "\"capped\": true, \"size\": 100, \"max\": 200";

private String collectionName;

Expand All @@ -43,7 +42,7 @@ public void createCollectionName() {

@Test
void toStringJsWithoutOptions() {
String expected = formatDoubleQuoted("db.runCommand({'create': '%s'});", collectionName);
String expected = String.format("db.runCommand({\"create\": \"%s\"});", collectionName);
final CreateCollectionStatement statement = new CreateCollectionStatement(collectionName, EMPTY_OPTION);
assertThat(statement.toJs())
.isEqualTo(expected)
Expand All @@ -53,7 +52,7 @@ void toStringJsWithoutOptions() {
@Test
void toStringJsWithOptions() {
String options = String.format("{ %s }", CREATE_OPTIONS);
String expected = formatDoubleQuoted("db.runCommand({'create': '%s', %s});", collectionName, CREATE_OPTIONS);
String expected = String.format("db.runCommand({\"create\": \"%s\", %s});", collectionName, CREATE_OPTIONS);
final CreateCollectionStatement statement = new CreateCollectionStatement(collectionName, options);
assertThat(statement.toJs())
.isEqualTo(expected)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* #L%
*/

import com.mongodb.MongoException;
import liquibase.ext.AbstractMongoIntegrationTest;
import org.bson.Document;
import org.junit.jupiter.api.Test;
Expand All @@ -28,17 +29,18 @@

import static liquibase.ext.mongodb.TestUtils.COLLECTION_NAME_1;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

class CreateIndexStatementIT extends AbstractMongoIntegrationTest {

@Test
void toStringJs() {
final String indexName = "locale_indx";
final CreateIndexStatement createIndexStatement = new CreateIndexStatement(COLLECTION_NAME_1, "{ locale: 1 }",
"{ name: \"" + indexName + "\", unique: true}");
"{ name: \"" + indexName + "\", unique: true}");
assertThat(createIndexStatement.toString())
.isEqualTo(createIndexStatement.toJs())
.isEqualTo("db.collectionName. createIndex({\"locale\": 1}, {\"name\": \"locale_indx\", \"unique\": true});");
.isEqualTo(createIndexStatement.toJs())
.isEqualTo("db.runCommand({\"createIndexes\": \"collectionName\", \"indexes\": [{\"key\": {\"locale\": 1}, \"name\": \"locale_indx\", \"unique\": true}]});");
}

@Test
Expand All @@ -48,16 +50,29 @@ void execute() {
mongoDatabase.createCollection(COLLECTION_NAME_1);
mongoDatabase.getCollection(COLLECTION_NAME_1).insertOne(initialDocument);
final CreateIndexStatement createIndexStatement = new CreateIndexStatement(COLLECTION_NAME_1, "{ locale: 1 }",
"{ name: \"" + indexName + "\", unique: true, expireAfterSeconds: NumberLong(\"30\") }");
"{ name: \"" + indexName + "\", unique: true, expireAfterSeconds: NumberLong(\"30\") }");
createIndexStatement.execute(database);

final Document document = StreamSupport.stream(mongoDatabase.getCollection(COLLECTION_NAME_1).listIndexes().spliterator(), false)
.filter(doc -> doc.get("name").equals(indexName))
.findAny()
.orElseThrow(() -> new IllegalStateException("Index not found"));
.filter(doc -> doc.get("name").equals(indexName))
.findAny()
.orElseThrow(() -> new IllegalStateException("Index not found"));

assertThat(document.get("unique")).isEqualTo(true);
assertThat(document.get("key")).isEqualTo(Document.parse("{ locale: 1 }"));
assertThat(document.get("expireAfterSeconds")).isEqualTo(30L);
assertThat(document.get("unique")).isEqualTo(true);
assertThat(document.get("key")).isEqualTo(Document.parse("{ locale: 1 }"));
assertThat(document.get("expireAfterSeconds")).isEqualTo(30L);

// Same index name exception
final CreateIndexStatement createDuplicateNameIndexStatement = new CreateIndexStatement(COLLECTION_NAME_1, "{ otherField: 1 }", "{ name: \"" + indexName + "\" }");
assertThatExceptionOfType(MongoException.class).isThrownBy(() -> createDuplicateNameIndexStatement.execute(database))
.withMessageStartingWith("Command failed with error")
.withMessageContaining("Index must have unique name");

// Same index name exception
final CreateIndexStatement createSameFieldsIndexStatement = new CreateIndexStatement(COLLECTION_NAME_1, "{ locale: 1 }", "{ name: \"otherName\" }");
assertThatExceptionOfType(MongoException.class).isThrownBy(() -> createSameFieldsIndexStatement.execute(database))
.withMessageStartingWith("Command failed with error")
.withMessageContaining("Index")
.withMessageContaining("already exists with different options");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static liquibase.ext.mongodb.TestUtils.formatDoubleQuoted;
import static liquibase.ext.mongodb.TestUtils.COLLECTION_NAME_1;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
Expand All @@ -50,10 +49,10 @@ public void createCollectionName() {
@Test
void toStringTest() {

String expected = formatDoubleQuoted(
"db.runCommand({'insert': '%s', " +
"'documents': [{'key1': 'value1'}, {'key1': 'value2'}], " +
"'ordered': false});", collectionName);
String expected = String.format(
"db.runCommand({\"insert\": \"%s\", " +
"\"documents\": [{\"key1\": \"value1\"}, {\"key1\": \"value2\"}], " +
"\"ordered\": false});", collectionName);

final InsertManyStatement statement = new InsertManyStatement(
collectionName,
Expand Down
Loading