Add hierachical partition key in add containers in SQL (#1377)

* Add hierachical partition key in add containers in SQL

* Add hierachical partition key in add containers in SQL

* fix unit test cases and update snapshot

* add learn more links and feature flag

* update snapsho

* separate subpartition key logic

* separate subpartition key logic
This commit is contained in:
sunghyunkang1111 2023-01-23 09:09:29 -06:00 committed by GitHub
parent 5059917edf
commit 701f486d8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 165 additions and 42 deletions

View File

@ -45,6 +45,8 @@ export class ArmResourceTypes {
export class BackendDefaults { export class BackendDefaults {
public static partitionKeyKind = "Hash"; public static partitionKeyKind = "Hash";
public static partitionKeyMultiHash = "MultiHash";
public static maxNumMultiHashPartition = 2;
public static singlePartitionStorageInGb: string = "10"; public static singlePartitionStorageInGb: string = "10";
public static multiPartitionStorageInGb: string = "100"; public static multiPartitionStorageInGb: string = "100";
public static maxChangeFeedRetentionDuration: number = 10; public static maxChangeFeedRetentionDuration: number = 10;

View File

@ -133,7 +133,6 @@ describe("ContainerSampleGenerator", () => {
} as DatabaseAccount, } as DatabaseAccount,
}); });
// Rejects with error that contains experience
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience); expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
}); });

View File

