-
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 all 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 |
|---|---|---|
|
|
@@ -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()) { | ||
kirankumarkolli marked this conversation as resolved.
Show resolved
Hide 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.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| 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) { | ||
|
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 +188,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 |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package com.azure.cosmos.models; | ||
SrinikhilReddy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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<Object> 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() { | ||
SrinikhilReddy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| this.partitionKeyValues = new ArrayList<Object>(); | ||
| } | ||
|
|
||
| /** | ||
| * 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"); | ||
SrinikhilReddy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| 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); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
SrinikhilReddy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| public void crudMultiHashContainer() throws Exception { | ||
|
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. Also a test case covering the NONE explicitly for above variants. |
||
| 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.
Show resolved
Hide 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.
Show resolved
Hide 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{ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.