UNPKG

@azure/cosmos

Version:
277 lines (276 loc) • 11.6 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var OrderByQueryRangeStrategy_exports = {}; __export(OrderByQueryRangeStrategy_exports, { OrderByQueryRangeStrategy: () => OrderByQueryRangeStrategy }); module.exports = __toCommonJS(OrderByQueryRangeStrategy_exports); class OrderByQueryRangeStrategy { getStrategyType() { return "OrderByQuery"; } filterPartitionRanges(targetRanges, continuationRanges, queryInfo) { if (!targetRanges || targetRanges.length === 0 || !continuationRanges || continuationRanges.length === 0) { return { rangeTokenPairs: [] }; } if (!queryInfo?.orderByItems || !Array.isArray(queryInfo.orderByItems) || queryInfo.orderByItems.length === 0) { throw new Error( "Unable to resume ORDER BY query from continuation token. orderByItems is required for ORDER BY queries." ); } const result = { rangeTokenPairs: [] }; let filteredRanges = []; let resumeRangeFound = false; if (continuationRanges && continuationRanges.length > 0) { resumeRangeFound = true; const targetRangeMapping = continuationRanges[continuationRanges.length - 1].range; const targetRange = targetRangeMapping; const targetContinuationToken = continuationRanges[continuationRanges.length - 1].continuationToken; const leftRanges = targetRanges.filter( (mapping) => this.isRangeBeforeAnother(mapping.maxExclusive, targetRangeMapping.minInclusive) ); const orderByItems = queryInfo.orderByItems; const leftFilter = this.createRangeFilterCondition(orderByItems, queryInfo, "left"); const rightRanges = targetRanges.filter( (mapping) => this.isRangeAfterAnother(mapping.minInclusive, targetRangeMapping.maxExclusive) ); const rightFilter = this.createRangeFilterCondition(orderByItems, queryInfo, "right"); if (leftRanges.length > 0) { leftRanges.forEach((range) => { result.rangeTokenPairs.push({ range, continuationToken: void 0, filteringCondition: leftFilter }); }); } result.rangeTokenPairs.push({ range: targetRange, continuationToken: targetContinuationToken, filteringCondition: rightFilter }); if (rightRanges.length > 0) { rightRanges.forEach((range) => { result.rangeTokenPairs.push({ range, continuationToken: void 0, filteringCondition: rightFilter }); }); } } if (!resumeRangeFound) { filteredRanges = [...targetRanges]; filteredRanges.forEach((range) => { result.rangeTokenPairs.push({ range, continuationToken: void 0, filteringCondition: void 0 }); }); } return result; } /** * Creates a filter condition for ranges based on ORDER BY items and sort orders * This filter ensures that ranges only return documents based on their position relative to the continuation point * @param orderByItems - Array of order by items from the continuation token * @param queryInfo - Query information containing sort orders and other metadata * @param rangePosition - Whether this is for "left" or "right" ranges relative to continuation point * @returns SQL filter condition string for the specified range position */ createRangeFilterCondition(orderByItems, queryInfo, rangePosition) { let sortOrders; try { sortOrders = this.extractSortOrders(queryInfo); } catch (error) { throw new Error( `Unable to resume ORDER BY query from continuation token. The ORDER BY sort direction configuration in the query plan is invalid or missing. This may indicate a client version mismatch or corrupted continuation token. Please retry the query without a continuation token. Original error: ${error}` ); } let orderByExpressions; if (queryInfo && queryInfo.queryInfo && typeof queryInfo.queryInfo === "object" && queryInfo.queryInfo.queryInfo && queryInfo.queryInfo.queryInfo.orderByExpressions && Array.isArray(queryInfo.queryInfo.queryInfo.orderByExpressions)) { orderByExpressions = queryInfo.queryInfo.queryInfo.orderByExpressions; } if (!orderByExpressions || !Array.isArray(orderByExpressions)) { throw new Error( "Unable to resume ORDER BY query from continuation token. The ORDER BY field configuration in the query plan is invalid or missing. This may indicate a client version mismatch or corrupted continuation token. Please retry the query without a continuation token." ); } const filterConditions = []; for (let i = 0; i < orderByItems.length && i < sortOrders.length; i++) { const orderByItem = orderByItems[i]; const sortOrder = sortOrders[i]; if (!orderByItem || orderByItem.item === void 0) { continue; } try { const fieldPath = this.extractFieldPath(queryInfo, i); const condition = this.createComparisonCondition( fieldPath, orderByItem.item, sortOrder, rangePosition ); if (condition) { filterConditions.push(condition); } } catch (error) { throw new Error( `Unable to resume ORDER BY query from continuation token. The ORDER BY field configuration in the query plan is invalid or incompatible with the continuation token format. This may indicate a client version mismatch or corrupted continuation token. Please retry the query without a continuation token. Original error: ${error}` ); } } const combinedFilter = filterConditions.length > 0 ? `(${filterConditions.join(" AND ")})` : ""; return combinedFilter; } /** * Extracts sort orders from query info * @throws Error if sort order information is missing or invalid */ extractSortOrders(queryInfo) { if (!queryInfo) { throw new Error("Query information is required to determine ORDER BY sort directions"); } let orderBy; if (queryInfo.queryInfo && typeof queryInfo.queryInfo === "object" && queryInfo.queryInfo.queryInfo && typeof queryInfo.queryInfo.queryInfo === "object" && queryInfo.queryInfo.queryInfo.orderBy && Array.isArray(queryInfo.queryInfo.queryInfo.orderBy)) { orderBy = queryInfo.queryInfo.queryInfo.orderBy; } if (!orderBy) { throw new Error("ORDER BY sort direction information is missing from query plan"); } return orderBy.map((order, index) => { if (typeof order === "string") { return order; } if (order && typeof order === "object") { const sortOrder = order.direction || order.order || order.sortOrder; if (sortOrder) { return sortOrder; } } throw new Error( `ORDER BY sort direction at position ${index + 1} has an invalid format in the query plan` ); }); } /** * Extracts field path from ORDER BY expressions in query plan * @throws Error if orderByExpressions are not found or index is out of bounds or expression format is invalid */ extractFieldPath(queryInfo, index) { let orderByExpressions; if (queryInfo) { if (queryInfo.orderByExpressions && Array.isArray(queryInfo.orderByExpressions)) { orderByExpressions = queryInfo.orderByExpressions; } else if (queryInfo.queryInfo && typeof queryInfo.queryInfo === "object" && queryInfo.queryInfo.queryInfo && queryInfo.queryInfo.queryInfo.orderByExpressions && Array.isArray(queryInfo.queryInfo.queryInfo.orderByExpressions)) { orderByExpressions = queryInfo.queryInfo.queryInfo.orderByExpressions; } } if (!orderByExpressions) { throw new Error("ORDER BY field information is missing from query plan"); } if (index >= orderByExpressions.length) { throw new Error( `ORDER BY field configuration mismatch: expected at least ${index + 1} fields but found ${orderByExpressions.length}` ); } const expression = orderByExpressions[index]; if (typeof expression === "string") { return expression; } if (expression && typeof expression === "object") { if (expression.expression) { return expression.expression; } if (expression.path) { return expression.path.replace(/^\//, ""); } if (expression.field) { return expression.field; } } throw new Error( `ORDER BY field at position ${index + 1} has an unrecognized format in the query plan` ); } /** * Creates a comparison condition based on the field, value, sort order, and range position */ createComparisonCondition(fieldPath, value, sortOrder, rangePosition) { const isDescending = sortOrder.toLowerCase() === "descending" || sortOrder.toLowerCase() === "desc"; let operator; if (rangePosition === "left") { operator = isDescending ? "<" : ">"; } else { operator = isDescending ? "<=" : ">="; } const formattedValue = this.formatValueForSQL(value); const condition = `${fieldPath} ${operator} ${formattedValue}`; return condition; } /** * Formats a value for use in SQL condition */ formatValueForSQL(value) { if (value === null || value === void 0) { return "null"; } const valueType = typeof value; switch (valueType) { case "string": return `'${value.toString().replace(/'/g, "''")}'`; case "number": case "bigint": return value.toString(); case "boolean": return value ? "true" : "false"; default: if (typeof value === "object") { return `'${JSON.stringify(value).replace(/'/g, "''")}'`; } return `'${value.toString().replace(/'/g, "''")}'`; } } /** * Compares partition key range boundaries with proper handling for inclusive/exclusive semantics * @param boundary1 - First boundary to compare * @param boundary2 - Second boundary to compare * @returns negative if boundary1 is less than boundary2, positive if boundary1 is greater than boundary2, 0 if equal */ comparePartitionKeyBoundaries(boundary1, boundary2) { if (boundary1 === "" && boundary2 === "") return 0; if (boundary1 === "") return -1; if (boundary2 === "") return 1; return boundary1.localeCompare(boundary2); } isRangeBeforeAnother(range1MaxExclusive, range2MinInclusive) { return this.comparePartitionKeyBoundaries(range1MaxExclusive, range2MinInclusive) <= 0; } isRangeAfterAnother(range1MinInclusive, range2MaxExclusive) { return this.comparePartitionKeyBoundaries(range1MinInclusive, range2MaxExclusive) >= 0; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { OrderByQueryRangeStrategy });