-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Subpartitioning: Adds SDK changes to support subpartitioning. #18503
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
Changes from 2 commits
8ab6ed8
d5d3b47
5459104
e1c3001
29f2cea
dd77aaf
a65e716
58c2746
4cb8cb1
036b7ec
f2905f4
24610d6
5a741c5
29bcfc9
14bd7a2
8f77d0d
741ce81
0b45b66
22fe137
5bf9d26
0c67ba6
00d5f8e
f008411
28926cf
ed325f9
b6e452e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1222,19 +1222,33 @@ private static PartitionKeyInternal extractPartitionKeyValueFromDocument( | |
| InternalObjectNode document, | ||
| PartitionKeyDefinition partitionKeyDefinition) { | ||
| if (partitionKeyDefinition != null) { | ||
| String path = partitionKeyDefinition.getPaths().iterator().next(); | ||
| List<String> 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()) { | ||
|
SrinikhilReddy marked this conversation as resolved.
|
||
| case HASH: | ||
|
|
||
| String path = partitionKeyDefinition.getPaths().iterator().next(); | ||
| List<String> 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 path_iter = 0 ; path_iter < partitionKeyDefinition.getPaths().size(); path_iter++) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @moderakh any suggestions on elegant way of iteration.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is possible to use java stream. partitionKeyDefinition.getPaths().stream().map .... but not much difference functional wise. |
||
| { | ||
|
SrinikhilReddy marked this conversation as resolved.
Outdated
|
||
| String partitionPath = partitionKeyDefinition.getPaths().get(path_iter); | ||
| List<String> partitionPathParts = PathParser.getPathParts(partitionPath); | ||
| partitionKeyValues[path_iter] = ModelBridgeInternal.getObjectByPathFromJsonSerializable(document, partitionPathParts); | ||
| } | ||
| return PartitionKeyInternal.fromObjectArray(partitionKeyValues, false); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,6 +92,32 @@ static public String getEffectivePartitionKeyForHashPartitioningV2(PartitionKeyI | |
| } | ||
| } | ||
|
|
||
| static String getEffectivePartitionKeyForMultiHashPartitioning(PartitionKeyInternal partitionKeyInternal) { | ||
| StringBuilder stringBuilder = new StringBuilder(); | ||
|
SrinikhilReddy marked this conversation as resolved.
Outdated
|
||
| for (int i = 0; i < partitionKeyInternal.components.size(); i++) { | ||
| try(ByteBufferOutputStream byteArrayBuffer = new ByteBufferOutputStream()) { | ||
|
kirankumarkolli marked this conversation as resolved.
|
||
|
|
||
| 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)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: How about allocate byte[] and then convert to hex at the end? |
||
| } catch (IOException e) { | ||
| throw new IllegalArgumentException(e); | ||
| } | ||
| } | ||
| return stringBuilder.toString(); | ||
|
SrinikhilReddy marked this conversation as resolved.
|
||
| } | ||
|
|
||
| static String getEffectivePartitionKeyForHashPartitioning(PartitionKeyInternal partitionKeyInternal) { | ||
| IPartitionKeyComponent[] truncatedComponents = new IPartitionKeyComponent[partitionKeyInternal.components.size()]; | ||
|
|
||
|
|
@@ -138,7 +164,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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this to cover only for query? If so do we need two variations? |
||
| throw new IllegalArgumentException(RMResources.TooFewPartitionKeyComponents); | ||
| } | ||
|
|
||
|
|
@@ -161,6 +187,9 @@ public static String getEffectivePartitionKeyString(PartitionKeyInternal partiti | |
| return getEffectivePartitionKeyForHashPartitioning(partitionKeyInternal); | ||
| } | ||
|
|
||
| case MULTI_HASH: | ||
| return getEffectivePartitionKeyForMultiHashPartitioning(partitionKeyInternal); | ||
|
|
||
| default: | ||
| return toHexEncodedBinaryString(partitionKeyInternal.components); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,17 @@ public PartitionKey(final Object key) { | |
| this.internalPartitionKey = PartitionKeyInternal.fromObjectArray(new Object[] {key}, true); | ||
| } | ||
|
|
||
| /** | ||
| * Constructor. CREATE a new instance of the PartitionKey object. | ||
| * | ||
| * @param keys the value of partition keys. | ||
| */ | ||
| @SuppressWarnings("serial") | ||
| public PartitionKey(final Object[] keys) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Object can be any non primitive type, however for subpartitioning we only support primitive types, we should consider using a builder pattern to construct and validate the content instead of passing raw object array. Could you check what DotNet v3 is doing?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the pattern as suggested. |
||
| this.keyObject = keys; | ||
| this.internalPartitionKey = PartitionKeyInternal.fromObjectArray(keys, true); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the object used to create partition key | ||
| * @return the partition key object | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,16 +6,9 @@ | |
|
|
||
| package com.azure.cosmos; | ||
|
|
||
| import com.azure.cosmos.implementation.Document; | ||
| import com.azure.cosmos.implementation.HttpConstants; | ||
| import com.azure.cosmos.models.CosmosContainerProperties; | ||
| import com.azure.cosmos.models.CosmosContainerRequestOptions; | ||
| import com.azure.cosmos.models.CosmosContainerResponse; | ||
| import com.azure.cosmos.models.CosmosQueryRequestOptions; | ||
| import com.azure.cosmos.models.FeedRange; | ||
| import com.azure.cosmos.models.IndexingMode; | ||
| import com.azure.cosmos.models.IndexingPolicy; | ||
| import com.azure.cosmos.models.SqlQuerySpec; | ||
| import com.azure.cosmos.models.ThroughputProperties; | ||
| import com.azure.cosmos.models.*; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. code style: we don't do wildcard import. You can change your intellj/eclipse default behaviour not not use wildcard import. see this: https://stackoverflow.com/questions/3348816/intellij-never-use-wildcard-imports |
||
| import com.azure.cosmos.rx.TestSuiteBase; | ||
| import com.azure.cosmos.util.CosmosPagedIterable; | ||
| import org.testng.annotations.AfterClass; | ||
|
|
@@ -26,6 +19,7 @@ | |
|
|
||
| import java.util.List; | ||
| import java.util.UUID; | ||
| import java.util.ArrayList; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
|
|
@@ -264,6 +258,40 @@ public void readAllContainers() throws Exception{ | |
| assertThat(feedResponseIterator1.iterator().hasNext()).isTrue(); | ||
| } | ||
|
|
||
| @Test(groups = { "emulator" }, timeOut = TIMEOUT) | ||
|
SrinikhilReddy marked this conversation as resolved.
|
||
| public void crudMultiHashContainerTest() throws Exception{ | ||
|
SrinikhilReddy marked this conversation as resolved.
Outdated
|
||
| String collectionName = UUID.randomUUID().toString(); | ||
|
|
||
| PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition(); | ||
| partitionKeyDefinition.setKind(PartitionKind.MULTI_HASH); | ||
| partitionKeyDefinition.setVersion(PartitionKeyDefinitionVersion.V2); | ||
| ArrayList<String> paths = new ArrayList<>(); | ||
| paths.add("/city"); | ||
| paths.add("/zipcode"); | ||
| partitionKeyDefinition.setPaths(paths); | ||
|
|
||
| CosmosContainerProperties containerProperties = getCollectionDefinition(collectionName, partitionKeyDefinition); | ||
|
SrinikhilReddy marked this conversation as resolved.
|
||
|
|
||
| //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)); | ||
|
SrinikhilReddy marked this conversation as resolved.
|
||
| 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{ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| /* | ||
| * Copyright (c) Microsoft Corporation. All rights reserved. | ||
| * Licensed under the MIT License. | ||
| * | ||
| */ | ||
|
|
||
| package com.azure.cosmos; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please run the full CI run against the live endpoints. |
||
|
|
||
| import com.azure.cosmos.implementation.HttpConstants; | ||
| import com.azure.cosmos.implementation.InternalObjectNode; | ||
| import com.azure.cosmos.implementation.apachecommons.lang.StringUtils; | ||
| import com.azure.cosmos.implementation.routing.PartitionKeyInternal; | ||
| import com.azure.cosmos.models.*; | ||
|
SrinikhilReddy marked this conversation as resolved.
Outdated
|
||
| 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.ObjectNode; | ||
| import org.testng.annotations.AfterClass; | ||
| import org.testng.annotations.BeforeClass; | ||
| import org.testng.annotations.Factory; | ||
| import org.testng.annotations.Test; | ||
|
|
||
| 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 { | ||
|
SrinikhilReddy marked this conversation as resolved.
|
||
|
|
||
| private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||
| private String preExistingDatabaseId = CosmosDatabaseForTest.generateId(); | ||
| private CosmosClient client; | ||
| private CosmosDatabase createdDatabase; | ||
| private CosmosContainer createdMultiHashContainer; | ||
|
|
||
| @Factory(dataProvider = "clientBuilders") | ||
| public CosmosMultiHashTest(CosmosClientBuilder clientBuilder) { | ||
|
SrinikhilReddy marked this conversation as resolved.
|
||
| super(clientBuilder); | ||
| } | ||
|
|
||
| @BeforeClass(groups = {"simple"}, timeOut = SETUP_TIMEOUT) | ||
| public void before_CosmosItemTest() { | ||
|
|
||
| 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<String> paths = new ArrayList<>(); | ||
| paths.add("/city"); | ||
| paths.add("/zipcode"); | ||
| partitionKeyDefinition.setPaths(paths); | ||
|
|
||
| CosmosContainerProperties containerProperties = getCollectionDefinition(collectionName, partitionKeyDefinition); | ||
|
SrinikhilReddy marked this conversation as resolved.
|
||
|
|
||
| //MultiHash collection create | ||
| CosmosContainerResponse containerResponse = createdDatabase.createContainer(containerProperties); | ||
|
|
||
| //MultiHash collection read | ||
| createdMultiHashContainer = createdDatabase.getContainer(collectionName); | ||
| } | ||
|
|
||
| @AfterClass(groups = {"simple"}, timeOut = SHUTDOWN_TIMEOUT, alwaysRun = true) | ||
| public void afterClass() { | ||
| logger.info("starting ...."); | ||
| //MultiHash Collection delete | ||
| createdMultiHashContainer.delete(); | ||
|
SrinikhilReddy marked this conversation as resolved.
Outdated
|
||
| safeDeleteSyncDatabase(createdDatabase); | ||
| safeCloseSyncClient(client); | ||
| } | ||
|
|
||
| @Test(groups = { "simple" }, timeOut = TIMEOUT) | ||
| public void itemCRUDTest() throws Exception { | ||
|
SrinikhilReddy marked this conversation as resolved.
Outdated
|
||
|
|
||
| List<String> pkIds = new ArrayList<>(); | ||
| pkIds.add("Redmond"); | ||
| pkIds.add("98052"); | ||
|
|
||
| Object[] pkValue = new Object[2]; | ||
| pkValue[0] = pkIds.get(0); | ||
| pkValue[1] = pkIds.get(1); | ||
|
|
||
| PartitionKey partitionKey = new PartitionKey(pkValue); | ||
|
|
||
| String documentId = UUID.randomUUID().toString(); | ||
| ObjectNode properties = getDocumentDefinition(documentId, pkIds); | ||
| createdMultiHashContainer.createItem(properties); | ||
|
|
||
| CosmosItemResponse<ObjectNode> readResponse1 = createdMultiHashContainer.readItem( | ||
| documentId, partitionKey, ObjectNode.class); | ||
| validateIdOfItemResponse(documentId, readResponse1); | ||
| assertThat(readResponse1.getItem().equals(properties)); | ||
| } | ||
|
|
||
| private ObjectNode getDocumentDefinition(String documentId, List<String> pkIds) throws JsonProcessingException { | ||
|
SrinikhilReddy marked this conversation as resolved.
Outdated
|
||
|
|
||
| String json = String.format("{ " | ||
| + "\"id\": \"%s\", " | ||
| + "\"city\": \"%s\", " | ||
| + "\"zipcode\": \"%s\", " | ||
| + "\"sgmts\": [[6519456, 1471916863], [2498434, 1455671440]]" | ||
| + "}" | ||
| , documentId, pkIds.get(0), pkIds.get(1)); | ||
| return | ||
| OBJECT_MAPPER.readValue(json, ObjectNode.class); | ||
| } | ||
|
|
||
| private void validateItemResponse(InternalObjectNode containerProperties, | ||
| CosmosItemResponse<InternalObjectNode> 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<ObjectNode> createResponse) { | ||
| // Basic validation | ||
| assertThat(BridgeInternal.getProperties(createResponse).getId()).isNotNull(); | ||
| assertThat(BridgeInternal.getProperties(createResponse).getId()) | ||
| .as("check Resource Id") | ||
| .isEqualTo(expectedId); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.