diff --git a/README.md b/README.md index ad96ee9..d1311c1 100644 --- a/README.md +++ b/README.md @@ -50,19 +50,22 @@ All subsequent changes are applied to the created DB. * createContainer [REST](https://docs.microsoft.com/en-us/rest/api/cosmos-db/create-a-collection) [SDK](https://docs.microsoft.com/en-us/java/api/com.azure.cosmos.cosmosdatabase.createcontainer?view=azure-java-stable)

-Creates a Cosmos container while passing additional request options. +Creates a Cosmos container while passing additional request properties. +There is a possibility to pass throughput properties either manual as a number or auto as a json. There is a flag to skip if exists and do not fail. -If no options are specified then a ``/null`` partition key path is the default one. +If no properties are specified then a ``/null`` partition key path is the default one.

* replaceContainer [REST](https://docs.microsoft.com/en-us/rest/api/cosmos-db/replace-a-collection) [SDK](https://docs.microsoft.com/en-us/java/api/com.azure.cosmos.cosmoscontainer.replace?view=azure-java-stable)

-Replaces the container properties by container name. +Replaces the container properties by container id. +There is a possibility to pass throughput properties either manual as a number or auto as a json. +If only properties specified no throughput properties will be amended and viceversa.

* deleteContainer [REST](https://docs.microsoft.com/en-us/rest/api/cosmos-db/delete-a-collection) [SDK](https://docs.microsoft.com/en-us/java/api/com.azure.cosmos.cosmoscontainer.delete?view=azure-java-stable)

-Deletes the Cosmos container by name. +Deletes the Cosmos container by id. There is a flag to skip if missing and do not fail.

diff --git a/src/main/java/liquibase/ext/cosmosdb/change/CreateContainerChange.java b/src/main/java/liquibase/ext/cosmosdb/change/CreateContainerChange.java index 16c9ff7..2b79f84 100644 --- a/src/main/java/liquibase/ext/cosmosdb/change/CreateContainerChange.java +++ b/src/main/java/liquibase/ext/cosmosdb/change/CreateContainerChange.java @@ -23,16 +23,12 @@ import liquibase.change.ChangeMetaData; import liquibase.change.DatabaseChange; import liquibase.database.Database; -import liquibase.ext.cosmosdb.statement.CreateContainerIfNotExistsStatement; import liquibase.ext.cosmosdb.statement.CreateContainerStatement; import liquibase.statement.SqlStatement; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import static java.lang.Boolean.FALSE; -import static java.util.Optional.ofNullable; - @DatabaseChange(name = "createContainer", description = "Create container " + "https://docs.microsoft.com/en-us/java/api/com.azure.cosmos.cosmosdatabase.createcontainer?view=azure-java-stable\n" + @@ -45,6 +41,7 @@ public class CreateContainerChange extends AbstractCosmosChange { private String containerName; private String options; + private String throughput; private Boolean skipExisting; @Override @@ -56,7 +53,7 @@ public String getConfirmationMessage() { public SqlStatement[] generateStatements(final Database database) { final CreateContainerStatement createContainerStatement = - ofNullable(skipExisting).orElse(FALSE) ? new CreateContainerIfNotExistsStatement(containerName, options) : new CreateContainerStatement(containerName, options); + new CreateContainerStatement(containerName, options, throughput, skipExisting); return new SqlStatement[]{ createContainerStatement diff --git a/src/main/java/liquibase/ext/cosmosdb/change/ReplaceContainerChange.java b/src/main/java/liquibase/ext/cosmosdb/change/ReplaceContainerChange.java index 0ae7dcf..c4a82c7 100644 --- a/src/main/java/liquibase/ext/cosmosdb/change/ReplaceContainerChange.java +++ b/src/main/java/liquibase/ext/cosmosdb/change/ReplaceContainerChange.java @@ -24,6 +24,7 @@ import liquibase.change.DatabaseChange; import liquibase.database.Database; import liquibase.ext.cosmosdb.statement.CreateContainerStatement; +import liquibase.ext.cosmosdb.statement.ReplaceContainerStatement; import liquibase.statement.SqlStatement; import lombok.Getter; import lombok.NoArgsConstructor; @@ -42,6 +43,7 @@ public class ReplaceContainerChange extends AbstractCosmosChange { private String containerName; private String options; + private String throughput; @Override public String getConfirmationMessage() { @@ -52,7 +54,7 @@ public String getConfirmationMessage() { public SqlStatement[] generateStatements(final Database database) { final CreateContainerStatement createContainerStatement - = new CreateContainerStatement(containerName, options); + = new ReplaceContainerStatement(containerName, options, throughput); return new SqlStatement[]{ createContainerStatement diff --git a/src/main/java/liquibase/ext/cosmosdb/statement/AbstractNoSqlStatement.java b/src/main/java/liquibase/ext/cosmosdb/statement/AbstractNoSqlStatement.java index a2fc14c..feb5245 100644 --- a/src/main/java/liquibase/ext/cosmosdb/statement/AbstractNoSqlStatement.java +++ b/src/main/java/liquibase/ext/cosmosdb/statement/AbstractNoSqlStatement.java @@ -38,4 +38,9 @@ public boolean skipOnUnsupported() { public abstract String toJs(); + @Override + public String toString() { + return toJs(); + } + } diff --git a/src/main/java/liquibase/ext/cosmosdb/statement/CreateContainerIfNotExistsStatement.java b/src/main/java/liquibase/ext/cosmosdb/statement/CreateContainerIfNotExistsStatement.java deleted file mode 100644 index d736370..0000000 --- a/src/main/java/liquibase/ext/cosmosdb/statement/CreateContainerIfNotExistsStatement.java +++ /dev/null @@ -1,55 +0,0 @@ -package liquibase.ext.cosmosdb.statement; - -/*- - * #%L - * Liquibase CosmosDB Extension - * %% - * Copyright (C) 2020 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.azure.cosmos.CosmosDatabase; -import com.azure.cosmos.models.CosmosContainerProperties; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; - -@AllArgsConstructor -@Getter -@EqualsAndHashCode(callSuper = true) -public class CreateContainerIfNotExistsStatement extends CreateContainerStatement { - - public static final String COMMAND_NAME = "createContainerIfNotExists"; - - public CreateContainerIfNotExistsStatement(final String containerName, final String options) { - super(containerName, options); - } - - @Override - public String getCommandName() { - return COMMAND_NAME; - } - - @Override - public void execute(final CosmosDatabase cosmosDatabase) { - final CosmosContainerProperties cosmosContainerProperties = JsonUtils.toContainerProperties(containerName, options); - cosmosDatabase.createContainerIfNotExists(cosmosContainerProperties); - } - - @Override - public String toString() { - return toJs(); - } -} diff --git a/src/main/java/liquibase/ext/cosmosdb/statement/CreateContainerStatement.java b/src/main/java/liquibase/ext/cosmosdb/statement/CreateContainerStatement.java index aff3148..9646813 100644 --- a/src/main/java/liquibase/ext/cosmosdb/statement/CreateContainerStatement.java +++ b/src/main/java/liquibase/ext/cosmosdb/statement/CreateContainerStatement.java @@ -22,11 +22,17 @@ import com.azure.cosmos.CosmosDatabase; import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.ThroughputProperties; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; +import static java.lang.Boolean.FALSE; +import static java.util.Optional.ofNullable; +import static liquibase.ext.cosmosdb.statement.JsonUtils.toContainerProperties; +import static liquibase.ext.cosmosdb.statement.JsonUtils.toThroughputProperties; + @AllArgsConstructor @NoArgsConstructor @Getter @@ -35,8 +41,18 @@ public class CreateContainerStatement extends AbstractNoSqlStatement implements public static final String COMMAND_NAME = "createContainer"; - protected String containerName; - protected String options; + private String containerName; + private String options; + private String throughput; + private Boolean skipExisting; + + public CreateContainerStatement(final String containerName, final String options, final String throughput) { + this(containerName, options, throughput, FALSE); + } + + public CreateContainerStatement(final String containerName, final String options) { + this(containerName, options, null); + } public CreateContainerStatement(final String containerName) { this(containerName, null); @@ -53,22 +69,25 @@ public String toJs() { "db." + getCommandName() + "(" - + containerName + + getContainerName() + + ", " + + getOptions() + ", " - + options + + getThroughput() + + ", " + + getSkipExisting() + ");"; } @Override public void execute(final CosmosDatabase cosmosDatabase) { - - final CosmosContainerProperties cosmosContainerProperties = JsonUtils.toContainerProperties(containerName, options); - cosmosDatabase.createContainer(cosmosContainerProperties); + final CosmosContainerProperties cosmosContainerProperties = toContainerProperties(getContainerName(), getOptions()); + final ThroughputProperties throughputProperties = toThroughputProperties(getThroughput()); + if (ofNullable(skipExisting).orElse(FALSE)) { + cosmosDatabase.createContainerIfNotExists(cosmosContainerProperties, throughputProperties); + } else { + cosmosDatabase.createContainer(cosmosContainerProperties, throughputProperties); + } } - - @Override - public String toString() { - return toJs(); - } } diff --git a/src/main/java/liquibase/ext/cosmosdb/statement/JsonUtils.java b/src/main/java/liquibase/ext/cosmosdb/statement/JsonUtils.java index 3115117..3f4effb 100644 --- a/src/main/java/liquibase/ext/cosmosdb/statement/JsonUtils.java +++ b/src/main/java/liquibase/ext/cosmosdb/statement/JsonUtils.java @@ -24,15 +24,20 @@ import com.azure.cosmos.implementation.JsonSerializable; import com.azure.cosmos.implementation.Utils; import com.azure.cosmos.models.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.ObjectMapper; -import liquibase.util.StringUtil; +import com.fasterxml.jackson.databind.node.ContainerNode; +import com.fasterxml.jackson.databind.node.ValueNode; import lombok.NoArgsConstructor; import com.azure.cosmos.implementation.Document; import java.util.Map; +import static com.azure.cosmos.implementation.Constants.Properties.AUTOPILOT_MAX_THROUGHPUT; import static java.util.Objects.nonNull; import static java.util.Optional.ofNullable; +import static liquibase.util.StringUtil.isNotEmpty; import static liquibase.util.StringUtil.trimToNull; import static lombok.AccessLevel.PRIVATE; @@ -97,7 +102,7 @@ public static Document mergeDocuments(final Document destination, final Document public static CosmosContainerProperties toContainerProperties(final String containerName, final String optionsJson) { final CosmosContainerProperties cosmosContainerProperties = new CosmosContainerProperties(containerName, DEFAULT_PARTITION_KEY_PATH); - if (StringUtil.isNotEmpty(StringUtil.trimToNull(optionsJson))) { + if (isNotEmpty(trimToNull(optionsJson))) { final DocumentCollection documentCollection = new DocumentCollection(optionsJson); if(nonNull(documentCollection.getPartitionKey())) { cosmosContainerProperties.setPartitionKeyDefinition(documentCollection.getPartitionKey()); @@ -120,4 +125,23 @@ public static CosmosContainerProperties toContainerProperties(final String conta } return cosmosContainerProperties; } + + public static ThroughputProperties toThroughputProperties(final String throughput) { + + if (nonNull(trimToNull(throughput))) { + final TreeNode node; + try { + node = OBJECT_MAPPER.readTree(throughput); + } catch (final JsonProcessingException e) { + throw new IllegalArgumentException(String.format("Unable to parse JSON %s", throughput), e); + } + if(node.isValueNode()) { + return ThroughputProperties.createManualThroughput(((ValueNode)node).asInt()); + } + if(node.isContainerNode() && ((ContainerNode)node).has(AUTOPILOT_MAX_THROUGHPUT)) { + return ThroughputProperties.createAutoscaledThroughput(((ValueNode)node.get(AUTOPILOT_MAX_THROUGHPUT)).asInt()); + } + } + return null; + } } diff --git a/src/main/java/liquibase/ext/cosmosdb/statement/ReplaceContainerStatement.java b/src/main/java/liquibase/ext/cosmosdb/statement/ReplaceContainerStatement.java index a600abb..4ad77da 100644 --- a/src/main/java/liquibase/ext/cosmosdb/statement/ReplaceContainerStatement.java +++ b/src/main/java/liquibase/ext/cosmosdb/statement/ReplaceContainerStatement.java @@ -22,10 +22,16 @@ import com.azure.cosmos.CosmosDatabase; import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.ThroughputProperties; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; +import static java.util.Objects.nonNull; +import static liquibase.ext.cosmosdb.statement.JsonUtils.toContainerProperties; +import static liquibase.ext.cosmosdb.statement.JsonUtils.toThroughputProperties; +import static liquibase.util.StringUtil.trimToNull; + @AllArgsConstructor @Getter @EqualsAndHashCode(callSuper = true) @@ -33,8 +39,8 @@ public class ReplaceContainerStatement extends CreateContainerStatement { public static final String COMMAND_NAME = "replaceContainer"; - public ReplaceContainerStatement(final String containerName, final String options) { - super(containerName, options); + public ReplaceContainerStatement(final String containerName, final String options, String throughput) { + super(containerName, options, throughput); } @Override @@ -44,8 +50,14 @@ public String getCommandName() { @Override public void execute(final CosmosDatabase cosmosDatabase) { - final CosmosContainerProperties cosmosContainerProperties = JsonUtils.toContainerProperties(containerName, options); - cosmosDatabase.getContainer(containerName).replace(cosmosContainerProperties); + if(nonNull(trimToNull(getOptions()))) { + final CosmosContainerProperties cosmosContainerProperties = toContainerProperties(getContainerName(), getOptions()); + cosmosDatabase.getContainer(getContainerName()).replace(cosmosContainerProperties); + } + if(nonNull(trimToNull(getThroughput()))) { + final ThroughputProperties throughputProperties = toThroughputProperties(getThroughput()); + cosmosDatabase.getContainer(getContainerName()).replaceThroughput(throughputProperties); + } } @Override diff --git a/src/main/resources/liquibase.parser.core.xml/dbchangelog-ext.xsd b/src/main/resources/liquibase.parser.core.xml/dbchangelog-ext.xsd index 1e6dab6..47953c7 100644 --- a/src/main/resources/liquibase.parser.core.xml/dbchangelog-ext.xsd +++ b/src/main/resources/liquibase.parser.core.xml/dbchangelog-ext.xsd @@ -9,9 +9,10 @@ - + - + + @@ -24,9 +25,10 @@ - - - + + + + diff --git a/src/test/java/liquibase/ext/cosmosdb/CosmosLiquibaseIT.java b/src/test/java/liquibase/ext/cosmosdb/CosmosLiquibaseIT.java index 82bfd01..14c5625 100644 --- a/src/test/java/liquibase/ext/cosmosdb/CosmosLiquibaseIT.java +++ b/src/test/java/liquibase/ext/cosmosdb/CosmosLiquibaseIT.java @@ -23,6 +23,8 @@ import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.models.CosmosContainerProperties; import com.azure.cosmos.models.CosmosStoredProcedureProperties; +import com.azure.cosmos.models.IndexingMode; +import com.azure.cosmos.models.ThroughputProperties; import liquibase.Liquibase; import liquibase.ext.cosmosdb.changelog.CosmosRanChangeSet; import liquibase.resource.ClassLoaderResourceAccessor; @@ -77,7 +79,33 @@ void testUpdateCreateContainer() { final CosmosContainerProperties maximal = containerProperties.stream().filter(c -> c.getId().equals("maximal")).findFirst().orElse(null); assertThat(maximal).isNotNull(); - assertThat(containerProperties).hasSize(5); + assertThat(containerProperties).hasSize(7); + } + + @SneakyThrows + @Test + void testUpdateReplaceContainer() { + final Liquibase liquibase = new Liquibase("liquibase/ext/changelog.replace-container.test.xml", new ClassLoaderResourceAccessor(), cosmosLiquibaseDatabase); + liquibase.update(""); + assertThat(cosmosDatabase.getContainer(cosmosLiquibaseDatabase.getDatabaseChangeLogLockTableName()).read()).isNotNull(); + assertThat(cosmosDatabase.getContainer(cosmosLiquibaseDatabase.getDatabaseChangeLogTableName()).read()).isNotNull(); + + assertThat(cosmosDatabase.getContainer("minimal").read()).isNotNull(); + assertThat(cosmosDatabase.getContainer("minimal").read().getProperties()).isNotNull(); + assertThat(cosmosDatabase.getContainer("minimal").readThroughput()).isNotNull(); + + final CosmosContainerProperties maximalProperties = cosmosDatabase.getContainer("maximal").read().getProperties(); + assertThat(maximalProperties).isNotNull(); + assertThat(maximalProperties.getId()).isEqualTo("maximal"); + //TODO: Review after fixed replace + assertThat(maximalProperties.getIndexingPolicy().getIndexingMode()).isEqualTo(IndexingMode.CONSISTENT); + assertThat(maximalProperties.getIndexingPolicy().isAutomatic()).isTrue(); + assertThat(maximalProperties.getIndexingPolicy().getIncludedPaths()).hasSize(1); + assertThat(maximalProperties.getIndexingPolicy().getExcludedPaths()).hasSize(1); + + final ThroughputProperties maximalThroughput = cosmosDatabase.getContainer("maximal").readThroughput().getProperties(); + assertThat(maximalThroughput).isNotNull(); + assertThat(maximalThroughput.getAutoscaleMaxThroughput()).isEqualTo(8000); } @SneakyThrows diff --git a/src/test/java/liquibase/ext/cosmosdb/change/CreateContainerChangeTest.java b/src/test/java/liquibase/ext/cosmosdb/change/CreateContainerChangeTest.java index 1762526..35472cb 100644 --- a/src/test/java/liquibase/ext/cosmosdb/change/CreateContainerChangeTest.java +++ b/src/test/java/liquibase/ext/cosmosdb/change/CreateContainerChangeTest.java @@ -20,62 +20,78 @@ * #L% */ +import liquibase.change.Change; +import liquibase.change.CheckSum; +import liquibase.changelog.ChangeSet; +import liquibase.database.core.PostgresDatabase; +import liquibase.ext.cosmosdb.database.CosmosLiquibaseDatabase; +import liquibase.ext.cosmosdb.statement.CreateContainerStatement; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; +import java.util.List; + +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; +import static liquibase.ext.cosmosdb.TestUtils.getChangeSets; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; class CreateContainerChangeTest extends AbstractCosmosChangeTest { @Test void getConfirmationMessage() { assertThat(new CreateContainerChange().getConfirmationMessage()) - .isNotNull(); + .isNotNull(); + } + + @Test + void supports() { + assertThat(new CreateContainerChange().supports(new CosmosLiquibaseDatabase())).isTrue(); + assertThat(new CreateContainerChange().supports(new PostgresDatabase())).isFalse(); } @Test @SneakyThrows + @SuppressWarnings("unchecked") void testGenerateStatements() { - //TODO: fix + final List changeSets = getChangeSets("liquibase/ext/changelog.create-container.test.xml", database); + + assertThat(changeSets) + .isNotNull() + .hasSize(2); + + assertThat(changeSets.get(0).generateCheckSum()).isEqualTo(CheckSum.parse("8:868c41109ad4c6dd47707e31a3cab8c8")); + assertThat(changeSets.get(0).getChanges()) + .hasSize(5) + .hasOnlyElementsOfType(CreateContainerChange.class) + .extracting(c -> (CreateContainerChange) c) + .extracting( + CreateContainerChange::getContainerName, CreateContainerChange::getSkipExisting, CreateContainerChange::getOptions, CreateContainerChange::getThroughput, + Change::generateCheckSum, c -> c.generateStatements(database).length, c -> c.generateStatements(database)[0].getClass() + ) + .containsExactly( + tuple("minimal", null, null, null, CheckSum.parse("8:327fed49ce36964794facea53c0347d7"), 1, CreateContainerStatement.class), + tuple("minimal", TRUE, null, null, CheckSum.parse("8:eca3cd04f26a6b48945dfb2babf5ceda"), 1, CreateContainerStatement.class), + tuple("skipExisting", TRUE, null, null, CheckSum.parse("8:b3c7d43df39817432d284463344685d3"), 1, CreateContainerStatement.class), + tuple("skipExisting", TRUE, null, null, CheckSum.parse("8:b3c7d43df39817432d284463344685d3"), 1, CreateContainerStatement.class), + tuple("notSkipExisting", FALSE, null, null, CheckSum.parse("8:9d3d714841f4ebe33d6568a1d246efe9"), 1, CreateContainerStatement.class) + ); + + assertThat(changeSets.get(1).generateCheckSum()).isEqualTo(CheckSum.parse("8:d5eba9218b91327627c7174dd6630307")); + assertThat(changeSets.get(1).getChanges()) + .hasSize(2) + .hasOnlyElementsOfType(CreateContainerChange.class) + .extracting(c -> (CreateContainerChange) c) + .extracting( + CreateContainerChange::getContainerName, CreateContainerChange::getSkipExisting, c -> c.getOptions().length(), CreateContainerChange::getThroughput, + Change::generateCheckSum, c -> c.generateStatements(database).length, c -> c.generateStatements(database)[0].getClass() + ) + .containsExactly( + tuple("maximal", null, 1548, "500", CheckSum.parse("8:96ff35442800b98c8054b4c0b19a6817"), 1, CreateContainerStatement.class), + tuple("maximalAutoRU", null, 790, "{\"maxThroughput\": 8000}", CheckSum.parse("8:ee1566c77e15d830c5b90edf1576067a"), 1, CreateContainerStatement.class) + ); -// final List changeSets = getChangesets("liquibase/ext/changelog.create-container.test.xml", database); -// -// assertThat(changeSets) -// .isNotNull() -// .hasSize(1); -// assertThat(changeSets.get(0).getChanges()) -// .hasSize(3) -// .hasOnlyElementsOfType(CreateContainerChange.class); -// -// final CreateContainerChange ch1 = (CreateContainerChange) changeSets.get(0).getChanges().get(0); -// assertThat(ch1.getContainerName()).isEqualTo("createCollectionWithValidatorAndOptionsTest"); -// assertThat(ch1.getPartitionKeyPath()).isNotBlank(); -// final SqlStatement[] sqlStatement1 = ch1.generateStatements(database); -// assertThat(sqlStatement1) -// .hasSize(1); -// assertThat(((CreateContainerStatement) sqlStatement1[0])) -// .hasNoNullFieldsOrProperties() -// .hasFieldOrPropertyWithValue("collectionName", "createCollectionWithValidatorAndOptionsTest"); -// -// final CreateContainerChange ch2 = (CreateContainerChange) changeSets.get(0).getChanges().get(1); -// assertThat(ch2.getContainerName()).isEqualTo("createCollectionWithEmptyValidatorTest"); -// assertThat(ch2.getPartitionKeyPath()).isBlank(); -// final SqlStatement[] sqlStatement2 = ch2.generateStatements(database); -// assertThat(sqlStatement2) -// .hasSize(1); -// assertThat(((CreateContainerStatement) sqlStatement2[0])) -// .hasNoNullFieldsOrPropertiesExcept("options") -// .hasFieldOrPropertyWithValue("collectionName", "createCollectionWithEmptyValidatorTest"); -// -// final CreateContainerChange ch3 = (CreateContainerChange) changeSets.get(0).getChanges().get(2); -// assertThat(ch3.getContainerName()).isEqualTo("createCollectionWithNoValidator"); -// assertThat(ch3.getPartitionKeyPath()).isBlank(); -// final SqlStatement[] sqlStatement3 = ch3.generateStatements(database); -// assertThat(sqlStatement3) -// .hasSize(1); -// assertThat(((CreateContainerStatement) sqlStatement3[0])) -// .hasNoNullFieldsOrPropertiesExcept("options") -// .hasFieldOrPropertyWithValue("collectionName", "createCollectionWithNoValidator"); } -} +} \ No newline at end of file diff --git a/src/test/java/liquibase/ext/cosmosdb/statement/CreateContainerIfNotExistsStatementIT.java b/src/test/java/liquibase/ext/cosmosdb/statement/CreateContainerIfNotExistsStatementIT.java deleted file mode 100644 index c026037..0000000 --- a/src/test/java/liquibase/ext/cosmosdb/statement/CreateContainerIfNotExistsStatementIT.java +++ /dev/null @@ -1,29 +0,0 @@ -package liquibase.ext.cosmosdb.statement; - -import com.azure.cosmos.CosmosContainer; -import liquibase.ext.cosmosdb.AbstractCosmosWithConnectionIntegrationTest; -import lombok.SneakyThrows; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.*; - -class CreateContainerIfNotExistsStatementIT extends AbstractCosmosWithConnectionIntegrationTest { - public static final String CONTAINER_NAME_1 = "containerName1"; - public static final String PARTITION_KEY_PATH_1 = "{ \"partitionKey\": {\"paths\": [\"/partitionField1\"], \"kind\": \"Hash\" } }"; - - @SneakyThrows - @Test - void testExecute() { - final CreateContainerStatement createContainerIfNotExistsStatement - = new CreateContainerIfNotExistsStatement(CONTAINER_NAME_1, PARTITION_KEY_PATH_1); - - createContainerIfNotExistsStatement.execute(cosmosDatabase); - - final CosmosContainer cosmosContainer = cosmosDatabase.getContainer(CONTAINER_NAME_1); - - assertThat(cosmosContainer).isNotNull(); - assertThat(cosmosContainer.getId()).isEqualTo(CONTAINER_NAME_1); - - assertThatNoException().isThrownBy(() -> createContainerIfNotExistsStatement.execute(cosmosDatabase)); - } -} \ No newline at end of file diff --git a/src/test/java/liquibase/ext/cosmosdb/statement/CreateContainerStatementIT.java b/src/test/java/liquibase/ext/cosmosdb/statement/CreateContainerStatementIT.java index 062158e..7457aed 100644 --- a/src/test/java/liquibase/ext/cosmosdb/statement/CreateContainerStatementIT.java +++ b/src/test/java/liquibase/ext/cosmosdb/statement/CreateContainerStatementIT.java @@ -26,11 +26,41 @@ void testExecute() { final CosmosContainer cosmosContainer = cosmosDatabase.getContainer(CONTAINER_NAME_1); assertThat(cosmosContainer).isNotNull(); - assertThat(cosmosContainer.getId()).isEqualTo(CONTAINER_NAME_1); + assertThat(cosmosContainer.read().getProperties().getId()).isEqualTo(CONTAINER_NAME_1); + // should fail if tried once more assertThatExceptionOfType(CosmosException.class).isThrownBy(() -> createContainerStatement.execute(cosmosDatabase)); assertThat(cosmosDatabase.getContainer("testcoll")).isNotNull(); } + + @SneakyThrows + @Test + void testExecuteWithThroughput() { + CreateContainerStatement createContainerStatement + = new CreateContainerStatement("container_manual", PARTITION_KEY_PATH_1, "500"); + + createContainerStatement.execute(cosmosDatabase); + + CosmosContainer cosmosContainer = cosmosDatabase.getContainer("container_manual"); + + assertThat(cosmosContainer).isNotNull(); + assertThat(cosmosContainer.read().getProperties().getId()).isEqualTo("container_manual"); + assertThat(cosmosContainer.readThroughput().getProperties().getManualThroughput()).isEqualTo(500); + + // AutoscaleMaxThroughput + + createContainerStatement + = new CreateContainerStatement("container_auto", PARTITION_KEY_PATH_1, "{\"maxThroughput\": 8000}"); + + createContainerStatement.execute(cosmosDatabase); + + cosmosContainer = cosmosDatabase.getContainer("container_auto"); + + assertThat(cosmosContainer).isNotNull(); + assertThat(cosmosContainer.read().getProperties().getId()).isEqualTo("container_auto"); + assertThat(cosmosContainer.readThroughput().getProperties().getAutoscaleMaxThroughput()).isEqualTo(8000); + + } } \ No newline at end of file diff --git a/src/test/java/liquibase/ext/cosmosdb/statement/JsonUtilsTest.java b/src/test/java/liquibase/ext/cosmosdb/statement/JsonUtilsTest.java index 6a57ea8..3603f4d 100644 --- a/src/test/java/liquibase/ext/cosmosdb/statement/JsonUtilsTest.java +++ b/src/test/java/liquibase/ext/cosmosdb/statement/JsonUtilsTest.java @@ -22,12 +22,14 @@ import com.azure.cosmos.implementation.Document; import com.azure.cosmos.models.SqlQuerySpec; +import com.azure.cosmos.models.ThroughputProperties; import org.junit.jupiter.api.Test; import java.util.Map; import static liquibase.ext.cosmosdb.statement.JsonUtils.mergeDocuments; import static liquibase.ext.cosmosdb.statement.JsonUtils.orEmptyDocument; +import static liquibase.ext.cosmosdb.statement.JsonUtils.toThroughputProperties; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -193,11 +195,28 @@ void testMergeDocuments() { assertThat(source.get("LastName")).isEqualTo("AndersenFromSource"); assertThat(source.getList("Parents", Map.class, false)).hasSize(1); assertThat(source.getList("Children", Map.class, false)).isNull(); - assertThat((Map)source.getObject("Address", Map.class)) + assertThat((Map) source.getObject("Address", Map.class)) .hasFieldOrPropertyWithValue("State", "WAFromSource") .hasFieldOrPropertyWithValue("County", "KingFromSource") .containsKey("City").hasFieldOrPropertyWithValue("City", null); assertThat(source.get("DestinationOnly")).isNull(); assertThat(source.get("SourceOnly")).isEqualTo(true); } + + @Test + @SuppressWarnings("ConstantConditions") + void testToThroughputProperties() { + assertThat(toThroughputProperties(null)).isNull(); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> toThroughputProperties("{500}")) + .withMessage("Unable to parse JSON {500}"); + + assertThat(toThroughputProperties("500")).isNotNull() + .returns(500, ThroughputProperties::getManualThroughput) + .returns(0, ThroughputProperties::getAutoscaleMaxThroughput); + + assertThat(toThroughputProperties(" {\"maxThroughput\": 800}")).isNotNull() + .returns(800, ThroughputProperties::getAutoscaleMaxThroughput); + + assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> toThroughputProperties(" {\"maxThroughput\": 800}").getManualThroughput()); + } } diff --git a/src/test/resources/liquibase/ext/changelog.create-container.test.xml b/src/test/resources/liquibase/ext/changelog.create-container.test.xml index 523c567..089f862 100644 --- a/src/test/resources/liquibase/ext/changelog.create-container.test.xml +++ b/src/test/resources/liquibase/ext/changelog.create-container.test.xml @@ -33,7 +33,12 @@ - + + + + + + @@ -44,7 +49,7 @@ - { + { "id": "Container Id will be taken from: containerName attribute", "indexingPolicy": { "indexingMode": "Consistent", @@ -89,9 +94,51 @@ "conflictResolutionPath": "/path" } } - + + + + + 500 + + + + + + + + + { + "indexingPolicy": { + "automatic": true, + "indexingMode": "Consistent", + "includedPaths": [ + { + "path": "/*", + "indexes": [ + { + "dataType": "String", + "precision": -1, + "kind": "Range" + } + ] + } + ] + }, + "partitionKey": { + "paths": [ + "/AccountNumber" + ], + "kind": "Hash", + "Version": 2 + } + } + + + {"maxThroughput": 8000} + + diff --git a/src/test/resources/liquibase/ext/changelog.replace-container.test.xml b/src/test/resources/liquibase/ext/changelog.replace-container.test.xml new file mode 100644 index 0000000..3f24b44 --- /dev/null +++ b/src/test/resources/liquibase/ext/changelog.replace-container.test.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + { + "indexingPolicy": { + "indexingMode": "Consistent", + "automatic": true, + "includedPaths": [ + { + "path": "/*", + "indexes": [ + { + "kind": "Range", + "dataType": "String", + "precision": -1 + }, + { + "kind": "Range", + "dataType": "Number", + "precision": -1 + } + ] + } + ], + "excludedPaths": [] + }, + "partitionKey": { + "paths": [ + "/accountNumber" + ], + "kind": "Hash" + }, + "defaultTtl": 100, + "uniqueKeyPolicy": { + "uniqueKeys": [ + { + "paths": [ + "/transactionId" + ] + } + ] + }, + "conflictResolutionPolicy": { + "mode": "LastWriterWins", + "conflictResolutionPath": "/path" + } + } + + + + + 500 + + + + + + + + + + + + + + + + + + + Maximum configured replaced containers + + + + + + + + + + + {"maxThroughput": 8000} + + + + + + + diff --git a/src/test/resources/liquibase/ext/test.json b/src/test/resources/liquibase/ext/test.json new file mode 100644 index 0000000..160a0e9 --- /dev/null +++ b/src/test/resources/liquibase/ext/test.json @@ -0,0 +1,61 @@ +{ + "indexingPolicy": { + "indexingMode": "Lazy", + "automatic": false, + "includedPaths": [ + { + "path": "/*", + "indexes": [ + { + "kind": "Range", + "dataType": "String", + "precision": -1 + }, + { + "kind": "Range", + "dataType": "Number", + "precision": -1 + } + ] + }, + { + "path": "/payload/*", + "indexes": [ + { + "kind": "Range", + "dataType": "String", + "precision": -1 + }, + { + "kind": "Range", + "dataType": "Number", + "precision": -1 + } + ] + } + ], + "excludedPaths": [ + "/payload/detail/*" + ] + }, + "partitionKey": { + "paths": [ + "/accountNumber" + ], + "kind": "Hash" + }, + "defaultTtl": 100, + "uniqueKeyPolicy": { + "uniqueKeys": [ + { + "paths": [ + "/transactionId" + ] + } + ] + }, + "conflictResolutionPolicy": { + "mode": "LastWriterWins", + "conflictResolutionPath": "/path" + } +} \ No newline at end of file