@ -89,9 +89,10 @@ export interface AddCollectionPanelState {
enableIndexing: boolean; enableIndexing: boolean;
isSharded: boolean; isSharded: boolean;
partitionKey: string; partitionKey: string;
subPartitionKeys: string[];
enableDedicatedThroughput: boolean; enableDedicatedThroughput: boolean;
createMongoWildCardIndex: boolean; createMongoWildCardIndex: boolean;
useHashV2: boolean; useHashV1: boolean;
enableAnalyticalStore: boolean; enableAnalyticalStore: boolean;
uniqueKeys: string[]; uniqueKeys: string[];
errorMessage: string; errorMessage: string;
@ -121,10 +122,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
enableIndexing: true, enableIndexing: true,
isSharded: userContext.apiType !== "Tables", isSharded: userContext.apiType !== "Tables",
partitionKey: this.getPartitionKey(), partitionKey: this.getPartitionKey(),
subPartitionKeys: [],
enableDedicatedThroughput: false, enableDedicatedThroughput: false,
createMongoWildCardIndex: createMongoWildCardIndex:
isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"), isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"),
useHashV2: false, useHashV1: false,
enableAnalyticalStore: false, enableAnalyticalStore: false,
uniqueKeys: [], uniqueKeys: [],
errorMessage: "", errorMessage: "",
@ -546,6 +548,77 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
}} }}
/> />
{userContext.apiType === "SQL" &&
this.state.subPartitionKeys.map((subPartitionKey: string, index: number) => {
return (
<Stack style={{ marginBottom: 8 }} key={`uniqueKey${index}`} horizontal>
<div
style={{
width: "20px",
border: "solid",
borderWidth: "0px 0px 1px 1px",
marginRight: "5px",
}}
></div>
<input
type="text"
id="addCollection-partitionKeyValue"
key={`addCollection-partitionKeyValue_${index}`}
aria-required
required
size={40}
tabIndex={index > 0 ? 1 : 0}
className="panelTextField"
autoComplete="off"
placeholder={this.getPartitionKeyPlaceHolder(index)}
aria-label={this.getPartitionKeyName()}
pattern={".*"}
title={""}
value={subPartitionKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const subPartitionKeys = [...this.state.subPartitionKeys];
if (!this.state.subPartitionKeys[index] && !event.target.value.startsWith("/")) {
subPartitionKeys[index] = "/" + event.target.value.trim();
this.setState({ subPartitionKeys });
} else {
subPartitionKeys[index] = event.target.value.trim();
this.setState({ subPartitionKeys });
}
}}
/>
<IconButton
iconProps={{ iconName: "Delete" }}
style={{ height: 27 }}
onClick={() => {
const subPartitionKeys = this.state.subPartitionKeys.filter((uniqueKey, j) => index !== j);
this.setState({ subPartitionKeys });
}}
/>
</Stack>
);
})}
{userContext.apiType === "SQL" && userContext.features.enableHierarchicalKeys && (
<Stack className="panelGroupSpacing">
<DefaultButton
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
hidden={this.state.useHashV1}
disabled={this.state.subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition}
onClick={() => this.setState({ subPartitionKeys: [...this.state.subPartitionKeys, ""] })}
>
Add hierarchical partition key
</DefaultButton>
{this.state.subPartitionKeys.length > 0 && (
<Text variant="small">
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> This feature allows you to
partition your data with up to three levels of keys for better data distribution. Requires preview
version of .NET V3 or Java V4 SDK.{" "}
<Link href="https://aka.ms/cosmos-hierarchical-partitioning" target="_blank">
Learn more
</Link>
</Text>
)}
</Stack>
)}
</Stack> </Stack>
)} )}
@ -603,9 +676,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Text> </Text>
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge} directionalHint={DirectionalHint.bottomLeftEdge}
content="Unique keys provide developers with the ability to add a layer of data integrity to their database. By content={
creating a unique key policy when a container is created, you ensure the uniqueness of one or more values "Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key."
per partition key." }
> >
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost> </TooltipHost>
@ -767,18 +840,29 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)} )}
{userContext.apiType === "SQL" && ( {userContext.apiType === "SQL" && (
<Checkbox <Stack className="panelGroupSpacing">
label="My partition key is larger than 101 bytes" <Checkbox
checked={this.state.useHashV2} label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)"
styles={{ checked={this.state.useHashV1}
text: { fontSize: 12 }, styles={{
checkbox: { width: 12, height: 12 }, text: { fontSize: 12 },
label: { padding: 0, alignItems: "center" }, checkbox: { width: 12, height: 12 },
}} label: { padding: 0, alignItems: "center", wordWrap: "break-word", whiteSpace: "break-spaces" },
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => }}
this.setState({ useHashV2: isChecked }) onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
} this.setState({ useHashV1: isChecked, subPartitionKeys: [] })
/> }
/>
<Text variant="small">
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> To ensure compatibility with
older SDKs, the created container will use a legacy partitioning scheme that supports partition
key values of size only up to 101 bytes. If this is enabled, you will not be able to use
hierarchical partition keys.{" "}
<Link href="https://aka.ms/cosmos-large-pk" target="_blank">
Learn more
</Link>
</Text>
</Stack>
)} )}
</Stack> </Stack>
</CollapsibleSectionComponent> </CollapsibleSectionComponent>
@ -833,12 +917,20 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName; return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
} }
private getPartitionKeyPlaceHolder(): string { private getPartitionKeyPlaceHolder(index?: number): string {
switch (userContext.apiType) { switch (userContext.apiType) {
case "Mongo": case "Mongo":
return "e.g., address.zipCode"; return "e.g., address.zipCode";
case "Gremlin": case "Gremlin":
return "e.g., /address"; return "e.g., /address";
case "SQL":
return `${
index === undefined
? "Required - first partition key e.g., /TenantId"
: index === 0
? "second partition key e.g., /UserId"
: "third partition key e.g., /SessionId"
}`;
default: default:
return "e.g., /address/zipCode"; return "e.g., /address/zipCode";
} }
@ -1164,11 +1256,16 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this.parseUniqueKeys(); const uniqueKeyPolicy: DataModels.UniqueKeyPolicy = this.parseUniqueKeys();
const partitionKeyVersion = this.state.useHashV2 ? 2 : undefined; const partitionKeyVersion = this.state.useHashV1 ? undefined : 2;
const partitionKey: DataModels.PartitionKey = partitionKeyString const partitionKey: DataModels.PartitionKey = partitionKeyString
? { ? {
paths: [partitionKeyString], paths: [
kind: "Hash", partitionKeyString,
...(userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0
? this.state.subPartitionKeys
: []),
],
kind: userContext.apiType === "SQL" && this.state.subPartitionKeys.length > 0 ? "MultiHash" : "Hash",
version: partitionKeyVersion, version: partitionKeyVersion,
} }
: undefined; : undefined;

View File

@ -223,7 +223,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
id="addCollection-partitionKeyValue" id="addCollection-partitionKeyValue"
onChange={[Function]} onChange={[Function]}
pattern=".*" pattern=".*"
placeholder="e.g., /address/zipCode" placeholder="Required - first partition key e.g., /TenantId"
required={true} required={true}
size={40} size={40}
title="" title=""
@ -396,26 +396,49 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
className="panelGroupSpacing" className="panelGroupSpacing"
id="collapsibleSectionContent" id="collapsibleSectionContent"
> >
<StyledCheckboxBase <Stack
checked={false} className="panelGroupSpacing"
label="My partition key is larger than 101 bytes" >
onChange={[Function]} <StyledCheckboxBase
styles={ checked={false}
Object { label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)"
"checkbox": Object { onChange={[Function]}
"height": 12, styles={
"width": 12, Object {
}, "checkbox": Object {
"label": Object { "height": 12,
"alignItems": "center", "width": 12,
"padding": 0, },
}, "label": Object {
"text": Object { "alignItems": "center",
"fontSize": 12, "padding": 0,
}, "whiteSpace": "break-spaces",
"wordWrap": "break-word",
},
"text": Object {
"fontSize": 12,
},
}
} }
} />
/> <Text
variant="small"
>
<Icon
className="removeIcon"
iconName="InfoSolid"
tabIndex={0}
/>
To ensure compatibility with older SDKs, the created container will use a legacy partitioning scheme that supports partition key values of size only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.
<StyledLinkBase
href="https://aka.ms/cosmos-large-pk"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text>
</Stack>
</Stack> </Stack>
</CollapsibleSectionComponent> </CollapsibleSectionComponent>
</div> </div>

View File

@ -29,6 +29,7 @@ export type Features = {
readonly mongoProxyEndpoint?: string; readonly mongoProxyEndpoint?: string;
readonly mongoProxyAPIs?: string; readonly mongoProxyAPIs?: string;
readonly enableThroughputCap: boolean; readonly enableThroughputCap: boolean;
readonly enableHierarchicalKeys: boolean;
// can be set via both flight and feature flag // can be set via both flight and feature flag
autoscaleDefault: boolean; autoscaleDefault: boolean;
@ -90,6 +91,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
partitionKeyDefault2: "true" === get("pkpartitionkeytest"), partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
notebooksDownBanner: "true" === get("notebooksDownBanner"), notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"), enableThroughputCap: "true" === get("enablethroughputcap"),
enableHierarchicalKeys: "true" === get("enablehierarchicalkeys"),
}; };
} }