diff --git a/common/api-review/firestore-lite-pipelines.api.md b/common/api-review/firestore-lite-pipelines.api.md index 37882d2eb50..c3594467eef 100644 --- a/common/api-review/firestore-lite-pipelines.api.md +++ b/common/api-review/firestore-lite-pipelines.api.md @@ -6,6 +6,17 @@ import { FirebaseApp } from '@firebase/app'; +// Warning: (ae-incompatible-release-tags) The symbol "abs" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "abs" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function abs(expr: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "abs" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function abs(fieldName: string): FunctionExpression; + // @beta export function add(first: Expression, second: Expression | unknown): FunctionExpression; @@ -101,12 +112,35 @@ export function arrayContainsAny(array: Expression, values: Expression): Boolean // @beta export function arrayContainsAny(fieldName: string, values: Expression): BooleanExpression; +// @beta +export function arrayGet(arrayField: string, offset: number): FunctionExpression; + +// @beta +export function arrayGet(arrayField: string, offsetExpr: Expression): FunctionExpression; + +// @beta +export function arrayGet(arrayExpression: Expression, offset: number): FunctionExpression; + +// @beta +export function arrayGet(arrayExpression: Expression, offsetExpr: Expression): FunctionExpression; + // @beta export function arrayLength(fieldName: string): FunctionExpression; // @beta export function arrayLength(array: Expression): FunctionExpression; +// Warning: (ae-incompatible-release-tags) The symbol "arraySum" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function arraySum(fieldName: string): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "arraySum" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "arraySum" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function arraySum(expression: Expression): FunctionExpression; + // @beta export function ascending(expr: Expression): Ordering; @@ -135,6 +169,17 @@ export function byteLength(expr: Expression): FunctionExpression; // @beta export function byteLength(fieldName: string): FunctionExpression; +// Warning: (ae-incompatible-release-tags) The symbol "ceil" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function ceil(fieldName: string): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "ceil" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "ceil" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function ceil(expression: Expression): FunctionExpression; + // @beta export function charLength(fieldName: string): FunctionExpression; @@ -147,12 +192,35 @@ export type CollectionGroupStageOptions = StageOptions & { forceIndex?: string; }; +// Warning: (ae-incompatible-release-tags) The symbol "collectionId" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function collectionId(fieldName: string): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "collectionId" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "collectionId" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function collectionId(expression: Expression): FunctionExpression; + // @public export type CollectionStageOptions = StageOptions & { collection: string | Query; forceIndex?: string; }; +// Warning: (ae-incompatible-release-tags) The symbol "concat" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "concat" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function concat(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "concat" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "concat" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function concat(fieldName: string, second: Expression | unknown, ...others: Array): FunctionExpression; + // @beta export function conditional(condition: BooleanExpression, thenExpr: Expression, elseExpr: Expression): FunctionExpression; @@ -234,9 +302,18 @@ export function count(fieldName: string): AggregateFunction; // @beta export function countAll(): AggregateFunction; +// Warning: (ae-incompatible-release-tags) The symbol "countDistinct" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "countDistinct" is marked as @public, but its signature references "AggregateFunction" which is marked as @beta +// +// @public +export function countDistinct(expr: Expression | string): AggregateFunction; + // @beta export function countIf(booleanExpr: BooleanExpression): AggregateFunction; +// @beta +export function currentTimestamp(): FunctionExpression; + // @public export type DatabaseStageOptions = StageOptions & {}; @@ -345,6 +422,17 @@ export function exists(value: Expression): BooleanExpression; // @beta export function exists(fieldName: string): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "exp" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "exp" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function exp(expression: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "exp" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function exp(fieldName: string): FunctionExpression; + // @beta export abstract class Expression { abs(): FunctionExpression; @@ -456,14 +544,6 @@ export abstract class Expression { /* Excluded from this release type: _readUserData */ isError(): BooleanExpression; /* Excluded from this release type: _readUserData */ - isNan(): BooleanExpression; - /* Excluded from this release type: _readUserData */ - isNotNan(): BooleanExpression; - /* Excluded from this release type: _readUserData */ - isNotNull(): BooleanExpression; - /* Excluded from this release type: _readUserData */ - isNull(): BooleanExpression; - /* Excluded from this release type: _readUserData */ join(delimiterExpression: Expression): Expression; /* Excluded from this release type: _readUserData */ join(delimiter: string): Expression; @@ -626,6 +706,17 @@ export type FindNearestStageOptions = StageOptions & { distanceField?: string; }; +// Warning: (ae-incompatible-release-tags) The symbol "floor" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "floor" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function floor(expr: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "floor" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function floor(fieldName: string): FunctionExpression; + // @beta export class FunctionExpression extends Expression { constructor(name: string, params: Expression[]); @@ -658,6 +749,26 @@ export function greaterThanOrEqual(fieldName: string, value: Expression): Boolea // @beta export function greaterThanOrEqual(fieldName: string, value: unknown): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "ifAbsent" is marked as @public, but its signature references "Expression" which is marked as @beta +// +// @public +export function ifAbsent(ifExpr: Expression, elseExpr: Expression): Expression; + +// Warning: (ae-incompatible-release-tags) The symbol "ifAbsent" is marked as @public, but its signature references "Expression" which is marked as @beta +// +// @public +export function ifAbsent(ifExpr: Expression, elseValue: unknown): Expression; + +// Warning: (ae-incompatible-release-tags) The symbol "ifAbsent" is marked as @public, but its signature references "Expression" which is marked as @beta +// +// @public +export function ifAbsent(ifFieldName: string, elseExpr: Expression): Expression; + +// Warning: (ae-incompatible-release-tags) The symbol "ifAbsent" is marked as @public, but its signature references "Expression" which is marked as @beta +// +// @public +export function ifAbsent(ifFieldName: string | Expression, elseValue: Expression | unknown): Expression; + // @beta export function ifError(tryExpr: BooleanExpression, catchExpr: BooleanExpression): BooleanExpression; @@ -676,29 +787,38 @@ export function isAbsent(field: string): BooleanExpression; // @beta export function isError(value: Expression): BooleanExpression; -// @beta -export function isNan(value: Expression): BooleanExpression; - -// @beta -export function isNan(fieldName: string): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "join" is marked as @public, but its signature references "Expression" which is marked as @beta +// +// @public +export function join(arrayFieldName: string, delimiter: string): Expression; -// @beta -export function isNotNan(value: Expression): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "join" is marked as @public, but its signature references "Expression" which is marked as @beta +// +// @public +export function join(arrayExpression: Expression, delimiterExpression: Expression): Expression; -// @beta -export function isNotNan(value: string): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "join" is marked as @public, but its signature references "Expression" which is marked as @beta +// +// @public +export function join(arrayExpression: Expression, delimiter: string): Expression; -// @beta -export function isNotNull(value: Expression): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "join" is marked as @public, but its signature references "Expression" which is marked as @beta +// +// @public +export function join(arrayFieldName: string, delimiterExpression: Expression): Expression; -// @beta -export function isNotNull(value: string): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "length_2" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +function length_2(fieldName: string): FunctionExpression; -// @beta -export function isNull(value: Expression): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "length_2" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "length_2" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +function length_2(expression: Expression): FunctionExpression; -// @beta -export function isNull(value: string): BooleanExpression; +export { length_2 as length } // @beta export function lessThan(left: Expression, right: Expression): BooleanExpression; @@ -744,6 +864,51 @@ export type LimitStageOptions = StageOptions & { limit: number; }; +// Warning: (ae-incompatible-release-tags) The symbol "ln" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function ln(fieldName: string): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "ln" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "ln" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function ln(expression: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "log" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "log" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function log(expression: Expression, base: number): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "log" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "log" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function log(expression: Expression, base: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "log" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function log(fieldName: string, base: number): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "log" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "log" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function log(fieldName: string, base: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "log10" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function log10(fieldName: string): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "log10" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "log10" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function log10(expression: Expression): FunctionExpression; + // @beta export function logicalMaximum(first: Expression, second: Expression | unknown, ...others: Array): FunctionExpression; @@ -966,6 +1131,29 @@ export class PipelineSource { documents(options: DocumentsStageOptions): PipelineType; } +// Warning: (ae-incompatible-release-tags) The symbol "pow" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "pow" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function pow(base: Expression, exponent: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "pow" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "pow" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function pow(base: Expression, exponent: number): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "pow" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "pow" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function pow(base: string, exponent: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "pow" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function pow(base: string, exponent: number): FunctionExpression; + // @beta export function regexContains(fieldName: string, pattern: string): BooleanExpression; @@ -1006,6 +1194,29 @@ export function reverse(stringExpression: Expression): FunctionExpression; // @beta export function reverse(field: string): FunctionExpression; +// Warning: (ae-incompatible-release-tags) The symbol "round" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function round(fieldName: string): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "round" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "round" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function round(expression: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "round" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "round" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function round(fieldName: string, decimalPlaces: number | Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "round" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "round" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function round(expression: Expression, decimalPlaces: number | Expression): FunctionExpression; + // @public export type SampleStageOptions = StageOptions & OneOf<{ percentage: number; @@ -1028,6 +1239,17 @@ export type SortStageOptions = StageOptions & { orderings: Ordering[]; }; +// Warning: (ae-incompatible-release-tags) The symbol "sqrt" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "sqrt" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function sqrt(expression: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "sqrt" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function sqrt(fieldName: string): FunctionExpression; + // @public export interface StageOptions { rawOptions?: { @@ -1065,6 +1287,17 @@ export function stringContains(stringExpression: Expression, substring: string): // @beta export function stringContains(stringExpression: Expression, substring: Expression): BooleanExpression; +// Warning: (ae-incompatible-release-tags) The symbol "stringReverse" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "stringReverse" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function stringReverse(stringExpression: Expression): FunctionExpression; + +// Warning: (ae-incompatible-release-tags) The symbol "stringReverse" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// +// @public +export function stringReverse(field: string): FunctionExpression; + // @beta export function substring(field: string, position: number, length?: number): FunctionExpression; @@ -1089,6 +1322,12 @@ export function subtract(fieldName: string, expression: Expression): FunctionExp // @beta export function subtract(fieldName: string, value: unknown): FunctionExpression; +// @beta +export function sum(expression: Expression): AggregateFunction; + +// @beta +export function sum(fieldName: string): AggregateFunction; + // @beta export function timestampAdd(timestamp: Expression, unit: Expression, amount: Expression): FunctionExpression; @@ -1189,19 +1428,19 @@ export function xor(first: BooleanExpression, second: BooleanExpression, ...addi // Warnings were encountered during analysis: // -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:55:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:92:5 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AliasedAggregate" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:97:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:606:5 - (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:862:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:2871:5 - (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5095:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Field" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5105:5 - (ae-incompatible-release-tags) The symbol "map" is marked as @public, but its signature references "Expression" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5175:5 - (ae-incompatible-release-tags) The symbol "selections" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5185:5 - (ae-incompatible-release-tags) The symbol "orderings" is marked as @public, but its signature references "Ordering" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5734:5 - (ae-incompatible-release-tags) The symbol "other" is marked as @public, but its signature references "Pipeline" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5834:5 - (ae-incompatible-release-tags) The symbol "selectable" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5876:5 - (ae-incompatible-release-tags) The symbol "condition" is marked as @public, but its signature references "BooleanExpression" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:69:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:106:5 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AliasedAggregate" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:111:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:759:5 - (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:1063:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:3046:5 - (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5469:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Field" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5479:5 - (ae-incompatible-release-tags) The symbol "map" is marked as @public, but its signature references "Expression" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5599:5 - (ae-incompatible-release-tags) The symbol "selections" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:5609:5 - (ae-incompatible-release-tags) The symbol "orderings" is marked as @public, but its signature references "Ordering" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:6236:5 - (ae-incompatible-release-tags) The symbol "other" is marked as @public, but its signature references "Pipeline" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:6336:5 - (ae-incompatible-release-tags) The symbol "selectable" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:6378:5 - (ae-incompatible-release-tags) The symbol "condition" is marked as @public, but its signature references "BooleanExpression" which is marked as @beta // (No @packageDocumentation comment for this package) diff --git a/common/api-review/firestore-pipelines.api.md b/common/api-review/firestore-pipelines.api.md index add73ae322f..5988e86bae9 100644 --- a/common/api-review/firestore-pipelines.api.md +++ b/common/api-review/firestore-pipelines.api.md @@ -23,21 +23,11 @@ export function add(first: Expression, second: Expression | unknown): FunctionEx // @beta export function add(fieldName: string, second: Expression | unknown): FunctionExpression; -// @beta (undocumented) -export class AddFields extends Stage { - constructor(fields: Map, options: StageOptions); - } - // @public export type AddFieldsStageOptions = StageOptions & { fields: Selectable[]; }; -// @beta (undocumented) -export class Aggregate extends Stage { - constructor(groups: Map, accumulators: Map, options: StageOptions); - } - // @beta export class AggregateFunction { constructor(name: string, params: Expression[]); @@ -196,11 +186,6 @@ export function charLength(fieldName: string): FunctionExpression; // @beta export function charLength(stringExpression: Expression): FunctionExpression; -// @beta (undocumented) -export class CollectionGroupSource extends Stage { - constructor(collectionId: string, options: StageOptions); - } - // @public export type CollectionGroupStageOptions = StageOptions & { collectionId: string; @@ -218,11 +203,6 @@ export function collectionId(fieldName: string): FunctionExpression; // @public export function collectionId(expression: Expression): FunctionExpression; -// @beta (undocumented) -export class CollectionSource extends Stage { - constructor(collection: string, options: StageOptions); - } - // @public export type CollectionStageOptions = StageOptions & { collection: string | Query; @@ -334,10 +314,6 @@ export function countIf(booleanExpr: BooleanExpression): AggregateFunction; // @beta export function currentTimestamp(): FunctionExpression; -// @beta (undocumented) -export class DatabaseSource extends Stage { -} - // @public export type DatabaseStageOptions = StageOptions & {}; @@ -347,11 +323,6 @@ export function descending(expr: Expression): Ordering; // @beta export function descending(fieldName: string): Ordering; -// @beta (undocumented) -export class Distinct extends Stage { - constructor(groups: Map, options: StageOptions); - } - // @public export type DistinctStageOptions = StageOptions & { groups: Array; @@ -375,11 +346,6 @@ export function documentId(documentPath: string | DocumentReference): FunctionEx // @beta export function documentId(documentPathExpr: Expression): FunctionExpression; -// @beta (undocumented) -export class DocumentsSource extends Stage { - constructor(docPaths: string[], options: StageOptions); - } - // @public export type DocumentsStageOptions = StageOptions & { docs: Array; @@ -433,11 +399,6 @@ export function equalAny(fieldName: string, values: Array) // @beta export function equalAny(fieldName: string, arrayExpression: Expression): BooleanExpression; -// Warning: (ae-incompatible-release-tags) The symbol "error" is marked as @public, but its signature references "Expression" which is marked as @beta -// -// @public -export function error(message: string): Expression; - // @beta export function euclideanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpression; @@ -584,14 +545,6 @@ export abstract class Expression { /* Excluded from this release type: _readUserData */ isError(): BooleanExpression; /* Excluded from this release type: _readUserData */ - isNan(): BooleanExpression; - /* Excluded from this release type: _readUserData */ - isNotNan(): BooleanExpression; - /* Excluded from this release type: _readUserData */ - isNotNull(): BooleanExpression; - /* Excluded from this release type: _readUserData */ - isNull(): BooleanExpression; - /* Excluded from this release type: _readUserData */ join(delimiterExpression: Expression): Expression; /* Excluded from this release type: _readUserData */ join(delimiter: string): Expression; @@ -745,11 +698,6 @@ export function field(name: string): Field; // @public (undocumented) export function field(path: FieldPath): Field; -// @beta (undocumented) -export class FindNearest extends Stage { - constructor(vectorValue: Expression, field: Field, distanceMeasure: 'euclidean' | 'cosine' | 'dot_product', options: StageOptions); - } - // @public export type FindNearestStageOptions = StageOptions & { field: Field | string; @@ -840,30 +788,6 @@ export function isAbsent(field: string): BooleanExpression; // @beta export function isError(value: Expression): BooleanExpression; -// @beta -export function isNan(value: Expression): BooleanExpression; - -// @beta -export function isNan(fieldName: string): BooleanExpression; - -// @beta -export function isNotNan(value: Expression): BooleanExpression; - -// @beta -export function isNotNan(value: string): BooleanExpression; - -// @beta -export function isNotNull(value: Expression): BooleanExpression; - -// @beta -export function isNotNull(value: string): BooleanExpression; - -// @beta -export function isNull(value: Expression): BooleanExpression; - -// @beta -export function isNull(value: string): BooleanExpression; - // Warning: (ae-incompatible-release-tags) The symbol "join" is marked as @public, but its signature references "Expression" which is marked as @beta // // @public @@ -884,16 +808,18 @@ export function join(arrayExpression: Expression, delimiter: string): Expression // @public export function join(arrayFieldName: string, delimiterExpression: Expression): Expression; -// Warning: (ae-incompatible-release-tags) The symbol "len" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "length_2" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta // // @public -export function len(fieldName: string): FunctionExpression; +function length_2(fieldName: string): FunctionExpression; -// Warning: (ae-incompatible-release-tags) The symbol "len" is marked as @public, but its signature references "Expression" which is marked as @beta -// Warning: (ae-incompatible-release-tags) The symbol "len" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "length_2" is marked as @public, but its signature references "Expression" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "length_2" is marked as @public, but its signature references "FunctionExpression" which is marked as @beta // // @public -export function len(expression: Expression): FunctionExpression; +function length_2(expression: Expression): FunctionExpression; + +export { length_2 as length } // @beta export function lessThan(left: Expression, right: Expression): BooleanExpression; @@ -934,11 +860,6 @@ export function like(stringExpression: Expression, pattern: string): BooleanExpr // @beta export function like(stringExpression: Expression, pattern: Expression): BooleanExpression; -// @beta (undocumented) -export class Limit extends Stage { - constructor(limit: number, options: StageOptions); - } - // @public export type LimitStageOptions = StageOptions & { limit: number; @@ -1085,11 +1006,6 @@ export function notEqualAny(element: Expression, arrayExpression: Expression): B // @beta export function notEqualAny(fieldName: string, arrayExpression: Expression): BooleanExpression; -// @beta (undocumented) -export class Offset extends Stage { - constructor(offset: number, options: StageOptions); - } - // @public export type OffsetStageOptions = StageOptions & { offset: number; @@ -1245,10 +1161,6 @@ export function pow(base: string, exponent: Expression): FunctionExpression; // @public export function pow(base: string, exponent: number): FunctionExpression; -// @beta (undocumented) -export class RawStage extends Stage { - } - // @beta export function regexContains(fieldName: string, pattern: string): BooleanExpression; @@ -1318,11 +1230,6 @@ export type SampleStageOptions = StageOptions & OneOf<{ documents: number; }>; -// @beta (undocumented) -export class Select extends Stage { - constructor(selections: Map, options: StageOptions); - } - // @beta export interface Selectable { // (undocumented) @@ -1334,11 +1241,6 @@ export type SelectStageOptions = StageOptions & { selections: Array; }; -// @beta (undocumented) -export class Sort extends Stage { - constructor(orderings: Ordering[], options: StageOptions); - } - // @public export type SortStageOptions = StageOptions & { orderings: Ordering[]; @@ -1355,16 +1257,6 @@ export function sqrt(expression: Expression): FunctionExpression; // @public export function sqrt(fieldName: string): FunctionExpression; -// @beta (undocumented) -export abstract class Stage { - /* Excluded from this release type: optionsProto */ - constructor(options: StageOptions); - // (undocumented) - protected knownOptions: Record; - // (undocumented) - protected rawOptions?: Record; -} - // @public export interface StageOptions { rawOptions?: { @@ -1532,11 +1424,6 @@ export function vectorLength(vectorExpression: Expression): FunctionExpression; // @beta export function vectorLength(fieldName: string): FunctionExpression; -// @beta (undocumented) -export class Where extends Stage { - constructor(condition: BooleanExpression, options: StageOptions); - } - // @public export type WhereStageOptions = StageOptions & { condition: BooleanExpression; @@ -1548,19 +1435,19 @@ export function xor(first: BooleanExpression, second: BooleanExpression, ...addi // Warnings were encountered during analysis: // -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:73:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:118:5 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AliasedAggregate" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:123:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:785:5 - (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:1100:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:3160:5 - (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5041:59 - (ae-incompatible-release-tags) The symbol "__index" is marked as @public, but its signature references "Expression" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5447:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Field" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5457:5 - (ae-incompatible-release-tags) The symbol "map" is marked as @public, but its signature references "Expression" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5584:5 - (ae-incompatible-release-tags) The symbol "selections" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5601:5 - (ae-incompatible-release-tags) The symbol "orderings" is marked as @public, but its signature references "Ordering" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:6337:5 - (ae-incompatible-release-tags) The symbol "selectable" is marked as @public, but its signature references "Selectable" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:6386:5 - (ae-incompatible-release-tags) The symbol "condition" is marked as @public, but its signature references "BooleanExpression" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:66:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:103:5 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AliasedAggregate" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:108:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:756:5 - (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:1059:5 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:3043:5 - (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:4799:59 - (ae-incompatible-release-tags) The symbol "__index" is marked as @public, but its signature references "Expression" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5198:5 - (ae-incompatible-release-tags) The symbol "fields" is marked as @public, but its signature references "Field" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5208:5 - (ae-incompatible-release-tags) The symbol "map" is marked as @public, but its signature references "Expression" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5328:5 - (ae-incompatible-release-tags) The symbol "selections" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:5338:5 - (ae-incompatible-release-tags) The symbol "orderings" is marked as @public, but its signature references "Ordering" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:6065:5 - (ae-incompatible-release-tags) The symbol "selectable" is marked as @public, but its signature references "Selectable" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:6107:5 - (ae-incompatible-release-tags) The symbol "condition" is marked as @public, but its signature references "BooleanExpression" which is marked as @beta // (No @packageDocumentation comment for this package) diff --git a/integration/firestore/gulpfile.js b/integration/firestore/gulpfile.js index 6dfbeb20e5c..7dce242e6cc 100644 --- a/integration/firestore/gulpfile.js +++ b/integration/firestore/gulpfile.js @@ -70,13 +70,13 @@ if (typeof process === 'undefined') { ) ) .pipe( - replace( - /** - * This regex is designed to match the Firebase import in our - * integration tests. - */ - /\s+from '\.(\.\/util)?\/pipeline_export';/, - ` from '${resolve(__dirname, './pipeline_export')}'; + replace( + /** + * This regex is designed to match the Firebase import in our + * integration tests. + */ + /\s+from '\.(\.\/util)?\/pipeline_export';/, + ` from '${resolve(__dirname, './pipeline_export')}'; if (typeof process === 'undefined') { process = { env: { INCLUDE_FIRESTORE_PERSISTENCE: '${isPersistenceEnabled()}' } } as any; @@ -84,7 +84,7 @@ if (typeof process === 'undefined') { process.env.INCLUDE_FIRESTORE_PERSISTENCE = '${isPersistenceEnabled()}'; } ` - ) + ) ) .pipe( /** diff --git a/packages/firestore/lite/pipelines/pipelines.ts b/packages/firestore/lite/pipelines/pipelines.ts index 1e5195c8e8c..130047e1526 100644 --- a/packages/firestore/lite/pipelines/pipelines.ts +++ b/packages/firestore/lite/pipelines/pipelines.ts @@ -102,10 +102,7 @@ export { isError, or, divide, - isNotNan, map, - isNotNull, - isNull, mod, documentId, equal, @@ -128,7 +125,6 @@ export { logicalMaximum, logicalMinimum, exists, - isNan, reverse, byteLength, charLength, @@ -160,6 +156,27 @@ export { timestampSubtract, ascending, descending, + arrayGet, + abs, + sum, + countDistinct, + ceil, + floor, + exp, + pow, + round, + collectionId, + ln, + log, + sqrt, + stringReverse, + log10, + concat, + currentTimestamp, + ifAbsent, + join, + length, + arraySum, AliasedExpression, Field, Constant, diff --git a/packages/firestore/src/api_pipelines.ts b/packages/firestore/src/api_pipelines.ts index 057695ab5f4..3592113e836 100644 --- a/packages/firestore/src/api_pipelines.ts +++ b/packages/firestore/src/api_pipelines.ts @@ -53,24 +53,6 @@ export { SortStageOptions } from './lite-api/stage_options'; -export { - Stage, - AddFields, - Aggregate, - Distinct, - CollectionSource, - CollectionGroupSource, - DatabaseSource, - DocumentsSource, - Where, - FindNearest, - Limit, - Offset, - Select, - Sort, - RawStage -} from './lite-api/stage'; - export { field, constant, @@ -98,7 +80,6 @@ export { logicalMaximum, logicalMinimum, exists, - isNan, reverse, byteLength, charLength, @@ -141,9 +122,6 @@ export { isError, ifError, isAbsent, - isNull, - isNotNull, - isNotNan, map, mapRemove, mapMerge, @@ -160,11 +138,10 @@ export { log, sqrt, stringReverse, - length as len, + length, abs, concat, currentTimestamp, - error, ifAbsent, join, log10, @@ -175,10 +152,7 @@ export { FunctionExpression, Ordering, BooleanExpression, - AggregateFunction -} from './lite-api/expressions'; - -export type { + AggregateFunction, ExpressionType, AliasedAggregate, Selectable diff --git a/packages/firestore/src/core/pipeline-util.ts b/packages/firestore/src/core/pipeline-util.ts index 3cf754e46f8..b27babf6951 100644 --- a/packages/firestore/src/core/pipeline-util.ts +++ b/packages/firestore/src/core/pipeline-util.ts @@ -28,7 +28,6 @@ import { } from '../lite-api/expressions'; import { Pipeline } from '../lite-api/pipeline'; import { doc } from '../lite-api/reference'; -import { isNanValue, isNullValue } from '../model/values'; import { fail } from '../util/assert'; import { Bound } from './bound'; @@ -53,90 +52,76 @@ import { export function toPipelineBooleanExpr(f: FilterInternal): BooleanExpression { if (f instanceof FieldFilterInternal) { const fieldValue = field(f.field.toString()); - if (isNanValue(f.value)) { - if (f.op === Operator.EQUAL) { - return and(fieldValue.exists(), fieldValue.isNan()); - } else { - return and(fieldValue.exists(), fieldValue.isNotNan()); + // Comparison filters + const value = f.value; + switch (f.op) { + case Operator.LESS_THAN: + return and( + fieldValue.exists(), + fieldValue.lessThan(Constant._fromProto(value)) + ); + case Operator.LESS_THAN_OR_EQUAL: + return and( + fieldValue.exists(), + fieldValue.lessThanOrEqual(Constant._fromProto(value)) + ); + case Operator.GREATER_THAN: + return and( + fieldValue.exists(), + fieldValue.greaterThan(Constant._fromProto(value)) + ); + case Operator.GREATER_THAN_OR_EQUAL: + return and( + fieldValue.exists(), + fieldValue.greaterThanOrEqual(Constant._fromProto(value)) + ); + case Operator.EQUAL: + return and( + fieldValue.exists(), + fieldValue.equal(Constant._fromProto(value)) + ); + case Operator.NOT_EQUAL: + return and( + fieldValue.exists(), + fieldValue.notEqual(Constant._fromProto(value)) + ); + case Operator.ARRAY_CONTAINS: + return and( + fieldValue.exists(), + fieldValue.arrayContains(Constant._fromProto(value)) + ); + case Operator.IN: { + const values = value?.arrayValue?.values?.map((val: any) => + Constant._fromProto(val) + ); + if (!values) { + return and(fieldValue.exists(), fieldValue.equalAny([])); + } else if (values.length === 1) { + return and(fieldValue.exists(), fieldValue.equal(values[0])); + } else { + return and(fieldValue.exists(), fieldValue.equalAny(values)); + } } - } else if (isNullValue(f.value)) { - if (f.op === Operator.EQUAL) { - return and(fieldValue.exists(), fieldValue.isNull()); - } else { - return and(fieldValue.exists(), fieldValue.isNotNull()); + case Operator.ARRAY_CONTAINS_ANY: { + const values = value?.arrayValue?.values?.map((val: any) => + Constant._fromProto(val) + ); + return and(fieldValue.exists(), fieldValue.arrayContainsAny(values!)); } - } else { - // Comparison filters - const value = f.value; - switch (f.op) { - case Operator.LESS_THAN: - return and( - fieldValue.exists(), - fieldValue.lessThan(Constant._fromProto(value)) - ); - case Operator.LESS_THAN_OR_EQUAL: - return and( - fieldValue.exists(), - fieldValue.lessThanOrEqual(Constant._fromProto(value)) - ); - case Operator.GREATER_THAN: - return and( - fieldValue.exists(), - fieldValue.greaterThan(Constant._fromProto(value)) - ); - case Operator.GREATER_THAN_OR_EQUAL: - return and( - fieldValue.exists(), - fieldValue.greaterThanOrEqual(Constant._fromProto(value)) - ); - case Operator.EQUAL: - return and( - fieldValue.exists(), - fieldValue.equal(Constant._fromProto(value)) - ); - case Operator.NOT_EQUAL: - return and( - fieldValue.exists(), - fieldValue.notEqual(Constant._fromProto(value)) - ); - case Operator.ARRAY_CONTAINS: - return and( - fieldValue.exists(), - fieldValue.arrayContains(Constant._fromProto(value)) - ); - case Operator.IN: { - const values = value?.arrayValue?.values?.map((val: any) => - Constant._fromProto(val) - ); - if (!values) { - return and(fieldValue.exists(), fieldValue.equalAny([])); - } else if (values.length === 1) { - return and(fieldValue.exists(), fieldValue.equal(values[0])); - } else { - return and(fieldValue.exists(), fieldValue.equalAny(values)); - } - } - case Operator.ARRAY_CONTAINS_ANY: { - const values = value?.arrayValue?.values?.map((val: any) => - Constant._fromProto(val) - ); - return and(fieldValue.exists(), fieldValue.arrayContainsAny(values!)); - } - case Operator.NOT_IN: { - const values = value?.arrayValue?.values?.map((val: any) => - Constant._fromProto(val) - ); - if (!values) { - return and(fieldValue.exists(), fieldValue.notEqualAny([])); - } else if (values.length === 1) { - return and(fieldValue.exists(), fieldValue.notEqual(values[0])); - } else { - return and(fieldValue.exists(), fieldValue.notEqualAny(values)); - } + case Operator.NOT_IN: { + const values = value?.arrayValue?.values?.map((val: any) => + Constant._fromProto(val) + ); + if (!values) { + return and(fieldValue.exists(), fieldValue.notEqualAny([])); + } else if (values.length === 1) { + return and(fieldValue.exists(), fieldValue.notEqual(values[0])); + } else { + return and(fieldValue.exists(), fieldValue.notEqualAny(values)); } - default: - fail(0x9047, 'Unexpected operator'); } + default: + fail(0x9047, 'Unexpected operator'); } } else if (f instanceof CompositeFilterInternal) { switch (f.op) { diff --git a/packages/firestore/src/lite-api/expressions.ts b/packages/firestore/src/lite-api/expressions.ts index c30b2444cf9..99fde6732b8 100644 --- a/packages/firestore/src/lite-api/expressions.ts +++ b/packages/firestore/src/lite-api/expressions.ts @@ -721,34 +721,6 @@ export abstract class Expression implements ProtoValueSerializable, UserData { ); } - /** - * Creates an expression that checks if this expression evaluates to 'NaN' (Not a Number). - * - * ```typescript - * // Check if the result of a calculation is NaN - * field("value").divide(0).isNaN(); - * ``` - * - * @return A new `Expr` representing the 'isNaN' check. - */ - isNan(): BooleanExpression { - return new BooleanExpression('is_nan', [this], 'isNan'); - } - - /** - * Creates an expression that checks if this expression evaluates to 'Null'. - * - * ```typescript - * // Check if the result of a calculation is NaN - * field("value").isNull(); - * ``` - * - * @return A new `Expr` representing the 'isNull' check. - */ - isNull(): BooleanExpression { - return new BooleanExpression('is_null', [this], 'isNull'); - } - /** * Creates an expression that checks if a field exists in the document. * @@ -1800,38 +1772,6 @@ export abstract class Expression implements ProtoValueSerializable, UserData { return new BooleanExpression('is_absent', [this], 'isAbsent'); } - /** - * @beta - * - * Creates an expression that checks if tbe result of an expression is not null. - * - * ```typescript - * // Check if the value of the 'name' field is not null - * field("name").isNotNull(); - * ``` - * - * @return A new {@code BooleanExpr} representing the 'isNotNull' check. - */ - isNotNull(): BooleanExpression { - return new BooleanExpression('is_not_null', [this], 'isNotNull'); - } - - /** - * @beta - * - * Creates an expression that checks if the results of this expression is NOT 'NaN' (Not a Number). - * - * ```typescript - * // Check if the result of a calculation is NOT NaN - * field("value").divide(0).isNotNan(); - * ``` - * - * @return A new {@code Expr} representing the 'isNaN' check. - */ - isNotNan(): BooleanExpression { - return new BooleanExpression('is_not_nan', [this], 'isNotNan'); - } - /** * @beta * @@ -3091,105 +3031,6 @@ export function isAbsent(value: Expression | string): BooleanExpression { return fieldOrExpression(value).isAbsent(); } -/** - * @beta - * - * Creates an expression that checks if an expression evaluates to 'NaN' (Not a Number). - * - * ```typescript - * // Check if the result of a calculation is NaN - * isNaN(field("value").divide(0)); - * ``` - * - * @param value The expression to check. - * @return A new {@code Expr} representing the 'isNaN' check. - */ -export function isNull(value: Expression): BooleanExpression; - -/** - * @beta - * - * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). - * - * ```typescript - * // Check if the result of a calculation is NaN - * isNaN("value"); - * ``` - * - * @param value The name of the field to check. - * @return A new {@code Expr} representing the 'isNaN' check. - */ -export function isNull(value: string): BooleanExpression; -export function isNull(value: Expression | string): BooleanExpression { - return fieldOrExpression(value).isNull(); -} - -/** - * @beta - * - * Creates an expression that checks if tbe result of an expression is not null. - * - * ```typescript - * // Check if the value of the 'name' field is not null - * isNotNull(field("name")); - * ``` - * - * @param value The expression to check. - * @return A new {@code Expr} representing the 'isNaN' check. - */ -export function isNotNull(value: Expression): BooleanExpression; - -/** - * @beta - * - * Creates an expression that checks if tbe value of a field is not null. - * - * ```typescript - * // Check if the value of the 'name' field is not null - * isNotNull("name"); - * ``` - * - * @param value The name of the field to check. - * @return A new {@code Expr} representing the 'isNaN' check. - */ -export function isNotNull(value: string): BooleanExpression; -export function isNotNull(value: Expression | string): BooleanExpression { - return fieldOrExpression(value).isNotNull(); -} - -/** - * @beta - * - * Creates an expression that checks if the results of this expression is NOT 'NaN' (Not a Number). - * - * ```typescript - * // Check if the result of a calculation is NOT NaN - * isNotNaN(field("value").divide(0)); - * ``` - * - * @param value The expression to check. - * @return A new {@code Expr} representing the 'isNotNaN' check. - */ -export function isNotNan(value: Expression): BooleanExpression; - -/** - * @beta - * - * Creates an expression that checks if the results of this expression is NOT 'NaN' (Not a Number). - * - * ```typescript - * // Check if the value of a field is NOT NaN - * isNotNaN("value"); - * ``` - * - * @param value The name of the field to check. - * @return A new {@code Expr} representing the 'isNotNaN' check. - */ -export function isNotNan(value: string): BooleanExpression; -export function isNotNan(value: Expression | string): BooleanExpression { - return fieldOrExpression(value).isNotNan(); -} - /** * @beta * @@ -5072,39 +4913,6 @@ export function exists(valueOrField: Expression | string): BooleanExpression { return fieldOrExpression(valueOrField).exists(); } -/** - * @beta - * - * Creates an expression that checks if an expression evaluates to 'NaN' (Not a Number). - * - * ```typescript - * // Check if the result of a calculation is NaN - * isNaN(field("value").divide(0)); - * ``` - * - * @param value The expression to check. - * @return A new {@code Expr} representing the 'isNaN' check. - */ -export function isNan(value: Expression): BooleanExpression; - -/** - * @beta - * - * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). - * - * ```typescript - * // Check if the result of a calculation is NaN - * isNaN("value"); - * ``` - * - * @param fieldName The name of the field to check. - * @return A new {@code Expr} representing the 'isNaN' check. - */ -export function isNan(fieldName: string): BooleanExpression; -export function isNan(value: Expression | string): BooleanExpression { - return fieldOrExpression(value).isNan(); -} - /** * @beta * @@ -6884,25 +6692,6 @@ export function currentTimestamp(): FunctionExpression { return new FunctionExpression('current_timestamp', [], 'currentTimestamp'); } -/** - * Creates an expression that raises an error with the given message. This could be useful for - * debugging purposes. - * - * ```typescript - * // Raise an error with the message "simulating an evaluation error". - * error("simulating an evaluation error") - * ``` - * - * @return A new Expression representing the error() operation. - */ -export function error(message: string): Expression { - return new FunctionExpression( - 'error', - [constant(message)], - 'currentTimestamp' - ); -} - /** * @beta * diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index 081b8cf5c9a..4c84fca96ec 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -263,19 +263,17 @@ export async function invokeExecutePipeline( ); const result: PipelineStreamElement[] = []; - response - .filter(proto => !!proto.results) - .forEach(proto => { - if (proto.results!.length === 0) { - result.push(fromPipelineResponse(datastoreImpl.serializer, proto)); - } else { - return proto.results!.forEach(document => - result.push( - fromPipelineResponse(datastoreImpl.serializer, proto, document) - ) - ); - } - }); + response.forEach(proto => { + if (!proto.results || proto.results!.length === 0) { + result.push(fromPipelineResponse(datastoreImpl.serializer, proto)); + } else { + return proto.results!.forEach(document => + result.push( + fromPipelineResponse(datastoreImpl.serializer, proto, document) + ) + ); + } + }); return result; } diff --git a/packages/firestore/test/integration/api/pipeline.test.ts b/packages/firestore/test/integration/api/pipeline.test.ts index 9ebf9ced4b5..e8d3ff04ef7 100644 --- a/packages/firestore/test/integration/api/pipeline.test.ts +++ b/packages/firestore/test/integration/api/pipeline.test.ts @@ -48,7 +48,6 @@ import { pipelineResultEqual, sum, descending, - isNan, map, execute, add, @@ -97,9 +96,6 @@ import { ifError, trim, isAbsent, - isNull, - isNotNull, - isNotNan, timestampSubtract, mapRemove, mapMerge, @@ -136,10 +132,9 @@ import { log, sqrt, stringReverse, - len as length, + length, abs, concat, - error, currentTimestamp, ifAbsent, join, @@ -360,6 +355,17 @@ const timestampDeltaMS = 1000; expectedStructuredPipelineProto ); }); + + it('performs validation', async () => { + expect(() => { + const pipeline = firestore + .pipeline() + .collection('customers') + .where(field('country').equal(new Map([]))); + + _internalPipelineToExecutePipelineRequestProto(pipeline); + }).to.throw(); + }); }); describe('pipeline results', () => { @@ -1218,7 +1224,6 @@ const timestampDeltaMS = 1000; .select('title', 'author') .sort(field('author').ascending()) .removeFields(field('author')) - .sort(field('author').ascending()) ); expectResults( snapshot, @@ -1259,7 +1264,6 @@ const timestampDeltaMS = 1000; .removeFields({ fields: [field('author'), 'genre'] }) - .sort(field('author').ascending()) ); expectResults( snapshot, @@ -1300,7 +1304,6 @@ const timestampDeltaMS = 1000; .select('title', 'author') .sort(field('author').ascending()) .removeFields(field('author')) - .sort(field('author').ascending()) ); expectResults( snapshot, @@ -1341,7 +1344,6 @@ const timestampDeltaMS = 1000; .removeFields({ fields: [field('author'), 'genre'] }) - .sort(field('author').ascending()) ); expectResults( snapshot, @@ -2586,8 +2588,8 @@ const timestampDeltaMS = 1000; .sort(field('rating').descending()) .limit(1) .select( - isNull('rating').as('ratingIsNull'), - isNan('rating').as('ratingIsNaN'), + equal('rating', null).as('ratingIsNull'), + equal('rating', NaN).as('ratingIsNaN'), isError(divide(constant(1), constant(0))).as('isError'), ifError(divide(constant(1), constant(0)), constant('was error')).as( 'ifError' @@ -2599,8 +2601,8 @@ const timestampDeltaMS = 1000; .not() .as('ifErrorBooleanExpression'), isAbsent('foo').as('isAbsent'), - isNotNull('title').as('titleIsNotNull'), - isNotNan('cost').as('costIsNotNan'), + notEqual('title', null).as('titleIsNotNull'), + notEqual('cost', NaN).as('costIsNotNan'), exists('fooBarBaz').as('fooBarBazExists'), field('title').exists().as('titleExists') ) @@ -2625,8 +2627,8 @@ const timestampDeltaMS = 1000; .sort(field('rating').descending()) .limit(1) .select( - field('rating').isNull().as('ratingIsNull'), - field('rating').isNan().as('ratingIsNaN'), + field('rating').equal(null).as('ratingIsNull'), + field('rating').equal(NaN).as('ratingIsNaN'), divide(constant(1), constant(0)).isError().as('isError'), divide(constant(1), constant(0)) .ifError(constant('was error')) @@ -2637,8 +2639,8 @@ const timestampDeltaMS = 1000; .not() .as('ifErrorBooleanExpression'), field('foo').isAbsent().as('isAbsent'), - field('title').isNotNull().as('titleIsNotNull'), - field('cost').isNotNan().as('costIsNotNan') + field('title').notEqual(null).as('titleIsNotNull'), + field('cost').notEqual(NaN).as('costIsNotNan') ) ); expectResults(snapshot, { @@ -3892,22 +3894,6 @@ const timestampDeltaMS = 1000; ).lessThan(5000); }); - // Not implemented in backend - // eslint-disable-next-line no-restricted-properties - it.skip('supports error', async () => { - const snapshot = await execute( - firestore - .pipeline() - .collection(randomCol.path) - .limit(1) - .select(isError(error('test error')).as('error')) - ); - - expectResults(snapshot, { - 'error': true - }); - }); - it('supports ifAbsent', async () => { const snapshot = await execute( firestore diff --git a/packages/firestore/test/integration/api/query_to_pipeline.test.ts b/packages/firestore/test/integration/api/query_to_pipeline.test.ts index 3cb3b765d41..d071023fd65 100644 --- a/packages/firestore/test/integration/api/query_to_pipeline.test.ts +++ b/packages/firestore/test/integration/api/query_to_pipeline.test.ts @@ -40,7 +40,8 @@ import { documentId, addDoc, getDoc, - or + or, + getDocs } from '../util/firebase_export'; import { apiDescribe, @@ -647,7 +648,7 @@ const testUnsupportedFeatures: boolean | 'only' = false; async (collRef, db) => { const query1 = query(collRef, where('bar', '!=', NaN)); const snapshot = await execute(db.pipeline().createFrom(query1)); - verifyResults(snapshot, { foo: 2, bar: 1 }); + verifyResults(snapshot, { foo: 2, bar: 1 }, { foo: 3, bar: 'bar' }); } ); }); @@ -661,8 +662,10 @@ const testUnsupportedFeatures: boolean | 'only' = false; }, async (collRef, db) => { const query1 = query(collRef, where('bar', '==', null)); + const classicSnapshot = await getDocs(query1); + const classicData = classicSnapshot.docs.map(d => d.data()); const snapshot = await execute(db.pipeline().createFrom(query1)); - verifyResults(snapshot, { foo: 1, bar: null }); + verifyResults(snapshot, ...classicData); } ); }); @@ -676,8 +679,10 @@ const testUnsupportedFeatures: boolean | 'only' = false; }, async (collRef, db) => { const query1 = query(collRef, where('bar', '!=', null)); + const classicSnapshot = await getDocs(query1); + const classicData = classicSnapshot.docs.map(d => d.data()); const snapshot = await execute(db.pipeline().createFrom(query1)); - verifyResults(snapshot, { foo: 2, bar: 1 }); + verifyResults(snapshot, ...classicData); } ); }); diff --git a/packages/firestore/test/lite/pipeline.test.ts b/packages/firestore/test/lite/pipeline.test.ts index cc549dcda7e..7e9aa4b438a 100644 --- a/packages/firestore/test/lite/pipeline.test.ts +++ b/packages/firestore/test/lite/pipeline.test.ts @@ -16,6 +16,7 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies +import { FirebaseError } from '@firebase/util'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; @@ -25,6 +26,29 @@ import { getFirestore, terminate } from '../../src/lite-api/database'; +import { documentId as documentIdFieldPath } from '../../src/lite-api/field_path'; +import { vector } from '../../src/lite-api/field_value_impl'; +import { GeoPoint } from '../../src/lite-api/geo_point'; +import { + pipelineResultEqual, + PipelineSnapshot +} from '../../src/lite-api/pipeline-result'; +import { execute } from '../../src/lite-api/pipeline_impl'; +import { + DocumentData, + CollectionReference, + collection, + doc, + DocumentReference +} from '../../src/lite-api/reference'; +import { addDoc, setDoc, deleteDoc } from '../../src/lite-api/reference_impl'; +import { FindNearestStageOptions } from '../../src/lite-api/stage_options'; +import { Timestamp } from '../../src/lite-api/timestamp'; +import { writeBatch } from '../../src/lite-api/write_batch'; +import { addEqualityMatcher } from '../util/equality_matcher'; +import { Deferred } from '../util/promise'; + +import { withTestCollection } from './helpers'; import { field, and, @@ -42,10 +66,8 @@ import { isAbsent, isError, or, - isNotNan, map, - isNotNull, - isNull, + length, mod, documentId, equal, @@ -54,7 +76,6 @@ import { countIf, lessThanOrEqual, greaterThan, - arrayConcat, arrayContains, arrayContainsAny, equalAny, @@ -64,7 +85,6 @@ import { logicalMaximum, logicalMinimum, exists, - isNan, reverse, like, regexContains, @@ -93,51 +113,44 @@ import { FunctionExpression, BooleanExpression, AggregateFunction, - sum, stringConcat, arrayContainsAll, arrayLength, charLength, divide, - abs, not, toLower, toUpper, trim, + byteLength, arrayGet, - byteLength -} from '../../src/lite-api/expressions'; -import { documentId as documentIdFieldPath } from '../../src/lite-api/field_path'; -import { vector } from '../../src/lite-api/field_value_impl'; -import { GeoPoint } from '../../src/lite-api/geo_point'; -import { - pipelineResultEqual, - PipelineSnapshot -} from '../../src/lite-api/pipeline-result'; -import { execute } from '../../src/lite-api/pipeline_impl'; -import { - DocumentData, - CollectionReference, - collection, - doc -} from '../../src/lite-api/reference'; -import { addDoc, setDoc } from '../../src/lite-api/reference_impl'; -import { FindNearestStageOptions } from '../../src/lite-api/stage_options'; -import { Timestamp } from '../../src/lite-api/timestamp'; -import { writeBatch } from '../../src/lite-api/write_batch'; -import { itIf } from '../integration/util/helpers'; -import { addEqualityMatcher } from '../util/equality_matcher'; -import { Deferred } from '../util/promise'; - -import { withTestCollection } from './helpers'; + abs, + sum, + countDistinct, + ceil, + floor, + exp, + pow, + round, + collectionId, + ln, + log, + sqrt, + stringReverse, + log10, + concat, + currentTimestamp, + ifAbsent, + join, + arraySum +} from './pipeline_export'; use(chaiAsPromised); -const testUnsupportedFeatures = false; const timestampDeltaMS = 1000; // eslint-disable-next-line no-restricted-properties -describe.skip('Firestore Pipelines', () => { +describe.only('Firestore Pipelines', () => { addEqualityMatcher(); let firestore: Firestore; @@ -329,8 +342,7 @@ describe.skip('Firestore Pipelines', () => { expect(snapshot.results.length).to.equal(0); }); - // Skipping because __name__ is not currently working in DBE - itIf(testUnsupportedFeatures)('full snapshot as expected', async () => { + it('full snapshot as expected', async () => { const ppl = firestore .pipeline() .collection(randomCol.path) @@ -512,31 +524,26 @@ describe.skip('Firestore Pipelines', () => { await terminate(db2); }); - // Subcollections not currently supported in DBE - itIf(testUnsupportedFeatures)( - 'supports collection group as source', - async () => { - const randomSubCollectionId = Math.random().toString(16).slice(2); - const doc1 = await addDoc( - collection(randomCol, 'book1', randomSubCollectionId), - { order: 1 } - ); - const doc2 = await addDoc( - collection(randomCol, 'book2', randomSubCollectionId), - { order: 2 } - ); - const snapshot = await execute( - firestore - .pipeline() - .collectionGroup(randomSubCollectionId) - .sort(ascending('order')) - ); - expectResults(snapshot, doc1.id, doc2.id); - } - ); + it('supports collection group as source', async () => { + const randomSubCollectionId = Math.random().toString(16).slice(2); + const doc1 = await addDoc( + collection(randomCol, 'book1', randomSubCollectionId), + { order: 1 } + ); + const doc2 = await addDoc( + collection(randomCol, 'book2', randomSubCollectionId), + { order: 2 } + ); + const snapshot = await execute( + firestore + .pipeline() + .collectionGroup(randomSubCollectionId) + .sort(ascending('order')) + ); + expectResults(snapshot, doc1.id, doc2.id); + }); - // subcollections not currently supported in dbe - itIf(testUnsupportedFeatures)('supports database as source', async () => { + it('supports database as source', async () => { const randomId = Math.random().toString(16).slice(2); const doc1 = await addDoc(collection(randomCol, 'book1', 'sub'), { order: 1, @@ -555,6 +562,17 @@ describe.skip('Firestore Pipelines', () => { ); expectResults(snapshot, doc1.id, doc2.id); }); + + it('can create pipeline from a query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .createFrom(randomCol) + .sort(field('__name__').ascending()) + .limit(1) + ); + expectResults(snapshot, 'book1'); + }); }); describe('supported data types', () => { @@ -630,7 +648,6 @@ describe.skip('Firestore Pipelines', () => { 'bytes': Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), 'documentReference': doc(firestore, 'foo', 'bar'), 'vectorValue': vector([1, 2, 3]), - 'vectorValue2': vector([1, 2, 3]), 'map': { 'number': 1, 'string': 'a string', @@ -788,6 +805,30 @@ describe.skip('Firestore Pipelines', () => { } }); }); + + it('supports boolean value constants as a BooleanExpression', async () => { + const snapshots = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + conditional(constant(true), constant('TRUE'), constant('FALSE')).as( + 'true' + ), + conditional( + constant(false), + constant('TRUE'), + constant('FALSE') + ).as('false') + ) + ); + + expectResults(snapshots, { + 'true': 'TRUE', + 'false': 'FALSE' + }); + }); }); describe('stages', () => { @@ -821,6 +862,37 @@ describe.skip('Firestore Pipelines', () => { }); }); + it('supports aggregate options', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [countAll().as('count')] + }) + ); + expectResults(snapshot, { count: 10 }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('genre', 'Science Fiction')) + .aggregate( + countAll().as('count'), + average('rating').as('avgRating'), + maximum('rating').as('maxRating'), + sum('rating').as('sumRating') + ) + ); + expectResults(snapshot, { + count: 2, + avgRating: 4.4, + maxRating: 4.6, + sumRating: 8.8 + }); + }); + it('rejects groups without accumulators', async () => { await expect( execute( @@ -897,6 +969,16 @@ describe.skip('Firestore Pipelines', () => { ); expectResults(snapshot, expectedResults); }); + + it('returns countDistinct accumulation', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countDistinct('genre').as('distinctGenres')) + ); + expectResults(snapshot, { distinctGenres: 8 }); + }); }); describe('distinct stage', () => { @@ -922,6 +1004,34 @@ describe.skip('Firestore Pipelines', () => { { genre: 'Southern Gothic', author: 'Harper Lee' } ); }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort({ + orderings: [ + field('genre').ascending(), + field('author').ascending() + ] + }) + ); + expectResults( + snapshot, + { genre: 'Dystopian', author: 'George Orwell' }, + { genre: 'Dystopian', author: 'Margaret Atwood' }, + { genre: 'Fantasy', author: 'J.R.R. Tolkien' }, + { genre: 'Magical Realism', author: 'Gabriel García Márquez' }, + { genre: 'Modernist', author: 'F. Scott Fitzgerald' }, + { genre: 'Psychological Thriller', author: 'Fyodor Dostoevsky' }, + { genre: 'Romance', author: 'Jane Austen' }, + { genre: 'Science Fiction', author: 'Douglas Adams' }, + { genre: 'Science Fiction', author: 'Frank Herbert' }, + { genre: 'Southern Gothic', author: 'Harper Lee' } + ); + }); }); describe('select stage', () => { @@ -953,6 +1063,25 @@ describe.skip('Firestore Pipelines', () => { { title: "The Handmaid's Tale", author: 'Margaret Atwood' } ); }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select({ selections: ['title', field('author').as('auth0r')] }) + .sort(field('auth0r').ascending()) + .limit(2) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + auth0r: 'Douglas Adams' + }, + { title: 'The Great Gatsby', auth0r: 'F. Scott Fitzgerald' } + ); + }); }); describe('addField stage', () => { @@ -1007,6 +1136,60 @@ describe.skip('Firestore Pipelines', () => { } ); }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields({ + fields: [constant('bar').as('foo')] + }) + .sort(field('author').ascending()) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + foo: 'bar' + }, + { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + foo: 'bar' + }, + { title: 'Dune', author: 'Frank Herbert', foo: 'bar' }, + { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + foo: 'bar' + }, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + foo: 'bar' + }, + { title: '1984', author: 'George Orwell', foo: 'bar' }, + { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + foo: 'bar' + }, + { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + foo: 'bar' + }, + { title: 'Pride and Prejudice', author: 'Jane Austen', foo: 'bar' }, + { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + foo: 'bar' + } + ); + }); }); describe('removeFields stage', () => { @@ -1018,7 +1201,6 @@ describe.skip('Firestore Pipelines', () => { .select('title', 'author') .sort(field('author').ascending()) .removeFields(field('author')) - .sort(field('author').ascending()) ); expectResults( snapshot, @@ -1048,38 +1230,160 @@ describe.skip('Firestore Pipelines', () => { } ); }); - }); - describe('where stage', () => { - it('where with and (2 conditions)', async () => { + it('supports options', async () => { const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .where( - and( - greaterThan('rating', 4.5), - equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']) - ) - ) + .select('title', 'author', 'genre') + .sort(field('author').ascending()) + .removeFields({ + fields: [field('author'), 'genre'] + }) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } ); - expectResults(snapshot, 'book10', 'book4'); }); - it('where with and (3 conditions)', async () => { + }); + + describe('findNearest stage', () => { + it('can find nearest', async () => { const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .where( - and( - greaterThan('rating', 4.5), - equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), - lessThan('published', 1965) - ) - ) - ); + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'genre') + .sort(field('author').ascending()) + .removeFields({ + fields: [field('author'), 'genre'] + }) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'The Great Gatsby' + }, + { title: 'Dune' }, + { + title: 'Crime and Punishment' + }, + { + title: 'One Hundred Years of Solitude' + }, + { title: '1984' }, + { + title: 'To Kill a Mockingbird' + }, + { + title: 'The Lord of the Rings' + }, + { title: 'Pride and Prejudice' }, + { + title: "The Handmaid's Tale" + } + ); + }); + }); + + describe('where stage', () => { + it('where with and (2 conditions)', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']) + ) + ) + ); + expectResults(snapshot, 'book10', 'book4'); + }); + + it('where with and (3 conditions)', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), + lessThan('published', 1965) + ) + ) + ); expectResults(snapshot, 'book4'); }); + it('where with or', async () => { const snapshot = await execute( firestore @@ -1126,6 +1430,21 @@ describe.skip('Firestore Pipelines', () => { { title: "The Handmaid's Tale" } ); }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where({ + condition: and( + greaterThan('rating', 4.5), + equalAny('genre', ['Science Fiction', 'Romance', 'Fantasy']) + ) + }) + ); + expectResults(snapshot, 'book10', 'book4'); + }); }); describe('sort, offset, and limit stages', () => { @@ -1146,6 +1465,26 @@ describe.skip('Firestore Pipelines', () => { { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' } ); }); + + it('sort, offset, and limit stages support options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort({ + orderings: [field('author').ascending()] + }) + .offset({ offset: 5 }) + .limit({ limit: 3 }) + .select('title', 'author') + ); + expectResults( + snapshot, + { title: '1984', author: 'George Orwell' }, + { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, + { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' } + ); + }); }); describe('raw stage', () => { @@ -1158,7 +1497,7 @@ describe.skip('Firestore Pipelines', () => { { title: field('title'), metadata: { - 'author': field('author') + author: field('author') } } ]) @@ -1279,6 +1618,38 @@ describe.skip('Firestore Pipelines', () => { } ); }); + + it('can perform FindNearest query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .rawStage( + 'find_nearest', + [ + field('embedding'), + vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), + 'euclidean' + ], + { + 'distance_field': field('computedDistance'), + limit: 2 + } + ) + .select('title', 'computedDistance') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + computedDistance: 1 + }, + { + title: 'One Hundred Years of Solitude', + computedDistance: 12.041594578792296 + } + ); + }); }); describe('replaceWith stage', () => { @@ -1317,6 +1688,21 @@ describe.skip('Firestore Pipelines', () => { baz: { title: "The Hitchhiker's Guide to the Galaxy" } }); }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith({ map: 'awards' }) + ); + expectResults(snapshot, { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }); + }); }); describe('sample stage', () => { @@ -1339,7 +1725,7 @@ describe.skip('Firestore Pipelines', () => { it('run pipeline with sample limit of {percentage: 0.6}', async () => { let avgSize = 0; - const numIterations = 20; + const numIterations = 30; for (let i = 0; i < numIterations; i++) { const snapshot = await execute( firestore @@ -1356,8 +1742,7 @@ describe.skip('Firestore Pipelines', () => { }); describe('union stage', () => { - // __name__ not currently supported by dbe - itIf(testUnsupportedFeatures)('run pipeline with union', async () => { + it('run pipeline with union', async () => { const snapshot = await execute( firestore .pipeline() @@ -1389,6 +1774,39 @@ describe.skip('Firestore Pipelines', () => { 'book9' ); }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .union({ other: firestore.pipeline().collection(randomCol.path) }) + .sort(field(documentIdFieldPath()).ascending()) + ); + expectResults( + snapshot, + 'book1', + 'book1', + 'book10', + 'book10', + 'book2', + 'book2', + 'book3', + 'book3', + 'book4', + 'book4', + 'book5', + 'book5', + 'book6', + 'book6', + 'book7', + 'book7', + 'book8', + 'book8', + 'book9', + 'book9' + ); + }); }); describe('unnest stage', () => { @@ -1460,13 +1878,14 @@ describe.skip('Firestore Pipelines', () => { } ); }); - it('unnest an expr', async () => { + + it('unnest with index field', async () => { const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) - .unnest(array([1, 2, 3]).as('copy')) + .unnest(field('tags').as('tag'), 'tagsIndex') .select( 'title', 'author', @@ -1474,9 +1893,10 @@ describe.skip('Firestore Pipelines', () => { 'published', 'rating', 'tags', - 'copy', + 'tag', 'awards', - 'nestedField' + 'nestedField', + 'tagsIndex' ) ); expectResults( @@ -1488,13 +1908,14 @@ describe.skip('Firestore Pipelines', () => { published: 1979, rating: 4.2, tags: ['comedy', 'space', 'adventure'], - copy: 1, + tag: 'comedy', awards: { hugo: true, nebula: false, others: { unknown: { year: 1980 } } }, - nestedField: { 'level.1': { 'level.2': true } } + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 0 }, { title: "The Hitchhiker's Guide to the Galaxy", @@ -1503,13 +1924,14 @@ describe.skip('Firestore Pipelines', () => { published: 1979, rating: 4.2, tags: ['comedy', 'space', 'adventure'], - copy: 2, + tag: 'space', awards: { hugo: true, nebula: false, others: { unknown: { year: 1980 } } }, - nestedField: { 'level.1': { 'level.2': true } } + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 1 }, { title: "The Hitchhiker's Guide to the Galaxy", @@ -1518,67 +1940,213 @@ describe.skip('Firestore Pipelines', () => { published: 1979, rating: 4.2, tags: ['comedy', 'space', 'adventure'], - copy: 3, + tag: 'adventure', awards: { hugo: true, nebula: false, others: { unknown: { year: 1980 } } }, - nestedField: { 'level.1': { 'level.2': true } } + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 2 } ); }); - }); - - describe('findNearest stage', () => { - it('run pipeline with findNearest', async () => { - const measures: Array = [ - 'euclidean', - 'dot_product', - 'cosine' - ]; - for (const measure of measures) { - const snapshot = await execute( - firestore - .pipeline() - .collection(randomCol) - .findNearest({ - field: 'embedding', - vectorValue: vector([10, 1, 3, 1, 2, 1, 1, 1, 1, 1]), - limit: 3, - distanceMeasure: measure - }) - .select('title') - ); - expectResults( - snapshot, - { - title: "The Hitchhiker's Guide to the Galaxy" - }, - { - title: 'One Hundred Years of Solitude' - }, - { - title: "The Handmaid's Tale" - } - ); - } - }); - it('optionally returns the computed distance', async () => { + it('unnest an expr', async () => { const snapshot = await execute( firestore .pipeline() - .collection(randomCol) - .findNearest({ - field: 'embedding', - vectorValue: vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), - limit: 2, - distanceMeasure: 'euclidean', - distanceField: 'computedDistance' - }) - .select('title', 'computedDistance') - ); + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(array([1, 2, 3]).as('copy')) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'copy', + 'awards', + 'nestedField' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 1, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 2, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + copy: 3, + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + } + ); + }); + + it('supports options', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(equal('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest({ + selectable: field('tags').as('tag'), + indexField: 'tagsIndex' + }) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + 'awards', + 'nestedField', + 'tagsIndex' + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'comedy', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 0 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'space', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 1 + }, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + tag: 'adventure', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + tagsIndex: 2 + } + ); + }); + }); + + describe('findNearest stage', () => { + it('run pipeline with findNearest', async () => { + const measures: Array = [ + 'euclidean', + 'dot_product', + 'cosine' + ]; + for (const measure of measures) { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .findNearest({ + field: 'embedding', + vectorValue: vector([10, 1, 3, 1, 2, 1, 1, 1, 1, 1]), + limit: 3, + distanceMeasure: measure + }) + .select('title') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + title: 'One Hundred Years of Solitude' + }, + { + title: "The Handmaid's Tale" + } + ); + } + }); + + it('optionally returns the computed distance', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .findNearest({ + field: 'embedding', + vectorValue: vector([10, 1, 2, 1, 1, 1, 1, 1, 1, 1]), + limit: 2, + distanceMeasure: 'euclidean', + distanceField: 'computedDistance' + }) + .select('title', 'computedDistance') + ); expectResults( snapshot, { @@ -1594,8 +2162,36 @@ describe.skip('Firestore Pipelines', () => { }); }); + describe('error handling', () => { + it('error properties are propagated from the firestore backend', async () => { + try { + const myPipeline = firestore + .pipeline() + .collection(randomCol.path) + .rawStage('select', [ + // incorrect parameter type + field('title') + ]); + + await execute(myPipeline); + + expect.fail('expected pipeline.execute() to throw'); + } catch (e: unknown) { + expect(e instanceof FirebaseError).to.be.true; + const err = e as FirebaseError; + // Backend returns the code as `failed-precondition` when using the REST transport + expect(err['code']).to.equal('failed-precondition'); + expect(typeof err['message']).to.equal('string'); + + expect(err['message']).to.match( + /Request failed with error: Expected value type of MAP_VALUE when parsing 'fields' but received FIELD_REFERENCE_VALUE instead/ + ); + } + }); + }); + describe('function expressions', () => { - it('logical maximum works', async () => { + it('logical max works', async () => { const snapshot = await execute( firestore .pipeline() @@ -1617,7 +2213,7 @@ describe.skip('Firestore Pipelines', () => { ); }); - it('logical minimum works', async () => { + it('logical min works', async () => { const snapshot = await execute( firestore .pipeline() @@ -1639,7 +2235,7 @@ describe.skip('Firestore Pipelines', () => { ); }); - it('conditiona works', async () => { + it('conditional works', async () => { const snapshot = await execute( firestore .pipeline() @@ -1650,20 +2246,28 @@ describe.skip('Firestore Pipelines', () => { lessThan(field('published'), 1960), constant(1960), field('published') - ).as('published-safe') + ).as('published-safe'), + field('rating') + .greaterThanOrEqual(4.5) + .conditional(constant('great'), constant('good')) + .as('rating') ) .sort(field('title').ascending()) .limit(3) ); expectResults( snapshot, - { title: '1984', 'published-safe': 1960 }, - { title: 'Crime and Punishment', 'published-safe': 1960 }, - { title: 'Dune', 'published-safe': 1965 } + { title: '1984', 'published-safe': 1960, rating: 'good' }, + { + title: 'Crime and Punishment', + 'published-safe': 1960, + rating: 'good' + }, + { title: 'Dune', 'published-safe': 1965, rating: 'great' } ); }); - it('eqAny works', async () => { + it('equalAny works', async () => { const snapshot = await execute( firestore .pipeline() @@ -1679,7 +2283,7 @@ describe.skip('Firestore Pipelines', () => { ); }); - it('notEqAny works', async () => { + it('notEqualAny works', async () => { const snapshot = await execute( firestore .pipeline() @@ -1888,8 +2492,8 @@ describe.skip('Firestore Pipelines', () => { subtract(field('published'), 1900).as('yearsSince1900'), field('rating').multiply(10).as('ratingTimesTen'), divide('rating', 2).as('ratingDividedByTwo'), - multiply('rating', 10).as('ratingTimes20'), - add('rating', 1).as('ratingPlus3'), + multiply('rating', 20).as('ratingTimes20'), + add('rating', 3).as('ratingPlus3'), mod('rating', 2).as('ratingMod2') ) .limit(1) @@ -1905,20 +2509,6 @@ describe.skip('Firestore Pipelines', () => { }); }); - it('testAbs', async () => { - const snapshot = await execute( - firestore - .pipeline() - .collection(randomCol.path) - .where(equal('title', 'To Kill a Mockingbird')) - .select(abs(field('rating')).as('absRating')) - .limit(1) - ); - expectResults(snapshot, { - absRating: 4.2 - }); - }); - it('testComparisonOperators', async () => { const snapshot = await execute( firestore @@ -1978,13 +2568,21 @@ describe.skip('Firestore Pipelines', () => { .sort(field('rating').descending()) .limit(1) .select( - isNull('rating').as('ratingIsNull'), - isNan('rating').as('ratingIsNaN'), - isError(arrayGet('title', 0)).as('isError'), - ifError(arrayGet('title', 0), constant('was error')).as('ifError'), + equal('rating', null).as('ratingIsNull'), + equal('rating', NaN).as('ratingIsNaN'), + isError(divide(constant(1), constant(0))).as('isError'), + ifError(divide(constant(1), constant(0)), constant('was error')).as( + 'ifError' + ), + ifError( + divide(constant(1), constant(0)).greaterThan(1), + constant(true) + ) + .not() + .as('ifErrorBooleanExpression'), isAbsent('foo').as('isAbsent'), - isNotNull('title').as('titleIsNotNull'), - isNotNan('cost').as('costIsNotNan'), + notEqual('title', null).as('titleIsNotNull'), + notEqual('cost', NaN).as('costIsNotNan'), exists('fooBarBaz').as('fooBarBazExists'), field('title').exists().as('titleExists') ) @@ -1994,6 +2592,7 @@ describe.skip('Firestore Pipelines', () => { ratingIsNaN: false, isError: true, ifError: 'was error', + ifErrorBooleanExpression: false, isAbsent: true, titleIsNotNull: true, costIsNotNan: false, @@ -2008,13 +2607,20 @@ describe.skip('Firestore Pipelines', () => { .sort(field('rating').descending()) .limit(1) .select( - field('rating').isNull().as('ratingIsNull'), - field('rating').isNan().as('ratingIsNaN'), - arrayGet('title', 0).isError().as('isError'), - arrayGet('title', 0).ifError(constant('was error')).as('ifError'), + field('rating').equal(null).as('ratingIsNull'), + field('rating').equal(NaN).as('ratingIsNaN'), + divide(constant(1), constant(0)).isError().as('isError'), + divide(constant(1), constant(0)) + .ifError(constant('was error')) + .as('ifError'), + divide(constant(1), constant(0)) + .greaterThan(1) + .ifError(constant(true)) + .not() + .as('ifErrorBooleanExpression'), field('foo').isAbsent().as('isAbsent'), - field('title').isNotNull().as('titleIsNotNull'), - field('cost').isNotNan().as('costIsNotNan') + field('title').notEqual(null).as('titleIsNotNull'), + field('cost').notEqual(NaN).as('costIsNotNan') ) ); expectResults(snapshot, { @@ -2022,6 +2628,7 @@ describe.skip('Firestore Pipelines', () => { ratingIsNaN: false, isError: true, ifError: 'was error', + ifErrorBooleanExpression: false, isAbsent: true, titleIsNotNull: true, costIsNotNan: false @@ -2048,7 +2655,7 @@ describe.skip('Firestore Pipelines', () => { title: "The Hitchhiker's Guide to the Galaxy", others: { unknown: { year: 1980 } } }, - { hugoAward: true, title: 'Dune', others: null } + { hugoAward: true, title: 'Dune' } ); }); @@ -2141,26 +2748,34 @@ describe.skip('Firestore Pipelines', () => { firestore .pipeline() .collection(randomCol.path) - .where(equal('awards.hugo', true)) + .limit(1) + .replaceWith( + map({ + title: 'foo', + nested: { + level: { + '1': 'bar' + }, + 'level.1': { + 'level.2': 'baz' + } + } + }) + ) .select( 'title', - field('nestedField.level.1'), - mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') + field('nested.level.1'), + mapGet('nested', 'level.1').mapGet('level.2').as('nested') ) - .sort(descending('title')) - ); - expectResults( - snapshot, - { - title: "The Hitchhiker's Guide to the Galaxy", - 'nestedField.level.`1`': null, - nested: true - }, - { title: 'Dune', 'nestedField.level.`1`': null, nested: null } ); + expectResults(snapshot, { + title: 'foo', + 'nested.level.`1`': 'bar', + nested: 'baz' + }); }); - describe('genericFunction', () => { + describe('rawFunction', () => { it('add selectable', async () => { const snapshot = await execute( firestore @@ -2293,7 +2908,7 @@ describe.skip('Firestore Pipelines', () => { }); }); - it('supports arrayOffset', async () => { + it('supports arrayGet', async () => { let snapshot = await execute( firestore .pipeline() @@ -2536,130 +3151,610 @@ describe.skip('Firestore Pipelines', () => { }); }); - it('supports Document_id', async () => { - let snapshot = await execute( + it('can reverse an array', async () => { + const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .sort(field('rating').descending()) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) .limit(1) - .select(documentId(field('__path__')).as('docId')) + .select(field('tags').arrayReverse().as('reversedTags')) ); expectResults(snapshot, { - docId: 'book4' + reversedTags: ['adventure', 'space', 'comedy'] }); - snapshot = await execute( + }); + + it('can reverse an array with the top-level function', async () => { + const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .sort(field('rating').descending()) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) .limit(1) - .select(field('__path__').documentId().as('docId')) + .select(reverse('tags').as('reversedTags')) ); expectResults(snapshot, { - docId: 'book4' + reversedTags: ['adventure', 'space', 'comedy'] }); }); - it('supports Substr', async () => { - let snapshot = await execute( + it('can compute the ceiling of a numeric value', async () => { + const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .sort(field('rating').descending()) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) .limit(1) - .select(substring('title', 9, 2).as('of')) + .select(field('rating').ceil().as('ceilingRating')) ); expectResults(snapshot, { - of: 'of' + ceilingRating: 5 }); - snapshot = await execute( + }); + + it('can compute the ceiling of a numeric value with the top-level function', async () => { + const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .sort(field('rating').descending()) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) .limit(1) - .select(field('title').substring(9, 2).as('of')) + .select(ceil('rating').as('ceilingRating')) ); expectResults(snapshot, { - of: 'of' + ceilingRating: 5 }); }); - it('supports Substr without length', async () => { - let snapshot = await execute( + it('can compute the floor of a numeric value', async () => { + const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .sort(field('rating').descending()) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) .limit(1) - .select(substring('title', 9).as('of')) + .select(field('rating').floor().as('floorRating')) ); expectResults(snapshot, { - of: 'of the Rings' + floorRating: 4 }); - snapshot = await execute( + }); + + it('can compute the floor of a numeric value with the top-level function', async () => { + const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .sort(field('rating').descending()) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) .limit(1) - .select(field('title').substring(9).as('of')) + .select(floor('rating').as('floorRating')) + ); + expectResults(snapshot, { + floorRating: 4 + }); + }); + + it('can compute e to the power of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(field('rating').exp().as('expRating')) + ); + expectResults(snapshot, { + expRating: 109.94717245212352 + }); + }); + + it('can compute e to the power of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(exp('rating').as('expRating')) + ); + expect(snapshot.results[0].get('expRating')).to.be.approximately( + 109.94717245212351, + 0.000001 + ); + }); + + it('can compute the power of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').pow(2).as('powerRating')) + ); + expect(snapshot.results[0].get('powerRating')).to.be.approximately( + 17.64, + 0.0001 + ); + }); + + it('can compute the power of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(pow('rating', 2).as('powerRating')) + ); + expect(snapshot.results[0].get('powerRating')).to.be.approximately( + 17.64, + 0.0001 + ); + }); + + it('can round a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(round('rating').as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value away from zero for positive half-way values', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .addFields(constant(1.5).as('positiveHalf')) + .select(field('positiveHalf').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 2 + }); + }); + + it('can round a numeric value away from zero for negative half-way values', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .addFields(constant(-1.5).as('negativeHalf')) + .select(field('negativeHalf').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: -2 + }); + }); + + it('can round a numeric value to specified precision', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + foo: 4.123456 + }) + ) + .select( + field('foo').round(0).as('0'), + round('foo', 1).as('1'), + round('foo', constant(2)).as('2'), + round(field('foo'), 4).as('4') + ) + ); + expectResults(snapshot, { + '0': 4, + '1': 4.1, + '2': 4.12, + '4': 4.1235 + }); + }); + + it('can get the collectionId from a path', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(field('__name__').collectionId().as('collectionId')) + ); + expectResults(snapshot, { + collectionId: randomCol.id + }); + }); + + it('can get the collectionId from a path with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(collectionId('__name__').as('collectionId')) + ); + expectResults(snapshot, { + collectionId: randomCol.id + }); + }); + + it('can compute the length of a string value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('title').length().as('titleLength')) + ); + expectResults(snapshot, { + titleLength: 36 + }); + }); + + it('can compute the length of a string value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(length('title').as('titleLength')) + ); + expectResults(snapshot, { + titleLength: 36 + }); + }); + + it('can compute the length of an array value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('tags').length().as('tagsLength')) + ); + expectResults(snapshot, { + tagsLength: 3 + }); + }); + + it('can compute the length of an array value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(length('tags').as('tagsLength')) + ); + expectResults(snapshot, { + tagsLength: 3 + }); + }); + + it('can compute the length of a map value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('awards').length().as('awardsLength')) + ); + expectResults(snapshot, { + awardsLength: 3 + }); + }); + + it('can compute the length of a vector value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('embedding').length().as('embeddingLength')) + ); + expectResults(snapshot, { + embeddingLength: 10 + }); + }); + + it('can compute the length of a bytes value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(constant('12é').as('value')) + .limit(1) + .select(field('value').byteLength().as('valueLength')) + ); + expectResults(snapshot, { + valueLength: 4 + }); + }); + + it('can compute the natural logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').ln().as('lnRating')) + ); + expect(snapshot.results[0]!.data().lnRating).to.be.closeTo(1.435, 0.001); + }); + + it('can compute the natural logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ln('rating').as('lnRating')) + ); + expect(snapshot.results[0]!.data().lnRating).to.be.closeTo(1.435, 0.001); + }); + + it('can compute the natural logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(ln('rating').as('lnRating')) + ); + expectResults(snapshot, { + lnRating: 1.4350845252893227 + }); + }); + + it('can compute the logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(log(field('rating'), 10).as('logRating')) + ); + expectResults(snapshot, { + logRating: 0.6232492903979004 + }); + }); + + it('can compute the logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(log('rating', 10).as('logRating')) + ); + expectResults(snapshot, { + logRating: 0.6232492903979004 + }); + }); + + it('can round a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').round().as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can round a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(round('rating').as('roundedRating')) + ); + expectResults(snapshot, { + roundedRating: 4 + }); + }); + + it('can compute the square root of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('rating').sqrt().as('sqrtRating')) + ); + expectResults(snapshot, { + sqrtRating: 2.04939015319192 + }); + }); + + it('can compute the square root of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(sqrt('rating').as('sqrtRating')) + ); + expectResults(snapshot, { + sqrtRating: 2.04939015319192 + }); + }); + + it('can reverse a string', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(field('title').reverse().as('reversedTitle')) + ); + expectResults(snapshot, { + reversedTitle: "yxalaG eht ot ediuG s'rekihhctiH ehT" + }); + }); + + it('can reverse a string with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal("The Hitchhiker's Guide to the Galaxy")) + .limit(1) + .select(stringReverse('title').as('reversedTitle')) + ); + expectResults(snapshot, { + reversedTitle: "yxalaG eht ot ediuG s'rekihhctiH ehT" + }); + }); + + it('supports Document_id', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + documentId(field('__name__')).as('docId'), + documentId(field('__path__')).as('noDocId') + ) + ); + expectResults(snapshot, { + docId: 'book4', + noDocId: null + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('__name__').documentId().as('docId')) ); expectResults(snapshot, { - of: 'of the Rings' + docId: 'book4' }); }); - it('arrayConcat works', async () => { - const snapshot = await execute( + it('supports substring', async () => { + let snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .select( - arrayConcat('tags', ['newTag1', 'newTag2'], field('tags'), [ - null - ]).as('modifiedTags') - ) + .sort(field('rating').descending()) .limit(1) + .select(substring('title', 9, 2).as('of')) ); expectResults(snapshot, { - modifiedTags: [ - 'comedy', - 'space', - 'adventure', - 'newTag1', - 'newTag2', - 'comedy', - 'space', - 'adventure', - null - ] + of: 'of' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substring(9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + }); + + it('supports substring without length', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substring('title', 9).as('of')) + ); + expectResults(snapshot, { + of: 'of the Rings' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substring(9).as('of')) + ); + expectResults(snapshot, { + of: 'of the Rings' }); }); - it('testToLowercase', async () => { + it('test toLower', async () => { const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) - .select(toLower('title').as('lowercaseTitle')) + .sort(ascending('title')) + .select(toLower('author').as('lowercaseAuthor')) .limit(1) ); expectResults(snapshot, { - lowercaseTitle: "the hitchhiker's guide to the galaxy" + lowercaseAuthor: 'george orwell' }); }); - it('testToUppercase', async () => { + it('test toUpper', async () => { const snapshot = await execute( firestore .pipeline() .collection(randomCol.path) + .sort(ascending('title')) .select(toUpper('author').as('uppercaseAuthor')) .limit(1) ); - expectResults(snapshot, { uppercaseAuthor: 'DOUGLAS ADAMS' }); + expectResults(snapshot, { uppercaseAuthor: 'GEORGE ORWELL' }); }); it('testTrim', async () => { @@ -2688,11 +3783,179 @@ describe.skip('Firestore Pipelines', () => { .limit(1) .select(reverse('title').as('reverseTitle')) ); - expectResults(snapshot, { title: '4891' }); + expectResults(snapshot, { reverseTitle: '4891' }); + }); + + it('testAbs', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(-10).as('neg10'), + constant(-22.22).as('neg22'), + constant(1).as('pos1') + ) + .select( + abs('neg10').as('10'), + abs(field('neg22')).as('22'), + field('pos1').as('1') + ) + ); + expectResults(snapshot, { + '10': 10, + '22': 22.22, + '1': 1 + }); + }); + + it('can compute the base-10 logarithm of a numeric value', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(field('rating').log10().as('log10Rating')) + ); + expect(snapshot.results[0]!.data().log10Rating).to.be.closeTo( + 0.672, + 0.001 + ); + }); + + it('can compute the base-10 logarithm of a numeric value with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .select(log10('rating').as('log10Rating')) + ); + expect(snapshot.results[0]!.data().log10Rating).to.be.closeTo( + 0.672, + 0.001 + ); + }); + + it('can concat fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .addFields( + concat('author', ' ', field('title')).as('display'), + field('author').concat(': ', field('title')).as('display2') + ) + .where(equal('author', 'Douglas Adams')) + .select('display', 'display2') + ); + expectResults(snapshot, { + display: "Douglas Adams The Hitchhiker's Guide to the Galaxy", + display2: "Douglas Adams: The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('supports currentTimestamp', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .addFields(currentTimestamp().as('now')) + .select('now') + ); + const now = snapshot.results[0].get('now') as Timestamp; + expect(now).instanceof(Timestamp); + expect( + now.toDate().getUTCSeconds() - new Date().getUTCSeconds() + ).lessThan(5000); + }); + + it('supports ifAbsent', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + title: 'foo' + }) + ) + .select( + ifAbsent('title', 'default title').as('title'), + field('name').ifAbsent('default name').as('name'), + field('name').ifAbsent(field('title')).as('nameOrTitle') + ) + ); + + expectResults(snapshot, { + title: 'foo', + name: 'default name', + nameOrTitle: 'foo' + }); + }); + + it('supports join', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .replaceWith( + map({ + tags: ['foo', 'bar', 'baz'], + delimeter: '|' + }) + ) + .select(join('tags', ',').as('csv'), field('tags').join('|').as('or')) + ); + + expectResults(snapshot, { + csv: 'foo,bar,baz', + or: 'foo|bar|baz' + }); + }); + + it('can compute the sum of the elements in an array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .addFields(array([150, 200]).as('sales')) + .select(field('sales').arraySum().as('totalSales')) + ); + expectResults(snapshot, { + totalSales: 350 + }); + }); + + it('can compute the sum of the elements in an array with the top-level function', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('title').equal('The Lord of the Rings')) + .limit(1) + .addFields(array([150, 200]).as('sales')) + .select(arraySum('sales').as('totalSales')) + ); + expectResults(snapshot, { + totalSales: 350 + }); }); + + // TODO(new-expression): Add new expression tests above this line }); describe('pagination', () => { + let addedDocs: DocumentReference[] = []; + /** * Adds several books to the test collection. These * additional books support pagination test scenarios @@ -2703,7 +3966,9 @@ describe.skip('Firestore Pipelines', () => { async function addBooks( collectionReference: CollectionReference ): Promise { - await setDoc(doc(collectionReference, 'book11'), { + let docRef = doc(collectionReference, 'book11'); + addedDocs.push(docRef); + await setDoc(docRef, { title: 'Jonathan Strange & Mr Norrell', author: 'Susanna Clarke', genre: 'Fantasy', @@ -2712,7 +3977,9 @@ describe.skip('Firestore Pipelines', () => { tags: ['historical fantasy', 'magic', 'alternate history', 'england'], awards: { hugo: false, nebula: false } }); - await setDoc(doc(collectionReference, 'book12'), { + docRef = doc(collectionReference, 'book12'); + addedDocs.push(docRef); + await setDoc(docRef, { title: 'The Master and Margarita', author: 'Mikhail Bulgakov', genre: 'Satire', @@ -2726,7 +3993,9 @@ describe.skip('Firestore Pipelines', () => { ], awards: {} }); - await setDoc(doc(collectionReference, 'book13'), { + docRef = doc(collectionReference, 'book13'); + addedDocs.push(docRef); + await setDoc(docRef, { title: 'A Long Way to a Small, Angry Planet', author: 'Becky Chambers', genre: 'Science Fiction', @@ -2737,108 +4006,135 @@ describe.skip('Firestore Pipelines', () => { }); } - // sort on __name__ is not working - itIf(testUnsupportedFeatures)( - 'supports pagination with filters', - async () => { - await addBooks(randomCol); - const pageSize = 2; - const pipeline = firestore - .pipeline() - .collection(randomCol.path) - .select('title', 'rating', '__name__') - .sort(field('rating').descending(), field('__name__').ascending()); + afterEach(async () => { + for (let i = 0; i < addedDocs.length; i++) { + await deleteDoc(addedDocs[i]); + } + addedDocs = []; + }); - let snapshot = await execute(pipeline.limit(pageSize)); - expectResults( - snapshot, - { title: 'The Lord of the Rings', rating: 4.7 }, - { title: 'Jonathan Strange & Mr Norrell', rating: 4.6 } - ); + it('supports pagination with filters', async () => { + await addBooks(randomCol); + const pageSize = 2; + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'rating', '__name__') + .sort(field('rating').descending(), field('__name__').ascending()); + + let snapshot = await execute(pipeline.limit(pageSize)); + expectResults( + snapshot, + { title: 'The Lord of the Rings', rating: 4.7 }, + { title: 'Dune', rating: 4.6 } + ); - const lastDoc = snapshot.results[snapshot.results.length - 1]; + const lastDoc = snapshot.results[snapshot.results.length - 1]; - snapshot = await execute( - pipeline - .where( - or( - and( - field('rating').equal(lastDoc.get('rating')), - field('__path__').greaterThan(lastDoc.ref?.id) - ), - field('rating').lessThan(lastDoc.get('rating')) - ) + snapshot = await execute( + pipeline + .where( + or( + and( + field('rating').equal(lastDoc.get('rating')), + field('__name__').greaterThan(lastDoc.ref) + ), + field('rating').lessThan(lastDoc.get('rating')) ) - .limit(pageSize) - ); - expectResults( - snapshot, - { title: 'Pride and Prejudice', rating: 4.5 }, - { title: 'Crime and Punishment', rating: 4.3 } - ); - } - ); + ) + .limit(pageSize) + ); + expectResults( + snapshot, + { title: 'Jonathan Strange & Mr Norrell', rating: 4.6 }, + { title: 'The Master and Margarita', rating: 4.6 } + ); + }); - // sort on __name__ is not working - itIf(testUnsupportedFeatures)( - 'supports pagination with offsets', - async () => { - await addBooks(randomCol); + it('supports pagination with offsets', async () => { + await addBooks(randomCol); - const secondFilterField = '__path__'; + const secondFilterField = '__name__'; - const pipeline = firestore - .pipeline() - .collection(randomCol.path) - .select('title', 'rating', secondFilterField) - .sort( - field('rating').descending(), - field(secondFilterField).ascending() - ); + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'rating', secondFilterField) + .sort( + field('rating').descending(), + field(secondFilterField).ascending() + ); - const pageSize = 2; - let currPage = 0; + const pageSize = 2; + let currPage = 0; - let snapshot = await execute( - pipeline.offset(currPage++ * pageSize).limit(pageSize) - ); + let snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); - expectResults( - snapshot, - { - title: 'The Lord of the Rings', - rating: 4.7 - }, - { title: 'Dune', rating: 4.6 } - ); + expectResults( + snapshot, + { + title: 'The Lord of the Rings', + rating: 4.7 + }, + { title: 'Dune', rating: 4.6 } + ); - snapshot = await execute( - pipeline.offset(currPage++ * pageSize).limit(pageSize) - ); - expectResults( - snapshot, - { - title: 'Jonathan Strange & Mr Norrell', - rating: 4.6 - }, - { title: 'The Master and Margarita', rating: 4.6 } - ); + snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); + expectResults( + snapshot, + { + title: 'Jonathan Strange & Mr Norrell', + rating: 4.6 + }, + { title: 'The Master and Margarita', rating: 4.6 } + ); - snapshot = await execute( - pipeline.offset(currPage++ * pageSize).limit(pageSize) + snapshot = await execute( + pipeline.offset(currPage++ * pageSize).limit(pageSize) + ); + expectResults( + snapshot, + { + title: 'A Long Way to a Small, Angry Planet', + rating: 4.6 + }, + { + title: 'Pride and Prejudice', + rating: 4.5 + } + ); + }); + }); + + describe('stage options', () => { + describe('forceIndex', () => { + // SKIP: requires pre-existing index + // eslint-disable-next-line no-restricted-properties + it.skip('Collection Stage', async () => { + const snapshot = await execute( + firestore.pipeline().collection({ + collection: randomCol, + forceIndex: 'unknown' + }) ); - expectResults( - snapshot, - { - title: 'A Long Way to a Small, Angry Planet', - rating: 4.6 - }, - { - title: 'Pride and Prejudice', - rating: 4.5 - } + expect(snapshot.results.length).to.equal(10); + }); + + // SKIP: requires pre-existing index + // eslint-disable-next-line no-restricted-properties + it.skip('CollectionGroup Stage', async () => { + const snapshot = await execute( + firestore.pipeline().collectionGroup({ + collectionId: randomCol.id, + forceIndex: 'unknown' + }) ); - } - ); + expect(snapshot.results.length).to.equal(10); + }); + }); }); }); diff --git a/packages/firestore/test/lite/pipeline_export.ts b/packages/firestore/test/lite/pipeline_export.ts new file mode 100644 index 00000000000..d5f60db9d30 --- /dev/null +++ b/packages/firestore/test/lite/pipeline_export.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Imports firebase via the raw sources and re-exports it. The +// "/integration/firestore" test suite replaces this file with a +// reference to the minified sources. If you change any exports in this file, +// you need to also adjust "integration/firestore/pipeline_export.ts". + +// @ts-ignore +export * from '../../lite/pipelines/pipelines.ts'; diff --git a/repo-scripts/prune-dts/prune-dts.ts b/repo-scripts/prune-dts/prune-dts.ts index 087d12a3d4b..bbfad0a3f3c 100644 --- a/repo-scripts/prune-dts/prune-dts.ts +++ b/repo-scripts/prune-dts/prune-dts.ts @@ -564,6 +564,21 @@ function dropPrivateApiTransformer( return (sourceFile: ts.SourceFile) => { const imports: Record> = {}; + // Get exported symbols + const directExportedSymbols = typeChecker.getExportsOfModule( + typeChecker.getSymbolAtLocation(sourceFile)! + ); + // Map exported symbols to aliases. + // For the statement `export { X as Y };`, this list would contain a symbol + // for `X`. + const aliasedExportedSymbols = directExportedSymbols + .map(symbol => + symbol.flags & ts.SymbolFlags.Alias + ? typeChecker.getAliasedSymbol(symbol) + : undefined + ) + .filter(symbol => symbol !== undefined); + function ensureImportsForFile(filename: string): Array { let importsForFile = imports[filename]; if (!importsForFile) { @@ -584,12 +599,27 @@ function dropPrivateApiTransformer( ts.isEnumDeclaration(node) ) { // Remove any types that are not exported. + // First we check the modifiers for the symbol `export function X`. If + // the export keyword is found, the symbol is modified. + // Second we check if the symbol has an alias that is exported elsewhere, + // for example: `function X; export { X as Y }`. If the alias is + // exported elsewhere, then we also have to keep the symbol. if ( !ts .getModifiers(node) ?.find(m => m.kind === ts.SyntaxKind.ExportKeyword) ) { - return ts.factory.createNotEmittedStatement(node); + // Try to get a symbol for this node. + const symbol = + 'name' in node && node.name + ? typeChecker.getSymbolAtLocation(node.name) + : undefined; + // Check if that symbol is in the list of aliased exported symbols. + // If it is, we keep the symbol. Otherwise, we remove the symbol. + if (!symbol || !aliasedExportedSymbols.includes(symbol)) { + // NO-OP block to keep the condition readable + return ts.factory.createNotEmittedStatement(node); + } } }