Query Plan API (#304)
This commit is contained in:
Родитель
498b1ac734
Коммит
fb6dd7982c
|
@ -117,17 +117,14 @@ export class ChangeFeedIterator<T> {
|
|||
feedOptions.initialHeaders[Constants.HttpHeaders.IfModifiedSince] = this.ifModifiedSince;
|
||||
}
|
||||
|
||||
if (this.partitionKey !== undefined) {
|
||||
feedOptions.partitionKey = this.partitionKey as any; // TODO: our partition key is too restrictive on the main object
|
||||
}
|
||||
|
||||
const response: Response<Array<T & Resource>> = await (this.clientContext.queryFeed<T>({
|
||||
path: this.resourceLink,
|
||||
resourceType: ResourceType.item,
|
||||
resourceId: this.resourceId,
|
||||
resultFn: result => (result ? result.Documents : []),
|
||||
query: undefined,
|
||||
options: feedOptions
|
||||
options: feedOptions,
|
||||
partitionKey: this.partitionKey
|
||||
}) as Promise<any>); // TODO: some funky issues with query feed. Probably need to change it up.
|
||||
|
||||
return new ChangeFeedResponse(
|
||||
|
|
|
@ -12,6 +12,7 @@ import { CosmosHeaders } from "./queryExecutionContext/CosmosHeaders";
|
|||
import { QueryIterator } from "./queryIterator";
|
||||
import { FeedOptions, RequestOptions, Response } from "./request";
|
||||
import { ErrorResponse } from "./request";
|
||||
import { PartitionedQueryExecutionInfo } from "./request/ErrorResponse";
|
||||
import { getHeaders } from "./request/request";
|
||||
import { RequestContext } from "./request/RequestContext";
|
||||
import { request as executeRequest } from "./request/RequestHandler";
|
||||
|
@ -86,7 +87,8 @@ export class ClientContext {
|
|||
resultFn,
|
||||
query,
|
||||
options,
|
||||
partitionKeyRangeId
|
||||
partitionKeyRangeId,
|
||||
partitionKey
|
||||
}: {
|
||||
path: string;
|
||||
resourceType: ResourceType;
|
||||
|
@ -99,6 +101,7 @@ export class ClientContext {
|
|||
query: SqlQuerySpec | string;
|
||||
options: FeedOptions;
|
||||
partitionKeyRangeId?: string;
|
||||
partitionKey?: PartitionKey;
|
||||
}): Promise<Response<T & Resource>> {
|
||||
// Query operations will use ReadEndpoint even though it uses
|
||||
// GET(for queryFeed) and POST(for regular query operations)
|
||||
|
@ -117,7 +120,7 @@ export class ClientContext {
|
|||
options,
|
||||
body: query,
|
||||
plugins: this.cosmosClientOptions.plugins,
|
||||
partitionKey: options.partitionKey
|
||||
partitionKey
|
||||
};
|
||||
|
||||
if (query !== undefined) {
|
||||
|
@ -138,6 +141,45 @@ export class ClientContext {
|
|||
return this.processQueryFeedResponse(response, !!query, resultFn);
|
||||
}
|
||||
|
||||
public async getQueryPlan(
|
||||
path: string,
|
||||
resourceType: ResourceType,
|
||||
resourceId: string,
|
||||
query: SqlQuerySpec | string,
|
||||
options: FeedOptions = {}
|
||||
): Promise<Response<PartitionedQueryExecutionInfo>> {
|
||||
const request: RequestContext = {
|
||||
globalEndpointManager: this.globalEndpointManager,
|
||||
requestAgent: this.cosmosClientOptions.agent,
|
||||
connectionPolicy: this.connectionPolicy,
|
||||
method: HTTPMethod.post,
|
||||
path,
|
||||
operationType: OperationType.Read,
|
||||
client: this,
|
||||
resourceId,
|
||||
resourceType,
|
||||
options,
|
||||
body: query,
|
||||
plugins: this.cosmosClientOptions.plugins
|
||||
};
|
||||
|
||||
request.endpoint = await this.globalEndpointManager.resolveServiceEndpoint(request);
|
||||
request.headers = await this.buildHeaders(request);
|
||||
request.headers[Constants.HttpHeaders.IsQueryPlan] = "True";
|
||||
request.headers[Constants.HttpHeaders.QueryVersion] = "1.4";
|
||||
request.headers[Constants.HttpHeaders.SupportedQueryFeatures] =
|
||||
"Aggregate, Distinct, MultipleOrderBy, OffsetAndLimit, OrderBy, Top, CompositeAggregate";
|
||||
request.headers[Constants.HttpHeaders.ContentType] = Constants.MediaTypes.QueryJson;
|
||||
if (typeof query === "string") {
|
||||
request.body = { query }; // Converts query text to query object.
|
||||
}
|
||||
|
||||
this.applySessionToken(request);
|
||||
const response = await executeRequest(request);
|
||||
this.captureSessionToken(undefined, path, OperationType.Query, response.headers);
|
||||
return response as any;
|
||||
}
|
||||
|
||||
public queryPartitionKeyRanges(collectionLink: string, query?: string | SqlQuerySpec, options?: FeedOptions) {
|
||||
const path = getPathFromLink(collectionLink, ResourceType.pkranges);
|
||||
const id = getIdFromLink(collectionLink);
|
||||
|
|
|
@ -7,8 +7,10 @@ import {
|
|||
ResourceType
|
||||
} from "../../common";
|
||||
import { PartitionKeyDefinition } from "../../documents";
|
||||
import { SqlQuerySpec } from "../../queryExecutionContext";
|
||||
import { QueryIterator } from "../../queryIterator";
|
||||
import { FeedOptions, RequestOptions, ResourceResponse } from "../../request";
|
||||
import { FeedOptions, RequestOptions, ResourceResponse, Response } from "../../request";
|
||||
import { PartitionedQueryExecutionInfo } from "../../request/ErrorResponse";
|
||||
import { Conflict, Conflicts } from "../Conflict";
|
||||
import { Database } from "../Database";
|
||||
import { Item, Items } from "../Item";
|
||||
|
@ -188,6 +190,11 @@ export class Container {
|
|||
);
|
||||
}
|
||||
|
||||
public async getQueryPlan(query: string | SqlQuerySpec): Promise<Response<PartitionedQueryExecutionInfo>> {
|
||||
const path = getPathFromLink(this.url);
|
||||
return this.clientContext.getQueryPlan(path + "/docs", ResourceType.item, getIdFromLink(this.url), query);
|
||||
}
|
||||
|
||||
public readPartitionKeyRanges(feedOptions?: FeedOptions): QueryIterator<PartitionKeyRange> {
|
||||
feedOptions = feedOptions || {};
|
||||
return this.clientContext.queryPartitionKeyRanges(this.url, undefined, feedOptions);
|
||||
|
|
|
@ -60,8 +60,8 @@ export class Items {
|
|||
* const {result: items} = await items.query<{firstName: string}>(querySpec).toArray();
|
||||
* ```
|
||||
*/
|
||||
public query<T>(query: string | SqlQuerySpec, options?: FeedOptions): QueryIterator<T>;
|
||||
public query<T>(query: string | SqlQuerySpec, options?: FeedOptions): QueryIterator<T> {
|
||||
public query<T>(query: string | SqlQuerySpec, options: FeedOptions): QueryIterator<T>;
|
||||
public query<T>(query: string | SqlQuerySpec, options: FeedOptions = {}): QueryIterator<T> {
|
||||
const path = getPathFromLink(this.container.url, ResourceType.item);
|
||||
const id = getIdFromLink(this.container.url);
|
||||
|
||||
|
@ -76,7 +76,7 @@ export class Items {
|
|||
});
|
||||
};
|
||||
|
||||
return new QueryIterator(this.clientContext, query, options, fetchFunction, this.container.url);
|
||||
return new QueryIterator(this.clientContext, query, options, fetchFunction, this.container.url, ResourceType.item);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,7 +168,7 @@ export class Items {
|
|||
*/
|
||||
public readAll<T extends ItemDefinition>(options?: FeedOptions): QueryIterator<T>;
|
||||
public readAll<T extends ItemDefinition>(options?: FeedOptions): QueryIterator<T> {
|
||||
return this.query<T>(undefined, options);
|
||||
return this.query<T>("SELECT * from c", options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,6 +65,9 @@ export const Constants = {
|
|||
// Query
|
||||
Query: "x-ms-documentdb-query",
|
||||
IsQuery: "x-ms-documentdb-isquery",
|
||||
IsQueryPlan: "x-ms-cosmos-is-query-plan-request",
|
||||
SupportedQueryFeatures: "x-ms-cosmos-supported-query-features",
|
||||
QueryVersion: "x-ms-cosmos-query-version",
|
||||
|
||||
// Our custom Azure Cosmos DB headers
|
||||
Continuation: "x-ms-continuation",
|
||||
|
|
|
@ -217,30 +217,18 @@ export function isResourceValid(resource: any, err: any) {
|
|||
}
|
||||
|
||||
/** @ignore */
|
||||
export function getIdFromLink(resourceLink: string, isNameBased: boolean = true) {
|
||||
if (isNameBased) {
|
||||
resourceLink = trimSlashes(resourceLink);
|
||||
return resourceLink;
|
||||
} else {
|
||||
return parseLink(resourceLink).objectBody.id.toLowerCase();
|
||||
}
|
||||
export function getIdFromLink(resourceLink: string) {
|
||||
resourceLink = trimSlashes(resourceLink);
|
||||
return resourceLink;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
export function getPathFromLink(resourceLink: string, resourceType?: string, isNameBased: boolean = true) {
|
||||
if (isNameBased) {
|
||||
resourceLink = trimSlashes(resourceLink);
|
||||
if (resourceType) {
|
||||
return "/" + encodeURI(resourceLink) + "/" + resourceType;
|
||||
} else {
|
||||
return "/" + encodeURI(resourceLink);
|
||||
}
|
||||
export function getPathFromLink(resourceLink: string, resourceType?: string) {
|
||||
resourceLink = trimSlashes(resourceLink);
|
||||
if (resourceType) {
|
||||
return "/" + encodeURI(resourceLink) + "/" + resourceType;
|
||||
} else {
|
||||
if (resourceType) {
|
||||
return "/" + resourceLink + resourceType + "/";
|
||||
} else {
|
||||
return "/" + resourceLink;
|
||||
}
|
||||
return "/" + encodeURI(resourceLink);
|
||||
}
|
||||
}
|
||||
export function isStringNullOrEmpty(inputString: string) {
|
||||
|
|
|
@ -10,6 +10,7 @@ export class AggregateEndpointComponent implements ExecutionContext {
|
|||
private aggregateValues: any[];
|
||||
private aggregateValuesIndex: number;
|
||||
private localAggregators: any[];
|
||||
private started: boolean;
|
||||
|
||||
/**
|
||||
* Represents an endpoint in handling aggregate queries.
|
||||
|
@ -50,31 +51,27 @@ export class AggregateEndpointComponent implements ExecutionContext {
|
|||
this.aggregateValues = [];
|
||||
this.aggregateValuesIndex = -1;
|
||||
|
||||
try {
|
||||
const { result: resources, headers } = await this._getQueryResults();
|
||||
const { result: resources, headers } = await this._getQueryResults();
|
||||
|
||||
resources.forEach((resource: any) => {
|
||||
// TODO: any
|
||||
this.localAggregators.forEach(aggregator => {
|
||||
let itemValue;
|
||||
// Get the value of the first property if it exists
|
||||
if (resource && Object.keys(resource).length > 0) {
|
||||
const key = Object.keys(resource)[0];
|
||||
itemValue = resource[key];
|
||||
}
|
||||
aggregator.aggregate(itemValue);
|
||||
});
|
||||
});
|
||||
|
||||
// Get the aggregated results
|
||||
resources.forEach((resource: any) => {
|
||||
// TODO: any
|
||||
this.localAggregators.forEach(aggregator => {
|
||||
this.aggregateValues.push(aggregator.getResult());
|
||||
let itemValue;
|
||||
// Get the value of the first property if it exists
|
||||
if (resource && Object.keys(resource).length > 0) {
|
||||
const key = Object.keys(resource)[0];
|
||||
itemValue = resource[key];
|
||||
}
|
||||
aggregator.aggregate(itemValue);
|
||||
});
|
||||
});
|
||||
|
||||
return { result: this.aggregateValues, headers };
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
// Get the aggregated results
|
||||
this.localAggregators.forEach(aggregator => {
|
||||
this.aggregateValues.push(aggregator.getResult());
|
||||
});
|
||||
|
||||
return { result: this.aggregateValues, headers };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,18 +79,15 @@ export class AggregateEndpointComponent implements ExecutionContext {
|
|||
* @ignore
|
||||
*/
|
||||
public async _getQueryResults(): Promise<Response<any>> {
|
||||
try {
|
||||
const { result: item, headers } = await this.executionContext.nextItem();
|
||||
if (item === undefined) {
|
||||
// no more results
|
||||
return { result: this.toArrayTempResources, headers };
|
||||
}
|
||||
|
||||
this.toArrayTempResources = this.toArrayTempResources.concat(item);
|
||||
return this._getQueryResults();
|
||||
} catch (err) {
|
||||
throw err;
|
||||
this.started = true;
|
||||
const { result: item, headers } = await this.executionContext.nextItem();
|
||||
if (item === undefined) {
|
||||
// no more results
|
||||
return { result: this.toArrayTempResources, headers };
|
||||
}
|
||||
|
||||
this.toArrayTempResources = this.toArrayTempResources.concat(item);
|
||||
return this._getQueryResults();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,20 +98,16 @@ export class AggregateEndpointComponent implements ExecutionContext {
|
|||
* the function takes two parameters error, element.
|
||||
*/
|
||||
public async nextItem(): Promise<Response<any>> {
|
||||
try {
|
||||
let resHeaders: CosmosHeaders;
|
||||
if (this.aggregateValues === undefined) {
|
||||
({ headers: resHeaders } = await this._getAggregateResult());
|
||||
}
|
||||
const resource =
|
||||
this.aggregateValuesIndex < this.aggregateValues.length
|
||||
? this.aggregateValues[++this.aggregateValuesIndex]
|
||||
: undefined;
|
||||
|
||||
return { result: resource, headers: resHeaders };
|
||||
} catch (err) {
|
||||
throw err;
|
||||
let resHeaders: CosmosHeaders;
|
||||
if (this.aggregateValues === undefined) {
|
||||
({ headers: resHeaders } = await this._getAggregateResult());
|
||||
}
|
||||
const resource =
|
||||
this.aggregateValuesIndex < this.aggregateValues.length
|
||||
? this.aggregateValues[++this.aggregateValuesIndex]
|
||||
: undefined;
|
||||
|
||||
return { result: resource, headers: resHeaders };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,6 +139,9 @@ export class AggregateEndpointComponent implements ExecutionContext {
|
|||
* @returns {Boolean} true if there is other elements to process in the AggregateEndpointComponent.
|
||||
*/
|
||||
public hasMoreResults() {
|
||||
return this.aggregateValues != null && this.aggregateValuesIndex < this.aggregateValues.length - 1;
|
||||
if (!this.started) {
|
||||
return true;
|
||||
}
|
||||
return !this.started && this.aggregateValues != null && this.aggregateValuesIndex < this.aggregateValues.length - 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,4 +10,3 @@ export * from "./parallelQueryExecutionContextBase";
|
|||
export * from "./parallelQueryExecutionContext";
|
||||
export * from "./orderByQueryExecutionContext";
|
||||
export * from "./pipelinedQueryExecutionContext";
|
||||
export * from "./proxyQueryExecutionContext";
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
import { ClientContext } from "../ClientContext";
|
||||
import { StatusCodes, SubStatusCodes } from "../common/statusCodes";
|
||||
import { ErrorResponse, Response } from "../request";
|
||||
import { PartitionedQueryExecutionInfo } from "../request/ErrorResponse";
|
||||
import { DefaultQueryExecutionContext, FetchFunctionCallback } from "./defaultQueryExecutionContext";
|
||||
import { ExecutionContext } from "./ExecutionContext";
|
||||
import { PipelinedQueryExecutionContext } from "./pipelinedQueryExecutionContext";
|
||||
import { SqlQuerySpec } from "./SqlQuerySpec";
|
||||
|
||||
/** @hidden */
|
||||
export class ProxyQueryExecutionContext implements ExecutionContext {
|
||||
private queryExecutionContext: ExecutionContext;
|
||||
|
||||
constructor(
|
||||
private clientContext: ClientContext,
|
||||
private query: SqlQuerySpec | string,
|
||||
private options: any, // TODO: any options
|
||||
private fetchFunctions: FetchFunctionCallback | FetchFunctionCallback[],
|
||||
private resourceLink: string | string[]
|
||||
) {
|
||||
this.query = query;
|
||||
this.fetchFunctions = fetchFunctions;
|
||||
// clone options
|
||||
this.options = JSON.parse(JSON.stringify(options || {}));
|
||||
this.resourceLink = resourceLink;
|
||||
this.queryExecutionContext = new DefaultQueryExecutionContext(this.options, this.fetchFunctions);
|
||||
}
|
||||
/**
|
||||
* Execute a provided function on the next element in the ProxyQueryExecutionContext.
|
||||
* @memberof ProxyQueryExecutionContext
|
||||
* @instance
|
||||
* @param {callback} callback - Function to execute for each element. \
|
||||
* the function takes two parameters error, element.
|
||||
*/
|
||||
public async nextItem(): Promise<Response<any>> {
|
||||
try {
|
||||
const r = await this.queryExecutionContext.nextItem();
|
||||
return r;
|
||||
} catch (err) {
|
||||
if (this._hasPartitionedExecutionInfo(err)) {
|
||||
// if this's a partitioned execution info switches the execution context
|
||||
const partitionedExecutionInfo = this._getParitionedExecutionInfo(err);
|
||||
this.queryExecutionContext = this._createPipelinedExecutionContext(partitionedExecutionInfo);
|
||||
try {
|
||||
// TODO: recusion might be bad...
|
||||
return this.nextItem();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _createPipelinedExecutionContext(partitionedExecutionInfo: PartitionedQueryExecutionInfo) {
|
||||
if (!this.resourceLink) {
|
||||
throw new Error("for top/orderby resourceLink is required");
|
||||
}
|
||||
if (Array.isArray(this.resourceLink) && this.resourceLink.length !== 1) {
|
||||
throw new Error("for top/orderby exactly one collectionLink is required");
|
||||
}
|
||||
|
||||
const collectionLink = Array.isArray(this.resourceLink) ? this.resourceLink[0] : this.resourceLink;
|
||||
|
||||
return new PipelinedQueryExecutionContext(
|
||||
this.clientContext,
|
||||
collectionLink,
|
||||
this.query,
|
||||
this.options,
|
||||
partitionedExecutionInfo
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current element on the ProxyQueryExecutionContext.
|
||||
* @memberof ProxyQueryExecutionContext
|
||||
* @instance
|
||||
* @param {callback} callback - Function to execute for the current element. \
|
||||
* the function takes two parameters error, element.
|
||||
*/
|
||||
public async current(): Promise<Response<any>> {
|
||||
try {
|
||||
return await this.queryExecutionContext.current();
|
||||
} catch (err) {
|
||||
if (this._hasPartitionedExecutionInfo(err)) {
|
||||
// if this's a partitioned execution info switches the execution context
|
||||
const partitionedExecutionInfo = this._getParitionedExecutionInfo(err);
|
||||
this.queryExecutionContext = this._createPipelinedExecutionContext(partitionedExecutionInfo);
|
||||
|
||||
// TODO: recursion
|
||||
try {
|
||||
return this.current();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there are still remaining resources to process.
|
||||
* @memberof ProxyQueryExecutionContext
|
||||
* @instance
|
||||
* @returns {Boolean} true if there is other elements to process in the ProxyQueryExecutionContext.
|
||||
*/
|
||||
public hasMoreResults() {
|
||||
return this.queryExecutionContext.hasMoreResults();
|
||||
}
|
||||
|
||||
public async fetchMore(): Promise<Response<any>> {
|
||||
try {
|
||||
return await this.queryExecutionContext.fetchMore();
|
||||
} catch (err) {
|
||||
if (this._hasPartitionedExecutionInfo(err)) {
|
||||
// if this's a partitioned execution info switches the execution context
|
||||
const partitionedExecutionInfo = this._getParitionedExecutionInfo(err);
|
||||
this.queryExecutionContext = this._createPipelinedExecutionContext(partitionedExecutionInfo);
|
||||
try {
|
||||
// TODO: maybe should move the others to use this pattern as it avoid the recursion issue.
|
||||
return this.queryExecutionContext.fetchMore();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _hasPartitionedExecutionInfo(error: ErrorResponse) {
|
||||
return (
|
||||
error.code === StatusCodes.BadRequest &&
|
||||
"substatus" in error &&
|
||||
error["substatus"] === SubStatusCodes.CrossPartitionQueryNotServable
|
||||
);
|
||||
}
|
||||
|
||||
private _getParitionedExecutionInfo(error: ErrorResponse) {
|
||||
return error.body.additionalErrorInfo;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,18 @@
|
|||
/// <reference lib="esnext.asynciterable" />
|
||||
import { ClientContext } from "./ClientContext";
|
||||
import { getPathFromLink, ResourceType, StatusCodes, SubStatusCodes } from "./common";
|
||||
import {
|
||||
CosmosHeaders,
|
||||
DefaultQueryExecutionContext,
|
||||
ExecutionContext,
|
||||
FetchFunctionCallback,
|
||||
getInitialHeader,
|
||||
mergeHeaders,
|
||||
ProxyQueryExecutionContext,
|
||||
PipelinedQueryExecutionContext,
|
||||
SqlQuerySpec
|
||||
} from "./queryExecutionContext";
|
||||
import { Response } from "./request";
|
||||
import { ErrorResponse, PartitionedQueryExecutionInfo } from "./request/ErrorResponse";
|
||||
import { FeedOptions } from "./request/FeedOptions";
|
||||
import { FeedResponse } from "./request/FeedResponse";
|
||||
|
||||
|
@ -21,6 +25,7 @@ export class QueryIterator<T> {
|
|||
private fetchAllTempResources: T[]; // TODO
|
||||
private fetchAllLastResHeaders: CosmosHeaders;
|
||||
private queryExecutionContext: ExecutionContext;
|
||||
private queryPlanPromise: Promise<Response<PartitionedQueryExecutionInfo>>;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
|
@ -29,14 +34,15 @@ export class QueryIterator<T> {
|
|||
private query: SqlQuerySpec | string,
|
||||
private options: FeedOptions,
|
||||
private fetchFunctions: FetchFunctionCallback | FetchFunctionCallback[],
|
||||
private resourceLink?: string
|
||||
private resourceLink?: string,
|
||||
private resourceType?: ResourceType
|
||||
) {
|
||||
this.query = query;
|
||||
this.fetchFunctions = fetchFunctions;
|
||||
this.options = options;
|
||||
this.resourceLink = resourceLink;
|
||||
this.queryExecutionContext = this.createQueryExecutionContext();
|
||||
this.fetchAllLastResHeaders = getInitialHeader();
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,14 +69,25 @@ export class QueryIterator<T> {
|
|||
*/
|
||||
public async *getAsyncIterator(): AsyncIterable<FeedResponse<T>> {
|
||||
this.reset();
|
||||
this.queryPlanPromise = this.fetchQueryPlan();
|
||||
while (this.queryExecutionContext.hasMoreResults()) {
|
||||
const result = await this.queryExecutionContext.fetchMore();
|
||||
let response: Response<any>;
|
||||
try {
|
||||
response = await this.queryExecutionContext.fetchMore();
|
||||
} catch (error) {
|
||||
if (this.needsQueryPlan(error)) {
|
||||
await this.createPipelinedExecutionContext();
|
||||
response = await this.queryExecutionContext.fetchMore();
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const feedResponse = new FeedResponse<T>(
|
||||
result.result,
|
||||
result.headers,
|
||||
response.result,
|
||||
response.headers,
|
||||
this.queryExecutionContext.hasMoreResults()
|
||||
);
|
||||
if (result.result !== undefined) {
|
||||
if (response.result !== undefined) {
|
||||
yield feedResponse;
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +120,18 @@ export class QueryIterator<T> {
|
|||
* before returning the first batch of responses.
|
||||
*/
|
||||
public async fetchNext(): Promise<FeedResponse<T>> {
|
||||
const response = await this.queryExecutionContext.fetchMore();
|
||||
this.queryPlanPromise = this.fetchQueryPlan();
|
||||
let response: Response<any>;
|
||||
try {
|
||||
response = await this.queryExecutionContext.fetchMore();
|
||||
} catch (error) {
|
||||
if (this.needsQueryPlan(error)) {
|
||||
await this.createPipelinedExecutionContext();
|
||||
response = await this.queryExecutionContext.fetchMore();
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return new FeedResponse<T>(response.result, response.headers, this.queryExecutionContext.hasMoreResults());
|
||||
}
|
||||
|
||||
|
@ -111,12 +139,26 @@ export class QueryIterator<T> {
|
|||
* Reset the QueryIterator to the beginning and clear all the resources inside it
|
||||
*/
|
||||
public reset() {
|
||||
this.queryExecutionContext = this.createQueryExecutionContext();
|
||||
this.queryPlanPromise = undefined;
|
||||
this.queryExecutionContext = new DefaultQueryExecutionContext(this.options, this.fetchFunctions);
|
||||
}
|
||||
|
||||
private async toArrayImplementation(): Promise<FeedResponse<T>> {
|
||||
this.queryPlanPromise = this.fetchQueryPlan();
|
||||
|
||||
while (this.queryExecutionContext.hasMoreResults()) {
|
||||
const { result, headers } = await this.queryExecutionContext.nextItem();
|
||||
let response: Response<any>;
|
||||
try {
|
||||
response = await this.queryExecutionContext.nextItem();
|
||||
} catch (error) {
|
||||
if (this.needsQueryPlan(error)) {
|
||||
await this.createPipelinedExecutionContext();
|
||||
response = await this.queryExecutionContext.nextItem();
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const { result, headers } = response;
|
||||
// concatenate the results and fetch more
|
||||
mergeHeaders(this.fetchAllLastResHeaders, headers);
|
||||
|
||||
|
@ -131,13 +173,40 @@ export class QueryIterator<T> {
|
|||
);
|
||||
}
|
||||
|
||||
private createQueryExecutionContext() {
|
||||
return new ProxyQueryExecutionContext(
|
||||
private async createPipelinedExecutionContext() {
|
||||
const queryPlanResponse = await this.queryPlanPromise;
|
||||
const queryPlan = queryPlanResponse.result;
|
||||
const queryInfo = queryPlan.queryInfo;
|
||||
if (queryInfo.aggregates.length > 0 && queryInfo.hasSelectValue === false) {
|
||||
throw new Error("Aggregate queries must use the VALUE keyword");
|
||||
}
|
||||
this.queryExecutionContext = new PipelinedQueryExecutionContext(
|
||||
this.clientContext,
|
||||
this.resourceLink,
|
||||
this.query,
|
||||
this.options,
|
||||
this.fetchFunctions,
|
||||
this.resourceLink
|
||||
queryPlan
|
||||
);
|
||||
}
|
||||
|
||||
private async fetchQueryPlan() {
|
||||
if (!this.queryPlanPromise && this.resourceType === ResourceType.item) {
|
||||
return this.clientContext.getQueryPlan(
|
||||
getPathFromLink(this.resourceLink) + "/docs",
|
||||
ResourceType.item,
|
||||
this.resourceLink,
|
||||
this.query,
|
||||
this.options
|
||||
);
|
||||
}
|
||||
return this.queryPlanPromise;
|
||||
}
|
||||
|
||||
private needsQueryPlan(error: any): error is ErrorResponse {
|
||||
return (
|
||||
error.code === StatusCodes.BadRequest &&
|
||||
error.substatus &&
|
||||
error.substatus === SubStatusCodes.CrossPartitionQueryNotServable
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ interface QueryInfo {
|
|||
aggregates?: any[];
|
||||
rewrittenQuery?: any;
|
||||
distinctType: string;
|
||||
hasSelectValue: boolean;
|
||||
}
|
||||
|
||||
export interface ErrorResponse extends Error {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { PartitionKey } from "../documents";
|
||||
import { SharedOptions } from "./SharedOptions";
|
||||
|
||||
/**
|
||||
|
@ -7,14 +6,6 @@ import { SharedOptions } from "./SharedOptions";
|
|||
export interface FeedOptions extends SharedOptions {
|
||||
/** Opaque token for continuing the enumeration. */
|
||||
continuation?: string;
|
||||
/** Specifies a partition key definition for a particular path in the Azure Cosmos DB database service. */
|
||||
partitionKey?: PartitionKey | PartitionKey[];
|
||||
/**
|
||||
* A value indicating whether users are enabled to send more than one request to execute the query in the Azure Cosmos DB database service.
|
||||
*
|
||||
* More than one request is necessary if the query is not scoped to single partition key value.
|
||||
*/
|
||||
enableCrossPartitionQuery?: boolean;
|
||||
/** Allow scan on the queries which couldn't be served as indexing was opted out on the requested paths. */
|
||||
enableScanInQuery?: boolean;
|
||||
/**
|
||||
|
|
|
@ -71,15 +71,12 @@ async function httpRequest(requestContext: RequestContext) {
|
|||
});
|
||||
|
||||
if (response.status >= 400) {
|
||||
const errorResponse: ErrorResponse = new Error();
|
||||
const errorResponse: ErrorResponse = new Error(result.message);
|
||||
|
||||
errorResponse.code = response.status;
|
||||
errorResponse.body = result;
|
||||
errorResponse.headers = headers;
|
||||
|
||||
if (result.additionalErrorInfo) {
|
||||
errorResponse.body.additionalErrorInfo = JSON.parse(result.additionalErrorInfo);
|
||||
}
|
||||
if (Constants.HttpHeaders.ActivityId in headers) {
|
||||
errorResponse.activityId = headers[Constants.HttpHeaders.ActivityId];
|
||||
}
|
||||
|
|
|
@ -45,12 +45,12 @@ export async function getHeaders({
|
|||
path,
|
||||
resourceId,
|
||||
resourceType,
|
||||
options,
|
||||
options = {},
|
||||
partitionKeyRangeId,
|
||||
useMultipleWriteLocations,
|
||||
partitionKey
|
||||
}: GetHeadersOptions): Promise<CosmosHeaders> {
|
||||
const headers: CosmosHeaders = { ...defaultHeaders };
|
||||
const headers: CosmosHeaders = { [Constants.HttpHeaders.EnableCrossPartitionQuery]: true, ...defaultHeaders };
|
||||
|
||||
if (useMultipleWriteLocations) {
|
||||
headers[Constants.HttpHeaders.ALLOW_MULTIPLE_WRITES] = true;
|
||||
|
@ -118,10 +118,6 @@ export async function getHeaders({
|
|||
headers[Constants.HttpHeaders.EnableScanInQuery] = options.enableScanInQuery;
|
||||
}
|
||||
|
||||
if (options.enableCrossPartitionQuery) {
|
||||
headers[Constants.HttpHeaders.EnableCrossPartitionQuery] = options.enableCrossPartitionQuery;
|
||||
}
|
||||
|
||||
if (options.populateQuotaInfo) {
|
||||
headers[Constants.HttpHeaders.PopulateQuotaInfo] = options.populateQuotaInfo;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ export class TestData {
|
|||
public sum: number;
|
||||
public docs: any[];
|
||||
constructor(public partitionKey: string, public uniquePartitionKey: string) {
|
||||
this.numberOfDocuments = 800;
|
||||
this.numberOfDocuments = 50;
|
||||
this.field = "field";
|
||||
const docs = [];
|
||||
|
||||
|
@ -18,7 +18,7 @@ export class TestData {
|
|||
docs.push(d);
|
||||
}
|
||||
|
||||
this.numberOfDocsWithSamePartitionKey = 400;
|
||||
this.numberOfDocsWithSamePartitionKey = 20;
|
||||
for (let i = 0; i < this.numberOfDocsWithSamePartitionKey; ++i) {
|
||||
const d: any = {};
|
||||
d[partitionKey] = uniquePartitionKey;
|
||||
|
|
|
@ -43,7 +43,6 @@ describe("NodeJS CRUD Tests", function() {
|
|||
await client.getDatabaseAccount({ abortSignal: signal });
|
||||
assert.fail("Must throw when trying to connect to database");
|
||||
} catch (err) {
|
||||
// console.log(err);
|
||||
assert.equal(err.name, "AbortError", "client should throw exception");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,8 +2,8 @@ import assert from "assert";
|
|||
import { Constants } from "../../dist-esm";
|
||||
import { ContainerDefinition, Database } from "../../dist-esm/client";
|
||||
import { ContainerRequest } from "../../dist-esm/client/Container/ContainerRequest";
|
||||
import { DataType, Index, IndexedPath, IndexingMode, IndexingPolicy, IndexKind } from "../../dist-esm/documents";
|
||||
import { getTestDatabase, removeAllDatabases } from "../common/TestHelpers";
|
||||
import { DataType, IndexedPath, IndexingMode, IndexingPolicy, IndexKind } from "../../dist-esm/documents";
|
||||
import { getTestDatabase, removeAllDatabases, getTestContainer } from "../common/TestHelpers";
|
||||
|
||||
describe("Containers", function() {
|
||||
this.timeout(process.env.MOCHA_TIMEOUT || 10000);
|
||||
|
|
|
@ -59,7 +59,7 @@ describe("Item CRUD", function() {
|
|||
assert.equal(document.name, itemDefinition.name);
|
||||
assert(document.id !== undefined);
|
||||
// read documents after creation
|
||||
const { resources: documents2 } = await container.items.readAll({ enableCrossPartitionQuery: true }).fetchAll();
|
||||
const { resources: documents2 } = await container.items.readAll().fetchAll();
|
||||
assert.equal(documents2.length, beforeCreateDocumentsCount + 1, "create should increase the number of documents");
|
||||
// query documents
|
||||
const querySpec = {
|
||||
|
@ -71,13 +71,9 @@ describe("Item CRUD", function() {
|
|||
}
|
||||
]
|
||||
};
|
||||
const { resources: results } = await container.items
|
||||
.query(querySpec, { enableCrossPartitionQuery: true })
|
||||
.fetchAll();
|
||||
const { resources: results } = await container.items.query(querySpec).fetchAll();
|
||||
assert(results.length > 0, "number of results for the query should be > 0");
|
||||
const { resources: results2 } = await container.items
|
||||
.query(querySpec, { enableCrossPartitionQuery: true })
|
||||
.fetchAll();
|
||||
const { resources: results2 } = await container.items.query(querySpec).fetchAll();
|
||||
assert(results2.length > 0, "number of results for the query should be > 0");
|
||||
|
||||
// replace document
|
||||
|
@ -169,15 +165,8 @@ describe("Item CRUD", function() {
|
|||
const querySpec = {
|
||||
query: "SELECT * FROM Root"
|
||||
};
|
||||
try {
|
||||
const { resources: badUpdate } = await container.items.query(querySpec, { enableScanInQuery: true }).fetchAll();
|
||||
assert.fail("Must fail");
|
||||
} catch (err) {
|
||||
const badRequestErrorCode = 400;
|
||||
assert.equal(err.code, badRequestErrorCode, "response should return error code " + badRequestErrorCode);
|
||||
}
|
||||
const { resources: results } = await container.items
|
||||
.query<ItemDefinition>(querySpec, { enableScanInQuery: true, enableCrossPartitionQuery: true })
|
||||
.query<ItemDefinition>(querySpec, { enableScanInQuery: true })
|
||||
.fetchAll();
|
||||
assert(results !== undefined, "error querying documents");
|
||||
results.sort(function(doc1, doc2) {
|
||||
|
|
|
@ -73,13 +73,11 @@ describe("Non Partitioned Container", function() {
|
|||
assert.equal(item4.newProp, newProp);
|
||||
|
||||
// read documents after creation
|
||||
const { resources: documents } = await container.items.readAll({ enableCrossPartitionQuery: true }).fetchAll();
|
||||
const { resources: documents } = await container.items.readAll().fetchAll();
|
||||
assert.equal(documents.length, 2, "create should increase the number of documents");
|
||||
|
||||
// query documents
|
||||
const { resources: results } = await container.items
|
||||
.query("SELECT * FROM root r", { enableCrossPartitionQuery: true })
|
||||
.fetchAll();
|
||||
const { resources: results } = await container.items.query("SELECT * FROM root r").fetchAll();
|
||||
assert(results.length === 2, "Container should contain two items");
|
||||
|
||||
// delete a document
|
||||
|
|
|
@ -12,83 +12,44 @@ if (!Symbol || !Symbol.asyncIterator) {
|
|||
(Symbol as any).asyncIterator = Symbol.for("Symbol.asyncIterator");
|
||||
}
|
||||
|
||||
describe("NodeJS CRUD Tests", function() {
|
||||
describe("Queries", function() {
|
||||
this.timeout(process.env.MOCHA_TIMEOUT || 10000);
|
||||
before(async function() {
|
||||
await removeAllDatabases();
|
||||
});
|
||||
|
||||
describe("Validate Queries CRUD", function() {
|
||||
const queriesCRUDTest = async function() {
|
||||
try {
|
||||
// create a database
|
||||
const database = await getTestDatabase("query test database");
|
||||
// query databases
|
||||
const querySpec0 = {
|
||||
query: "SELECT * FROM root r WHERE r.id=@id",
|
||||
parameters: [
|
||||
{
|
||||
name: "@id",
|
||||
value: database.id
|
||||
}
|
||||
]
|
||||
};
|
||||
const { resources: results } = await client.databases.query(querySpec0).fetchAll();
|
||||
assert(results.length > 0, "number of results for the query should be > 0");
|
||||
const querySpec1 = {
|
||||
query: "SELECT * FROM root r WHERE r.id='" + database.id + "'"
|
||||
};
|
||||
const { resources: results2 } = await client.databases.query(querySpec1).fetchAll();
|
||||
assert(results2.length > 0, "number of results for the query should be > 0");
|
||||
const querySpec2 = "SELECT * FROM root r WHERE r.id='" + database.id + "'";
|
||||
const { resources: results3 } = await client.databases.query(querySpec2).fetchAll();
|
||||
assert(results3.length > 0, "number of results for the query should be > 0");
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
describe("Query CRUD", function() {
|
||||
it("nativeApi Should do queries CRUD operations successfully name based", async function() {
|
||||
try {
|
||||
await queriesCRUDTest();
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Validate QueryIterator Functionality For Multiple Partition container", function() {
|
||||
const documentDefinitions = [
|
||||
{ id: "document1" },
|
||||
{ id: "document2", key: null, prop: 1 },
|
||||
{ id: "document3", key: false, prop: 1 },
|
||||
{ id: "document4", key: true, prop: 1 },
|
||||
{ id: "document5", key: 1, prop: 1 },
|
||||
{ id: "document6", key: "A", prop: 1 }
|
||||
];
|
||||
|
||||
let container: Container;
|
||||
|
||||
// creates a new database, creates a new collecton, bulk inserts documents to the container
|
||||
beforeEach(async function() {
|
||||
const partitionKey = "key";
|
||||
const containerDefinition = {
|
||||
id: "coll1",
|
||||
partitionKey: {
|
||||
paths: ["/" + partitionKey]
|
||||
}
|
||||
// create a database
|
||||
const database = await getTestDatabase("query test database");
|
||||
// query databases
|
||||
const querySpec0 = {
|
||||
query: "SELECT * FROM root r WHERE r.id=@id",
|
||||
parameters: [
|
||||
{
|
||||
name: "@id",
|
||||
value: database.id
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const containerOptions = { offerThroughput: 12000 };
|
||||
container = await getTestContainer("query CRUD database 中文", client, containerDefinition, containerOptions);
|
||||
await bulkInsertItems(container, documentDefinitions);
|
||||
const { resources: results } = await client.databases.query(querySpec0).fetchAll();
|
||||
assert(results.length > 0, "number of results for the query should be > 0");
|
||||
const querySpec1 = {
|
||||
query: "SELECT * FROM root r WHERE r.id='" + database.id + "'"
|
||||
};
|
||||
const { resources: results2 } = await client.databases.query(querySpec1).fetchAll();
|
||||
assert(results2.length > 0, "number of results for the query should be > 0");
|
||||
const querySpec2 = "SELECT * FROM root r WHERE r.id='" + database.id + "'";
|
||||
const { resources: results3 } = await client.databases.query(querySpec2).fetchAll();
|
||||
assert(results3.length > 0, "number of results for the query should be > 0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Validate QueryIterator Functionality", function() {
|
||||
describe("QueryIterator", function() {
|
||||
this.timeout(process.env.MOCHA_TIMEOUT || 30000);
|
||||
let resources: { container: Container; doc1: any; doc2: any; doc3: any };
|
||||
beforeEach(async function() {
|
||||
|
||||
before(async function() {
|
||||
const container = await getTestContainer("Validate QueryIterator Functionality", client);
|
||||
const { resource: doc1 } = await container.items.create({ id: "doc1", prop1: "value1" });
|
||||
const { resource: doc2 } = await container.items.create({ id: "doc2", prop1: "value2" });
|
||||
|
@ -96,16 +57,16 @@ describe("NodeJS CRUD Tests", function() {
|
|||
resources = { container, doc1, doc2, doc3 };
|
||||
});
|
||||
|
||||
const queryIteratorToArrayTest = async function() {
|
||||
it("toArray", async function() {
|
||||
const queryIterator = resources.container.items.readAll({ maxItemCount: 2 });
|
||||
const { resources: docs } = await queryIterator.fetchAll();
|
||||
assert.equal(docs.length, 3, "queryIterator should return all documents using continuation");
|
||||
assert.equal(docs[0].id, resources.doc1.id);
|
||||
assert.equal(docs[1].id, resources.doc2.id);
|
||||
assert.equal(docs[2].id, resources.doc3.id);
|
||||
};
|
||||
});
|
||||
|
||||
const queryIteratorAsyncIteratorTest = async function() {
|
||||
it("asyncIterator", async function() {
|
||||
const queryIterator = resources.container.items.readAll({ maxItemCount: 2 });
|
||||
let counter = 0;
|
||||
for await (const { resources: docs } of queryIterator.getAsyncIterator()) {
|
||||
|
@ -118,10 +79,12 @@ describe("NodeJS CRUD Tests", function() {
|
|||
counter++;
|
||||
}
|
||||
assert(counter === 2, "iterator should have run 3 times");
|
||||
};
|
||||
});
|
||||
|
||||
const queryIteratorExecuteNextTest = async function() {
|
||||
let queryIterator = resources.container.items.readAll({ maxItemCount: 2 });
|
||||
it("executeNext", async function() {
|
||||
let queryIterator = resources.container.items.readAll({
|
||||
maxItemCount: 2
|
||||
});
|
||||
const firstResponse = await queryIterator.fetchNext();
|
||||
|
||||
assert(firstResponse.requestCharge > 0, "RequestCharge has to be non-zero");
|
||||
|
@ -135,24 +98,12 @@ describe("NodeJS CRUD Tests", function() {
|
|||
// validate Iterator.executeNext with continuation token
|
||||
queryIterator = resources.container.items.readAll({
|
||||
maxItemCount: 2,
|
||||
continuation: firstResponse.continuation as string
|
||||
continuation: firstResponse.continuation
|
||||
});
|
||||
const secondResponse = await queryIterator.fetchNext();
|
||||
assert(secondResponse.requestCharge > 0, "RequestCharge has to be non-zero");
|
||||
assert.equal(secondResponse.resources.length, 1, "second batch size with continuation token is unexpected");
|
||||
assert.equal(secondResponse.resources[0].id, resources.doc3.id, "second batch element should be doc3");
|
||||
};
|
||||
|
||||
it("nativeApi validate QueryIterator iterator toArray name based", async function() {
|
||||
await queryIteratorToArrayTest();
|
||||
});
|
||||
|
||||
it("validate queryIterator asyncIterator", async function() {
|
||||
await queryIteratorAsyncIteratorTest();
|
||||
});
|
||||
|
||||
it("nativeApi validate queryIterator iterator executeNext name based", async function() {
|
||||
await queryIteratorExecuteNextTest();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -54,7 +54,7 @@ describe("Spatial Indexes", function() {
|
|||
await createOrUpsertItem(container, location2, undefined, isUpsertTest);
|
||||
const query =
|
||||
"SELECT * FROM root WHERE (ST_DISTANCE(root.Location, {type: 'Point', coordinates: [20.1, 20]}) < 20000) ";
|
||||
const { resources: results } = await container.items.query(query, { enableCrossPartitionQuery: true }).fetchAll();
|
||||
const { resources: results } = await container.items.query(query).fetchAll();
|
||||
assert.equal(1, results.length);
|
||||
assert.equal("location1", results[0].id);
|
||||
};
|
||||
|
|
|
@ -264,7 +264,7 @@ describe("Container TTL", function() {
|
|||
// the upserted Item should be gone now after 10 secs from the last write(upsert) of the Item
|
||||
await checkItemGone(container, upsertedItem);
|
||||
const query = "SELECT * FROM root r";
|
||||
const { resources: results } = await container.items.query(query, { enableCrossPartitionQuery: true }).fetchAll();
|
||||
const { resources: results } = await container.items.query(query).fetchAll();
|
||||
assert.equal(results.length, 0);
|
||||
|
||||
await container.replace({ id: container.id, partitionKey: containerResult.partitionKey });
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import assert from "assert";
|
||||
import * as util from "util";
|
||||
import { Container, ContainerDefinition, Database } from "../../dist-esm/client";
|
||||
import { DataType, IndexKind } from "../../dist-esm/documents";
|
||||
import { QueryIterator } from "../../dist-esm/index";
|
||||
|
@ -8,13 +7,12 @@ import { FeedOptions } from "../../dist-esm/request";
|
|||
import { TestData } from "../common/TestData";
|
||||
import { bulkInsertItems, getTestContainer, removeAllDatabases } from "../common/TestHelpers";
|
||||
|
||||
describe("NodeJS Aggregate Query Tests", async function() {
|
||||
describe("Aggregate Query", function() {
|
||||
this.timeout(process.env.MOCHA_TIMEOUT || 20000);
|
||||
const partitionKey = "key";
|
||||
const uniquePartitionKey = "uniquePartitionKey";
|
||||
const testdata = new TestData(partitionKey, uniquePartitionKey);
|
||||
const documentDefinitions = testdata.docs;
|
||||
let db: Database;
|
||||
let container: Container;
|
||||
|
||||
const containerDefinition: ContainerDefinition = {
|
||||
|
@ -43,220 +41,189 @@ describe("NodeJS Aggregate Query Tests", async function() {
|
|||
|
||||
const containerOptions = { offerThroughput: 10100 };
|
||||
|
||||
describe("Validate Aggregate Document Query", function() {
|
||||
// - removes all the databases,
|
||||
// - creates a new database,
|
||||
// - creates a new collecton,
|
||||
// - bulk inserts documents to the container
|
||||
before(async function() {
|
||||
await removeAllDatabases();
|
||||
container = await getTestContainer(
|
||||
"Validate Aggregate Document Query",
|
||||
undefined,
|
||||
containerDefinition,
|
||||
containerOptions
|
||||
);
|
||||
db = container.database;
|
||||
await bulkInsertItems(container, documentDefinitions);
|
||||
before(async function() {
|
||||
await removeAllDatabases();
|
||||
container = await getTestContainer(
|
||||
"Validate Aggregate Document Query",
|
||||
undefined,
|
||||
containerDefinition,
|
||||
containerOptions
|
||||
);
|
||||
await bulkInsertItems(container, documentDefinitions);
|
||||
});
|
||||
|
||||
const validateResult = function(actualValue: any, expectedValue: any) {
|
||||
assert.deepEqual(actualValue, expectedValue, "actual value doesn't match with expected value.");
|
||||
};
|
||||
|
||||
const validateToArray = async function(queryIterator: QueryIterator<any>, expectedResults: any) {
|
||||
const { resources: results } = await queryIterator.fetchAll();
|
||||
assert.equal(results.length, expectedResults.length, "invalid number of results");
|
||||
assert.equal(queryIterator.hasMoreResults(), false, "hasMoreResults: no more results is left");
|
||||
};
|
||||
|
||||
const validateExecuteNextAndHasMoreResults = async function(
|
||||
queryIterator: QueryIterator<any>,
|
||||
options: any,
|
||||
expectedResults: any[]
|
||||
) {
|
||||
const pageSize = options["maxItemCount"];
|
||||
const listOfResultPages: any[] = [];
|
||||
let totalFetchedResults: any[] = [];
|
||||
|
||||
while (totalFetchedResults.length <= expectedResults.length) {
|
||||
const { resources: results } = await queryIterator.fetchNext();
|
||||
listOfResultPages.push(results);
|
||||
|
||||
if (results === undefined || totalFetchedResults.length === expectedResults.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
totalFetchedResults = totalFetchedResults.concat(results);
|
||||
|
||||
if (totalFetchedResults.length < expectedResults.length) {
|
||||
// there are more results
|
||||
assert(results.length <= pageSize, "executeNext: invalid fetch block size");
|
||||
assert.equal(results.length, pageSize, "executeNext: invalid fetch block size");
|
||||
assert(queryIterator.hasMoreResults(), "hasMoreResults expects to return true");
|
||||
} else {
|
||||
// no more results
|
||||
assert.equal(expectedResults.length, totalFetchedResults.length, "executeNext: didn't fetch all the results");
|
||||
assert(results.length <= pageSize, "executeNext: actual fetch size is more than the requested page size");
|
||||
}
|
||||
}
|
||||
|
||||
// no more results
|
||||
validateResult(totalFetchedResults, expectedResults);
|
||||
assert.equal(queryIterator.hasMoreResults(), false, "hasMoreResults: no more results is left");
|
||||
};
|
||||
|
||||
const ValidateAsyncIterator = async function(queryIterator: QueryIterator<any>, expectedResults: any[]) {
|
||||
const results: any[] = [];
|
||||
let completed = false;
|
||||
// forEach uses callbacks still, so just wrap in a promise
|
||||
for await (const { resources: items } of queryIterator.getAsyncIterator()) {
|
||||
// if the previous invocation returned false, forEach must avoid invoking the callback again!
|
||||
assert.equal(completed, false, "forEach called callback after the first false returned");
|
||||
results.push(...items);
|
||||
if (results.length === expectedResults.length) {
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
assert.equal(completed, true, "AsyncIterator should fetch expected number of results");
|
||||
validateResult(results, expectedResults);
|
||||
};
|
||||
|
||||
const executeQueryAndValidateResults = async function(query: string | SqlQuerySpec, expectedResults: any[]) {
|
||||
const options: FeedOptions = { maxDegreeOfParallelism: 2, maxItemCount: 1 };
|
||||
|
||||
const queryIterator = container.items.query(query, options);
|
||||
await validateToArray(queryIterator, expectedResults);
|
||||
queryIterator.reset();
|
||||
await validateExecuteNextAndHasMoreResults(queryIterator, options, expectedResults);
|
||||
queryIterator.reset();
|
||||
await ValidateAsyncIterator(queryIterator, expectedResults);
|
||||
};
|
||||
|
||||
const generateTestConfigs = function() {
|
||||
const testConfigs: any[] = [];
|
||||
const aggregateConfigs = [
|
||||
{
|
||||
operator: "AVG",
|
||||
expected: testdata.sum / testdata.numberOfDocumentsWithNumbericId,
|
||||
condition: `IS_NUMBER(r.${partitionKey})`
|
||||
},
|
||||
{
|
||||
operator: "COUNT",
|
||||
expected: testdata.numberOfDocuments,
|
||||
condition: "true"
|
||||
},
|
||||
{ operator: "MAX", expected: "xyz", condition: "true" },
|
||||
{ operator: "MIN", expected: null, condition: "true" },
|
||||
{
|
||||
operator: "SUM",
|
||||
expected: testdata.sum,
|
||||
condition: `IS_NUMBER(r.${partitionKey})`
|
||||
}
|
||||
];
|
||||
|
||||
aggregateConfigs.forEach(function({ operator, condition, expected }) {
|
||||
let query = `SELECT VALUE ${operator}(r.${partitionKey}) FROM r WHERE ${condition}`;
|
||||
let testName = `${operator} ${condition}`;
|
||||
|
||||
testConfigs.push({
|
||||
testName,
|
||||
query,
|
||||
expected
|
||||
});
|
||||
|
||||
query = `SELECT VALUE ${operator}(r.${partitionKey}) FROM r WHERE ${condition} ORDER BY r.${partitionKey}`;
|
||||
testName = `${operator} ${condition} OrderBy`;
|
||||
testConfigs.push({
|
||||
testName,
|
||||
query,
|
||||
expected
|
||||
});
|
||||
});
|
||||
|
||||
const validateResult = function(actualValue: any, expectedValue: any) {
|
||||
assert.deepEqual(actualValue, expectedValue, "actual value doesn't match with expected value.");
|
||||
};
|
||||
const samePartitionSum =
|
||||
(testdata.numberOfDocsWithSamePartitionKey * (testdata.numberOfDocsWithSamePartitionKey + 1)) / 2.0;
|
||||
const aggregateSinglePartitionConfigs = [
|
||||
{
|
||||
operator: "AVG",
|
||||
expected: samePartitionSum / testdata.numberOfDocsWithSamePartitionKey
|
||||
},
|
||||
{
|
||||
operator: "COUNT",
|
||||
expected: testdata.numberOfDocsWithSamePartitionKey
|
||||
},
|
||||
{
|
||||
operator: "MAX",
|
||||
expected: testdata.numberOfDocsWithSamePartitionKey
|
||||
},
|
||||
{ operator: "MIN", expected: 1 },
|
||||
{ operator: "SUM", expected: samePartitionSum }
|
||||
];
|
||||
|
||||
const validateToArray = async function(queryIterator: QueryIterator<any>, expectedResults: any) {
|
||||
try {
|
||||
const { resources: results } = await queryIterator.fetchAll();
|
||||
assert.equal(results.length, expectedResults.length, "invalid number of results");
|
||||
assert.equal(queryIterator.hasMoreResults(), false, "hasMoreResults: no more results is left");
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const validateExecuteNextAndHasMoreResults = async function(
|
||||
queryIterator: QueryIterator<any>,
|
||||
options: any,
|
||||
expectedResults: any[]
|
||||
) {
|
||||
////////////////////////////////
|
||||
// validate executeNext()
|
||||
////////////////////////////////
|
||||
const pageSize = options["maxItemCount"];
|
||||
const listOfResultPages: any[] = [];
|
||||
let totalFetchedResults: any[] = [];
|
||||
|
||||
try {
|
||||
while (totalFetchedResults.length <= expectedResults.length) {
|
||||
const { resources: results } = await queryIterator.fetchNext();
|
||||
listOfResultPages.push(results);
|
||||
|
||||
if (results === undefined || totalFetchedResults.length === expectedResults.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
totalFetchedResults = totalFetchedResults.concat(results);
|
||||
|
||||
if (totalFetchedResults.length < expectedResults.length) {
|
||||
// there are more results
|
||||
assert(results.length <= pageSize, "executeNext: invalid fetch block size");
|
||||
assert.equal(results.length, pageSize, "executeNext: invalid fetch block size");
|
||||
assert(queryIterator.hasMoreResults(), "hasMoreResults expects to return true");
|
||||
} else {
|
||||
// no more results
|
||||
assert.equal(
|
||||
expectedResults.length,
|
||||
totalFetchedResults.length,
|
||||
"executeNext: didn't fetch all the results"
|
||||
);
|
||||
assert(results.length <= pageSize, "executeNext: actual fetch size is more than the requested page size");
|
||||
}
|
||||
}
|
||||
|
||||
// no more results
|
||||
validateResult(totalFetchedResults, expectedResults);
|
||||
assert.equal(queryIterator.hasMoreResults(), false, "hasMoreResults: no more results is left");
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const ValidateAsyncIterator = async function(queryIterator: QueryIterator<any>, expectedResults: any[]) {
|
||||
////////////////////////////////
|
||||
// validate AsyncIterator()
|
||||
////////////////////////////////
|
||||
|
||||
const results: any[] = [];
|
||||
let completed = false;
|
||||
// forEach uses callbacks still, so just wrap in a promise
|
||||
for await (const { resources: items } of queryIterator.getAsyncIterator()) {
|
||||
// if the previous invocation returned false, forEach must avoid invoking the callback again!
|
||||
assert.equal(completed, false, "forEach called callback after the first false returned");
|
||||
results.push(...items);
|
||||
if (results.length === expectedResults.length) {
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
assert.equal(completed, true, "AsyncIterator should fetch expected number of results");
|
||||
validateResult(results, expectedResults);
|
||||
};
|
||||
|
||||
const executeQueryAndValidateResults = async function(query: string | SqlQuerySpec, expectedResults: any[]) {
|
||||
const options: FeedOptions = { enableCrossPartitionQuery: true, maxDegreeOfParallelism: 2, maxItemCount: 1 };
|
||||
|
||||
const queryIterator = container.items.query(query, options);
|
||||
await validateToArray(queryIterator, expectedResults);
|
||||
queryIterator.reset();
|
||||
await validateExecuteNextAndHasMoreResults(queryIterator, options, expectedResults);
|
||||
queryIterator.reset();
|
||||
await ValidateAsyncIterator(queryIterator, expectedResults);
|
||||
};
|
||||
|
||||
const generateTestConfigs = function() {
|
||||
const testConfigs: any[] = [];
|
||||
const aggregateQueryFormat = "SELECT VALUE %s(r.%s) FROM r WHERE %s";
|
||||
const aggregateOrderByQueryFormat = "SELECT VALUE %s(r.%s) FROM r WHERE %s ORDER BY r.%s";
|
||||
const aggregateConfigs = [
|
||||
{
|
||||
operator: "AVG",
|
||||
expected: testdata.sum / testdata.numberOfDocumentsWithNumbericId,
|
||||
condition: util.format("IS_NUMBER(r.%s)", partitionKey)
|
||||
},
|
||||
{
|
||||
operator: "COUNT",
|
||||
expected: testdata.numberOfDocuments,
|
||||
condition: "true"
|
||||
},
|
||||
{ operator: "MAX", expected: "xyz", condition: "true" },
|
||||
{ operator: "MIN", expected: null, condition: "true" },
|
||||
{
|
||||
operator: "SUM",
|
||||
expected: testdata.sum,
|
||||
condition: util.format("IS_NUMBER(r.%s)", partitionKey)
|
||||
}
|
||||
];
|
||||
|
||||
aggregateConfigs.forEach(function(config) {
|
||||
let query = util.format(aggregateQueryFormat, config.operator, partitionKey, config.condition);
|
||||
let testName = util.format("%s %s", config.operator, config.condition);
|
||||
testConfigs.push({
|
||||
testName,
|
||||
query,
|
||||
expected: config.expected
|
||||
});
|
||||
|
||||
query = util.format(aggregateOrderByQueryFormat, config.operator, partitionKey, config.condition, partitionKey);
|
||||
testName = util.format("%s %s OrderBy", config.operator, config.condition);
|
||||
testConfigs.push({
|
||||
testName,
|
||||
query,
|
||||
expected: config.expected
|
||||
});
|
||||
aggregateSinglePartitionConfigs.forEach(function({ operator, expected }) {
|
||||
const query = `SELECT VALUE ${operator}(r.${
|
||||
testdata.field
|
||||
}) FROM r WHERE r.${partitionKey} = '${uniquePartitionKey}'`;
|
||||
let testName = `${operator} SinglePartition SELECT VALUE`;
|
||||
testConfigs.push({
|
||||
testName,
|
||||
query,
|
||||
expected
|
||||
});
|
||||
});
|
||||
|
||||
const aggregateSinglePartitionQueryFormat = "SELECT VALUE %s(r.%s) FROM r WHERE r.%s = '%s'";
|
||||
const aggregateSinglePartitionQueryFormatSelect = "SELECT %s(r.%s) FROM r WHERE r.%s = '%s'";
|
||||
const samePartitionSum =
|
||||
(testdata.numberOfDocsWithSamePartitionKey * (testdata.numberOfDocsWithSamePartitionKey + 1)) / 2.0;
|
||||
const aggregateSinglePartitionConfigs = [
|
||||
{
|
||||
operator: "AVG",
|
||||
expected: samePartitionSum / testdata.numberOfDocsWithSamePartitionKey
|
||||
},
|
||||
{
|
||||
operator: "COUNT",
|
||||
expected: testdata.numberOfDocsWithSamePartitionKey
|
||||
},
|
||||
{
|
||||
operator: "MAX",
|
||||
expected: testdata.numberOfDocsWithSamePartitionKey
|
||||
},
|
||||
{ operator: "MIN", expected: 1 },
|
||||
{ operator: "SUM", expected: samePartitionSum }
|
||||
];
|
||||
return testConfigs;
|
||||
};
|
||||
|
||||
aggregateSinglePartitionConfigs.forEach(function(config) {
|
||||
let query = util.format(
|
||||
aggregateSinglePartitionQueryFormat,
|
||||
config.operator,
|
||||
testdata.field,
|
||||
partitionKey,
|
||||
uniquePartitionKey
|
||||
);
|
||||
let testName = util.format("%s SinglePartition %s", config.operator, "SELECT VALUE");
|
||||
testConfigs.push({
|
||||
testName,
|
||||
query,
|
||||
expected: config.expected
|
||||
});
|
||||
|
||||
query = util.format(
|
||||
aggregateSinglePartitionQueryFormatSelect,
|
||||
config.operator,
|
||||
testdata.field,
|
||||
partitionKey,
|
||||
uniquePartitionKey
|
||||
);
|
||||
testName = util.format("%s SinglePartition %s", config.operator, "SELECT");
|
||||
testConfigs.push({
|
||||
testName,
|
||||
query,
|
||||
expected: { $1: config.expected }
|
||||
});
|
||||
});
|
||||
|
||||
return testConfigs;
|
||||
};
|
||||
|
||||
generateTestConfigs().forEach(function(test) {
|
||||
it(test.testName, async function() {
|
||||
try {
|
||||
const expected = test.expected === undefined ? [] : [test.expected];
|
||||
await executeQueryAndValidateResults(test.query, expected);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
generateTestConfigs().forEach(function(test) {
|
||||
it(test.testName, async function() {
|
||||
const expected = test.expected === undefined ? [] : [test.expected];
|
||||
await executeQueryAndValidateResults(test.query, expected);
|
||||
});
|
||||
});
|
||||
|
||||
it("should error for non-VALUE queries", async () => {
|
||||
try {
|
||||
const queryIterator = container.items.query("SELECT SUM(r.key) from r WHERE IS_NUMBER(r.key)");
|
||||
const response = await queryIterator.fetchAll();
|
||||
assert.fail("Should throw an error");
|
||||
} catch (error) {
|
||||
assert(error);
|
||||
}
|
||||
});
|
||||
|
||||
it("should error for GROUP BY queries", async () => {
|
||||
try {
|
||||
const queryIterator = container.items.query("SELECT * from r GROUP BY r.key");
|
||||
const response = await queryIterator.fetchAll();
|
||||
assert.fail("Should throw an error");
|
||||
} catch (error) {
|
||||
assert(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -208,7 +208,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: 0
|
||||
};
|
||||
|
@ -224,7 +223,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: -1,
|
||||
populateQueryMetrics: true
|
||||
|
@ -241,7 +239,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: 1
|
||||
};
|
||||
|
@ -257,7 +254,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: 3
|
||||
};
|
||||
|
@ -273,7 +269,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r order by r.spam";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: 0
|
||||
};
|
||||
|
@ -290,7 +285,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r order by r.spam";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: 1
|
||||
};
|
||||
|
@ -307,7 +301,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r order by r.spam";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: 3
|
||||
};
|
||||
|
@ -324,7 +317,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r order by r.spam";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: -1
|
||||
};
|
||||
|
@ -341,7 +333,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT DISTINCT VALUE r.spam3 FROM root r";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -353,7 +344,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT DISTINCT VALUE r.spam3 FROM root r order by r.spam3 DESC";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -367,7 +357,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT DISTINCT VALUE r.spam3 FROM root r order by r.spam3";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: 3
|
||||
};
|
||||
|
@ -382,7 +371,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT DISTINCT VALUE r.spam3 FROM root r order by r.spam3";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 1
|
||||
};
|
||||
|
||||
|
@ -396,7 +384,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT DISTINCT VALUE r.spam3 FROM root r order by r.spam3";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 20
|
||||
};
|
||||
|
||||
|
@ -410,7 +397,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r order by r.spam";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -428,7 +414,6 @@ describe("Cross Partition", function() {
|
|||
query: "SELECT * FROM root r order by r.spam"
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -450,7 +435,6 @@ describe("Cross Partition", function() {
|
|||
query: "SELECT * FROM root r order by r.spam ASC"
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -472,7 +456,6 @@ describe("Cross Partition", function() {
|
|||
query: "SELECT * FROM root r order by r.spam DESC"
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -498,7 +481,6 @@ describe("Cross Partition", function() {
|
|||
query: util.format("SELECT top %d * FROM root r order by r.spam", topCount)
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -525,7 +507,6 @@ describe("Cross Partition", function() {
|
|||
query: util.format("SELECT top %d * FROM root r order by r.spam", topCount)
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -548,7 +529,6 @@ describe("Cross Partition", function() {
|
|||
|
||||
const query = util.format("SELECT top %d * FROM root r", topCount);
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2,
|
||||
maxDegreeOfParallelism: 3
|
||||
};
|
||||
|
@ -576,7 +556,6 @@ describe("Cross Partition", function() {
|
|||
|
||||
const query = util.format("SELECT top %d * FROM root r", topCount);
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -603,7 +582,6 @@ describe("Cross Partition", function() {
|
|||
|
||||
const query = util.format("SELECT top %d * FROM root r", topCount);
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -634,7 +612,6 @@ describe("Cross Partition", function() {
|
|||
parameters: [{ name: "@n", value: topCount }]
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -665,7 +642,6 @@ describe("Cross Partition", function() {
|
|||
parameters: [{ name: "@n", value: topCount }]
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -690,7 +666,6 @@ describe("Cross Partition", function() {
|
|||
parameters: [{ name: "@cnt", value: 5 }]
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -717,7 +692,6 @@ describe("Cross Partition", function() {
|
|||
query: "SELECT * FROM root r order by r.spam2"
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -734,7 +708,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r order by r.cnt";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -750,7 +723,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r order by r.number";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -766,7 +738,6 @@ describe("Cross Partition", function() {
|
|||
// simple order by query in string format
|
||||
const query = "SELECT * FROM root r order by r.boolVar";
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -798,7 +769,6 @@ describe("Cross Partition", function() {
|
|||
query: `SELECT * FROM root r OFFSET ${offset} LIMIT ${limit}`
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -819,7 +789,6 @@ describe("Cross Partition", function() {
|
|||
query: `SELECT * FROM root r WHERE r.number > 5 OFFSET ${offset} LIMIT ${limit}`
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -842,7 +811,6 @@ describe("Cross Partition", function() {
|
|||
query: `SELECT * FROM root r order by r.spam ASC OFFSET ${offset} LIMIT ${limit}`
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -870,7 +838,6 @@ describe("Cross Partition", function() {
|
|||
query: `SELECT * FROM root r order by r.spam ASC OFFSET ${offset} LIMIT ${limit}`
|
||||
};
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
@ -894,7 +861,6 @@ describe("Cross Partition", function() {
|
|||
const query = "SELECT * FROM root r order by r.spam";
|
||||
|
||||
const options = {
|
||||
enableCrossPartitionQuery: true,
|
||||
maxItemCount: 2
|
||||
};
|
||||
|
||||
|
|
|
@ -30,8 +30,7 @@ describe("ResourceLink Trimming of leading and trailing slashes", function() {
|
|||
|
||||
await container.items.create(doc);
|
||||
const query = "SELECT * from " + containerId;
|
||||
const queryOptions = { partitionKey: "pk" };
|
||||
const queryIterator = container.items.query(query, queryOptions);
|
||||
const queryIterator = container.items.query(query);
|
||||
|
||||
const { resources } = await queryIterator.fetchAll();
|
||||
assert.equal(resources[0]["id"], "myId");
|
||||
|
@ -57,7 +56,7 @@ describe("Test Query Metrics", function() {
|
|||
|
||||
await createdContainer.items.create(document);
|
||||
const query = "SELECT * from " + collectionId;
|
||||
const queryOptions: FeedOptions = { populateQueryMetrics: true, enableCrossPartitionQuery: true };
|
||||
const queryOptions: FeedOptions = { populateQueryMetrics: true };
|
||||
const queryIterator = createdContainer.items.query(query, queryOptions);
|
||||
|
||||
while (queryIterator.hasMoreResults()) {
|
||||
|
|
|
@ -221,9 +221,8 @@ describe("Session Token", function() {
|
|||
);
|
||||
firstPartitionToken = containerTokens.get(firstPartition);
|
||||
|
||||
const query = "SELECT * from " + containerId;
|
||||
const queryOptions = { partitionKey: "1" };
|
||||
const queryIterator = container.items.query(query, queryOptions);
|
||||
const query = `SELECT * from c WHERE c.id = "1"`;
|
||||
const queryIterator = container.items.query(query);
|
||||
|
||||
const queryToken = sessionContainer.get({
|
||||
isNameBased: true,
|
||||
|
|
Загрузка…
Ссылка в новой задаче