diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java index f8f68bb03af0..b34da312fd81 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/HttpConstants.java @@ -273,7 +273,7 @@ public static class A_IMHeaderValues { } public static class Versions { - public static final String CURRENT_VERSION = "2018-12-31"; + public static final String CURRENT_VERSION = "2020-07-15"; public static final String QUERY_VERSION = "1.0"; public static final String AZURE_COSMOS_PROPERTIES_FILE_NAME = "azure-cosmos.properties"; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java index 6d65e1cc98a3..4f5bb2509656 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxDocumentClientImpl.java @@ -1310,20 +1310,35 @@ public static PartitionKeyInternal extractPartitionKeyValueFromDocument( InternalObjectNode document, PartitionKeyDefinition partitionKeyDefinition) { if (partitionKeyDefinition != null) { - String path = partitionKeyDefinition.getPaths().iterator().next(); - List parts = PathParser.getPathParts(path); - if (parts.size() >= 1) { - Object value = ModelBridgeInternal.getObjectByPathFromJsonSerializable(document, parts); - if (value == null || value.getClass() == ObjectNode.class) { - value = ModelBridgeInternal.getNonePartitionKey(partitionKeyDefinition); - } + switch (partitionKeyDefinition.getKind()) { + case HASH: + String path = partitionKeyDefinition.getPaths().iterator().next(); + List parts = PathParser.getPathParts(path); + if (parts.size() >= 1) { + Object value = ModelBridgeInternal.getObjectByPathFromJsonSerializable(document, parts); + if (value == null || value.getClass() == ObjectNode.class) { + value = ModelBridgeInternal.getNonePartitionKey(partitionKeyDefinition); + } - if (value instanceof PartitionKeyInternal) { - return (PartitionKeyInternal) value; - } else { - return PartitionKeyInternal.fromObjectArray(Collections.singletonList(value), false); + if (value instanceof PartitionKeyInternal) { + return (PartitionKeyInternal) value; + } else { + return PartitionKeyInternal.fromObjectArray(Collections.singletonList(value), false); + } + } + break; + case MULTI_HASH: + Object[] partitionKeyValues = new Object[partitionKeyDefinition.getPaths().size()]; + for(int pathIter = 0 ; pathIter < partitionKeyDefinition.getPaths().size(); pathIter++){ + String partitionPath = partitionKeyDefinition.getPaths().get(pathIter); + List partitionPathParts = PathParser.getPathParts(partitionPath); + partitionKeyValues[pathIter] = ModelBridgeInternal.getObjectByPathFromJsonSerializable(document, partitionPathParts); + } + return PartitionKeyInternal.fromObjectArray(partitionKeyValues, false); + + default: + throw new IllegalArgumentException("Unrecognized Partition kind: " + partitionKeyDefinition.getKind()); } - } } return null; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/routing/PartitionKeyInternalHelper.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/routing/PartitionKeyInternalHelper.java index d27f16b46143..f7d692650bf4 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/routing/PartitionKeyInternalHelper.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/routing/PartitionKeyInternalHelper.java @@ -35,6 +35,8 @@ public class PartitionKeyInternalHelper { (byte) 0x3F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); + private static final int HASH_V2_EPK_LENGTH = 32; + static byte[] uIntToBytes(UInt128 unit) { ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES * 2); buffer.putLong(unit.low); @@ -92,6 +94,31 @@ static public String getEffectivePartitionKeyForHashPartitioningV2(PartitionKeyI } } + static String getEffectivePartitionKeyForMultiHashPartitioning(PartitionKeyInternal partitionKeyInternal) { + StringBuilder stringBuilder = new StringBuilder(partitionKeyInternal.components.size() * HASH_V2_EPK_LENGTH); + for (int i = 0; i < partitionKeyInternal.components.size(); i++) { + try(ByteBufferOutputStream byteArrayBuffer = new ByteBufferOutputStream()) { + partitionKeyInternal.components.get(i).writeForHashingV2(byteArrayBuffer); + + ByteBuffer byteBuffer = byteArrayBuffer.asByteBuffer(); + UInt128 hashAsUnit128 = MurmurHash3_128.hash128(byteBuffer.array(), byteBuffer.limit()); + + byte[] hash = uIntToBytes(hashAsUnit128); + Bytes.reverse(hash); + + // Reset 2 most significant bits, as max exclusive value is 'FF'. + // Plus one more just in case. + hash[0] &= 0x3F; + + stringBuilder.append(HexConvert.bytesToHex(hash)); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + return stringBuilder.toString(); + } + static String getEffectivePartitionKeyForHashPartitioning(PartitionKeyInternal partitionKeyInternal) { IPartitionKeyComponent[] truncatedComponents = new IPartitionKeyComponent[partitionKeyInternal.components.size()]; @@ -138,7 +165,7 @@ public static String getEffectivePartitionKeyString(PartitionKeyInternal partiti return MaximumExclusiveEffectivePartitionKey; } - if (partitionKeyInternal.components.size() < partitionKeyDefinition.getPaths().size()) { + if (partitionKeyInternal.components.size() < partitionKeyDefinition.getPaths().size() && partitionKeyDefinition.getKind() != PartitionKind.MULTI_HASH) { throw new IllegalArgumentException(RMResources.TooFewPartitionKeyComponents); } @@ -161,6 +188,9 @@ public static String getEffectivePartitionKeyString(PartitionKeyInternal partiti return getEffectivePartitionKeyForHashPartitioning(partitionKeyInternal); } + case MULTI_HASH: + return getEffectivePartitionKeyForMultiHashPartitioning(partitionKeyInternal); + default: return toHexEncodedBinaryString(partitionKeyInternal.components); } diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/PartitionKeyBuilder.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/PartitionKeyBuilder.java new file mode 100644 index 000000000000..3143b0e79520 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/PartitionKeyBuilder.java @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.cosmos.models; + +import com.azure.cosmos.implementation.Undefined; +import com.azure.cosmos.implementation.routing.PartitionKeyInternal; +import com.azure.cosmos.util.Beta; +import com.azure.cosmos.util.Beta.SinceVersion; + +import java.util.ArrayList; +import java.util.List; + +@Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) +public final class PartitionKeyBuilder { + private final List partitionKeyValues; + + /** + * Constructor. CREATE a new instance of the PartitionKeyBuilder object. + */ + @Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public PartitionKeyBuilder() { + this.partitionKeyValues = new ArrayList(); + } + + /** + * Adds partition value of type string + * @param value The value of type string to be used as partition key + * @return The current PartitionKeyBuilder object + */ + @Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public PartitionKeyBuilder add(String value) { + this.partitionKeyValues.add(value); + return this; + } + + /** + * Adds partition value of type double + * @param value The value of type double to be used as partition key + * @return The current PartitionKeyBuilder object + */ + @Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public PartitionKeyBuilder add(double value) { + this.partitionKeyValues.add(value); + return this; + } + + /** + * Adds partition value of type boolean + * @param value The value of type boolean to be used as partition key + * @return The current PartitionKeyBuilder object + */ + @Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public PartitionKeyBuilder add(boolean value) { + this.partitionKeyValues.add(value); + return this; + } + + /** + * Adds a null partition key value + * @return The current PartitionKeyBuilder object + */ + @Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public PartitionKeyBuilder addNullValue() { + this.partitionKeyValues.add(null); + return this; + } + + /** + * Adds a None Partition Key + * @return The current PartitionKeyBuilder object + */ + @Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public PartitionKeyBuilder addNoneValue() { + this.partitionKeyValues.add(PartitionKey.NONE); + return this; + } + + /** + * Builds a new instance of the type PartitionKey with the specified Partition Key values. + * @return PartitionKey object + */ + @Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + public PartitionKey build() { + // Why these checks? + // These changes are being added for SDK to support multiple paths in a partition key. + // + // Currently, when a resource does not specify a value for the PartitionKey, + // we assign a temporary value `PartitionKey.None` and later discern whether + // it is a PartitionKey.Undefined or PartitionKey.Empty based on the Collection Type. + // We retain this behaviour for single path partition keys. + // + // For collections with multiple path keys, absence of a partition key values is + // always treated as a PartitionKey.Undefined. + if(this.partitionKeyValues.size() == 0) { + throw new IllegalArgumentException("No partition key value has been specified"); + } + + if(this.partitionKeyValues.size() == 1 && PartitionKey.NONE.equals(this.partitionKeyValues.get(0))) { + return PartitionKey.NONE; + } + + PartitionKeyInternal partitionKeyInternal; + Object[] valueArray = new Object[this.partitionKeyValues.size()]; + for(int i = 0; i < this.partitionKeyValues.size(); i++) { + Object val = this.partitionKeyValues.get(i); + if(PartitionKey.NONE.equals(val)) { + valueArray[i] = Undefined.value(); + } + else { + valueArray[i] = val; + } + } + + partitionKeyInternal = PartitionKeyInternal.fromObjectArray(valueArray, true); + return new PartitionKey(partitionKeyInternal); + } +} diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/PartitionKind.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/PartitionKind.java index e90d0ef54cda..35f58f5f943d 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/PartitionKind.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/PartitionKind.java @@ -3,6 +3,9 @@ package com.azure.cosmos.models; +import com.azure.cosmos.util.Beta; +import com.azure.cosmos.util.Beta.SinceVersion; + /** * Specifies the partition scheme for an multiple-partitioned container in the Azure Cosmos DB database service. */ @@ -10,7 +13,14 @@ public enum PartitionKind { /** * The Partition of a item is calculated based on the hash value of the PartitionKey. */ - HASH("Hash"); + HASH("Hash"), + + RANGE("Range"), + /** + * The Partition of a item is calculated based on the hash value of multiple PartitionKeys. + */ + @Beta(value = SinceVersion.V4_16_0, warningText = Beta.PREVIEW_SUBJECT_TO_CHANGE_WARNING) + MULTI_HASH("MultiHash"); PartitionKind(String overWireValue) { this.overWireValue = overWireValue; diff --git a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/Beta.java b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/Beta.java index 3a825e6d1e4a..3a38884d82e1 100644 --- a/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/Beta.java +++ b/sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/util/Beta.java @@ -10,13 +10,14 @@ import java.lang.annotation.Target; import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE; @Documented @Retention(RetentionPolicy.CLASS) -@Target({ TYPE, METHOD, PARAMETER, CONSTRUCTOR }) +@Target({ TYPE, METHOD, PARAMETER, CONSTRUCTOR, FIELD }) @Inherited /** * Indicates functionality that is in preview and as such is subject to change in non-backwards compatible ways in future releases, diff --git a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerTest.java b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerTest.java index 3a52ed2901e2..4deebcd5c030 100644 --- a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerTest.java +++ b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosContainerTest.java @@ -27,6 +27,8 @@ import com.azure.cosmos.models.IndexingPolicy; import com.azure.cosmos.models.PartitionKey; import com.azure.cosmos.models.PartitionKeyDefinition; +import com.azure.cosmos.models.PartitionKeyDefinitionVersion; +import com.azure.cosmos.models.PartitionKind; import com.azure.cosmos.models.SqlQuerySpec; import com.azure.cosmos.models.ThroughputProperties; import com.azure.cosmos.rx.TestSuiteBase; @@ -47,6 +49,7 @@ import java.util.Base64; import java.util.List; import java.util.UUID; +import java.util.ArrayList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -764,6 +767,40 @@ public void readAllContainers() throws Exception{ assertThat(feedResponseIterator1.iterator().hasNext()).isTrue(); } + @Test(groups = { "emulator" }, timeOut = TIMEOUT) + public void crudMultiHashContainer() throws Exception { + String collectionName = UUID.randomUUID().toString(); + + PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition(); + partitionKeyDefinition.setKind(PartitionKind.MULTI_HASH); + partitionKeyDefinition.setVersion(PartitionKeyDefinitionVersion.V2); + ArrayList paths = new ArrayList<>(); + paths.add("/city"); + paths.add("/zipcode"); + partitionKeyDefinition.setPaths(paths); + + CosmosContainerProperties containerProperties = getCollectionDefinition(collectionName, partitionKeyDefinition); + + //MultiHash collection create + CosmosContainerResponse containerResponse = createdDatabase.createContainer(containerProperties); + validateContainerResponse(containerProperties, containerResponse); + assertThat(containerResponse.getProperties().getPartitionKeyDefinition().getKind() == PartitionKind.MULTI_HASH); + assertThat(containerResponse.getProperties().getPartitionKeyDefinition().getPaths().size() == paths.size()); + assertThat(containerResponse.getProperties().getPartitionKeyDefinition().getPaths().get(0) == paths.get(0)); + assertThat(containerResponse.getProperties().getPartitionKeyDefinition().getPaths().get(1) == paths.get(1)); + + //MultiHash collection read + CosmosContainer multiHashContainer = createdDatabase.getContainer(collectionName); + containerResponse = multiHashContainer.read(); + validateContainerResponse(containerProperties, containerResponse); + assertThat(containerResponse.getProperties().getPartitionKeyDefinition().getKind() == PartitionKind.MULTI_HASH); + assertThat(containerResponse.getProperties().getPartitionKeyDefinition().getPaths().size() == paths.size()); + assertThat(containerResponse.getProperties().getPartitionKeyDefinition().getPaths().get(0) == paths.get(0)); + assertThat(containerResponse.getProperties().getPartitionKeyDefinition().getPaths().get(1) == paths.get(1)); + + //MultiHash collection delete + CosmosContainerResponse deleteResponse = multiHashContainer.delete(); + } @Test(groups = { "emulator" }, timeOut = TIMEOUT) public void queryContainer() throws Exception{ diff --git a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosMultiHashTest.java b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosMultiHashTest.java new file mode 100644 index 000000000000..9f9ead242382 --- /dev/null +++ b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosMultiHashTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * + */ + +package com.azure.cosmos; + +import com.azure.cosmos.implementation.Document; +import com.azure.cosmos.implementation.InternalObjectNode; + +import com.azure.cosmos.models.CosmosContainerProperties; +import com.azure.cosmos.models.CosmosContainerResponse; +import com.azure.cosmos.models.CosmosItemRequestOptions; +import com.azure.cosmos.models.CosmosItemResponse; +import com.azure.cosmos.models.CosmosQueryRequestOptions; +import com.azure.cosmos.models.PartitionKey; +import com.azure.cosmos.models.PartitionKeyBuilder; +import com.azure.cosmos.models.PartitionKeyDefinition; +import com.azure.cosmos.models.PartitionKeyDefinitionVersion; +import com.azure.cosmos.models.PartitionKind; +import com.azure.cosmos.rx.TestSuiteBase; +import com.azure.cosmos.util.CosmosPagedIterable; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import org.apache.http.HttpStatus; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Factory; +import org.testng.annotations.Test; + +import javax.print.Doc; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CosmosMultiHashTest extends TestSuiteBase { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final JsonNodeFactory JSON_NODE_FACTORY_INSTANCE = JsonNodeFactory.withExactBigDecimals(true); + + private String preExistingDatabaseId = CosmosDatabaseForTest.generateId(); + private CosmosClient client; + private CosmosDatabase createdDatabase; + private CosmosContainer createdMultiHashContainer; + + @Factory(dataProvider = "clientBuilders") + public CosmosMultiHashTest(CosmosClientBuilder clientBuilder) { + super(clientBuilder); + } + + @BeforeClass(groups = {"emulator"}, timeOut = SETUP_TIMEOUT) + public void before_CosmosMultiHashTest() { + + client = getClientBuilder().buildClient(); + createdDatabase = createSyncDatabase(client, preExistingDatabaseId); + String collectionName = UUID.randomUUID().toString(); + + PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition(); + partitionKeyDefinition.setKind(PartitionKind.MULTI_HASH); + partitionKeyDefinition.setVersion(PartitionKeyDefinitionVersion.V2); + ArrayList paths = new ArrayList<>(); + paths.add("/city"); + paths.add("/zipcode"); + partitionKeyDefinition.setPaths(paths); + + CosmosContainerProperties containerProperties = getCollectionDefinition(collectionName, partitionKeyDefinition); + + //MultiHash collection create + CosmosContainerResponse containerResponse = createdDatabase.createContainer(containerProperties); + + //MultiHash collection read + createdMultiHashContainer = createdDatabase.getContainer(collectionName); + } + + @AfterClass(groups = {"emulator"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true) + public void afterClass() { + logger.info("starting cleanup...."); + safeDeleteSyncDatabase(createdDatabase); + safeCloseSyncClient(client); + } + + @Test(groups = { "emulator" }, timeOut = TIMEOUT) + public void itemCRUD() throws Exception { + + List pkIds = new ArrayList<>(); + pkIds.add("Redmond"); + pkIds.add("98052"); + + PartitionKey partitionKey = + new PartitionKeyBuilder() + .add(pkIds.get(0)) + .add(pkIds.get(1)) + .build(); + + String documentId = UUID.randomUUID().toString(); + ObjectNode properties = getItem(documentId, pkIds); + createdMultiHashContainer.createItem(properties); + + CosmosItemResponse readResponse1 = createdMultiHashContainer.readItem( + documentId, partitionKey, ObjectNode.class); + validateIdOfItemResponse(documentId, readResponse1); + assertThat(readResponse1.getItem().equals(properties)); + } + + private ObjectNode getItem(String documentId, List pkIds) throws JsonProcessingException { + + + String json = String.format("{ " + + "\"id\": \"%s\", " + + "\"city\": \"%s\", " + + "\"zipcode\": \"%s\" " + + "}" + , documentId, pkIds.get(0), pkIds.get(1)); + return + OBJECT_MAPPER.readValue(json, ObjectNode.class); + } + + private void validateItemResponse(InternalObjectNode containerProperties, + CosmosItemResponse createResponse) { + // Basic validation + assertThat(BridgeInternal.getProperties(createResponse).getId()).isNotNull(); + assertThat(BridgeInternal.getProperties(createResponse).getId()) + .as("check Resource Id") + .isEqualTo(containerProperties.getId()); + } + + private void validateIdOfItemResponse(String expectedId, CosmosItemResponse createResponse) { + // Basic validation + assertThat(BridgeInternal.getProperties(createResponse).getId()).isNotNull(); + assertThat(BridgeInternal.getProperties(createResponse).getId()) + .as("check Resource Id") + .isEqualTo(expectedId); + } + + @Test(groups = { "emulator" }, timeOut = TIMEOUT) + private void validateDocCRUDandQuery() throws Exception { + + ArrayList docs = new ArrayList(3); + + ObjectNode doc = new ObjectNode(JSON_NODE_FACTORY_INSTANCE); + doc.set("id", new TextNode(UUID.randomUUID().toString())); + doc.set("city", new TextNode("Redmond")); + doc.set("zipcode", new TextNode("98052")); + docs.add(doc); + + ObjectNode doc1 = new ObjectNode(JSON_NODE_FACTORY_INSTANCE); + doc1.set("id", new TextNode(UUID.randomUUID().toString())); + doc1.set("city", new TextNode("Pittsburgh")); + doc1.set("zipcode", new TextNode("15232")); + docs.add(doc1); + + ObjectNode doc2 = new ObjectNode(JSON_NODE_FACTORY_INSTANCE); + doc2.set("id", new TextNode(UUID.randomUUID().toString())); + doc2.set("city", new TextNode("Stonybrook")); + doc2.set("zipcode", new TextNode("11790")); + docs.add(doc2); + + ObjectNode doc3 = new ObjectNode(JSON_NODE_FACTORY_INSTANCE); + doc3.set("id", new TextNode(UUID.randomUUID().toString())); + doc3.set("city", new TextNode("Stonybrook")); + doc3.set("zipcode", new TextNode("11794")); + docs.add(doc3); + + ObjectNode doc4 = new ObjectNode(JSON_NODE_FACTORY_INSTANCE); + doc4.set("id", new TextNode(UUID.randomUUID().toString())); + doc4.set("city", new TextNode("Stonybrook")); + doc4.set("zipcode", new TextNode("11791")); + docs.add(doc4); + + //Document Create + for (int i = 0; i < docs.size(); i++) { + createdMultiHashContainer.createItem(docs.get(i)); + } + //Document Create - Negative test + { + PartitionKey partitionKey = + new PartitionKeyBuilder() + .add("Redmond") + .build(); + try { + CosmosItemResponse response = + createdMultiHashContainer.createItem(doc, partitionKey, new CosmosItemRequestOptions()); + } catch (Exception e) { + assertThat(e.getMessage().contains("Partition key provided either doesn't correspond to definition in the collection or doesn't match partition key field values specified in the document.\n")); + } + } + + //Document Read + { + for (int i = 0; i < docs.size(); i++) { + ObjectNode doc_current = docs.get(i); + PartitionKey partitionKey = new PartitionKeyBuilder() + .add(doc_current.get("city").asText()) + .add(doc_current.get("zipcode").asText()) + .build(); + CosmosItemResponse response = createdMultiHashContainer.readItem(doc_current.get("id").asText(), partitionKey, ObjectNode.class); + } + } + + // Query Tests. + + for (int i = 0; i < docs.size(); i++) { + ObjectNode doc_current = docs.get(i); + //Build the partition key + PartitionKey partitionKey = new PartitionKeyBuilder() + .add(doc_current.get("city").asText()) + .add(doc_current.get("zipcode").asText()) + .build(); + + //Build the query request options + CosmosQueryRequestOptions queryRequestOptions = new CosmosQueryRequestOptions(); + queryRequestOptions.setPartitionKey(partitionKey); + + //Run the query. + String query = String.format("SELECT * from c where c.id = '%s'", doc_current.get("id").asText()); + + CosmosPagedIterable feedResponseIterator1 = + createdMultiHashContainer.queryItems(query, queryRequestOptions, ObjectNode.class); + assertThat(feedResponseIterator1.iterator().hasNext()).isTrue(); + + query = String.format("SELECT * from c where c.id = '%s'", doc_current.get("id").asText()); + queryRequestOptions = new CosmosQueryRequestOptions(); + feedResponseIterator1 = + createdMultiHashContainer.queryItems(query, queryRequestOptions, ObjectNode.class); + assertThat(feedResponseIterator1.iterator().hasNext()).isTrue(); + } + + String query = String.format("SELECT * from c where c.city = '%s'", docs.get(2).get("city").asText()); + CosmosQueryRequestOptions queryRequestOptions = new CosmosQueryRequestOptions(); + CosmosPagedIterable feedResponseIterator1 = + createdMultiHashContainer.queryItems(query, queryRequestOptions, ObjectNode.class); + assertThat(feedResponseIterator1.stream().count()).isEqualTo(3); + + } +} diff --git a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/rx/TestSuiteBase.java b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/rx/TestSuiteBase.java index 8aa3d4df32d6..ae095ec1fb9e 100644 --- a/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/rx/TestSuiteBase.java +++ b/sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/rx/TestSuiteBase.java @@ -531,6 +531,10 @@ static protected CosmosContainerProperties getCollectionDefinition(String collec return collectionDefinition; } + static protected CosmosContainerProperties getCollectionDefinition(String collectionId, PartitionKeyDefinition partitionKeyDefinition) { + return new CosmosContainerProperties(collectionId, partitionKeyDefinition); + } + static protected CosmosContainerProperties getCollectionDefinitionForHashV2(String collectionId) { PartitionKeyDefinition partitionKeyDef = new PartitionKeyDefinition(); ArrayList paths = new ArrayList<>();