mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-04-17 03:49:23 +01:00
Added quote escaping for partition key extraction (#2403)
This commit is contained in:
@@ -789,7 +789,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
);
|
);
|
||||||
let partitionKeyProperties = useMemo(() => {
|
let partitionKeyProperties = useMemo(() => {
|
||||||
return partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader) =>
|
return partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader) =>
|
||||||
partitionKeyPropertyHeader.replace(/[/]+/g, ".").substring(1).replace(/[']+/g, ""),
|
partitionKeyPropertyHeader.replace(/[/]+/g, ".").substring(1).replace(/[']+/g, "").replace(/["]+/g, ""),
|
||||||
);
|
);
|
||||||
}, [partitionKeyPropertyHeaders]);
|
}, [partitionKeyPropertyHeaders]);
|
||||||
|
|
||||||
@@ -1470,7 +1470,11 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
const partitionKey = _partitionKey || (_collection && _collection.partitionKey);
|
const partitionKey = _partitionKey || (_collection && _collection.partitionKey);
|
||||||
const partitionKeyPropertyHeaders = _collection?.partitionKeyPropertyHeaders || partitionKey?.paths;
|
const partitionKeyPropertyHeaders = _collection?.partitionKeyPropertyHeaders || partitionKey?.paths;
|
||||||
const partitionKeyProperties = partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader) =>
|
const partitionKeyProperties = partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader) =>
|
||||||
partitionKeyPropertyHeader.replace(/[/]+/g, ".").substring(1).replace(/[']+/g, ""),
|
partitionKeyPropertyHeader
|
||||||
|
.replace(/[/]+/g, ".")
|
||||||
|
.substring(1)
|
||||||
|
.replace(/[']+/g, "")
|
||||||
|
.replace(/["]+/g, ""),
|
||||||
);
|
);
|
||||||
|
|
||||||
return newDocumentId(rawDocument, partitionKeyProperties, partitionKeyValue);
|
return newDocumentId(rawDocument, partitionKeyProperties, partitionKeyValue);
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import * as sinon from "sinon";
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import * as QueryUtils from "./QueryUtils";
|
import * as QueryUtils from "./QueryUtils";
|
||||||
import { defaultQueryFields, extractPartitionKeyValues, getValueForPath } from "./QueryUtils";
|
import {
|
||||||
|
defaultQueryFields,
|
||||||
|
extractPartitionKeyValues,
|
||||||
|
getValueForPath,
|
||||||
|
stripDoubleQuotesFromSegment,
|
||||||
|
} from "./QueryUtils";
|
||||||
|
|
||||||
const documentContent = {
|
const documentContent = {
|
||||||
"Volcano Name": "Adams",
|
"Volcano Name": "Adams",
|
||||||
@@ -279,5 +284,97 @@ describe("Query Utils", () => {
|
|||||||
expect(partitionKeyValues.length).toBe(2);
|
expect(partitionKeyValues.length).toBe(2);
|
||||||
expect(partitionKeyValues).toEqual([null, {}]);
|
expect(partitionKeyValues).toEqual([null, {}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should extract partition key value when path has enclosing double quotes", () => {
|
||||||
|
const docWithSpecialKey = {
|
||||||
|
id: "test-id",
|
||||||
|
"partition-key": "some-value",
|
||||||
|
};
|
||||||
|
|
||||||
|
const partitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
|
kind: PartitionKeyKind.Hash,
|
||||||
|
paths: ['/"partition-key"'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(docWithSpecialKey, partitionKeyDefinition);
|
||||||
|
expect(partitionKeyValues.length).toBe(1);
|
||||||
|
expect(partitionKeyValues[0]).toEqual("some-value");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should extract nested partition key value when path segments have enclosing double quotes", () => {
|
||||||
|
const docWithSpecialKey = {
|
||||||
|
id: "test-id",
|
||||||
|
"my-field": {
|
||||||
|
"sub-field": 42,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const partitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
|
kind: PartitionKeyKind.Hash,
|
||||||
|
paths: ['/"my-field"/"sub-field"'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(docWithSpecialKey, partitionKeyDefinition);
|
||||||
|
expect(partitionKeyValues.length).toBe(1);
|
||||||
|
expect(partitionKeyValues[0]).toEqual(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return {} for missing double-quoted partition key", () => {
|
||||||
|
const docWithSpecialKey = {
|
||||||
|
id: "test-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const partitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
|
kind: PartitionKeyKind.Hash,
|
||||||
|
paths: ['/"partition-key"'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(docWithSpecialKey, partitionKeyDefinition);
|
||||||
|
expect(partitionKeyValues.length).toBe(1);
|
||||||
|
expect(partitionKeyValues[0]).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle multi-hash with mixed quoted and unquoted paths", () => {
|
||||||
|
const doc = {
|
||||||
|
id: "test-id",
|
||||||
|
Country: "Japan",
|
||||||
|
"partition-key": "hello",
|
||||||
|
};
|
||||||
|
|
||||||
|
const partitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
|
kind: PartitionKeyKind.MultiHash,
|
||||||
|
paths: ["/Country", '/"partition-key"'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(doc, partitionKeyDefinition);
|
||||||
|
expect(partitionKeyValues.length).toBe(2);
|
||||||
|
expect(partitionKeyValues).toEqual(["Japan", "hello"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("stripDoubleQuotesFromSegment", () => {
|
||||||
|
it("should strip enclosing double quotes", () => {
|
||||||
|
expect(stripDoubleQuotesFromSegment('"partition-key"')).toBe("partition-key");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not strip if only opening quote", () => {
|
||||||
|
expect(stripDoubleQuotesFromSegment('"partition-key')).toBe('"partition-key');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not strip if only closing quote", () => {
|
||||||
|
expect(stripDoubleQuotesFromSegment('partition-key"')).toBe('partition-key"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty string when stripping quotes from empty quoted string", () => {
|
||||||
|
expect(stripDoubleQuotesFromSegment('""')).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not modify unquoted segments", () => {
|
||||||
|
expect(stripDoubleQuotesFromSegment("Country")).toBe("Country");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not strip single quotes", () => {
|
||||||
|
expect(stripDoubleQuotesFromSegment("'partition-key'")).toBe("'partition-key'");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -116,6 +116,17 @@ export const queryPagesUntilContentPresent = async (
|
|||||||
return await doRequest(firstItemIndex);
|
return await doRequest(firstItemIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips enclosing double quotes from a partition key path segment.
|
||||||
|
* e.g., '"partition-key"' -> 'partition-key'
|
||||||
|
*/
|
||||||
|
export const stripDoubleQuotesFromSegment = (segment: string): string => {
|
||||||
|
if (segment.length >= 2 && segment.charAt(0) === '"' && segment.charAt(segment.length - 1) === '"') {
|
||||||
|
return segment.slice(1, -1);
|
||||||
|
}
|
||||||
|
return segment;
|
||||||
|
};
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
export const getValueForPath = (content: any, pathSegments: string[]): any => {
|
export const getValueForPath = (content: any, pathSegments: string[]): any => {
|
||||||
if (pathSegments.length === 0) {
|
if (pathSegments.length === 0) {
|
||||||
@@ -146,7 +157,7 @@ export const extractPartitionKeyValues = (
|
|||||||
const partitionKeyValues: PartitionKey[] = [];
|
const partitionKeyValues: PartitionKey[] = [];
|
||||||
|
|
||||||
partitionKeyDefinition.paths.forEach((partitionKeyPath: string) => {
|
partitionKeyDefinition.paths.forEach((partitionKeyPath: string) => {
|
||||||
const pathSegments: string[] = partitionKeyPath.substring(1).split("/");
|
const pathSegments: string[] = partitionKeyPath.substring(1).split("/").map(stripDoubleQuotesFromSegment);
|
||||||
const value = getValueForPath(documentContent, pathSegments);
|
const value = getValueForPath(documentContent, pathSegments);
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
|
|||||||
@@ -249,4 +249,27 @@ export const documentTestCases: DocumentTestCase[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Single Double-Quoted Partition Key",
|
||||||
|
databaseId: "e2etests-sql-readonly",
|
||||||
|
containerId: "doubleQuotedPartitionKey",
|
||||||
|
documents: [
|
||||||
|
{
|
||||||
|
documentId: "doubleQuotedPartitionKey",
|
||||||
|
partitionKeys: [{ key: "/partition-key", value: "doubleQuotedValue" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
documentId: "doubleQuotedPartitionKey_empty_string",
|
||||||
|
partitionKeys: [{ key: "/partition-key", value: "" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
documentId: "doubleQuotedPartitionKey_null",
|
||||||
|
partitionKeys: [{ key: "/partition-key", value: null }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
documentId: "doubleQuotedPartitionKey_missing",
|
||||||
|
partitionKeys: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -251,7 +251,14 @@ export const setPartitionKeys = (partitionKeys: PartitionKey[]) => {
|
|||||||
partitionKeys.forEach((partitionKey) => {
|
partitionKeys.forEach((partitionKey) => {
|
||||||
const { key: keyPath, value: keyValue } = partitionKey;
|
const { key: keyPath, value: keyValue } = partitionKey;
|
||||||
const cleanPath = keyPath.startsWith("/") ? keyPath.slice(1) : keyPath;
|
const cleanPath = keyPath.startsWith("/") ? keyPath.slice(1) : keyPath;
|
||||||
const keys = cleanPath.split("/");
|
const keys = cleanPath.split("/").map((segment) => {
|
||||||
|
// Strip enclosing double quotes from partition key path segments
|
||||||
|
// e.g., '"partition-key"' -> 'partition-key'
|
||||||
|
if (segment.length >= 2 && segment.charAt(0) === '"' && segment.charAt(segment.length - 1) === '"') {
|
||||||
|
return segment.slice(1, -1);
|
||||||
|
}
|
||||||
|
return segment;
|
||||||
|
});
|
||||||
let current = result;
|
let current = result;
|
||||||
|
|
||||||
keys.forEach((key, index) => {
|
keys.forEach((key, index) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user