diff --git a/common/api-review/firestore-lite-pipelines.api.md b/common/api-review/firestore-lite-pipelines.api.md index d1536c8a626..28746295ae5 100644 --- a/common/api-review/firestore-lite-pipelines.api.md +++ b/common/api-review/firestore-lite-pipelines.api.md @@ -7,276 +7,204 @@ import { FirebaseApp } from '@firebase/app'; // @beta -export interface Accumulator { - // (undocumented) - accumulator: true; - add(other: Accumulator): Add; - add(other: any): Add; - arrayConcat(...arrays: Accumulator[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Accumulator): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Accumulator[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Accumulator[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Accumulator): CosineDistance; - // Warning: (ae-forgotten-export) The symbol "VectorValue" needs to be exported by the entry point pipelines.d.ts - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Accumulator): Divide; - divide(other: any): Divide; - dotProduct(other: Accumulator): DotProduct; - dotProduct(other: VectorValue): DotProduct; - // (undocumented) - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Accumulator): EndsWith; - eq(other: Accumulator): Eq; - eq(other: any): Eq; - eqAny(...others: Accumulator[]): EqAny; +export function add(first: Expr, second: Expr | unknown, ...others: Array): FunctionExpr; + +// @beta +export function add(fieldName: string, second: Expr | unknown, ...others: Array): FunctionExpr; + +// @beta (undocumented) +export class AddFields implements Stage { + constructor(fields: Map); // (undocumented) - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Accumulator): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; + name: string; +} + +// @beta (undocumented) +export class Aggregate implements Stage { + constructor(accumulators: Map, groups: Map); // (undocumented) - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; + name: string; +} + +// @beta +export class AggregateFunction { + constructor(name: string, params: Expr[]); + as(name: string): AggregateWithAlias; // (undocumented) exprType: ExprType; - gt(other: Accumulator): Gt; - gt(other: any): Gt; - gte(other: Accumulator): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; + } + +// @beta +export class AggregateWithAlias { + constructor(aggregate: AggregateFunction, alias: string); // (undocumented) - like(pattern: Accumulator): Like; - logicalMaximum(other: Accumulator): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Accumulator): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Accumulator): Lt; - lt(other: any): Lt; - lte(other: Accumulator): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Accumulator): Mod; - mod(other: any): Mod; - multiply(other: Accumulator): Multiply; - multiply(other: any): Multiply; - neq(other: Accumulator): Neq; - neq(other: any): Neq; - notEqAny(...others: Accumulator[]): FirestoreFunction; + readonly aggregate: AggregateFunction; // (undocumented) - notEqAny(...others: any[]): FirestoreFunction; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Accumulator): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Accumulator): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Accumulator, replace: Accumulator): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Accumulator, replace: Accumulator): ReplaceFirst; - reverse(): Reverse; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Accumulator): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Accumulator): StrContains; - subtract(other: Accumulator): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Accumulator, amount: Accumulator): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Accumulator, amount: Accumulator): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; + readonly alias: string; } // @beta -export type AccumulatorTarget = ExprWithAlias; +export function and(first: BooleanExpr, second: BooleanExpr, ...more: BooleanExpr[]): BooleanExpr; -// @beta (undocumented) -export class Add extends FirestoreFunction { - constructor(left: Accumulator, right: Accumulator); - } +// @beta +export function array(elements: unknown[]): FunctionExpr; // @beta -export function add(left: Accumulator, right: Accumulator): Add; +export function arrayConcat(firstArray: Expr, secondArray: Expr | unknown[], ...otherArrays: Array): FunctionExpr; // @beta -export function add(left: Accumulator, right: any): Add; +export function arrayConcat(firstArrayField: string, secondArray: Expr | unknown[], ...otherArrays: Array): FunctionExpr; // @beta -export function add(left: string, right: Accumulator): Add; +export function arrayContains(array: Expr, element: Expr): FunctionExpr; // @beta -export function add(left: string, right: any): Add; +export function arrayContains(array: Expr, element: unknown): FunctionExpr; -// @beta (undocumented) -export class AddFields implements Stage { - constructor(fields: Map); - // (undocumented) - name: string; -} +// @beta +export function arrayContains(fieldName: string, element: Expr): FunctionExpr; -// @beta (undocumented) -export class Aggregate implements Stage { - constructor(accumulators: Map, groups: Map); - // (undocumented) - name: string; -} +// @beta +export function arrayContains(fieldName: string, element: unknown): BooleanExpr; -// @beta (undocumented) -export class And extends FirestoreFunction implements FilterCondition { - constructor(conditions: FilterCondition[]); - // (undocumented) - filterable: true; -} +// @beta +export function arrayContainsAll(array: Expr, values: Array): BooleanExpr; -// @beta (undocumented) -export class ArrayConcat extends FirestoreFunction { - constructor(array: Accumulator, elements: Accumulator[]); - } +// @beta +export function arrayContainsAll(fieldName: string, values: Array): BooleanExpr; // @beta -export function arrayConcat(array: Accumulator, elements: Accumulator[]): ArrayConcat; +export function arrayContainsAll(array: Expr, arrayExpression: Expr): BooleanExpr; // @beta -export function arrayConcat(array: Accumulator, elements: any[]): ArrayConcat; +export function arrayContainsAll(fieldName: string, arrayExpression: Expr): BooleanExpr; // @beta -export function arrayConcat(array: string, elements: Accumulator[]): ArrayConcat; +export function arrayContainsAny(array: Expr, values: Array): BooleanExpr; // @beta -export function arrayConcat(array: string, elements: any[]): ArrayConcat; +export function arrayContainsAny(fieldName: string, values: Array): BooleanExpr; -// @beta (undocumented) -export class ArrayContains extends FirestoreFunction implements FilterCondition { - constructor(array: Accumulator, element: Accumulator); - // (undocumented) - filterable: true; -} +// @beta +export function arrayContainsAny(array: Expr, values: Expr): BooleanExpr; // @beta -export function arrayContains(array: Accumulator, element: Accumulator): ArrayContains; +export function arrayContainsAny(fieldName: string, values: Expr): BooleanExpr; // @beta -export function arrayContains(array: Accumulator, element: any): ArrayContains; +export function arrayLength(array: Expr): FunctionExpr; // @beta -export function arrayContains(array: string, element: Accumulator): ArrayContains; +export function arrayOffset(arrayField: string, offset: number): FunctionExpr; // @beta -export function arrayContains(array: string, element: any): ArrayContains; +export function arrayOffset(arrayField: string, offsetExpr: Expr): FunctionExpr; -// @beta (undocumented) -export class ArrayContainsAll extends FirestoreFunction implements FilterCondition { - constructor(array: Accumulator, values: Accumulator[]); - // (undocumented) - filterable: true; - } +// @beta +export function arrayOffset(arrayExpression: Expr, offset: number): FunctionExpr; // @beta -export function arrayContainsAll(array: Accumulator, values: Accumulator[]): ArrayContainsAll; +export function arrayOffset(arrayExpression: Expr, offsetExpr: Expr): FunctionExpr; // @beta -export function arrayContainsAll(array: Accumulator, values: any[]): ArrayContainsAll; +export function ascending(expr: Expr): Ordering; // @beta -export function arrayContainsAll(array: string, values: Accumulator[]): ArrayContainsAll; +export function ascending(fieldName: string): Ordering; // @beta -export function arrayContainsAll(array: string, values: any[]): ArrayContainsAll; +export function avg(expression: Expr): AggregateFunction; -// @beta (undocumented) -export class ArrayContainsAny extends FirestoreFunction implements FilterCondition { - constructor(array: Accumulator, values: Accumulator[]); - // (undocumented) - filterable: true; - } +// @beta +export function avg(fieldName: string): AggregateFunction; +// Warning: (ae-forgotten-export) The symbol "Bytes" needs to be exported by the entry point pipelines.d.ts +// // @beta -export function arrayContainsAny(array: Accumulator, values: Accumulator[]): ArrayContainsAny; +export function bitAnd(field: string, otherBits: number | Bytes): FunctionExpr; // @beta -export function arrayContainsAny(array: Accumulator, values: any[]): ArrayContainsAny; +export function bitAnd(field: string, bitsExpression: Expr): FunctionExpr; // @beta -export function arrayContainsAny(array: string, values: Accumulator[]): ArrayContainsAny; +export function bitAnd(bitsExpression: Expr, otherBits: number | Bytes): FunctionExpr; // @beta -export function arrayContainsAny(array: string, values: any[]): ArrayContainsAny; +export function bitAnd(bitsExpression: Expr, otherBitsExpression: Expr): FunctionExpr; -// @beta (undocumented) -export class ArrayElement extends FirestoreFunction { - constructor(); -} +// @beta +export function bitLeftShift(field: string, y: number): FunctionExpr; -// @beta (undocumented) -export class ArrayLength extends FirestoreFunction { - constructor(array: Accumulator); - } +// @beta +export function bitLeftShift(field: string, numberExpr: Expr): FunctionExpr; // @beta -export function arrayLength(array: Accumulator): ArrayLength; +export function bitLeftShift(xValue: Expr, y: number): FunctionExpr; -// @beta (undocumented) -export class ArrayReverse extends FirestoreFunction { - constructor(array: Accumulator); - } +// @beta +export function bitLeftShift(xValue: Expr, numberExpr: Expr): FunctionExpr; // @beta -export function ascending(expr: Accumulator): Ordering; +export function bitNot(field: string): FunctionExpr; -// @beta (undocumented) -export class Avg extends FirestoreFunction implements Accumulator { - constructor(value: Accumulator, distinct: boolean); - // (undocumented) - accumulator: true; - } +// @beta +export function bitNot(bitsValueExpression: Expr): FunctionExpr; -// @beta (undocumented) -export class ByteLength extends FirestoreFunction { - constructor(value: Accumulator); - } +// @beta +export function bitOr(field: string, otherBits: number | Bytes): FunctionExpr; // @beta -export function byteLength(expr: Accumulator): ByteLength; +export function bitOr(field: string, bitsExpression: Expr): FunctionExpr; // @beta -export function byteLength(field: string): ByteLength; +export function bitOr(bitsExpression: Expr, otherBits: number | Bytes): FunctionExpr; -// @beta (undocumented) -export class CharLength extends FirestoreFunction { - constructor(value: Accumulator); - } +// @beta +export function bitOr(bitsExpression: Expr, otherBitsExpression: Expr): FunctionExpr; + +// @beta +export function bitRightShift(field: string, y: number): FunctionExpr; + +// @beta +export function bitRightShift(field: string, numberExpr: Expr): FunctionExpr; + +// @beta +export function bitRightShift(xValue: Expr, y: number): FunctionExpr; + +// @beta +export function bitRightShift(xValue: Expr, numberExpr: Expr): FunctionExpr; + +// @beta +export function bitXor(field: string, otherBits: number | Bytes): FunctionExpr; // @beta -export function charLength(field: string): CharLength; +export function bitXor(field: string, bitsExpression: Expr): FunctionExpr; // @beta -export function charLength(expr: Accumulator): CharLength; +export function bitXor(bitsExpression: Expr, otherBits: number | Bytes): FunctionExpr; + +// @beta +export function bitXor(bitsExpression: Expr, otherBitsExpression: Expr): FunctionExpr; + +// @beta +export class BooleanExpr extends FunctionExpr { + countIf(): AggregateFunction; + // (undocumented) + filterable: true; + not(): BooleanExpr; +} + +// @beta +export function byteLength(expr: Expr): FunctionExpr; + +// @beta +export function byteLength(fieldName: string): FunctionExpr; + +// @beta +export function charLength(fieldName: string): FunctionExpr; + +// @beta +export function charLength(stringExpression: Expr): FunctionExpr; // @beta (undocumented) export class CollectionGroupSource implements Stage { @@ -292,169 +220,102 @@ export class CollectionSource implements Stage { name: string; } -// @beta (undocumented) -export class Cond extends FirestoreFunction { - constructor(condition: FilterCondition, thenExpr: Accumulator, elseExpr: Accumulator); - // (undocumented) - filterable: true; - } - // @beta -export function cond(condition: FilterCondition, thenExpr: Accumulator, elseExpr: Accumulator): Cond; +export function cond(condition: BooleanExpr, thenExpr: Expr, elseExpr: Expr): FunctionExpr; // @beta -export class Constant { - add(other: Accumulator): Add; - add(other: any): Add; - arrayConcat(...arrays: Accumulator[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Accumulator): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Accumulator[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Accumulator[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Accumulator): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Accumulator): Divide; - divide(other: any): Divide; - dotProduct(other: Accumulator): DotProduct; - dotProduct(other: VectorValue): DotProduct; - // (undocumented) - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Accumulator): EndsWith; - eq(other: Accumulator): Eq; - eq(other: any): Eq; - eqAny(...others: Accumulator[]): EqAny; +export class Constant extends Expr { // (undocumented) - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Accumulator): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; - // (undocumented) - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; - // (undocumented) - exprType: ExprType; - gt(other: Accumulator): Gt; - gt(other: any): Gt; - gte(other: Accumulator): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - // (undocumented) - like(pattern: Accumulator): Like; - logicalMaximum(other: Accumulator): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Accumulator): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Accumulator): Lt; - lt(other: any): Lt; - lte(other: Accumulator): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Accumulator): Mod; - mod(other: any): Mod; - multiply(other: Accumulator): Multiply; - multiply(other: any): Multiply; - neq(other: Accumulator): Neq; - neq(other: any): Neq; - notEqAny(...others: Accumulator[]): FirestoreFunction; - // (undocumented) - notEqAny(...others: any[]): FirestoreFunction; - static of(value: number): Constant; - static of(value: string): Constant; - static of(value: boolean): Constant; - static of(value: null): Constant; - static of(value: undefined): Constant; - // Warning: (ae-forgotten-export) The symbol "GeoPoint" needs to be exported by the entry point pipelines.d.ts - static of(value: GeoPoint): Constant; - // Warning: (ae-forgotten-export) The symbol "Timestamp" needs to be exported by the entry point pipelines.d.ts - static of(value: Timestamp): Constant; - static of(value: Date): Constant; - static of(value: Uint8Array): Constant; - // Warning: (ae-forgotten-export) The symbol "DocumentReference" needs to be exported by the entry point pipelines.d.ts - static of(value: DocumentReference): Constant; - static of(value: any[]): Constant; - static of(value: Map): Constant; - static of(value: VectorValue): Constant; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Accumulator): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Accumulator): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Accumulator, replace: Accumulator): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Accumulator, replace: Accumulator): ReplaceFirst; - reverse(): Reverse; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Accumulator): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Accumulator): StrContains; - subtract(other: Accumulator): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Accumulator, amount: Accumulator): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Accumulator, amount: Accumulator): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - static vector(value: number[] | VectorValue): Constant; - vectorLength(): VectorLength; -} - -// @beta (undocumented) -export class CosineDistance extends FirestoreFunction { - constructor(vector1: Accumulator, vector2: Accumulator); + readonly exprType: ExprType; } +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: number): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: string): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: boolean): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: null): Constant; + +// Warning: (ae-forgotten-export) The symbol "GeoPoint" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: GeoPoint): Constant; + +// Warning: (ae-forgotten-export) The symbol "Timestamp" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: Timestamp): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: Date): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: Bytes): Constant; + +// Warning: (ae-forgotten-export) The symbol "DocumentReference" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: DocumentReference): Constant; + +// Warning: (ae-forgotten-export) The symbol "VectorValue" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: VectorValue): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constantVector" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constantVector(value: number[] | VectorValue): Constant; + // @beta -export function cosineDistance(expr: string, other: number[]): CosineDistance; +export function cosineDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpr; // @beta -export function cosineDistance(expr: string, other: VectorValue): CosineDistance; +export function cosineDistance(fieldName: string, vectorExpression: Expr): FunctionExpr; // @beta -export function cosineDistance(expr: string, other: Accumulator): CosineDistance; +export function cosineDistance(vectorExpression: Expr, vector: number[] | Expr): FunctionExpr; // @beta -export function cosineDistance(expr: Accumulator, other: number[]): CosineDistance; +export function cosineDistance(vectorExpression: Expr, otherVectorExpression: Expr): FunctionExpr; // @beta -export function cosineDistance(expr: Accumulator, other: VectorValue): CosineDistance; +export function count(expression: Expr): AggregateFunction; + +// Warning: (ae-incompatible-release-tags) The symbol "count" is marked as @public, but its signature references "AggregateFunction" which is marked as @beta +// +// @public +export function count(fieldName: string): AggregateFunction; // @beta -export function cosineDistance(expr: Accumulator, other: Accumulator): CosineDistance; +export function countAll(): AggregateFunction; -// @beta (undocumented) -export class Count extends FirestoreFunction implements Accumulator { - constructor(value: Accumulator | undefined, distinct: boolean); - // (undocumented) - accumulator: true; - } +// @beta +export function countIf(booleanExpr: BooleanExpr): AggregateFunction; // @beta -export function countAll(): Count; +export function currentContext(): FunctionExpr; // @beta (undocumented) export class DatabaseSource implements Stage { @@ -463,31 +324,35 @@ export class DatabaseSource implements Stage { } // @beta -export function descending(expr: Accumulator): Ordering; +export function descending(expr: Expr): Ordering; + +// @beta +export function descending(fieldName: string): Ordering; // @beta (undocumented) export class Distinct implements Stage { - constructor(groups: Map); + constructor(groups: Map); // (undocumented) name: string; } -// @beta (undocumented) -export class Divide extends FirestoreFunction { - constructor(left: Accumulator, right: Accumulator); - } +// @beta +export function divide(left: Expr, right: Expr): FunctionExpr; + +// @beta +export function divide(expression: Expr, value: unknown): FunctionExpr; // @beta -export function divide(left: Accumulator, right: Accumulator): Divide; +export function divide(fieldName: string, expressions: Expr): FunctionExpr; // @beta -export function divide(left: Accumulator, right: any): Divide; +export function divide(fieldName: string, value: unknown): FunctionExpr; // @beta -export function divide(left: string, right: Accumulator): Divide; +export function documentId(documentPath: string | DocumentReference): FunctionExpr; // @beta -export function divide(left: string, right: any): Divide; +export function documentId(documentPathExpr: Expr): FunctionExpr; // @beta (undocumented) export class DocumentsSource implements Stage { @@ -495,574 +360,350 @@ export class DocumentsSource implements Stage { // (undocumented) name: string; // (undocumented) - static of(refs: DocumentReference[]): DocumentsSource; + static of(refs: Array): DocumentsSource; } -// @beta (undocumented) -export class DotProduct extends FirestoreFunction { - constructor(vector1: Accumulator, vector2: Accumulator); - } - // @beta -export function dotProduct(expr: string, other: number[]): DotProduct; +export function dotProduct(fieldName: string, vector: number[] | VectorValue): FunctionExpr; // @beta -export function dotProduct(expr: string, other: VectorValue): DotProduct; +export function dotProduct(fieldName: string, vectorExpression: Expr): FunctionExpr; // @beta -export function dotProduct(expr: string, other: Accumulator): DotProduct; +export function dotProduct(vectorExpression: Expr, vector: number[] | VectorValue): FunctionExpr; // @beta -export function dotProduct(expr: Accumulator, other: number[]): DotProduct; +export function dotProduct(vectorExpression: Expr, otherVectorExpression: Expr): FunctionExpr; // @beta -export function dotProduct(expr: Accumulator, other: VectorValue): DotProduct; +export function endsWith(fieldName: string, suffix: string): BooleanExpr; // @beta -export function dotProduct(expr: Accumulator, other: Accumulator): DotProduct; - -// @beta (undocumented) -export class EndsWith extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator, suffix: Accumulator); - // (undocumented) - filterable: true; - } +export function endsWith(fieldName: string, suffix: Expr): BooleanExpr; // @beta -export function endsWith(expr: string, suffix: string): EndsWith; +export function endsWith(stringExpression: Expr, suffix: string): BooleanExpr; // @beta -export function endsWith(expr: string, suffix: Accumulator): EndsWith; +export function endsWith(stringExpression: Expr, suffix: Expr): BooleanExpr; // @beta -export function endsWith(expr: Accumulator, suffix: string): EndsWith; +export function eq(left: Expr, right: Expr): BooleanExpr; // @beta -export function endsWith(expr: Accumulator, suffix: Accumulator): EndsWith; - -// @beta (undocumented) -export class Eq extends FirestoreFunction implements FilterCondition { - constructor(left: Accumulator, right: Accumulator); - // (undocumented) - filterable: true; - } - -// @beta -export function eq(left: Accumulator, right: Accumulator): Eq; - -// @beta -export function eq(left: Accumulator, right: any): Eq; - -// @beta -export function eq(left: string, right: Accumulator): Eq; - -// @beta -export function eq(left: string, right: any): Eq; - -// @beta (undocumented) -export class EqAny extends FirestoreFunction implements FilterCondition { - constructor(left: Accumulator, others: Accumulator[]); - // (undocumented) - filterable: true; - } - -// @beta -export function eqAny(element: Accumulator, others: Accumulator[]): EqAny; +export function eq(expression: Expr, value: unknown): BooleanExpr; // @beta -export function eqAny(element: Accumulator, others: any[]): EqAny; +export function eq(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function eqAny(element: string, others: Accumulator[]): EqAny; +export function eq(fieldName: string, value: unknown): BooleanExpr; // @beta -export function eqAny(element: string, others: any[]): EqAny; - -// @beta (undocumented) -export class EuclideanDistance extends FirestoreFunction { - constructor(vector1: Accumulator, vector2: Accumulator); - } +export function eqAny(expression: Expr, values: Array): BooleanExpr; // @beta -export function euclideanDistance(expr: string, other: number[]): EuclideanDistance; +export function eqAny(expression: Expr, arrayExpression: Expr): BooleanExpr; // @beta -export function euclideanDistance(expr: string, other: VectorValue): EuclideanDistance; +export function eqAny(fieldName: string, values: Array): BooleanExpr; // @beta -export function euclideanDistance(expr: string, other: Accumulator): EuclideanDistance; +export function eqAny(fieldName: string, arrayExpression: Expr): BooleanExpr; // @beta -export function euclideanDistance(expr: Accumulator, other: number[]): EuclideanDistance; +export function euclideanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpr; // @beta -export function euclideanDistance(expr: Accumulator, other: VectorValue): EuclideanDistance; +export function euclideanDistance(fieldName: string, vectorExpression: Expr): FunctionExpr; // @beta -export function euclideanDistance(expr: Accumulator, other: Accumulator): EuclideanDistance; +export function euclideanDistance(vectorExpression: Expr, vector: number[] | VectorValue): FunctionExpr; // @beta -export function execute(pipeline: Pipeline): Promise; +export function euclideanDistance(vectorExpression: Expr, otherVectorExpression: Expr): FunctionExpr; -// @beta (undocumented) -export class Exists extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator); - // (undocumented) - filterable: true; +// @public +export function execute(pipeline: Pipeline): Promise; + +// @beta +export function exists(value: Expr): BooleanExpr; + +// @beta +export function exists(fieldName: string): BooleanExpr; + +// @beta +export abstract class Expr { + add(second: Expr | unknown, ...others: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + arrayConcat(secondArray: Expr | unknown[], ...otherArrays: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + arrayContains(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContains(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContainsAll(values: Array): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContainsAll(arrayExpression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContainsAny(values: Array): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContainsAny(arrayExpression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayLength(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + arrayOffset(offset: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + arrayOffset(offsetExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + as(name: string): ExprWithAlias; + /* Excluded from this release type: _readUserData */ + ascending(): Ordering; + /* Excluded from this release type: _readUserData */ + avg(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + bitAnd(otherBits: number | Bytes): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitAnd(bitsExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitLeftShift(y: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitLeftShift(numberExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitNot(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitOr(otherBits: number | Bytes): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitOr(bitsExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitRightShift(y: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitRightShift(numberExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitXor(otherBits: number | Bytes): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitXor(bitsExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + byteLength(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + charLength(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + cosineDistance(vectorExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + cosineDistance(vector: VectorValue | number[]): FunctionExpr; + /* Excluded from this release type: _readUserData */ + count(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + descending(): Ordering; + /* Excluded from this release type: _readUserData */ + divide(other: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + divide(other: unknown): FunctionExpr; + /* Excluded from this release type: _readUserData */ + documentId(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + dotProduct(vectorExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + dotProduct(vector: VectorValue | number[]): FunctionExpr; + /* Excluded from this release type: _readUserData */ + endsWith(suffix: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + endsWith(suffix: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + eq(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + eq(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + eqAny(values: Array): BooleanExpr; + /* Excluded from this release type: _readUserData */ + eqAny(arrayExpression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + euclideanDistance(vectorExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + euclideanDistance(vector: VectorValue | number[]): FunctionExpr; + /* Excluded from this release type: _readUserData */ + exists(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + // (undocumented) + abstract readonly exprType: ExprType; + /* Excluded from this release type: _readUserData */ + gt(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + gt(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + gte(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + gte(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + ifError(catchExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + ifError(catchValue: unknown): FunctionExpr; + /* Excluded from this release type: _readUserData */ + isAbsent(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isError(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isNan(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isNotNan(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isNotNull(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isNull(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + like(pattern: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + like(pattern: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + logicalMaximum(second: Expr | unknown, ...others: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + logicalMinimum(second: Expr | unknown, ...others: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + lt(experession: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + lt(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + lte(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + lte(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + manhattanDistance(vector: VectorValue | number[]): FunctionExpr; + /* Excluded from this release type: _readUserData */ + manhattanDistance(vectorExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mapGet(subfield: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mapMerge(secondMap: Record | Expr, ...otherMaps: Array | Expr>): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mapRemove(key: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mapRemove(keyExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + maximum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + minimum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + mod(expression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mod(value: unknown): FunctionExpr; + /* Excluded from this release type: _readUserData */ + multiply(second: Expr | unknown, ...others: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + neq(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + neq(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + notEqAny(values: Array): BooleanExpr; + /* Excluded from this release type: _readUserData */ + notEqAny(arrayExpression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + regexContains(pattern: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + regexContains(pattern: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + regexMatch(pattern: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + regexMatch(pattern: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + replaceAll(find: string, replace: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + replaceAll(find: Expr, replace: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + replaceFirst(find: string, replace: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + replaceFirst(find: Expr, replace: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + reverse(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + startsWith(prefix: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + startsWith(prefix: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + strConcat(secondString: Expr | string, ...otherStrings: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + strContains(substring: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + strContains(expr: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + substr(position: number, length?: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + substr(position: Expr, length?: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + subtract(other: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + subtract(other: unknown): FunctionExpr; + /* Excluded from this release type: _readUserData */ + sum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + timestampAdd(unit: Expr, amount: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampSub(unit: Expr, amount: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampToUnixMicros(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampToUnixMillis(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampToUnixSeconds(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + toLower(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + toUpper(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + trim(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + unixMicrosToTimestamp(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + unixMillisToTimestamp(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + unixSecondsToTimestamp(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + vectorLength(): FunctionExpr; } // @beta -export function exists(value: Accumulator): Exists; - -// @beta -export function exists(field: string): Exists; - -// @beta -export type ExprType = 'Field' | 'Constant' | 'Function' | 'ListOfExprs' | 'ExprWithAlias'; +export type ExprType = 'Field' | 'Constant' | 'Function' | 'AggregateFunction' | 'ListOfExprs' | 'ExprWithAlias'; // @beta (undocumented) -export class ExprWithAlias implements Selectable { - constructor(expr: T, alias: string); - add(other: Accumulator): Add; - add(other: any): Add; +export class ExprWithAlias implements Selectable { + constructor(expr: Expr, alias: string); // (undocumented) readonly alias: string; - arrayConcat(...arrays: Accumulator[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Accumulator): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Accumulator[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Accumulator[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Accumulator): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Accumulator): Divide; - divide(other: any): Divide; - dotProduct(other: Accumulator): DotProduct; - dotProduct(other: VectorValue): DotProduct; - // (undocumented) - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Accumulator): EndsWith; - eq(other: Accumulator): Eq; - eq(other: any): Eq; - eqAny(...others: Accumulator[]): EqAny; - // (undocumented) - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Accumulator): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; - // (undocumented) - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; // (undocumented) - readonly expr: T; + readonly expr: Expr; // (undocumented) exprType: ExprType; - gt(other: Accumulator): Gt; - gt(other: any): Gt; - gte(other: Accumulator): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - // (undocumented) - like(pattern: Accumulator): Like; - logicalMaximum(other: Accumulator): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Accumulator): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Accumulator): Lt; - lt(other: any): Lt; - lte(other: Accumulator): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Accumulator): Mod; - mod(other: any): Mod; - multiply(other: Accumulator): Multiply; - multiply(other: any): Multiply; - neq(other: Accumulator): Neq; - neq(other: any): Neq; - notEqAny(...others: Accumulator[]): FirestoreFunction; - // (undocumented) - notEqAny(...others: any[]): FirestoreFunction; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Accumulator): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Accumulator): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Accumulator, replace: Accumulator): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Accumulator, replace: Accumulator): ReplaceFirst; - reverse(): Reverse; // (undocumented) selectable: true; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Accumulator): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Accumulator): StrContains; - subtract(other: Accumulator): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Accumulator, amount: Accumulator): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Accumulator, amount: Accumulator): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; } // @beta -export class Field implements Selectable { - add(other: Accumulator): Add; - add(other: any): Add; - arrayConcat(...arrays: Accumulator[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Accumulator): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Accumulator[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Accumulator[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Accumulator): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Accumulator): Divide; - divide(other: any): Divide; - dotProduct(other: Accumulator): DotProduct; - dotProduct(other: VectorValue): DotProduct; +export class Field extends Expr implements Selectable { // (undocumented) - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Accumulator): EndsWith; - eq(other: Accumulator): Eq; - eq(other: any): Eq; - eqAny(...others: Accumulator[]): EqAny; + get alias(): string; // (undocumented) - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Accumulator): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; + get expr(): Expr; // (undocumented) - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; - // (undocumented) - exprType: ExprType; + readonly exprType: ExprType; // (undocumented) fieldName(): string; - gt(other: Accumulator): Gt; - gt(other: any): Gt; - gte(other: Accumulator): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - // (undocumented) - like(pattern: Accumulator): Like; - logicalMaximum(other: Accumulator): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Accumulator): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Accumulator): Lt; - lt(other: any): Lt; - lte(other: Accumulator): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Accumulator): Mod; - mod(other: any): Mod; - multiply(other: Accumulator): Multiply; - multiply(other: any): Multiply; - neq(other: Accumulator): Neq; - neq(other: any): Neq; - notEqAny(...others: Accumulator[]): FirestoreFunction; - // (undocumented) - notEqAny(...others: any[]): FirestoreFunction; - static of(name: string): Field; - // Warning: (ae-forgotten-export) The symbol "FieldPath" needs to be exported by the entry point pipelines.d.ts - // - // (undocumented) - static of(path: FieldPath): Field; - // (undocumented) - static of(pipeline: Pipeline, name: string): Field; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Accumulator): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Accumulator): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Accumulator, replace: Accumulator): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Accumulator, replace: Accumulator): ReplaceFirst; - reverse(): Reverse; // (undocumented) selectable: true; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Accumulator): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Accumulator): StrContains; - subtract(other: Accumulator): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Accumulator, amount: Accumulator): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Accumulator, amount: Accumulator): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; } -// @beta (undocumented) -export class Fields implements Selectable { - add(other: Accumulator): Add; - add(other: any): Add; - arrayConcat(...arrays: Accumulator[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Accumulator): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Accumulator[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Accumulator[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Accumulator): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Accumulator): Divide; - divide(other: any): Divide; - dotProduct(other: Accumulator): DotProduct; - dotProduct(other: VectorValue): DotProduct; - // (undocumented) - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Accumulator): EndsWith; - eq(other: Accumulator): Eq; - eq(other: any): Eq; - eqAny(...others: Accumulator[]): EqAny; - // (undocumented) - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Accumulator): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; - // (undocumented) - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; - // (undocumented) - exprType: ExprType; - // (undocumented) - fieldList(): Field[]; - gt(other: Accumulator): Gt; - gt(other: any): Gt; - gte(other: Accumulator): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - // (undocumented) - like(pattern: Accumulator): Like; - logicalMaximum(other: Accumulator): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Accumulator): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Accumulator): Lt; - lt(other: any): Lt; - lte(other: Accumulator): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Accumulator): Mod; - mod(other: any): Mod; - multiply(other: Accumulator): Multiply; - multiply(other: any): Multiply; - neq(other: Accumulator): Neq; - neq(other: any): Neq; - notEqAny(...others: Accumulator[]): FirestoreFunction; - // (undocumented) - notEqAny(...others: any[]): FirestoreFunction; - // (undocumented) - static of(name: string, ...others: string[]): Fields; - // (undocumented) - static ofAll(): Fields; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Accumulator): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Accumulator): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Accumulator, replace: Accumulator): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Accumulator, replace: Accumulator): ReplaceFirst; - reverse(): Reverse; - // (undocumented) - selectable: true; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Accumulator): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Accumulator): StrContains; - subtract(other: Accumulator): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Accumulator, amount: Accumulator): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Accumulator, amount: Accumulator): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; -} +// Warning: (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta +// +// @public +export function field(name: string): Field; -// @beta -export interface FilterCondition { - add(other: Accumulator): Add; - add(other: any): Add; - arrayConcat(...arrays: Accumulator[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Accumulator): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Accumulator[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Accumulator[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Accumulator): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Accumulator): Divide; - divide(other: any): Divide; - dotProduct(other: Accumulator): DotProduct; - dotProduct(other: VectorValue): DotProduct; - // (undocumented) - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Accumulator): EndsWith; - eq(other: Accumulator): Eq; - eq(other: any): Eq; - eqAny(...others: Accumulator[]): EqAny; - // (undocumented) - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Accumulator): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; - // (undocumented) - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; - // (undocumented) - exprType: ExprType; - // (undocumented) - filterable: true; - gt(other: Accumulator): Gt; - gt(other: any): Gt; - gte(other: Accumulator): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - // (undocumented) - like(pattern: Accumulator): Like; - logicalMaximum(other: Accumulator): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Accumulator): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Accumulator): Lt; - lt(other: any): Lt; - lte(other: Accumulator): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Accumulator): Mod; - mod(other: any): Mod; - multiply(other: Accumulator): Multiply; - multiply(other: any): Multiply; - neq(other: Accumulator): Neq; - neq(other: any): Neq; - notEqAny(...others: Accumulator[]): FirestoreFunction; - // (undocumented) - notEqAny(...others: any[]): FirestoreFunction; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Accumulator): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Accumulator): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Accumulator, replace: Accumulator): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Accumulator, replace: Accumulator): ReplaceFirst; - reverse(): Reverse; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Accumulator): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Accumulator): StrContains; - subtract(other: Accumulator): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Accumulator, amount: Accumulator): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Accumulator, amount: Accumulator): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; -} +// Warning: (ae-forgotten-export) The symbol "FieldPath" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta +// +// @public (undocumented) +export function field(path: FieldPath): Field; // @beta (undocumented) export class FindNearest implements Stage { @@ -1077,7 +718,7 @@ export interface FindNearestOptions { // (undocumented) distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; // (undocumented) - field: Field; + field: Field | string; // (undocumented) limit?: number; // (undocumented) @@ -1085,189 +726,92 @@ export interface FindNearestOptions { } // @beta -export class FirestoreFunction { - constructor(name: string, params: Accumulator[]); - add(other: Accumulator): Add; - add(other: any): Add; - arrayConcat(...arrays: Accumulator[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Accumulator): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Accumulator[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Accumulator[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Accumulator): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Accumulator): Divide; - divide(other: any): Divide; - dotProduct(other: Accumulator): DotProduct; - dotProduct(other: VectorValue): DotProduct; - // (undocumented) - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Accumulator): EndsWith; - eq(other: Accumulator): Eq; - eq(other: any): Eq; - eqAny(...others: Accumulator[]): EqAny; - // (undocumented) - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Accumulator): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; - // (undocumented) - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; +export class FunctionExpr extends Expr { + constructor(name: string, params: Expr[]); // (undocumented) - exprType: ExprType; - gt(other: Accumulator): Gt; - gt(other: any): Gt; - gte(other: Accumulator): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - // (undocumented) - like(pattern: Accumulator): Like; - logicalMaximum(other: Accumulator): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Accumulator): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Accumulator): Lt; - lt(other: any): Lt; - lte(other: Accumulator): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Accumulator): Mod; - mod(other: any): Mod; - multiply(other: Accumulator): Multiply; - multiply(other: any): Multiply; - neq(other: Accumulator): Neq; - neq(other: any): Neq; - notEqAny(...others: Accumulator[]): FirestoreFunction; - // (undocumented) - notEqAny(...others: any[]): FirestoreFunction; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Accumulator): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Accumulator): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Accumulator, replace: Accumulator): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Accumulator, replace: Accumulator): ReplaceFirst; - reverse(): Reverse; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Accumulator): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Accumulator): StrContains; - subtract(other: Accumulator): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Accumulator, amount: Accumulator): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Accumulator, amount: Accumulator): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; -} - -// @beta -export function genericFunction(name: string, params: Accumulator[]): FirestoreFunction; + readonly exprType: ExprType; + } // @beta (undocumented) export class GenericStage implements Stage { - constructor(name: string, params: unknown[]); // (undocumented) name: string; -} - -// @beta (undocumented) -export class Gt extends FirestoreFunction implements FilterCondition { - constructor(left: Accumulator, right: Accumulator); - // (undocumented) - filterable: true; } // @beta -export function gt(left: Accumulator, right: Accumulator): Gt; +export function gt(left: Expr, right: Expr): BooleanExpr; // @beta -export function gt(left: Accumulator, right: any): Gt; +export function gt(expression: Expr, value: unknown): BooleanExpr; // @beta -export function gt(left: string, right: Accumulator): Gt; +export function gt(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function gt(left: string, right: any): Gt; +export function gt(fieldName: string, value: unknown): BooleanExpr; -// @beta (undocumented) -export class Gte extends FirestoreFunction implements FilterCondition { - constructor(left: Accumulator, right: Accumulator); - // (undocumented) - filterable: true; - } +// @beta +export function gte(left: Expr, right: Expr): BooleanExpr; // @beta -export function gte(left: Accumulator, right: Accumulator): Gte; +export function gte(expression: Expr, value: unknown): BooleanExpr; // @beta -export function gte(left: Accumulator, right: any): Gte; +export function gte(fieldName: string, value: Expr): BooleanExpr; // @beta -export function gte(left: string, right: Accumulator): Gte; +export function gte(fieldName: string, value: unknown): BooleanExpr; // @beta -export function gte(left: string, right: any): Gte; +export function ifError(tryExpr: Expr, catchExpr: Expr): FunctionExpr; -// @beta (undocumented) -export class IsNan extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator); - // (undocumented) - filterable: true; -} +// @beta +export function ifError(tryExpr: Expr, catchValue: unknown): FunctionExpr; // @beta -export function isNan(value: Accumulator): IsNan; +export function isAbsent(value: Expr): BooleanExpr; // @beta -export function isNan(value: string): IsNan; +export function isAbsent(field: string): BooleanExpr; -// @beta (undocumented) -export class Like extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator, pattern: Accumulator); - // (undocumented) - filterable: true; - } +// @beta +export function isError(value: Expr): BooleanExpr; + +// @beta +export function isNan(value: Expr): BooleanExpr; + +// @beta +export function isNan(fieldName: string): BooleanExpr; + +// @beta +export function isNotNan(value: Expr): BooleanExpr; + +// @beta +export function isNotNan(value: string): BooleanExpr; + +// @beta +export function isNotNull(value: Expr): BooleanExpr; // @beta -export function like(left: string, pattern: string): Like; +export function isNotNull(value: string): BooleanExpr; // @beta -export function like(left: string, pattern: Accumulator): Like; +export function isNull(value: Expr): BooleanExpr; // @beta -export function like(left: Accumulator, pattern: string): Like; +export function isNull(value: string): BooleanExpr; // @beta -export function like(left: Accumulator, pattern: Accumulator): Like; +export function like(fieldName: string, pattern: string): BooleanExpr; + +// @beta +export function like(fieldName: string, pattern: Expr): BooleanExpr; + +// @beta +export function like(stringExpression: Expr, pattern: string): BooleanExpr; + +// @beta +export function like(stringExpression: Expr, pattern: Expr): BooleanExpr; // @beta (undocumented) export class Limit implements Stage { @@ -1280,192 +824,140 @@ export class Limit implements Stage { name: string; } -// @beta (undocumented) -export class LogicalMaximum extends FirestoreFunction { - constructor(left: Accumulator, right: Accumulator); - } +// @beta +export function logicalMaximum(first: Expr, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function logicalMaximum(left: Accumulator, right: Accumulator): LogicalMaximum; +export function logicalMaximum(fieldName: string, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function logicalMaximum(left: Accumulator, right: any): LogicalMaximum; +export function logicalMinimum(first: Expr, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function logicalMaximum(left: string, right: Accumulator): LogicalMaximum; +export function logicalMinimum(fieldName: string, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function logicalMaximum(left: string, right: any): LogicalMaximum; +export function lt(left: Expr, right: Expr): BooleanExpr; -// @beta (undocumented) -export class LogicalMinimum extends FirestoreFunction { - constructor(left: Accumulator, right: Accumulator); - } +// @beta +export function lt(expression: Expr, value: unknown): BooleanExpr; // @beta -export function logicalMinimum(left: Accumulator, right: Accumulator): LogicalMinimum; +export function lt(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function logicalMinimum(left: Accumulator, right: any): LogicalMinimum; +export function lt(fieldName: string, value: unknown): BooleanExpr; // @beta -export function logicalMinimum(left: string, right: Accumulator): LogicalMinimum; +export function lte(left: Expr, right: Expr): BooleanExpr; // @beta -export function logicalMinimum(left: string, right: any): LogicalMinimum; +export function lte(expression: Expr, value: unknown): BooleanExpr; -// @beta (undocumented) -export class Lt extends FirestoreFunction implements FilterCondition { - constructor(left: Accumulator, right: Accumulator); - // (undocumented) - filterable: true; - } +// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "Expr" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "BooleanExpr" which is marked as @beta +// +// @public +export function lte(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function lt(left: Accumulator, right: Accumulator): Lt; +export function lte(fieldName: string, value: unknown): BooleanExpr; // @beta -export function lt(left: Accumulator, right: any): Lt; +export function manhattanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpr; // @beta -export function lt(left: string, right: Accumulator): Lt; +export function manhattanDistance(fieldName: string, vectorExpression: Expr): FunctionExpr; // @beta -export function lt(left: string, right: any): Lt; - -// @beta (undocumented) -export class Lte extends FirestoreFunction implements FilterCondition { - constructor(left: Accumulator, right: Accumulator); - // (undocumented) - filterable: true; - } +export function manhattanDistance(vectorExpression: Expr, vector: number[] | VectorValue): FunctionExpr; // @beta -export function lte(left: Accumulator, right: Accumulator): Lte; +export function manhattanDistance(vectorExpression: Expr, otherVectorExpression: Expr): FunctionExpr; // @beta -export function lte(left: Accumulator, right: any): Lte; - -// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "Accumulator" which is marked as @beta -// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "Lte" which is marked as @beta -// -// @public -export function lte(left: string, right: Accumulator): Lte; +export function map(elements: Record): FunctionExpr; // @beta -export function lte(left: string, right: any): Lte; - -// @beta (undocumented) -export class MapGet extends FirestoreFunction { - constructor(map: Accumulator, name: string); -} +export function mapGet(fieldName: string, subField: string): FunctionExpr; // @beta -export function mapGet(mapField: string, subField: string): MapGet; +export function mapGet(mapExpression: Expr, subField: string): FunctionExpr; // @beta -export function mapGet(mapExpr: Accumulator, subField: string): MapGet; - -// @beta (undocumented) -export class Maximum extends FirestoreFunction implements Accumulator { - constructor(value: Accumulator, distinct: boolean); - // (undocumented) - accumulator: true; - } +export function mapMerge(mapField: string, secondMap: Record | Expr, ...otherMaps: Array | Expr>): FunctionExpr; // @beta -export function maximum(value: Accumulator): Maximum; +export function mapMerge(firstMap: Record | Expr, secondMap: Record | Expr, ...otherMaps: Array | Expr>): FunctionExpr; // @beta -export function maximum(value: string): Maximum; - -// @beta (undocumented) -export class Minimum extends FirestoreFunction implements Accumulator { - constructor(value: Accumulator, distinct: boolean); - // (undocumented) - accumulator: true; - } +export function mapRemove(mapField: string, key: string): FunctionExpr; // @beta -export function minimum(value: Accumulator): Minimum; +export function mapRemove(mapExpr: Expr, key: string): FunctionExpr; // @beta -export function minimum(value: string): Minimum; - -// @beta (undocumented) -export class Mod extends FirestoreFunction { - constructor(left: Accumulator, right: Accumulator); - } +export function mapRemove(mapField: string, keyExpr: Expr): FunctionExpr; // @beta -export function mod(left: Accumulator, right: Accumulator): Mod; +export function mapRemove(mapExpr: Expr, keyExpr: Expr): FunctionExpr; // @beta -export function mod(left: Accumulator, right: any): Mod; +export function maximum(expression: Expr): AggregateFunction; // @beta -export function mod(left: string, right: Accumulator): Mod; +export function maximum(fieldName: string): AggregateFunction; // @beta -export function mod(left: string, right: any): Mod; +export function minimum(expression: Expr): AggregateFunction; -// @beta (undocumented) -export class Multiply extends FirestoreFunction { - constructor(left: Accumulator, right: Accumulator); - } +// @beta +export function minimum(fieldName: string): AggregateFunction; // @beta -export function multiply(left: Accumulator, right: Accumulator): Multiply; +export function mod(left: Expr, right: Expr): FunctionExpr; // @beta -export function multiply(left: Accumulator, right: any): Multiply; +export function mod(expression: Expr, value: unknown): FunctionExpr; // @beta -export function multiply(left: string, right: Accumulator): Multiply; +export function mod(fieldName: string, expression: Expr): FunctionExpr; // @beta -export function multiply(left: string, right: any): Multiply; +export function mod(fieldName: string, value: unknown): FunctionExpr; -// @beta (undocumented) -export class Neq extends FirestoreFunction implements FilterCondition { - constructor(left: Accumulator, right: Accumulator); - // (undocumented) - filterable: true; - } +// @beta +export function multiply(first: Expr, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function neq(left: Accumulator, right: Accumulator): Neq; +export function multiply(fieldName: string, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function neq(left: Accumulator, right: any): Neq; +export function neq(left: Expr, right: Expr): BooleanExpr; // @beta -export function neq(left: string, right: Accumulator): Neq; +export function neq(expression: Expr, value: unknown): BooleanExpr; // @beta -export function neq(left: string, right: any): Neq; +export function neq(fieldName: string, expression: Expr): BooleanExpr; -// @beta (undocumented) -export class Not extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator); - // (undocumented) - filterable: true; -} +// @beta +export function neq(fieldName: string, value: unknown): BooleanExpr; // @beta -export function not(filter: FilterCondition): Not; +export function not(booleanExpr: BooleanExpr): BooleanExpr; // @beta -export function notEqAny(element: Accumulator, others: Accumulator[]): FirestoreFunction; +export function notEqAny(element: Expr, values: Array): BooleanExpr; // @beta -export function notEqAny(element: Accumulator, others: any[]): FirestoreFunction; +export function notEqAny(fieldName: string, values: Array): BooleanExpr; // @beta -export function notEqAny(element: string, others: Accumulator[]): FirestoreFunction; +export function notEqAny(element: Expr, arrayExpression: Expr): BooleanExpr; // @beta -export function notEqAny(element: string, others: any[]): FirestoreFunction; +export function notEqAny(fieldName: string, arrayExpression: Expr): BooleanExpr; // @beta (undocumented) export class Offset implements Stage { @@ -1474,79 +966,75 @@ export class Offset implements Stage { name: string; } -// @beta (undocumented) -export class Or extends FirestoreFunction implements FilterCondition { - constructor(conditions: FilterCondition[]); - // (undocumented) - filterable: true; -} +// @beta +export function or(first: BooleanExpr, second: BooleanExpr, ...more: BooleanExpr[]): BooleanExpr; // @beta export class Ordering { - constructor(expr: Accumulator, direction: 'ascending' | 'descending'); + constructor(expr: Expr, direction: 'ascending' | 'descending'); // (undocumented) readonly direction: 'ascending' | 'descending'; // (undocumented) - readonly expr: Accumulator; + readonly expr: Expr; } // @public export class Pipeline { /* Excluded from this release type: _db */ // Warning: (ae-incompatible-release-tags) The symbol "addFields" is marked as @public, but its signature references "Selectable" which is marked as @beta - addFields(...fields: Selectable[]): Pipeline; + addFields(field: Selectable, ...additionalFields: Selectable[]): Pipeline; /* Excluded from this release type: _userDataWriter */ - // Warning: (ae-incompatible-release-tags) The symbol "aggregate" is marked as @public, but its signature references "AccumulatorTarget" which is marked as @beta - aggregate(...accumulators: AccumulatorTarget[]): Pipeline; + // Warning: (ae-incompatible-release-tags) The symbol "aggregate" is marked as @public, but its signature references "AggregateWithAlias" which is marked as @beta + aggregate(accumulator: AggregateWithAlias, ...additionalAccumulators: AggregateWithAlias[]): Pipeline; /* Excluded from this release type: _userDataWriter */ aggregate(options: { - accumulators: AccumulatorTarget[]; + accumulators: AggregateWithAlias[]; groups?: Array; }): Pipeline; /* Excluded from this release type: _userDataWriter */ // Warning: (ae-incompatible-release-tags) The symbol "distinct" is marked as @public, but its signature references "Selectable" which is marked as @beta - distinct(...groups: Array): Pipeline; - /* Excluded from this release type: _userDataWriter */ - // Warning: (ae-incompatible-release-tags) The symbol "execute" is marked as @public, but its signature references "PipelineResult" which is marked as @beta - execute(): Promise; + distinct(group: string | Selectable, ...additionalGroups: Array): Pipeline; /* Excluded from this release type: _userDataWriter */ // Warning: (ae-incompatible-release-tags) The symbol "findNearest" is marked as @public, but its signature references "FindNearestOptions" which is marked as @beta // // (undocumented) findNearest(options: FindNearestOptions): Pipeline; /* Excluded from this release type: _userDataWriter */ - genericStage(name: string, params: any[]): Pipeline; + genericStage(name: string, params: unknown[]): Pipeline; /* Excluded from this release type: _userDataWriter */ limit(limit: number): Pipeline; /* Excluded from this release type: _userDataWriter */ offset(offset: number): Pipeline; /* Excluded from this release type: _userDataWriter */ + // Warning: (ae-incompatible-release-tags) The symbol "removeFields" is marked as @public, but its signature references "Field" which is marked as @beta + removeFields(fieldValue: Field | string, ...additionalFields: Array): Pipeline; + /* Excluded from this release type: _userDataWriter */ + // Warning: (ae-incompatible-release-tags) The symbol "replaceWith" is marked as @public, but its signature references "Field" which is marked as @beta + replaceWith(fieldValue: Field | string): Pipeline; + /* Excluded from this release type: _userDataWriter */ + sample(documents: number): Pipeline; + /* Excluded from this release type: _userDataWriter */ + sample(options: { + percentage: number; + } | { + documents: number; + }): Pipeline; + /* Excluded from this release type: _userDataWriter */ // Warning: (ae-incompatible-release-tags) The symbol "select" is marked as @public, but its signature references "Selectable" which is marked as @beta - select(...selections: Array): Pipeline; + select(selection: Selectable | string, ...additionalSelections: Array): Pipeline; /* Excluded from this release type: _userDataWriter */ // Warning: (ae-incompatible-release-tags) The symbol "sort" is marked as @public, but its signature references "Ordering" which is marked as @beta - sort(...orderings: Ordering[]): Pipeline; + sort(ordering: Ordering, ...additionalOrderings: Ordering[]): Pipeline; /* Excluded from this release type: _userDataWriter */ - // (undocumented) - sort(options: { - orderings: Ordering[]; - }): Pipeline; + union(other: Pipeline): Pipeline; /* Excluded from this release type: _userDataWriter */ - // Warning: (ae-incompatible-release-tags) The symbol "where" is marked as @public, but its signature references "FilterCondition" which is marked as @beta - where(condition: FilterCondition): Pipeline; + // Warning: (ae-incompatible-release-tags) The symbol "unnest" is marked as @public, but its signature references "Selectable" which is marked as @beta + unnest(selectable: Selectable, indexField?: string): Pipeline; + /* Excluded from this release type: _userDataWriter */ + // Warning: (ae-incompatible-release-tags) The symbol "where" is marked as @public, but its signature references "BooleanExpr" which is marked as @beta + where(condition: BooleanExpr): Pipeline; } -// Warning: (ae-forgotten-export) The symbol "Firestore" needs to be exported by the entry point pipelines.d.ts -// Warning: (ae-incompatible-release-tags) The symbol "pipeline" is marked as @public, but its signature references "PipelineSource" which is marked as @beta -// -// @public -export function pipeline(firestore: Firestore): PipelineSource; - -// Warning: (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts -// -// @public -export function pipeline(query: Query): Pipeline; - // Warning: (ae-forgotten-export) The symbol "DocumentData" needs to be exported by the entry point pipelines.d.ts // // @beta @@ -1556,215 +1044,109 @@ export class PipelineResult { /* Excluded from this release type: __constructor */ get createTime(): Timestamp | undefined; data(): AppModelType | undefined; - get executionTime(): Timestamp; - get(fieldPath: string | FieldPath): any; + get(fieldPath: string | FieldPath | Field): any; get id(): string | undefined; get ref(): DocumentReference | undefined; get updateTime(): Timestamp | undefined; } +// @public (undocumented) +export class PipelineSnapshot { + // Warning: (ae-incompatible-release-tags) The symbol "__constructor" is marked as @public, but its signature references "PipelineResult" which is marked as @beta + constructor(pipeline: Pipeline, results: PipelineResult[], executionTime?: Timestamp); + get executionTime(): Timestamp; + get pipeline(): Pipeline; + // Warning: (ae-incompatible-release-tags) The symbol "results" is marked as @public, but its signature references "PipelineResult" which is marked as @beta + get results(): PipelineResult[]; +} + // @beta export class PipelineSource { + collection(collectionPath: string): PipelineType; + /* Excluded from this release type: _createPipeline */ + /* Excluded from this release type: __constructor */ + // Warning: (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts + collection(collectionReference: Query): PipelineType; /* Excluded from this release type: _createPipeline */ /* Excluded from this release type: __constructor */ - // (undocumented) - collection(collectionPath: string): PipelineType; - // (undocumented) collectionGroup(collectionId: string): PipelineType; - // (undocumented) + /* Excluded from this release type: _createPipeline */ + /* Excluded from this release type: __constructor */ + createFrom(query: Query): Pipeline; + /* Excluded from this release type: _createPipeline */ + /* Excluded from this release type: __constructor */ database(): PipelineType; - // (undocumented) - documents(docs: DocumentReference[]): PipelineType; + /* Excluded from this release type: _createPipeline */ + /* Excluded from this release type: __constructor */ + documents(docs: Array): PipelineType; } -// @beta (undocumented) -export class RegexContains extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator, pattern: Accumulator); - // (undocumented) - filterable: true; - } - // @beta -export function regexContains(left: string, pattern: string): RegexContains; +export function rand(): FunctionExpr; // @beta -export function regexContains(left: string, pattern: Accumulator): RegexContains; +export function regexContains(fieldName: string, pattern: string): BooleanExpr; // @beta -export function regexContains(left: Accumulator, pattern: string): RegexContains; +export function regexContains(fieldName: string, pattern: Expr): BooleanExpr; // @beta -export function regexContains(left: Accumulator, pattern: Accumulator): RegexContains; - -// @beta (undocumented) -export class RegexMatch extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator, pattern: Accumulator); - // (undocumented) - filterable: true; - } +export function regexContains(stringExpression: Expr, pattern: string): BooleanExpr; // @beta -export function regexMatch(left: string, pattern: string): RegexMatch; +export function regexContains(stringExpression: Expr, pattern: Expr): BooleanExpr; // @beta -export function regexMatch(left: string, pattern: Accumulator): RegexMatch; +export function regexMatch(fieldName: string, pattern: string): BooleanExpr; // @beta -export function regexMatch(left: Accumulator, pattern: string): RegexMatch; +export function regexMatch(fieldName: string, pattern: Expr): BooleanExpr; // @beta -export function regexMatch(left: Accumulator, pattern: Accumulator): RegexMatch; - -// @beta (undocumented) -export class ReplaceAll extends FirestoreFunction { - constructor(value: Accumulator, find: Accumulator, replace: Accumulator); - } +export function regexMatch(stringExpression: Expr, pattern: string): BooleanExpr; // @beta -export function replaceAll(value: Accumulator, find: string, replace: string): ReplaceAll; +export function regexMatch(stringExpression: Expr, pattern: Expr): BooleanExpr; // @beta -export function replaceAll(value: Accumulator, find: Accumulator, replace: Accumulator): ReplaceAll; +export function replaceAll(value: Expr, find: string, replace: string): FunctionExpr; // @beta -export function replaceAll(field: string, find: string, replace: string): ReplaceAll; - -// @beta (undocumented) -export class ReplaceFirst extends FirestoreFunction { - constructor(value: Accumulator, find: Accumulator, replace: Accumulator); - } +export function replaceAll(value: Expr, find: Expr, replace: Expr): FunctionExpr; // @beta -export function replaceFirst(value: Accumulator, find: string, replace: string): ReplaceFirst; +export function replaceAll(fieldName: string, find: string, replace: string): FunctionExpr; // @beta -export function replaceFirst(value: Accumulator, find: Accumulator, replace: Accumulator): ReplaceFirst; +export function replaceFirst(value: Expr, find: string, replace: string): FunctionExpr; // @beta -export function replaceFirst(field: string, find: string, replace: string): ReplaceFirst; +export function replaceFirst(value: Expr, find: Expr, replace: Expr): FunctionExpr; -// @beta (undocumented) -export class Reverse extends FirestoreFunction { - constructor(value: Accumulator); - } +// @beta +export function replaceFirst(fieldName: string, find: string, replace: string): FunctionExpr; // @beta -export function reverse(expr: Accumulator): Reverse; +export function reverse(stringExpression: Expr): FunctionExpr; // @beta -export function reverse(field: string): Reverse; +export function reverse(field: string): FunctionExpr; // @beta (undocumented) export class Select implements Stage { - constructor(projections: Map); + constructor(projections: Map); // (undocumented) name: string; } // @beta export interface Selectable { - add(other: Accumulator): Add; - add(other: any): Add; - arrayConcat(...arrays: Accumulator[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Accumulator): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Accumulator[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Accumulator[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Accumulator): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Accumulator): Divide; - divide(other: any): Divide; - dotProduct(other: Accumulator): DotProduct; - dotProduct(other: VectorValue): DotProduct; - // (undocumented) - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Accumulator): EndsWith; - eq(other: Accumulator): Eq; - eq(other: any): Eq; - eqAny(...others: Accumulator[]): EqAny; // (undocumented) - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Accumulator): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; - // (undocumented) - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; - // (undocumented) - exprType: ExprType; - gt(other: Accumulator): Gt; - gt(other: any): Gt; - gte(other: Accumulator): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - // (undocumented) - like(pattern: Accumulator): Like; - logicalMaximum(other: Accumulator): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Accumulator): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Accumulator): Lt; - lt(other: any): Lt; - lte(other: Accumulator): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Accumulator): Mod; - mod(other: any): Mod; - multiply(other: Accumulator): Multiply; - multiply(other: any): Multiply; - neq(other: Accumulator): Neq; - neq(other: any): Neq; - notEqAny(...others: Accumulator[]): FirestoreFunction; + readonly alias: string; // (undocumented) - notEqAny(...others: any[]): FirestoreFunction; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Accumulator): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Accumulator): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Accumulator, replace: Accumulator): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Accumulator, replace: Accumulator): ReplaceFirst; - reverse(): Reverse; + readonly expr: Expr; // (undocumented) selectable: true; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Accumulator): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Accumulator): StrContains; - subtract(other: Accumulator): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Accumulator, amount: Accumulator): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Accumulator, amount: Accumulator): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; } // @beta (undocumented) @@ -1780,240 +1162,153 @@ export interface Stage { name: string; } -// @beta (undocumented) -export class StartsWith extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator, prefix: Accumulator); - // (undocumented) - filterable: true; - } - // @beta -export function startsWith(expr: string, prefix: string): StartsWith; +export function startsWith(fieldName: string, prefix: string): BooleanExpr; // @beta -export function startsWith(expr: string, prefix: Accumulator): StartsWith; +export function startsWith(fieldName: string, prefix: Expr): BooleanExpr; // @beta -export function startsWith(expr: Accumulator, prefix: string): StartsWith; +export function startsWith(stringExpression: Expr, prefix: string): BooleanExpr; // @beta -export function startsWith(expr: Accumulator, prefix: Accumulator): StartsWith; - -// @beta (undocumented) -export class StrConcat extends FirestoreFunction { - constructor(first: Accumulator, rest: Accumulator[]); - } +export function startsWith(stringExpression: Expr, prefix: Expr): BooleanExpr; // @beta -export function strConcat(first: string, ...elements: Array): StrConcat; +export function strConcat(fieldName: string, secondString: Expr | string, ...otherStrings: Array): FunctionExpr; // @beta -export function strConcat(first: Accumulator, ...elements: Array): StrConcat; - -// @beta (undocumented) -export class StrContains extends FirestoreFunction implements FilterCondition { - constructor(expr: Accumulator, substring: Accumulator); - // (undocumented) - filterable: true; - } +export function strConcat(firstString: Expr, secondString: Expr | string, ...otherStrings: Array): FunctionExpr; // @beta -export function strContains(left: string, substring: string): StrContains; +export function strContains(fieldName: string, substring: string): BooleanExpr; // @beta -export function strContains(left: string, substring: Accumulator): StrContains; +export function strContains(fieldName: string, substring: Expr): BooleanExpr; // @beta -export function strContains(left: Accumulator, substring: string): StrContains; +export function strContains(stringExpression: Expr, substring: string): BooleanExpr; // @beta -export function strContains(left: Accumulator, substring: Accumulator): StrContains; - -// @beta (undocumented) -export class Subtract extends FirestoreFunction { - constructor(left: Accumulator, right: Accumulator); - } +export function strContains(stringExpression: Expr, substring: Expr): BooleanExpr; // @beta -export function subtract(left: Accumulator, right: Accumulator): Subtract; +export function substr(field: string, position: number, length?: number): FunctionExpr; // @beta -export function subtract(left: Accumulator, right: any): Subtract; +export function substr(input: Expr, position: number, length?: number): FunctionExpr; // @beta -export function subtract(left: string, right: Accumulator): Subtract; +export function substr(field: string, position: Expr, length?: Expr): FunctionExpr; // @beta -export function subtract(left: string, right: any): Subtract; - -// @beta (undocumented) -export class Sum extends FirestoreFunction implements Accumulator { - constructor(value: Accumulator, distinct: boolean); - // (undocumented) - accumulator: true; - } - -// @beta (undocumented) -export class TimestampAdd extends FirestoreFunction { - constructor(timestamp: Accumulator, unit: Accumulator, amount: Accumulator); - } +export function substr(input: Expr, position: Expr, length?: Expr): FunctionExpr; // @beta -export function timestampAdd(timestamp: Accumulator, unit: Accumulator, amount: Accumulator): TimestampAdd; +export function subtract(left: Expr, right: Expr): FunctionExpr; // @beta -export function timestampAdd(timestamp: Accumulator, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; +export function subtract(expression: Expr, value: unknown): FunctionExpr; // @beta -export function timestampAdd(field: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - -// @beta (undocumented) -export class TimestampSub extends FirestoreFunction { - constructor(timestamp: Accumulator, unit: Accumulator, amount: Accumulator); - } +export function subtract(fieldName: string, expression: Expr): FunctionExpr; // @beta -export function timestampSub(timestamp: Accumulator, unit: Accumulator, amount: Accumulator): TimestampSub; +export function subtract(fieldName: string, value: unknown): FunctionExpr; // @beta -export function timestampSub(timestamp: Accumulator, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; +export function timestampAdd(timestamp: Expr, unit: Expr, amount: Expr): FunctionExpr; // @beta -export function timestampSub(field: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - -// @beta (undocumented) -export class TimestampToUnixMicros extends FirestoreFunction { - constructor(input: Accumulator); - } +export function timestampAdd(timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; // @beta -export function timestampToUnixMicros(expr: Accumulator): TimestampToUnixMicros; +export function timestampAdd(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; // @beta -export function timestampToUnixMicros(field: string): TimestampToUnixMicros; - -// @beta (undocumented) -export class TimestampToUnixMillis extends FirestoreFunction { - constructor(input: Accumulator); - } +export function timestampSub(timestamp: Expr, unit: Expr, amount: Expr): FunctionExpr; // @beta -export function timestampToUnixMillis(expr: Accumulator): TimestampToUnixMillis; +export function timestampSub(timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; // @beta -export function timestampToUnixMillis(field: string): TimestampToUnixMillis; - -// @beta (undocumented) -export class TimestampToUnixSeconds extends FirestoreFunction { - constructor(input: Accumulator); - } +export function timestampSub(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; // @beta -export function timestampToUnixSeconds(expr: Accumulator): TimestampToUnixSeconds; +export function timestampToUnixMicros(expr: Expr): FunctionExpr; // @beta -export function timestampToUnixSeconds(field: string): TimestampToUnixSeconds; - -// @beta (undocumented) -export class ToLower extends FirestoreFunction { - constructor(expr: Accumulator); - } +export function timestampToUnixMicros(fieldName: string): FunctionExpr; // @beta -export function toLower(expr: string): ToLower; +export function timestampToUnixMillis(expr: Expr): FunctionExpr; // @beta -export function toLower(expr: Accumulator): ToLower; - -// @beta (undocumented) -export class ToUpper extends FirestoreFunction { - constructor(expr: Accumulator); - } +export function timestampToUnixMillis(fieldName: string): FunctionExpr; // @beta -export function toUpper(expr: string): ToUpper; +export function timestampToUnixSeconds(expr: Expr): FunctionExpr; // @beta -export function toUpper(expr: Accumulator): ToUpper; - -// @beta (undocumented) -export class Trim extends FirestoreFunction { - constructor(expr: Accumulator); - } +export function timestampToUnixSeconds(fieldName: string): FunctionExpr; // @beta -export function trim(expr: string): Trim; +export function toLower(fieldName: string): FunctionExpr; // @beta -export function trim(expr: Accumulator): Trim; +export function toLower(stringExpression: Expr): FunctionExpr; -// @beta (undocumented) -export class UnixMicrosToTimestamp extends FirestoreFunction { - constructor(input: Accumulator); - } +// @beta +export function toUpper(fieldName: string): FunctionExpr; // @beta -export function unixMicrosToTimestamp(expr: Accumulator): UnixMicrosToTimestamp; +export function toUpper(stringExpression: Expr): FunctionExpr; // @beta -export function unixMicrosToTimestamp(field: string): UnixMicrosToTimestamp; +export function trim(fieldName: string): FunctionExpr; -// @beta (undocumented) -export class UnixMillisToTimestamp extends FirestoreFunction { - constructor(input: Accumulator); - } +// @beta +export function trim(stringExpression: Expr): FunctionExpr; // @beta -export function unixMillisToTimestamp(expr: Accumulator): UnixMillisToTimestamp; +export function unixMicrosToTimestamp(expr: Expr): FunctionExpr; // @beta -export function unixMillisToTimestamp(field: string): UnixMillisToTimestamp; +export function unixMicrosToTimestamp(fieldName: string): FunctionExpr; -// @beta (undocumented) -export class UnixSecondsToTimestamp extends FirestoreFunction { - constructor(input: Accumulator); - } +// @beta +export function unixMillisToTimestamp(expr: Expr): FunctionExpr; // @beta -export function unixSecondsToTimestamp(expr: Accumulator): UnixSecondsToTimestamp; +export function unixMillisToTimestamp(fieldName: string): FunctionExpr; // @beta -export function unixSecondsToTimestamp(field: string): UnixSecondsToTimestamp; +export function unixSecondsToTimestamp(expr: Expr): FunctionExpr; -// @beta (undocumented) -export class VectorLength extends FirestoreFunction { - constructor(value: Accumulator); - } +// @beta +export function unixSecondsToTimestamp(fieldName: string): FunctionExpr; // @beta -export function vectorLength(expr: Accumulator): VectorLength; +export function vectorLength(vectorExpression: Expr): FunctionExpr; // @beta -export function vectorLength(field: string): VectorLength; +export function vectorLength(fieldName: string): FunctionExpr; // @beta (undocumented) export class Where implements Stage { - constructor(condition: FilterCondition); + constructor(condition: BooleanExpr); // (undocumented) name: string; } -// @beta (undocumented) -export class Xor extends FirestoreFunction implements FilterCondition { - constructor(conditions: FilterCondition[]); - // (undocumented) - filterable: true; -} - // @beta -export function xor(left: FilterCondition, ...right: FilterCondition[]): Xor; +export function xor(first: BooleanExpr, second: BooleanExpr, ...additionalConditions: BooleanExpr[]): BooleanExpr; // Warnings were encountered during analysis: // -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:10253:9 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AccumulatorTarget" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:10254:9 - (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:10283:9 - (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:4613:9 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AggregateWithAlias" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/lite/pipelines.d.ts:4614:9 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" 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 375f5dfd976..baedbf6b08a 100644 --- a/common/api-review/firestore-pipelines.api.md +++ b/common/api-review/firestore-pipelines.api.md @@ -7,30 +7,10 @@ import { FirebaseApp } from '@firebase/app'; // @beta -export interface Accumulator extends Expr { - // (undocumented) - accumulator: true; -} - -// @beta -export type AccumulatorTarget = ExprWithAlias; - -// @beta (undocumented) -export class Add extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } +export function add(first: Expr, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function add(left: Expr, right: Expr): Add; - -// @beta -export function add(left: Expr, right: any): Add; - -// @beta -export function add(left: string, right: Expr): Add; - -// @beta -export function add(left: string, right: any): Add; +export function add(fieldName: string, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta (undocumented) export class AddFields implements Stage { @@ -41,150 +21,190 @@ export class AddFields implements Stage { // @beta (undocumented) export class Aggregate implements Stage { - constructor(accumulators: Map, groups: Map); + constructor(accumulators: Map, groups: Map); // (undocumented) name: string; } -// @beta (undocumented) -export class And extends FirestoreFunction implements FilterCondition { - constructor(conditions: FilterCondition[]); +// @beta +export class AggregateFunction { + constructor(name: string, params: Expr[]); + as(name: string): AggregateWithAlias; // (undocumented) - filterable: true; + exprType: ExprType; + } + +// @beta +export class AggregateWithAlias { + constructor(aggregate: AggregateFunction, alias: string); + // (undocumented) + readonly aggregate: AggregateFunction; + // (undocumented) + readonly alias: string; } // @beta -export function andFunction(left: FilterCondition, ...right: FilterCondition[]): And; +export function and(first: BooleanExpr, second: BooleanExpr, ...more: BooleanExpr[]): BooleanExpr; -// @beta (undocumented) -export class ArrayConcat extends FirestoreFunction { - constructor(array: Expr, elements: Expr[]); - } +// @beta +export function array(elements: unknown[]): FunctionExpr; // @beta -export function arrayConcat(array: Expr, elements: Expr[]): ArrayConcat; +export function arrayConcat(firstArray: Expr, secondArray: Expr | unknown[], ...otherArrays: Array): FunctionExpr; // @beta -export function arrayConcat(array: Expr, elements: any[]): ArrayConcat; +export function arrayConcat(firstArrayField: string, secondArray: Expr | unknown[], ...otherArrays: Array): FunctionExpr; // @beta -export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; +export function arrayContains(array: Expr, element: Expr): FunctionExpr; // @beta -export function arrayConcat(array: string, elements: any[]): ArrayConcat; +export function arrayContains(array: Expr, element: unknown): FunctionExpr; -// @beta (undocumented) -export class ArrayContains extends FirestoreFunction implements FilterCondition { - constructor(array: Expr, element: Expr); - // (undocumented) - filterable: true; -} +// @beta +export function arrayContains(fieldName: string, element: Expr): FunctionExpr; // @beta -export function arrayContains(array: Expr, element: Expr): ArrayContains; +export function arrayContains(fieldName: string, element: unknown): BooleanExpr; // @beta -export function arrayContains(array: Expr, element: any): ArrayContains; +export function arrayContainsAll(array: Expr, values: Array): BooleanExpr; // @beta -export function arrayContains(array: string, element: Expr): ArrayContains; +export function arrayContainsAll(fieldName: string, values: Array): BooleanExpr; // @beta -export function arrayContains(array: string, element: any): ArrayContains; +export function arrayContainsAll(array: Expr, arrayExpression: Expr): BooleanExpr; -// @beta (undocumented) -export class ArrayContainsAll extends FirestoreFunction implements FilterCondition { - constructor(array: Expr, values: Expr[]); - // (undocumented) - filterable: true; - } +// @beta +export function arrayContainsAll(fieldName: string, arrayExpression: Expr): BooleanExpr; // @beta -export function arrayContainsAll(array: Expr, values: Expr[]): ArrayContainsAll; +export function arrayContainsAny(array: Expr, values: Array): BooleanExpr; // @beta -export function arrayContainsAll(array: Expr, values: any[]): ArrayContainsAll; +export function arrayContainsAny(fieldName: string, values: Array): BooleanExpr; // @beta -export function arrayContainsAll(array: string, values: Expr[]): ArrayContainsAll; +export function arrayContainsAny(array: Expr, values: Expr): BooleanExpr; // @beta -export function arrayContainsAll(array: string, values: any[]): ArrayContainsAll; +export function arrayContainsAny(fieldName: string, values: Expr): BooleanExpr; -// @beta (undocumented) -export class ArrayContainsAny extends FirestoreFunction implements FilterCondition { - constructor(array: Expr, values: Expr[]); - // (undocumented) - filterable: true; - } +// @beta +export function arrayLength(array: Expr): FunctionExpr; // @beta -export function arrayContainsAny(array: Expr, values: Expr[]): ArrayContainsAny; +export function arrayOffset(arrayField: string, offset: number): FunctionExpr; // @beta -export function arrayContainsAny(array: Expr, values: any[]): ArrayContainsAny; +export function arrayOffset(arrayField: string, offsetExpr: Expr): FunctionExpr; // @beta -export function arrayContainsAny(array: string, values: Expr[]): ArrayContainsAny; +export function arrayOffset(arrayExpression: Expr, offset: number): FunctionExpr; // @beta -export function arrayContainsAny(array: string, values: any[]): ArrayContainsAny; +export function arrayOffset(arrayExpression: Expr, offsetExpr: Expr): FunctionExpr; -// @beta (undocumented) -export class ArrayElement extends FirestoreFunction { - constructor(); -} +// @beta +export function ascending(expr: Expr): Ordering; -// @beta (undocumented) -export class ArrayLength extends FirestoreFunction { - constructor(array: Expr); - } +// @beta +export function ascending(fieldName: string): Ordering; // @beta -export function arrayLength(array: Expr): ArrayLength; +export function avg(expression: Expr): AggregateFunction; -// @beta (undocumented) -export class ArrayReverse extends FirestoreFunction { - constructor(array: Expr); - } +// @beta +export function avg(fieldName: string): AggregateFunction; +// Warning: (ae-forgotten-export) The symbol "Bytes" needs to be exported by the entry point pipelines.d.ts +// // @beta -export function ascending(expr: Expr): Ordering; +export function bitAnd(field: string, otherBits: number | Bytes): FunctionExpr; -// @beta (undocumented) -export class Avg extends FirestoreFunction implements Accumulator { - constructor(value: Expr, distinct: boolean); - // (undocumented) - accumulator: true; - } +// @beta +export function bitAnd(field: string, bitsExpression: Expr): FunctionExpr; // @beta -export function avgFunction(value: Expr): Avg; +export function bitAnd(bitsExpression: Expr, otherBits: number | Bytes): FunctionExpr; // @beta -export function avgFunction(value: string): Avg; +export function bitAnd(bitsExpression: Expr, otherBitsExpression: Expr): FunctionExpr; -// @beta (undocumented) -export class ByteLength extends FirestoreFunction { - constructor(value: Expr); - } +// @beta +export function bitLeftShift(field: string, y: number): FunctionExpr; // @beta -export function byteLength(expr: Expr): ByteLength; +export function bitLeftShift(field: string, numberExpr: Expr): FunctionExpr; // @beta -export function byteLength(field: string): ByteLength; +export function bitLeftShift(xValue: Expr, y: number): FunctionExpr; -// @beta (undocumented) -export class CharLength extends FirestoreFunction { - constructor(value: Expr); - } +// @beta +export function bitLeftShift(xValue: Expr, numberExpr: Expr): FunctionExpr; + +// @beta +export function bitNot(field: string): FunctionExpr; + +// @beta +export function bitNot(bitsValueExpression: Expr): FunctionExpr; // @beta -export function charLength(field: string): CharLength; +export function bitOr(field: string, otherBits: number | Bytes): FunctionExpr; // @beta -export function charLength(expr: Expr): CharLength; +export function bitOr(field: string, bitsExpression: Expr): FunctionExpr; + +// @beta +export function bitOr(bitsExpression: Expr, otherBits: number | Bytes): FunctionExpr; + +// @beta +export function bitOr(bitsExpression: Expr, otherBitsExpression: Expr): FunctionExpr; + +// @beta +export function bitRightShift(field: string, y: number): FunctionExpr; + +// @beta +export function bitRightShift(field: string, numberExpr: Expr): FunctionExpr; + +// @beta +export function bitRightShift(xValue: Expr, y: number): FunctionExpr; + +// @beta +export function bitRightShift(xValue: Expr, numberExpr: Expr): FunctionExpr; + +// @beta +export function bitXor(field: string, otherBits: number | Bytes): FunctionExpr; + +// @beta +export function bitXor(field: string, bitsExpression: Expr): FunctionExpr; + +// @beta +export function bitXor(bitsExpression: Expr, otherBits: number | Bytes): FunctionExpr; + +// @beta +export function bitXor(bitsExpression: Expr, otherBitsExpression: Expr): FunctionExpr; + +// @beta +export class BooleanExpr extends FunctionExpr { + countIf(): AggregateFunction; + // (undocumented) + filterable: true; + not(): BooleanExpr; +} + +// @beta +export function byteLength(expr: Expr): FunctionExpr; + +// @beta +export function byteLength(fieldName: string): FunctionExpr; + +// @beta +export function charLength(fieldName: string): FunctionExpr; + +// @beta +export function charLength(stringExpression: Expr): FunctionExpr; // @beta (undocumented) export class CollectionGroupSource implements Stage { @@ -200,80 +220,97 @@ export class CollectionSource implements Stage { name: string; } -// @beta (undocumented) -export class Cond extends FirestoreFunction { - constructor(condition: FilterCondition, thenExpr: Expr, elseExpr: Expr); - // (undocumented) - filterable: true; - } - // @beta -export function cond(condition: FilterCondition, thenExpr: Expr, elseExpr: Expr): Cond; +export function cond(condition: BooleanExpr, thenExpr: Expr, elseExpr: Expr): FunctionExpr; // @beta export class Constant extends Expr { // (undocumented) - exprType: ExprType; - static of(value: number): Constant; - static of(value: string): Constant; - static of(value: boolean): Constant; - static of(value: null): Constant; - static of(value: undefined): Constant; - // Warning: (ae-forgotten-export) The symbol "GeoPoint" needs to be exported by the entry point pipelines.d.ts - static of(value: GeoPoint): Constant; - // Warning: (ae-forgotten-export) The symbol "Timestamp" needs to be exported by the entry point pipelines.d.ts - static of(value: Timestamp): Constant; - static of(value: Date): Constant; - static of(value: Uint8Array): Constant; - // Warning: (ae-forgotten-export) The symbol "DocumentReference" needs to be exported by the entry point pipelines.d.ts - static of(value: DocumentReference): Constant; - static of(value: any[]): Constant; - static of(value: Map): Constant; - // Warning: (ae-forgotten-export) The symbol "VectorValue" needs to be exported by the entry point pipelines.d.ts - static of(value: VectorValue): Constant; - static vector(value: number[] | VectorValue): Constant; -} - -// @beta (undocumented) -export class CosineDistance extends FirestoreFunction { - constructor(vector1: Expr, vector2: Expr); + readonly exprType: ExprType; } -// @beta -export function cosineDistance(expr: string, other: number[]): CosineDistance; +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: number): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: string): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: boolean): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: null): Constant; + +// Warning: (ae-forgotten-export) The symbol "GeoPoint" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: GeoPoint): Constant; + +// Warning: (ae-forgotten-export) The symbol "Timestamp" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: Timestamp): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: Date): Constant; + +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: Bytes): Constant; + +// Warning: (ae-forgotten-export) The symbol "DocumentReference" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: DocumentReference): Constant; + +// Warning: (ae-forgotten-export) The symbol "VectorValue" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "constant" is marked as @public, but its signature references "Constant" which is marked as @beta +// +// @public +export function constant(value: VectorValue): Constant; // @beta -export function cosineDistance(expr: string, other: VectorValue): CosineDistance; +export function cosineDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpr; // @beta -export function cosineDistance(expr: string, other: Expr): CosineDistance; +export function cosineDistance(fieldName: string, vectorExpression: Expr): FunctionExpr; // @beta -export function cosineDistance(expr: Expr, other: number[]): CosineDistance; +export function cosineDistance(vectorExpression: Expr, vector: number[] | Expr): FunctionExpr; // @beta -export function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; +export function cosineDistance(vectorExpression: Expr, otherVectorExpression: Expr): FunctionExpr; // @beta -export function cosineDistance(expr: Expr, other: Expr): CosineDistance; +export function count(expression: Expr): AggregateFunction; -// @beta (undocumented) -export class Count extends FirestoreFunction implements Accumulator { - constructor(value: Expr | undefined, distinct: boolean); - // (undocumented) - accumulator: true; - } +// Warning: (ae-incompatible-release-tags) The symbol "count" is marked as @public, but its signature references "AggregateFunction" which is marked as @beta +// +// @public +export function count(fieldName: string): AggregateFunction; // @beta -export function countAll(): Count; +export function countAll(): AggregateFunction; // @beta -export function countFunction(value: Expr): Count; +export function countIf(booleanExpr: BooleanExpr): AggregateFunction; -// Warning: (ae-incompatible-release-tags) The symbol "countFunction" is marked as @public, but its signature references "Count" which is marked as @beta -// -// @public -export function countFunction(value: string): Count; +// @beta +export function currentContext(): FunctionExpr; // @beta (undocumented) export class DatabaseSource implements Stage { @@ -284,6 +321,9 @@ export class DatabaseSource implements Stage { // @beta export function descending(expr: Expr): Ordering; +// @beta +export function descending(fieldName: string): Ordering; + // @beta (undocumented) export class Distinct implements Stage { constructor(groups: Map); @@ -291,22 +331,23 @@ export class Distinct implements Stage { name: string; } -// @beta (undocumented) -export class Divide extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } +// @beta +export function divide(left: Expr, right: Expr): FunctionExpr; // @beta -export function divide(left: Expr, right: Expr): Divide; +export function divide(expression: Expr, value: unknown): FunctionExpr; // @beta -export function divide(left: Expr, right: any): Divide; +export function divide(fieldName: string, expressions: Expr): FunctionExpr; // @beta -export function divide(left: string, right: Expr): Divide; +export function divide(fieldName: string, value: unknown): FunctionExpr; // @beta -export function divide(left: string, right: any): Divide; +export function documentId(documentPath: string | DocumentReference): FunctionExpr; + +// @beta +export function documentId(documentPathExpr: Expr): FunctionExpr; // @beta (undocumented) export class DocumentsSource implements Stage { @@ -314,239 +355,320 @@ export class DocumentsSource implements Stage { // (undocumented) name: string; // (undocumented) - static of(refs: DocumentReference[]): DocumentsSource; + static of(refs: Array): DocumentsSource; } -// @beta (undocumented) -export class DotProduct extends FirestoreFunction { - constructor(vector1: Expr, vector2: Expr); - } - -// @beta -export function dotProduct(expr: string, other: number[]): DotProduct; - // @beta -export function dotProduct(expr: string, other: VectorValue): DotProduct; +export function dotProduct(fieldName: string, vector: number[] | VectorValue): FunctionExpr; // @beta -export function dotProduct(expr: string, other: Expr): DotProduct; +export function dotProduct(fieldName: string, vectorExpression: Expr): FunctionExpr; // @beta -export function dotProduct(expr: Expr, other: number[]): DotProduct; +export function dotProduct(vectorExpression: Expr, vector: number[] | VectorValue): FunctionExpr; // @beta -export function dotProduct(expr: Expr, other: VectorValue): DotProduct; +export function dotProduct(vectorExpression: Expr, otherVectorExpression: Expr): FunctionExpr; // @beta -export function dotProduct(expr: Expr, other: Expr): DotProduct; - -// @beta (undocumented) -export class EndsWith extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, suffix: Expr); - // (undocumented) - filterable: true; - } +export function endsWith(fieldName: string, suffix: string): BooleanExpr; // @beta -export function endsWith(expr: string, suffix: string): EndsWith; +export function endsWith(fieldName: string, suffix: Expr): BooleanExpr; // @beta -export function endsWith(expr: string, suffix: Expr): EndsWith; +export function endsWith(stringExpression: Expr, suffix: string): BooleanExpr; // @beta -export function endsWith(expr: Expr, suffix: string): EndsWith; +export function endsWith(stringExpression: Expr, suffix: Expr): BooleanExpr; // @beta -export function endsWith(expr: Expr, suffix: Expr): EndsWith; - -// @beta (undocumented) -export class Eq extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } +export function eq(left: Expr, right: Expr): BooleanExpr; // @beta -export function eq(left: Expr, right: Expr): Eq; +export function eq(expression: Expr, value: unknown): BooleanExpr; // @beta -export function eq(left: Expr, right: any): Eq; +export function eq(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function eq(left: string, right: Expr): Eq; +export function eq(fieldName: string, value: unknown): BooleanExpr; // @beta -export function eq(left: string, right: any): Eq; - -// @beta (undocumented) -export class EqAny extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, others: Expr[]); - // (undocumented) - filterable: true; - } +export function eqAny(expression: Expr, values: Array): BooleanExpr; // @beta -export function eqAny(element: Expr, others: Expr[]): EqAny; +export function eqAny(expression: Expr, arrayExpression: Expr): BooleanExpr; // @beta -export function eqAny(element: Expr, others: any[]): EqAny; +export function eqAny(fieldName: string, values: Array): BooleanExpr; // @beta -export function eqAny(element: string, others: Expr[]): EqAny; +export function eqAny(fieldName: string, arrayExpression: Expr): BooleanExpr; // @beta -export function eqAny(element: string, others: any[]): EqAny; - -// @beta (undocumented) -export class EuclideanDistance extends FirestoreFunction { - constructor(vector1: Expr, vector2: Expr); - } +export function euclideanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpr; // @beta -export function euclideanDistance(expr: string, other: number[]): EuclideanDistance; +export function euclideanDistance(fieldName: string, vectorExpression: Expr): FunctionExpr; // @beta -export function euclideanDistance(expr: string, other: VectorValue): EuclideanDistance; +export function euclideanDistance(vectorExpression: Expr, vector: number[] | VectorValue): FunctionExpr; // @beta -export function euclideanDistance(expr: string, other: Expr): EuclideanDistance; - -// @beta -export function euclideanDistance(expr: Expr, other: number[]): EuclideanDistance; - -// @beta -export function euclideanDistance(expr: Expr, other: VectorValue): EuclideanDistance; - -// @beta -export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; - -// Warning: (ae-incompatible-release-tags) The symbol "execute" is marked as @public, but its signature references "PipelineResult" which is marked as @beta -// -// @public (undocumented) -export function execute(pipeline: Pipeline): Promise; +export function euclideanDistance(vectorExpression: Expr, otherVectorExpression: Expr): FunctionExpr; -// @beta (undocumented) -export class Exists extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr); - // (undocumented) - filterable: true; -} +// @public +export function execute(pipeline: Pipeline): Promise; // @beta -export function exists(value: Expr): Exists; +export function exists(value: Expr): BooleanExpr; // @beta -export function exists(field: string): Exists; +export function exists(fieldName: string): BooleanExpr; // @beta export abstract class Expr { - add(other: Expr): Add; - add(other: any): Add; - arrayConcat(...arrays: Expr[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Expr): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Expr[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Expr[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; + add(second: Expr | unknown, ...others: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + arrayConcat(secondArray: Expr | unknown[], ...otherArrays: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + arrayContains(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContains(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContainsAll(values: Array): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContainsAll(arrayExpression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContainsAny(values: Array): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayContainsAny(arrayExpression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + arrayLength(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + arrayOffset(offset: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + arrayOffset(offsetExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + as(name: string): ExprWithAlias; + /* Excluded from this release type: _readUserData */ ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Expr): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; + /* Excluded from this release type: _readUserData */ + avg(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + bitAnd(otherBits: number | Bytes): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitAnd(bitsExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitLeftShift(y: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitLeftShift(numberExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitNot(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitOr(otherBits: number | Bytes): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitOr(bitsExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitRightShift(y: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitRightShift(numberExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitXor(otherBits: number | Bytes): FunctionExpr; + /* Excluded from this release type: _readUserData */ + bitXor(bitsExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + byteLength(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + charLength(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + cosineDistance(vectorExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + cosineDistance(vector: VectorValue | number[]): FunctionExpr; + /* Excluded from this release type: _readUserData */ + count(): AggregateFunction; + /* Excluded from this release type: _readUserData */ descending(): Ordering; - divide(other: Expr): Divide; - divide(other: any): Divide; - dotProduct(other: Expr): DotProduct; - dotProduct(other: VectorValue): DotProduct; - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Expr): EndsWith; - eq(other: Expr): Eq; - eq(other: any): Eq; - eqAny(...others: Expr[]): EqAny; - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Expr): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; - // (undocumented) - abstract exprType: ExprType; - gt(other: Expr): Gt; - gt(other: any): Gt; - gte(other: Expr): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - like(pattern: Expr): Like; - logicalMaximum(other: Expr): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Expr): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Expr): Lt; - lt(other: any): Lt; - lte(other: Expr): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Expr): Mod; - mod(other: any): Mod; - multiply(other: Expr): Multiply; - multiply(other: any): Multiply; - neq(other: Expr): Neq; - neq(other: any): Neq; - notEqAny(...others: Expr[]): NotEqAny; - notEqAny(...others: any[]): NotEqAny; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Expr): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Expr): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Expr, replace: Expr): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Expr, replace: Expr): ReplaceFirst; - reverse(): Reverse; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Expr): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Expr): StrContains; - subtract(other: Expr): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Expr, amount: Expr): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Expr, amount: Expr): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; + /* Excluded from this release type: _readUserData */ + divide(other: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + divide(other: unknown): FunctionExpr; + /* Excluded from this release type: _readUserData */ + documentId(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + dotProduct(vectorExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + dotProduct(vector: VectorValue | number[]): FunctionExpr; + /* Excluded from this release type: _readUserData */ + endsWith(suffix: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + endsWith(suffix: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + eq(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + eq(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + eqAny(values: Array): BooleanExpr; + /* Excluded from this release type: _readUserData */ + eqAny(arrayExpression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + euclideanDistance(vectorExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + euclideanDistance(vector: VectorValue | number[]): FunctionExpr; + /* Excluded from this release type: _readUserData */ + exists(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + // (undocumented) + abstract readonly exprType: ExprType; + /* Excluded from this release type: _readUserData */ + gt(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + gt(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + gte(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + gte(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + ifError(catchExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + ifError(catchValue: unknown): FunctionExpr; + /* Excluded from this release type: _readUserData */ + isAbsent(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isError(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isNan(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isNotNan(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isNotNull(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + isNull(): BooleanExpr; + /* Excluded from this release type: _readUserData */ + like(pattern: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + like(pattern: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + logicalMaximum(second: Expr | unknown, ...others: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + logicalMinimum(second: Expr | unknown, ...others: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + lt(experession: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + lt(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + lte(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + lte(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + manhattanDistance(vector: VectorValue | number[]): FunctionExpr; + /* Excluded from this release type: _readUserData */ + manhattanDistance(vectorExpression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mapGet(subfield: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mapMerge(secondMap: Record | Expr, ...otherMaps: Array | Expr>): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mapRemove(key: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mapRemove(keyExpr: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + maximum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + minimum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + mod(expression: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + mod(value: unknown): FunctionExpr; + /* Excluded from this release type: _readUserData */ + multiply(second: Expr | unknown, ...others: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + neq(expression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + neq(value: unknown): BooleanExpr; + /* Excluded from this release type: _readUserData */ + notEqAny(values: Array): BooleanExpr; + /* Excluded from this release type: _readUserData */ + notEqAny(arrayExpression: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + regexContains(pattern: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + regexContains(pattern: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + regexMatch(pattern: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + regexMatch(pattern: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + replaceAll(find: string, replace: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + replaceAll(find: Expr, replace: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + replaceFirst(find: string, replace: string): FunctionExpr; + /* Excluded from this release type: _readUserData */ + replaceFirst(find: Expr, replace: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + reverse(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + startsWith(prefix: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + startsWith(prefix: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + strConcat(secondString: Expr | string, ...otherStrings: Array): FunctionExpr; + /* Excluded from this release type: _readUserData */ + strContains(substring: string): BooleanExpr; + /* Excluded from this release type: _readUserData */ + strContains(expr: Expr): BooleanExpr; + /* Excluded from this release type: _readUserData */ + substr(position: number, length?: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + substr(position: Expr, length?: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + subtract(other: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + subtract(other: unknown): FunctionExpr; + /* Excluded from this release type: _readUserData */ + sum(): AggregateFunction; + /* Excluded from this release type: _readUserData */ + timestampAdd(unit: Expr, amount: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampSub(unit: Expr, amount: Expr): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampToUnixMicros(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampToUnixMillis(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + timestampToUnixSeconds(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + toLower(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + toUpper(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + trim(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + unixMicrosToTimestamp(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + unixMillisToTimestamp(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + unixSecondsToTimestamp(): FunctionExpr; + /* Excluded from this release type: _readUserData */ + vectorLength(): FunctionExpr; } // @beta -export type ExprType = 'Field' | 'Constant' | 'Function' | 'ListOfExprs' | 'ExprWithAlias'; +export type ExprType = 'Field' | 'Constant' | 'Function' | 'AggregateFunction' | 'ListOfExprs' | 'ExprWithAlias'; // @beta (undocumented) -export class ExprWithAlias extends Expr implements Selectable { - constructor(expr: T, alias: string); +export class ExprWithAlias implements Selectable { + constructor(expr: Expr, alias: string); // (undocumented) readonly alias: string; // (undocumented) - readonly expr: T; + readonly expr: Expr; // (undocumented) exprType: ExprType; // (undocumented) @@ -556,39 +678,27 @@ export class ExprWithAlias extends Expr implements Selectable { // @beta export class Field extends Expr implements Selectable { // (undocumented) - exprType: ExprType; + get alias(): string; // (undocumented) - fieldName(): string; - static of(name: string): Field; - // Warning: (ae-forgotten-export) The symbol "FieldPath" needs to be exported by the entry point pipelines.d.ts - // + get expr(): Expr; // (undocumented) - static of(path: FieldPath): Field; + readonly exprType: ExprType; // (undocumented) - static of(pipeline: Pipeline, name: string): Field; + fieldName(): string; // (undocumented) selectable: true; } -// @beta (undocumented) -export class Fields extends Expr implements Selectable { - // (undocumented) - exprType: ExprType; - // (undocumented) - fieldList(): Field[]; - // (undocumented) - static of(name: string, ...others: string[]): Fields; - // (undocumented) - static ofAll(): Fields; - // (undocumented) - selectable: true; -} +// Warning: (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta +// +// @public +export function field(name: string): Field; -// @beta -export interface FilterCondition extends Expr { - // (undocumented) - filterable: true; -} +// Warning: (ae-forgotten-export) The symbol "FieldPath" needs to be exported by the entry point pipelines.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "field" is marked as @public, but its signature references "Field" which is marked as @beta +// +// @public (undocumented) +export function field(path: FieldPath): Field; // @beta (undocumented) export class FindNearest implements Stage { @@ -603,7 +713,7 @@ export interface FindNearestOptions { // (undocumented) distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; // (undocumented) - field: Field; + field: Field | string; // (undocumented) limit?: number; // (undocumented) @@ -611,91 +721,92 @@ export interface FindNearestOptions { } // @beta -export class FirestoreFunction extends Expr { +export class FunctionExpr extends Expr { constructor(name: string, params: Expr[]); // (undocumented) - exprType: ExprType; + readonly exprType: ExprType; } -// @beta -export function genericFunction(name: string, params: Expr[]): FirestoreFunction; - // @beta (undocumented) export class GenericStage implements Stage { - constructor(name: string, params: unknown[]); // (undocumented) name: string; -} - -// @beta (undocumented) -export class Gt extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; } // @beta -export function gt(left: Expr, right: Expr): Gt; +export function gt(left: Expr, right: Expr): BooleanExpr; // @beta -export function gt(left: Expr, right: any): Gt; +export function gt(expression: Expr, value: unknown): BooleanExpr; // @beta -export function gt(left: string, right: Expr): Gt; +export function gt(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function gt(left: string, right: any): Gt; +export function gt(fieldName: string, value: unknown): BooleanExpr; -// @beta (undocumented) -export class Gte extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } +// @beta +export function gte(left: Expr, right: Expr): BooleanExpr; // @beta -export function gte(left: Expr, right: Expr): Gte; +export function gte(expression: Expr, value: unknown): BooleanExpr; // @beta -export function gte(left: Expr, right: any): Gte; +export function gte(fieldName: string, value: Expr): BooleanExpr; // @beta -export function gte(left: string, right: Expr): Gte; +export function gte(fieldName: string, value: unknown): BooleanExpr; // @beta -export function gte(left: string, right: any): Gte; +export function ifError(tryExpr: Expr, catchExpr: Expr): FunctionExpr; -// @beta (undocumented) -export class IsNan extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr); - // (undocumented) - filterable: true; -} +// @beta +export function ifError(tryExpr: Expr, catchValue: unknown): FunctionExpr; // @beta -export function isNan(value: Expr): IsNan; +export function isAbsent(value: Expr): BooleanExpr; // @beta -export function isNan(value: string): IsNan; +export function isAbsent(field: string): BooleanExpr; -// @beta (undocumented) -export class Like extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, pattern: Expr); - // (undocumented) - filterable: true; - } +// @beta +export function isError(value: Expr): BooleanExpr; + +// @beta +export function isNan(value: Expr): BooleanExpr; // @beta -export function like(left: string, pattern: string): Like; +export function isNan(fieldName: string): BooleanExpr; // @beta -export function like(left: string, pattern: Expr): Like; +export function isNotNan(value: Expr): BooleanExpr; // @beta -export function like(left: Expr, pattern: string): Like; +export function isNotNan(value: string): BooleanExpr; // @beta -export function like(left: Expr, pattern: Expr): Like; +export function isNotNull(value: Expr): BooleanExpr; + +// @beta +export function isNotNull(value: string): BooleanExpr; + +// @beta +export function isNull(value: Expr): BooleanExpr; + +// @beta +export function isNull(value: string): BooleanExpr; + +// @beta +export function like(fieldName: string, pattern: string): BooleanExpr; + +// @beta +export function like(fieldName: string, pattern: Expr): BooleanExpr; + +// @beta +export function like(stringExpression: Expr, pattern: string): BooleanExpr; + +// @beta +export function like(stringExpression: Expr, pattern: Expr): BooleanExpr; // @beta (undocumented) export class Limit implements Stage { @@ -708,199 +819,140 @@ export class Limit implements Stage { name: string; } -// @beta (undocumented) -export class LogicalMaximum extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } +// @beta +export function logicalMaximum(first: Expr, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function logicalMaximum(left: Expr, right: Expr): LogicalMaximum; +export function logicalMaximum(fieldName: string, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function logicalMaximum(left: Expr, right: any): LogicalMaximum; +export function logicalMinimum(first: Expr, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function logicalMaximum(left: string, right: Expr): LogicalMaximum; +export function logicalMinimum(fieldName: string, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function logicalMaximum(left: string, right: any): LogicalMaximum; +export function lt(left: Expr, right: Expr): BooleanExpr; -// @beta (undocumented) -export class LogicalMinimum extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } +// @beta +export function lt(expression: Expr, value: unknown): BooleanExpr; // @beta -export function logicalMinimum(left: Expr, right: Expr): LogicalMinimum; +export function lt(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function logicalMinimum(left: Expr, right: any): LogicalMinimum; +export function lt(fieldName: string, value: unknown): BooleanExpr; // @beta -export function logicalMinimum(left: string, right: Expr): LogicalMinimum; +export function lte(left: Expr, right: Expr): BooleanExpr; // @beta -export function logicalMinimum(left: string, right: any): LogicalMinimum; +export function lte(expression: Expr, value: unknown): BooleanExpr; -// @beta (undocumented) -export class Lt extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } +// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "Expr" which is marked as @beta +// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "BooleanExpr" which is marked as @beta +// +// @public +export function lte(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function lt(left: Expr, right: Expr): Lt; +export function lte(fieldName: string, value: unknown): BooleanExpr; // @beta -export function lt(left: Expr, right: any): Lt; +export function manhattanDistance(fieldName: string, vector: number[] | VectorValue): FunctionExpr; // @beta -export function lt(left: string, right: Expr): Lt; +export function manhattanDistance(fieldName: string, vectorExpression: Expr): FunctionExpr; // @beta -export function lt(left: string, right: any): Lt; - -// @beta (undocumented) -export class Lte extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } +export function manhattanDistance(vectorExpression: Expr, vector: number[] | VectorValue): FunctionExpr; // @beta -export function lte(left: Expr, right: Expr): Lte; +export function manhattanDistance(vectorExpression: Expr, otherVectorExpression: Expr): FunctionExpr; // @beta -export function lte(left: Expr, right: any): Lte; - -// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "Expr" which is marked as @beta -// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "Lte" which is marked as @beta -// -// @public -export function lte(left: string, right: Expr): Lte; +export function map(elements: Record): FunctionExpr; // @beta -export function lte(left: string, right: any): Lte; - -// @beta (undocumented) -export class MapGet extends FirestoreFunction { - constructor(map: Expr, name: string); -} +export function mapGet(fieldName: string, subField: string): FunctionExpr; // @beta -export function mapGet(mapField: string, subField: string): MapGet; +export function mapGet(mapExpression: Expr, subField: string): FunctionExpr; // @beta -export function mapGet(mapExpr: Expr, subField: string): MapGet; - -// @beta (undocumented) -export class Maximum extends FirestoreFunction implements Accumulator { - constructor(value: Expr, distinct: boolean); - // (undocumented) - accumulator: true; - } +export function mapMerge(mapField: string, secondMap: Record | Expr, ...otherMaps: Array | Expr>): FunctionExpr; // @beta -export function maximum(value: Expr): Maximum; +export function mapMerge(firstMap: Record | Expr, secondMap: Record | Expr, ...otherMaps: Array | Expr>): FunctionExpr; // @beta -export function maximum(value: string): Maximum; - -// @beta (undocumented) -export class Minimum extends FirestoreFunction implements Accumulator { - constructor(value: Expr, distinct: boolean); - // (undocumented) - accumulator: true; - } +export function mapRemove(mapField: string, key: string): FunctionExpr; // @beta -export function minimum(value: Expr): Minimum; +export function mapRemove(mapExpr: Expr, key: string): FunctionExpr; // @beta -export function minimum(value: string): Minimum; - -// @beta (undocumented) -export class Mod extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } +export function mapRemove(mapField: string, keyExpr: Expr): FunctionExpr; // @beta -export function mod(left: Expr, right: Expr): Mod; +export function mapRemove(mapExpr: Expr, keyExpr: Expr): FunctionExpr; // @beta -export function mod(left: Expr, right: any): Mod; +export function maximum(expression: Expr): AggregateFunction; // @beta -export function mod(left: string, right: Expr): Mod; +export function maximum(fieldName: string): AggregateFunction; // @beta -export function mod(left: string, right: any): Mod; - -// @beta (undocumented) -export class Multiply extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } +export function minimum(expression: Expr): AggregateFunction; // @beta -export function multiply(left: Expr, right: Expr): Multiply; +export function minimum(fieldName: string): AggregateFunction; // @beta -export function multiply(left: Expr, right: any): Multiply; +export function mod(left: Expr, right: Expr): FunctionExpr; // @beta -export function multiply(left: string, right: Expr): Multiply; +export function mod(expression: Expr, value: unknown): FunctionExpr; // @beta -export function multiply(left: string, right: any): Multiply; +export function mod(fieldName: string, expression: Expr): FunctionExpr; -// @beta (undocumented) -export class Neq extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } +// @beta +export function mod(fieldName: string, value: unknown): FunctionExpr; // @beta -export function neq(left: Expr, right: Expr): Neq; +export function multiply(first: Expr, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function neq(left: Expr, right: any): Neq; +export function multiply(fieldName: string, second: Expr | unknown, ...others: Array): FunctionExpr; // @beta -export function neq(left: string, right: Expr): Neq; +export function neq(left: Expr, right: Expr): BooleanExpr; // @beta -export function neq(left: string, right: any): Neq; +export function neq(expression: Expr, value: unknown): BooleanExpr; -// @beta (undocumented) -export class Not extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr); - // (undocumented) - filterable: true; -} +// @beta +export function neq(fieldName: string, expression: Expr): BooleanExpr; // @beta -export function not(filter: FilterCondition): Not; +export function neq(fieldName: string, value: unknown): BooleanExpr; -// @beta (undocumented) -export class NotEqAny extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, others: Expr[]); - // (undocumented) - filterable: true; - } +// @beta +export function not(booleanExpr: BooleanExpr): BooleanExpr; // @beta -export function notEqAny(element: Expr, others: Expr[]): NotEqAny; +export function notEqAny(element: Expr, values: Array): BooleanExpr; // @beta -export function notEqAny(element: Expr, others: any[]): NotEqAny; +export function notEqAny(fieldName: string, values: Array): BooleanExpr; // @beta -export function notEqAny(element: string, others: Expr[]): NotEqAny; +export function notEqAny(element: Expr, arrayExpression: Expr): BooleanExpr; // @beta -export function notEqAny(element: string, others: any[]): NotEqAny; +export function notEqAny(fieldName: string, arrayExpression: Expr): BooleanExpr; // @beta (undocumented) export class Offset implements Stage { @@ -909,12 +961,8 @@ export class Offset implements Stage { name: string; } -// @beta (undocumented) -export class Or extends FirestoreFunction implements FilterCondition { - constructor(conditions: FilterCondition[]); - // (undocumented) - filterable: true; -} +// @beta +export function or(first: BooleanExpr, second: BooleanExpr, ...more: BooleanExpr[]): BooleanExpr; // @beta export class Ordering { @@ -925,56 +973,46 @@ export class Ordering { readonly expr: Expr; } -// @beta -export function orFunction(left: FilterCondition, ...right: FilterCondition[]): Or; - // @public (undocumented) export class Pipeline { - /* Excluded from this release type: newPipeline */ // Warning: (ae-incompatible-release-tags) The symbol "addFields" is marked as @public, but its signature references "Selectable" which is marked as @beta - addFields(...fields: Selectable[]): Pipeline; - // Warning: (ae-incompatible-release-tags) The symbol "aggregate" is marked as @public, but its signature references "AccumulatorTarget" which is marked as @beta - aggregate(...accumulators: AccumulatorTarget[]): Pipeline; - aggregate(options: { accumulators: AccumulatorTarget[]; groups?: Array; }): Pipeline; + addFields(field: Selectable, ...additionalFields: Selectable[]): Pipeline; + // Warning: (ae-incompatible-release-tags) The symbol "aggregate" is marked as @public, but its signature references "AggregateWithAlias" which is marked as @beta + aggregate(accumulator: AggregateWithAlias, ...additionalAccumulators: AggregateWithAlias[]): Pipeline; + aggregate(options: { accumulators: AggregateWithAlias[]; groups?: Array; }): Pipeline; // Warning: (ae-incompatible-release-tags) The symbol "distinct" is marked as @public, but its signature references "Selectable" which is marked as @beta - distinct(...groups: Array): Pipeline; - // Warning: (ae-incompatible-release-tags) The symbol "execute" is marked as @public, but its signature references "PipelineResult" which is marked as @beta - execute(): Promise; + distinct(group: string | Selectable, ...additionalGroups: Array): Pipeline; // Warning: (ae-incompatible-release-tags) The symbol "findNearest" is marked as @public, but its signature references "FindNearestOptions" which is marked as @beta // // (undocumented) findNearest(options: FindNearestOptions): Pipeline; - genericStage(name: string, params: any[]): Pipeline; + genericStage(name: string, params: unknown[]): Pipeline; limit(limit: number): Pipeline; offset(offset: number): Pipeline; readUserData: any; + // Warning: (ae-incompatible-release-tags) The symbol "removeFields" is marked as @public, but its signature references "Field" which is marked as @beta + removeFields(fieldValue: Field | string, ...additionalFields: Array): Pipeline; + // Warning: (ae-incompatible-release-tags) The symbol "replaceWith" is marked as @public, but its signature references "Field" which is marked as @beta + replaceWith(fieldValue: Field | string): Pipeline; + sample(documents: number): Pipeline; + sample(options: { percentage: number; } | { documents: number; }): Pipeline; // Warning: (ae-incompatible-release-tags) The symbol "select" is marked as @public, but its signature references "Selectable" which is marked as @beta - select(...selections: Array): Pipeline; + select(selection: Selectable | string, ...additionalSelections: Array): Pipeline; // (undocumented) selectablesToMap: any; // Warning: (ae-incompatible-release-tags) The symbol "sort" is marked as @public, but its signature references "Ordering" which is marked as @beta - sort(...orderings: Ordering[]): Pipeline; - // (undocumented) - sort(options: { orderings: Ordering[]; }): Pipeline; + sort(ordering: Ordering, ...additionalOrderings: Ordering[]): Pipeline; // (undocumented) stages: any; + union(other: Pipeline): Pipeline; + // Warning: (ae-incompatible-release-tags) The symbol "unnest" is marked as @public, but its signature references "Selectable" which is marked as @beta + unnest(selectable: Selectable, indexField?: string): Pipeline; // (undocumented) userDataReader: any; - // Warning: (ae-incompatible-release-tags) The symbol "where" is marked as @public, but its signature references "FilterCondition" which is marked as @beta - where(condition: FilterCondition): Pipeline; + // Warning: (ae-incompatible-release-tags) The symbol "where" is marked as @public, but its signature references "BooleanExpr" which is marked as @beta + where(condition: BooleanExpr): Pipeline; } -// Warning: (ae-forgotten-export) The symbol "Firestore" needs to be exported by the entry point pipelines.d.ts -// Warning: (ae-incompatible-release-tags) The symbol "pipeline" is marked as @public, but its signature references "PipelineSource" which is marked as @beta -// -// @public -export function pipeline(firestore: Firestore): PipelineSource; - -// Warning: (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts -// -// @public -export function pipeline(query: Query): Pipeline; - // Warning: (ae-forgotten-export) The symbol "DocumentData" needs to be exported by the entry point pipelines.d.ts // // @beta @@ -984,103 +1022,93 @@ export class PipelineResult { /* Excluded from this release type: __constructor */ get createTime(): Timestamp | undefined; data(): AppModelType | undefined; - get executionTime(): Timestamp; - get(fieldPath: string | FieldPath): any; + get(fieldPath: string | FieldPath | Field): any; get id(): string | undefined; get ref(): DocumentReference | undefined; get updateTime(): Timestamp | undefined; } +// @public (undocumented) +export class PipelineSnapshot { + // Warning: (ae-incompatible-release-tags) The symbol "__constructor" is marked as @public, but its signature references "PipelineResult" which is marked as @beta + constructor(pipeline: Pipeline, results: PipelineResult[], executionTime?: Timestamp); + get executionTime(): Timestamp; + get pipeline(): Pipeline; + // Warning: (ae-incompatible-release-tags) The symbol "results" is marked as @public, but its signature references "PipelineResult" which is marked as @beta + get results(): PipelineResult[]; +} + // @beta export class PipelineSource { + collection(collectionPath: string): PipelineType; + /* Excluded from this release type: _createPipeline */ + /* Excluded from this release type: __constructor */ + // Warning: (ae-forgotten-export) The symbol "Query" needs to be exported by the entry point pipelines.d.ts + collection(collectionReference: Query): PipelineType; /* Excluded from this release type: _createPipeline */ /* Excluded from this release type: __constructor */ - // (undocumented) - collection(collectionPath: string): PipelineType; - // (undocumented) collectionGroup(collectionId: string): PipelineType; - // (undocumented) + /* Excluded from this release type: _createPipeline */ + /* Excluded from this release type: __constructor */ + createFrom(query: Query): Pipeline; + /* Excluded from this release type: _createPipeline */ + /* Excluded from this release type: __constructor */ database(): PipelineType; - // (undocumented) - documents(docs: DocumentReference[]): PipelineType; + /* Excluded from this release type: _createPipeline */ + /* Excluded from this release type: __constructor */ + documents(docs: Array): PipelineType; } -// @beta (undocumented) -export class RegexContains extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, pattern: Expr); - // (undocumented) - filterable: true; - } - // @beta -export function regexContains(left: string, pattern: string): RegexContains; +export function rand(): FunctionExpr; // @beta -export function regexContains(left: string, pattern: Expr): RegexContains; +export function regexContains(fieldName: string, pattern: string): BooleanExpr; // @beta -export function regexContains(left: Expr, pattern: string): RegexContains; +export function regexContains(fieldName: string, pattern: Expr): BooleanExpr; // @beta -export function regexContains(left: Expr, pattern: Expr): RegexContains; - -// @beta (undocumented) -export class RegexMatch extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, pattern: Expr); - // (undocumented) - filterable: true; - } +export function regexContains(stringExpression: Expr, pattern: string): BooleanExpr; // @beta -export function regexMatch(left: string, pattern: string): RegexMatch; +export function regexContains(stringExpression: Expr, pattern: Expr): BooleanExpr; // @beta -export function regexMatch(left: string, pattern: Expr): RegexMatch; +export function regexMatch(fieldName: string, pattern: string): BooleanExpr; // @beta -export function regexMatch(left: Expr, pattern: string): RegexMatch; +export function regexMatch(fieldName: string, pattern: Expr): BooleanExpr; // @beta -export function regexMatch(left: Expr, pattern: Expr): RegexMatch; - -// @beta (undocumented) -export class ReplaceAll extends FirestoreFunction { - constructor(value: Expr, find: Expr, replace: Expr); - } +export function regexMatch(stringExpression: Expr, pattern: string): BooleanExpr; // @beta -export function replaceAll(value: Expr, find: string, replace: string): ReplaceAll; +export function regexMatch(stringExpression: Expr, pattern: Expr): BooleanExpr; // @beta -export function replaceAll(value: Expr, find: Expr, replace: Expr): ReplaceAll; +export function replaceAll(value: Expr, find: string, replace: string): FunctionExpr; // @beta -export function replaceAll(field: string, find: string, replace: string): ReplaceAll; - -// @beta (undocumented) -export class ReplaceFirst extends FirestoreFunction { - constructor(value: Expr, find: Expr, replace: Expr); - } +export function replaceAll(value: Expr, find: Expr, replace: Expr): FunctionExpr; // @beta -export function replaceFirst(value: Expr, find: string, replace: string): ReplaceFirst; +export function replaceAll(fieldName: string, find: string, replace: string): FunctionExpr; // @beta -export function replaceFirst(value: Expr, find: Expr, replace: Expr): ReplaceFirst; +export function replaceFirst(value: Expr, find: string, replace: string): FunctionExpr; // @beta -export function replaceFirst(field: string, find: string, replace: string): ReplaceFirst; +export function replaceFirst(value: Expr, find: Expr, replace: Expr): FunctionExpr; -// @beta (undocumented) -export class Reverse extends FirestoreFunction { - constructor(value: Expr); - } +// @beta +export function replaceFirst(fieldName: string, find: string, replace: string): FunctionExpr; // @beta -export function reverse(expr: Expr): Reverse; +export function reverse(stringExpression: Expr): FunctionExpr; // @beta -export function reverse(field: string): Reverse; +export function reverse(field: string): FunctionExpr; // @beta (undocumented) export class Select implements Stage { @@ -1090,7 +1118,11 @@ export class Select implements Stage { } // @beta -export interface Selectable extends Expr { +export interface Selectable { + // (undocumented) + readonly alias: string; + // (undocumented) + readonly expr: Expr; // (undocumented) selectable: true; } @@ -1108,246 +1140,159 @@ export interface Stage { name: string; } -// @beta (undocumented) -export class StartsWith extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, prefix: Expr); - // (undocumented) - filterable: true; - } - // @beta -export function startsWith(expr: string, prefix: string): StartsWith; +export function startsWith(fieldName: string, prefix: string): BooleanExpr; // @beta -export function startsWith(expr: string, prefix: Expr): StartsWith; +export function startsWith(fieldName: string, prefix: Expr): BooleanExpr; // @beta -export function startsWith(expr: Expr, prefix: string): StartsWith; +export function startsWith(stringExpression: Expr, prefix: string): BooleanExpr; // @beta -export function startsWith(expr: Expr, prefix: Expr): StartsWith; - -// @beta (undocumented) -export class StrConcat extends FirestoreFunction { - constructor(first: Expr, rest: Expr[]); - } +export function startsWith(stringExpression: Expr, prefix: Expr): BooleanExpr; // @beta -export function strConcat(first: string, ...elements: Array): StrConcat; +export function strConcat(fieldName: string, secondString: Expr | string, ...otherStrings: Array): FunctionExpr; // @beta -export function strConcat(first: Expr, ...elements: Array): StrConcat; - -// @beta (undocumented) -export class StrContains extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, substring: Expr); - // (undocumented) - filterable: true; - } +export function strConcat(firstString: Expr, secondString: Expr | string, ...otherStrings: Array): FunctionExpr; // @beta -export function strContains(left: string, substring: string): StrContains; +export function strContains(fieldName: string, substring: string): BooleanExpr; // @beta -export function strContains(left: string, substring: Expr): StrContains; +export function strContains(fieldName: string, substring: Expr): BooleanExpr; // @beta -export function strContains(left: Expr, substring: string): StrContains; +export function strContains(stringExpression: Expr, substring: string): BooleanExpr; // @beta -export function strContains(left: Expr, substring: Expr): StrContains; - -// @beta (undocumented) -export class Subtract extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } +export function strContains(stringExpression: Expr, substring: Expr): BooleanExpr; // @beta -export function subtract(left: Expr, right: Expr): Subtract; +export function substr(field: string, position: number, length?: number): FunctionExpr; // @beta -export function subtract(left: Expr, right: any): Subtract; +export function substr(input: Expr, position: number, length?: number): FunctionExpr; // @beta -export function subtract(left: string, right: Expr): Subtract; +export function substr(field: string, position: Expr, length?: Expr): FunctionExpr; // @beta -export function subtract(left: string, right: any): Subtract; - -// @beta (undocumented) -export class Sum extends FirestoreFunction implements Accumulator { - constructor(value: Expr, distinct: boolean); - // (undocumented) - accumulator: true; - } +export function substr(input: Expr, position: Expr, length?: Expr): FunctionExpr; // @beta -export function sumFunction(value: Expr): Sum; +export function subtract(left: Expr, right: Expr): FunctionExpr; // @beta -export function sumFunction(value: string): Sum; - -// @beta (undocumented) -export class TimestampAdd extends FirestoreFunction { - constructor(timestamp: Expr, unit: Expr, amount: Expr); - } +export function subtract(expression: Expr, value: unknown): FunctionExpr; // @beta -export function timestampAdd(timestamp: Expr, unit: Expr, amount: Expr): TimestampAdd; +export function subtract(fieldName: string, expression: Expr): FunctionExpr; // @beta -export function timestampAdd(timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; +export function subtract(fieldName: string, value: unknown): FunctionExpr; // @beta -export function timestampAdd(field: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - -// @beta (undocumented) -export class TimestampSub extends FirestoreFunction { - constructor(timestamp: Expr, unit: Expr, amount: Expr); - } +export function sum(expression: Expr): AggregateFunction; // @beta -export function timestampSub(timestamp: Expr, unit: Expr, amount: Expr): TimestampSub; +export function sum(fieldName: string): AggregateFunction; // @beta -export function timestampSub(timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; +export function timestampAdd(timestamp: Expr, unit: Expr, amount: Expr): FunctionExpr; // @beta -export function timestampSub(field: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - -// @beta (undocumented) -export class TimestampToUnixMicros extends FirestoreFunction { - constructor(input: Expr); - } +export function timestampAdd(timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; // @beta -export function timestampToUnixMicros(expr: Expr): TimestampToUnixMicros; +export function timestampAdd(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; // @beta -export function timestampToUnixMicros(field: string): TimestampToUnixMicros; - -// @beta (undocumented) -export class TimestampToUnixMillis extends FirestoreFunction { - constructor(input: Expr); - } +export function timestampSub(timestamp: Expr, unit: Expr, amount: Expr): FunctionExpr; // @beta -export function timestampToUnixMillis(expr: Expr): TimestampToUnixMillis; +export function timestampSub(timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; // @beta -export function timestampToUnixMillis(field: string): TimestampToUnixMillis; - -// @beta (undocumented) -export class TimestampToUnixSeconds extends FirestoreFunction { - constructor(input: Expr); - } +export function timestampSub(fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): FunctionExpr; // @beta -export function timestampToUnixSeconds(expr: Expr): TimestampToUnixSeconds; +export function timestampToUnixMicros(expr: Expr): FunctionExpr; // @beta -export function timestampToUnixSeconds(field: string): TimestampToUnixSeconds; - -// @beta (undocumented) -export class ToLower extends FirestoreFunction { - constructor(expr: Expr); - } +export function timestampToUnixMicros(fieldName: string): FunctionExpr; // @beta -export function toLower(expr: string): ToLower; +export function timestampToUnixMillis(expr: Expr): FunctionExpr; // @beta -export function toLower(expr: Expr): ToLower; - -// @beta (undocumented) -export class ToUpper extends FirestoreFunction { - constructor(expr: Expr); - } +export function timestampToUnixMillis(fieldName: string): FunctionExpr; // @beta -export function toUpper(expr: string): ToUpper; +export function timestampToUnixSeconds(expr: Expr): FunctionExpr; // @beta -export function toUpper(expr: Expr): ToUpper; - -// @beta (undocumented) -export class Trim extends FirestoreFunction { - constructor(expr: Expr); - } +export function timestampToUnixSeconds(fieldName: string): FunctionExpr; // @beta -export function trim(expr: string): Trim; +export function toLower(fieldName: string): FunctionExpr; // @beta -export function trim(expr: Expr): Trim; +export function toLower(stringExpression: Expr): FunctionExpr; -// @beta (undocumented) -export class UnixMicrosToTimestamp extends FirestoreFunction { - constructor(input: Expr); - } +// @beta +export function toUpper(fieldName: string): FunctionExpr; // @beta -export function unixMicrosToTimestamp(expr: Expr): UnixMicrosToTimestamp; +export function toUpper(stringExpression: Expr): FunctionExpr; // @beta -export function unixMicrosToTimestamp(field: string): UnixMicrosToTimestamp; +export function trim(fieldName: string): FunctionExpr; -// @beta (undocumented) -export class UnixMillisToTimestamp extends FirestoreFunction { - constructor(input: Expr); - } +// @beta +export function trim(stringExpression: Expr): FunctionExpr; // @beta -export function unixMillisToTimestamp(expr: Expr): UnixMillisToTimestamp; +export function unixMicrosToTimestamp(expr: Expr): FunctionExpr; // @beta -export function unixMillisToTimestamp(field: string): UnixMillisToTimestamp; +export function unixMicrosToTimestamp(fieldName: string): FunctionExpr; -// @beta (undocumented) -export class UnixSecondsToTimestamp extends FirestoreFunction { - constructor(input: Expr); - } +// @beta +export function unixMillisToTimestamp(expr: Expr): FunctionExpr; // @beta -export function unixSecondsToTimestamp(expr: Expr): UnixSecondsToTimestamp; +export function unixMillisToTimestamp(fieldName: string): FunctionExpr; // @beta -export function unixSecondsToTimestamp(field: string): UnixSecondsToTimestamp; +export function unixSecondsToTimestamp(expr: Expr): FunctionExpr; -// @beta (undocumented) -export class VectorLength extends FirestoreFunction { - constructor(value: Expr); - } +// @beta +export function unixSecondsToTimestamp(fieldName: string): FunctionExpr; // @beta -export function vectorLength(expr: Expr): VectorLength; +export function vectorLength(vectorExpression: Expr): FunctionExpr; // @beta -export function vectorLength(field: string): VectorLength; +export function vectorLength(fieldName: string): FunctionExpr; // @beta (undocumented) export class Where implements Stage { - constructor(condition: FilterCondition); + constructor(condition: BooleanExpr); // (undocumented) name: string; } -// @beta (undocumented) -export class Xor extends FirestoreFunction implements FilterCondition { - constructor(conditions: FilterCondition[]); - // (undocumented) - filterable: true; -} - // @beta -export function xor(left: FilterCondition, ...right: FilterCondition[]): Xor; +export function xor(first: BooleanExpr, second: BooleanExpr, ...additionalConditions: BooleanExpr[]): BooleanExpr; // Warnings were encountered during analysis: // -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:3983:26 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AccumulatorTarget" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:3983:61 - (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:4010:21 - (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:4552:26 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AggregateWithAlias" which is marked as @beta +// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/pipelines.d.ts:4552:62 - (ae-incompatible-release-tags) The symbol "groups" is marked as @public, but its signature references "Selectable" which is marked as @beta // (No @packageDocumentation comment for this package) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 6f0e14206c2..34b56b97f21 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -9,54 +9,14 @@ import { FirebaseApp } from '@firebase/app'; import { FirebaseError } from '@firebase/util'; import { LogLevelString as LogLevel } from '@firebase/logger'; -// @beta -export interface Accumulator extends Expr { - // (undocumented) - accumulator: true; -} - -// @beta -export type AccumulatorTarget = ExprWithAlias; - -// @beta (undocumented) -export class Add extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } - -// @beta -export function add(left: Expr, right: Expr): Add; - -// @beta -export function add(left: Expr, right: any): Add; - -// @beta -export function add(left: string, right: Expr): Add; - -// @beta -export function add(left: string, right: any): Add; - // @public export function addDoc(reference: CollectionReference, data: WithFieldValue): Promise>; -// @beta (undocumented) -export class AddFields implements Stage { - constructor(fields: Map); - // (undocumented) - name: string; -} - // @public export type AddPrefixToKeys> = { [K in keyof T & string as `${Prefix}.${K}`]+?: string extends K ? any : T[K]; }; -// @beta (undocumented) -export class Aggregate implements Stage { - constructor(accumulators: Map, groups: Map); - // (undocumented) - name: string; -} - // @public export class AggregateField { readonly aggregateType: AggregateType; @@ -93,147 +53,18 @@ export type AggregateSpecData = { // @public export type AggregateType = 'count' | 'avg' | 'sum'; -// @beta (undocumented) -export class And extends FirestoreFunction implements FilterCondition { - constructor(conditions: (FilterCondition)[]); - // (undocumented) - filterable: true; -} - // @public export function and(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; -// @beta -export function andFunction(left: FilterCondition, ...right: (FilterCondition)[]): And; - -// @beta (undocumented) -export class ArrayConcat extends FirestoreFunction { - constructor(array: Expr, elements: Expr[]); - } - -// @beta -export function arrayConcat(array: Expr, elements: Expr[]): ArrayConcat; - -// @beta -export function arrayConcat(array: Expr, elements: any[]): ArrayConcat; - -// @beta -export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; - -// @beta -export function arrayConcat(array: string, elements: any[]): ArrayConcat; - -// @beta (undocumented) -export class ArrayContains extends FirestoreFunction implements FilterCondition { - constructor(array: Expr, element: Expr); - // (undocumented) - filterable: true; -} - -// @beta -export function arrayContains(array: Expr, element: Expr): ArrayContains; - -// @beta -export function arrayContains(array: Expr, element: any): ArrayContains; - -// @beta -export function arrayContains(array: string, element: Expr): ArrayContains; - -// @beta -export function arrayContains(array: string, element: any): ArrayContains; - -// @beta (undocumented) -export class ArrayContainsAll extends FirestoreFunction implements FilterCondition { - constructor(array: Expr, values: Expr[]); - // (undocumented) - filterable: true; - } - -// @beta -export function arrayContainsAll(array: Expr, values: Expr[]): ArrayContainsAll; - -// @beta -export function arrayContainsAll(array: Expr, values: any[]): ArrayContainsAll; - -// @beta -export function arrayContainsAll(array: string, values: Expr[]): ArrayContainsAll; - -// @beta -export function arrayContainsAll(array: string, values: any[]): ArrayContainsAll; - -// @beta (undocumented) -export class ArrayContainsAny extends FirestoreFunction implements FilterCondition { - constructor(array: Expr, values: Expr[]); - // (undocumented) - filterable: true; - } - -// @beta -export function arrayContainsAny(array: Expr, values: Expr[]): ArrayContainsAny; - -// @beta -export function arrayContainsAny(array: Expr, values: any[]): ArrayContainsAny; - -// @beta -export function arrayContainsAny(array: string, values: Expr[]): ArrayContainsAny; - -// @beta -export function arrayContainsAny(array: string, values: any[]): ArrayContainsAny; - -// @beta (undocumented) -export class ArrayElement extends FirestoreFunction { - constructor(); -} - -// @beta (undocumented) -export class ArrayLength extends FirestoreFunction { - constructor(array: Expr); - } - -// @beta -export function arrayLength(array: Expr): ArrayLength; - // @public export function arrayRemove(...elements: unknown[]): FieldValue; -// @beta (undocumented) -export class ArrayReverse extends FirestoreFunction { - constructor(array: Expr); - } - // @public export function arrayUnion(...elements: unknown[]): FieldValue; -// @beta -export function ascending(expr: Expr): Ordering; - // @public export function average(field: string | FieldPath): AggregateField; -// @beta (undocumented) -export class Avg extends FirestoreFunction implements Accumulator { - constructor(value: Expr, distinct: boolean); - // (undocumented) - accumulator: true; - } - -// @beta -export function avgFunction(value: Expr): Avg; - -// @beta -export function avgFunction(value: string): Avg; - -// @beta (undocumented) -export class ByteLength extends FirestoreFunction { - constructor(value: Expr); - } - -// @beta -export function byteLength(expr: Expr): ByteLength; - -// @beta -export function byteLength(field: string): ByteLength; - // @public export class Bytes { static fromBase64String(base64: string): Bytes; @@ -247,17 +78,6 @@ export class Bytes { // @public export const CACHE_SIZE_UNLIMITED = -1; -// @beta (undocumented) -export class CharLength extends FirestoreFunction { - constructor(value: Expr); - } - -// @beta -export function charLength(field: string): CharLength; - -// @beta -export function charLength(expr: Expr): CharLength; - // @public export type ChildUpdateFields = V extends Record ? AddPrefixToKeys> : never; @@ -276,13 +96,6 @@ export function collection(refer // @public export function collectionGroup(firestore: Firestore, collectionId: string): Query; -// @beta (undocumented) -export class CollectionGroupSource implements Stage { - constructor(collectionId: string); - // (undocumented) - name: string; -} - // @public export class CollectionReference extends Query { get id(): string; @@ -293,98 +106,14 @@ export class CollectionReference; } -// @beta (undocumented) -export class CollectionSource implements Stage { - constructor(collectionPath: string); - // (undocumented) - name: string; -} - -// @beta (undocumented) -export class Cond extends FirestoreFunction { - constructor(condition: FilterCondition, thenExpr: Expr, elseExpr: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function cond(condition: FilterCondition, thenExpr: Expr, elseExpr: Expr): Cond; - // @public export function connectFirestoreEmulator(firestore: Firestore, host: string, port: number, options?: { mockUserToken?: EmulatorMockTokenOptions | string; }): void; -// @beta -export class Constant extends Expr { - // (undocumented) - exprType: ExprType; - static of(value: number): Constant; - static of(value: string): Constant; - static of(value: boolean): Constant; - static of(value: null): Constant; - static of(value: undefined): Constant; - static of(value: GeoPoint): Constant; - static of(value: Timestamp): Constant; - static of(value: Date): Constant; - static of(value: Uint8Array): Constant; - static of(value: DocumentReference): Constant; - static of(value: any[]): Constant; - static of(value: Map): Constant; - static of(value: VectorValue): Constant; - static vector(value: number[] | VectorValue): Constant; -} - -// @beta (undocumented) -export class CosineDistance extends FirestoreFunction { - constructor(vector1: Expr, vector2: Expr); - } - -// @beta -export function cosineDistance(expr: string, other: number[]): CosineDistance; - -// @beta -export function cosineDistance(expr: string, other: VectorValue): CosineDistance; - -// @beta -export function cosineDistance(expr: string, other: Expr): CosineDistance; - -// @beta -export function cosineDistance(expr: Expr, other: number[]): CosineDistance; - -// @beta -export function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; - -// @beta -export function cosineDistance(expr: Expr, other: Expr): CosineDistance; - -// @beta (undocumented) -export class Count extends FirestoreFunction implements Accumulator { - constructor(value: Expr | undefined, distinct: boolean); - // (undocumented) - accumulator: true; - } - // @public export function count(): AggregateField; -// @beta -export function countAll(): Count; - -// @beta -export function countFunction(value: Expr): Count; - -// Warning: (ae-incompatible-release-tags) The symbol "countFunction" is marked as @public, but its signature references "Count" which is marked as @beta -// -// @public -export function countFunction(value: string): Count; - -// @beta (undocumented) -export class DatabaseSource implements Stage { - // (undocumented) - name: string; -} - // @public export function deleteAllPersistentCacheIndexes(indexManager: PersistentCacheIndexManager): void; @@ -394,39 +123,12 @@ export function deleteDoc(refere // @public export function deleteField(): FieldValue; -// @beta -export function descending(expr: Expr): Ordering; - // @public export function disableNetwork(firestore: Firestore): Promise; // @public export function disablePersistentCacheIndexAutoCreation(indexManager: PersistentCacheIndexManager): void; -// @beta (undocumented) -export class Distinct implements Stage { - constructor(groups: Map); - // (undocumented) - name: string; -} - -// @beta (undocumented) -export class Divide extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } - -// @beta -export function divide(left: Expr, right: Expr): Divide; - -// @beta -export function divide(left: Expr, right: any): Divide; - -// @beta -export function divide(left: string, right: Expr): Divide; - -// @beta -export function divide(left: string, right: any): Divide; - // @public export function doc(firestore: Firestore, path: string, ...pathSegments: string[]): DocumentReference; @@ -478,38 +180,6 @@ export class DocumentSnapshot; } -// @beta (undocumented) -export class DocumentsSource implements Stage { - constructor(docPaths: string[]); - // (undocumented) - name: string; - // (undocumented) - static of(refs: DocumentReference[]): DocumentsSource; -} - -// @beta (undocumented) -export class DotProduct extends FirestoreFunction { - constructor(vector1: Expr, vector2: Expr); - } - -// @beta -export function dotProduct(expr: string, other: number[]): DotProduct; - -// @beta -export function dotProduct(expr: string, other: VectorValue): DotProduct; - -// @beta -export function dotProduct(expr: string, other: Expr): DotProduct; - -// @beta -export function dotProduct(expr: Expr, other: number[]): DotProduct; - -// @beta -export function dotProduct(expr: Expr, other: VectorValue): DotProduct; - -// @beta -export function dotProduct(expr: Expr, other: Expr): DotProduct; - export { EmulatorMockTokenOptions } // @public @deprecated @@ -536,290 +206,22 @@ export function endBefore(snapsh // @public export function endBefore(...fieldValues: unknown[]): QueryEndAtConstraint; -// @beta (undocumented) -export class EndsWith extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, suffix: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function endsWith(expr: string, suffix: string): EndsWith; - -// @beta -export function endsWith(expr: string, suffix: Expr): EndsWith; - -// @beta -export function endsWith(expr: Expr, suffix: string): EndsWith; - -// @beta -export function endsWith(expr: Expr, suffix: Expr): EndsWith; - -// @beta (undocumented) -export class Eq extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function eq(left: Expr, right: Expr): Eq; - -// @beta -export function eq(left: Expr, right: any): Eq; - -// @beta -export function eq(left: string, right: Expr): Eq; - -// @beta -export function eq(left: string, right: any): Eq; - -// @beta (undocumented) -export class EqAny extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, others: Expr[]); - // (undocumented) - filterable: true; - } - -// @beta -export function eqAny(element: Expr, others: Expr[]): EqAny; - -// @beta -export function eqAny(element: Expr, others: any[]): EqAny; - -// @beta -export function eqAny(element: string, others: Expr[]): EqAny; - -// @beta -export function eqAny(element: string, others: any[]): EqAny; - -// @beta (undocumented) -export class EuclideanDistance extends FirestoreFunction { - constructor(vector1: Expr, vector2: Expr); - } - -// @beta -export function euclideanDistance(expr: string, other: number[]): EuclideanDistance; - -// @beta -export function euclideanDistance(expr: string, other: VectorValue): EuclideanDistance; - -// @beta -export function euclideanDistance(expr: string, other: Expr): EuclideanDistance; - -// @beta -export function euclideanDistance(expr: Expr, other: number[]): EuclideanDistance; - -// @beta -export function euclideanDistance(expr: Expr, other: VectorValue): EuclideanDistance; - -// @beta -export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; - -// Warning: (ae-incompatible-release-tags) The symbol "execute" is marked as @public, but its signature references "PipelineResult" which is marked as @beta -// -// @public (undocumented) -export function execute(pipeline: Pipeline): Promise; - -// @beta (undocumented) -export class Exists extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr); - // (undocumented) - filterable: true; -} - -// @beta -export function exists(value: Expr): Exists; - -// @beta -export function exists(field: string): Exists; - // @public export interface ExperimentalLongPollingOptions { timeoutSeconds?: number; } -// @beta -export abstract class Expr { - add(other: Expr): Add; - add(other: any): Add; - arrayConcat(...arrays: Expr[]): ArrayConcat; - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayContains(element: Expr): ArrayContains; - arrayContains(element: any): ArrayContains; - arrayContainsAll(...values: Expr[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAny(...values: Expr[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayLength(): ArrayLength; - as(name: string): ExprWithAlias; - ascending(): Ordering; - avg(): Avg; - byteLength(): ByteLength; - charLength(): CharLength; - cosineDistance(other: Expr): CosineDistance; - cosineDistance(other: VectorValue): CosineDistance; - cosineDistance(other: number[]): CosineDistance; - count(): Count; - descending(): Ordering; - divide(other: Expr): Divide; - divide(other: any): Divide; - dotProduct(other: Expr): DotProduct; - dotProduct(other: VectorValue): DotProduct; - dotProduct(other: number[]): DotProduct; - endsWith(suffix: string): EndsWith; - endsWith(suffix: Expr): EndsWith; - eq(other: Expr): Eq; - eq(other: any): Eq; - eqAny(...others: Expr[]): EqAny; - eqAny(...others: any[]): EqAny; - euclideanDistance(other: Expr): EuclideanDistance; - euclideanDistance(other: VectorValue): EuclideanDistance; - euclideanDistance(other: number[]): EuclideanDistance; - exists(): Exists; - // (undocumented) - abstract exprType: ExprType; - gt(other: Expr): Gt; - gt(other: any): Gt; - gte(other: Expr): Gte; - gte(other: any): Gte; - isNaN(): IsNan; - like(pattern: string): Like; - like(pattern: Expr): Like; - logicalMaximum(other: Expr): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum; - logicalMinimum(other: Expr): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum; - lt(other: Expr): Lt; - lt(other: any): Lt; - lte(other: Expr): Lte; - lte(other: any): Lte; - mapGet(subfield: string): MapGet; - maximum(): Maximum; - minimum(): Minimum; - mod(other: Expr): Mod; - mod(other: any): Mod; - multiply(other: Expr): Multiply; - multiply(other: any): Multiply; - neq(other: Expr): Neq; - neq(other: any): Neq; - notEqAny(...others: Expr[]): NotEqAny; - notEqAny(...others: any[]): NotEqAny; - regexContains(pattern: string): RegexContains; - regexContains(pattern: Expr): RegexContains; - regexMatch(pattern: string): RegexMatch; - regexMatch(pattern: Expr): RegexMatch; - replaceAll(find: string, replace: string): ReplaceAll; - replaceAll(find: Expr, replace: Expr): ReplaceAll; - replaceFirst(find: string, replace: string): ReplaceFirst; - replaceFirst(find: Expr, replace: Expr): ReplaceFirst; - reverse(): Reverse; - startsWith(prefix: string): StartsWith; - startsWith(prefix: Expr): StartsWith; - strConcat(...elements: Array): StrConcat; - strContains(substring: string): StrContains; - strContains(expr: Expr): StrContains; - subtract(other: Expr): Subtract; - subtract(other: any): Subtract; - sum(): Sum; - timestampAdd(unit: Expr, amount: Expr): TimestampAdd; - timestampAdd(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - timestampSub(unit: Expr, amount: Expr): TimestampSub; - timestampSub(unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - timestampToUnixMicros(): TimestampToUnixMicros; - timestampToUnixMillis(): TimestampToUnixMillis; - timestampToUnixSeconds(): TimestampToUnixSeconds; - toLower(): ToLower; - toUpper(): ToUpper; - trim(): Trim; - unixMicrosToTimestamp(): UnixMicrosToTimestamp; - unixMillisToTimestamp(): UnixMillisToTimestamp; - unixSecondsToTimestamp(): UnixSecondsToTimestamp; - vectorLength(): VectorLength; -} - -// @beta -export type ExprType = 'Field' | 'Constant' | 'Function' | 'ListOfExprs' | 'ExprWithAlias'; - -// @beta (undocumented) -export class ExprWithAlias extends Expr implements Selectable { - constructor(expr: T, alias: string); - // (undocumented) - readonly alias: string; - // (undocumented) - readonly expr: T; - // (undocumented) - exprType: ExprType; - // (undocumented) - selectable: true; -} - -// @beta -export class Field extends Expr implements Selectable { - // (undocumented) - exprType: ExprType; - // (undocumented) - fieldName(): string; - static of(name: string): Field; - // (undocumented) - static of(path: FieldPath): Field; - // (undocumented) - static of(pipeline: Pipeline, name: string): Field; - // (undocumented) - selectable: true; -} - // @public export class FieldPath { constructor(...fieldNames: string[]); isEqual(other: FieldPath): boolean; } -// @beta (undocumented) -export class Fields extends Expr implements Selectable { - // (undocumented) - exprType: ExprType; - // (undocumented) - fieldList(): Field[]; - // (undocumented) - static of(name: string, ...others: string[]): Fields; - // (undocumented) - static ofAll(): Fields; - // (undocumented) - selectable: true; -} - // @public export abstract class FieldValue { abstract isEqual(other: FieldValue): boolean; } -// @beta -export interface FilterCondition extends Expr { - // (undocumented) - filterable: true; -} - -// @beta (undocumented) -export class FindNearest implements Stage { - // (undocumented) - name: string; -} - -// @beta (undocumented) -export interface FindNearestOptions { - // (undocumented) - distanceField?: string; - // (undocumented) - distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; - // (undocumented) - field: Field; - // (undocumented) - limit?: number; - // (undocumented) - vectorValue: VectorValue | number[]; -} - // @public export class Firestore { get app(): FirebaseApp; @@ -844,13 +246,6 @@ export class FirestoreError extends FirebaseError { // @public export type FirestoreErrorCode = 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated'; -// @beta -export class FirestoreFunction extends Expr { - constructor(name: string, params: Expr[]); - // (undocumented) - exprType: ExprType; - } - // @public export type FirestoreLocalCache = MemoryLocalCache | PersistentLocalCache; @@ -866,16 +261,6 @@ export interface FirestoreSettings { ssl?: boolean; } -// @beta -export function genericFunction(name: string, params: Expr[]): FirestoreFunction; - -// @beta (undocumented) -export class GenericStage implements Stage { - constructor(name: string, params: unknown[]); - // (undocumented) - name: string; -} - // @public export class GeoPoint { constructor(latitude: number, longitude: number); @@ -929,44 +314,6 @@ export function getFirestore(app: FirebaseApp, databaseId: string): Firestore; // @public export function getPersistentCacheIndexManager(firestore: Firestore): PersistentCacheIndexManager | null; -// @beta (undocumented) -export class Gt extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function gt(left: Expr, right: Expr): Gt; - -// @beta -export function gt(left: Expr, right: any): Gt; - -// @beta -export function gt(left: string, right: Expr): Gt; - -// @beta -export function gt(left: string, right: any): Gt; - -// @beta (undocumented) -export class Gte extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function gte(left: Expr, right: Expr): Gte; - -// @beta -export function gte(left: Expr, right: any): Gte; - -// @beta -export function gte(left: string, right: Expr): Gte; - -// @beta -export function gte(left: string, right: any): Gte; - // @public export function increment(n: number): FieldValue; @@ -997,45 +344,6 @@ export interface IndexField { // @public export function initializeFirestore(app: FirebaseApp, settings: FirestoreSettings, databaseId?: string): Firestore; -// @beta (undocumented) -export class IsNan extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr); - // (undocumented) - filterable: true; -} - -// @beta -export function isNan(value: Expr): IsNan; - -// @beta -export function isNan(value: string): IsNan; - -// @beta (undocumented) -export class Like extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, pattern: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function like(left: string, pattern: string): Like; - -// @beta -export function like(left: string, pattern: Expr): Like; - -// @beta -export function like(left: Expr, pattern: string): Like; - -// @beta -export function like(left: Expr, pattern: Expr): Like; - -// @beta (undocumented) -export class Limit implements Stage { - constructor(limit: number); - // (undocumented) - name: string; -} - // @public export function limit(limit: number): QueryLimitConstraint; @@ -1056,114 +364,15 @@ export class LoadBundleTask implements PromiseLike { } // @public -export interface LoadBundleTaskProgress { - bytesLoaded: number; - documentsLoaded: number; - taskState: TaskState; - totalBytes: number; - totalDocuments: number; -} - -// @beta (undocumented) -export class LogicalMaximum extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } - -// @beta -export function logicalMaximum(left: Expr, right: Expr): LogicalMaximum; - -// @beta -export function logicalMaximum(left: Expr, right: any): LogicalMaximum; - -// @beta -export function logicalMaximum(left: string, right: Expr): LogicalMaximum; - -// @beta -export function logicalMaximum(left: string, right: any): LogicalMaximum; - -// @beta (undocumented) -export class LogicalMinimum extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } - -// @beta -export function logicalMinimum(left: Expr, right: Expr): LogicalMinimum; - -// @beta -export function logicalMinimum(left: Expr, right: any): LogicalMinimum; - -// @beta -export function logicalMinimum(left: string, right: Expr): LogicalMinimum; - -// @beta -export function logicalMinimum(left: string, right: any): LogicalMinimum; - -export { LogLevel } - -// @beta (undocumented) -export class Lt extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function lt(left: Expr, right: Expr): Lt; - -// @beta -export function lt(left: Expr, right: any): Lt; - -// @beta -export function lt(left: string, right: Expr): Lt; - -// @beta -export function lt(left: string, right: any): Lt; - -// @beta (undocumented) -export class Lte extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function lte(left: Expr, right: Expr): Lte; - -// @beta -export function lte(left: Expr, right: any): Lte; - -// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "Expr" which is marked as @beta -// Warning: (ae-incompatible-release-tags) The symbol "lte" is marked as @public, but its signature references "Lte" which is marked as @beta -// -// @public -export function lte(left: string, right: Expr): Lte; - -// @beta -export function lte(left: string, right: any): Lte; - -// @beta (undocumented) -export class MapGet extends FirestoreFunction { - constructor(map: Expr, name: string); -} - -// @beta -export function mapGet(mapField: string, subField: string): MapGet; - -// @beta -export function mapGet(mapExpr: Expr, subField: string): MapGet; - -// @beta (undocumented) -export class Maximum extends FirestoreFunction implements Accumulator { - constructor(value: Expr, distinct: boolean); - // (undocumented) - accumulator: true; - } - -// @beta -export function maximum(value: Expr): Maximum; +export interface LoadBundleTaskProgress { + bytesLoaded: number; + documentsLoaded: number; + taskState: TaskState; + totalBytes: number; + totalDocuments: number; +} -// @beta -export function maximum(value: string): Maximum; +export { LogLevel } // @public export interface MemoryCacheSettings { @@ -1202,116 +411,14 @@ export function memoryLruGarbageCollector(settings?: { cacheSizeBytes?: number; }): MemoryLruGarbageCollector; -// @beta (undocumented) -export class Minimum extends FirestoreFunction implements Accumulator { - constructor(value: Expr, distinct: boolean); - // (undocumented) - accumulator: true; - } - -// @beta -export function minimum(value: Expr): Minimum; - -// @beta -export function minimum(value: string): Minimum; - -// @beta (undocumented) -export class Mod extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } - -// @beta -export function mod(left: Expr, right: Expr): Mod; - -// @beta -export function mod(left: Expr, right: any): Mod; - -// @beta -export function mod(left: string, right: Expr): Mod; - -// @beta -export function mod(left: string, right: any): Mod; - -// @beta (undocumented) -export class Multiply extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } - -// @beta -export function multiply(left: Expr, right: Expr): Multiply; - -// @beta -export function multiply(left: Expr, right: any): Multiply; - -// @beta -export function multiply(left: string, right: Expr): Multiply; - -// @beta -export function multiply(left: string, right: any): Multiply; - // @public export function namedQuery(firestore: Firestore, name: string): Promise; -// @beta (undocumented) -export class Neq extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, right: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function neq(left: Expr, right: Expr): Neq; - -// @beta -export function neq(left: Expr, right: any): Neq; - -// @beta -export function neq(left: string, right: Expr): Neq; - -// @beta -export function neq(left: string, right: any): Neq; - // @public export type NestedUpdateFields> = UnionToIntersection<{ [K in keyof T & string]: ChildUpdateFields; }[keyof T & string]>; -// @beta (undocumented) -export class Not extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr); - // (undocumented) - filterable: true; -} - -// @beta -export function not(filter: FilterCondition): Not; - -// @beta (undocumented) -export class NotEqAny extends FirestoreFunction implements FilterCondition { - constructor(left: Expr, others: Expr[]); - // (undocumented) - filterable: true; - } - -// @beta -export function notEqAny(element: Expr, others: Expr[]): NotEqAny; - -// @beta -export function notEqAny(element: Expr, others: any[]): NotEqAny; - -// @beta -export function notEqAny(element: string, others: Expr[]): NotEqAny; - -// @beta -export function notEqAny(element: string, others: any[]): NotEqAny; - -// @beta (undocumented) -export class Offset implements Stage { - constructor(offset: number); - // (undocumented) - name: string; - } - // @public export function onSnapshot(reference: DocumentReference, observer: { next?: (snapshot: DocumentSnapshot) => void; @@ -1362,13 +469,6 @@ export function onSnapshotsInSync(firestore: Firestore, observer: { // @public export function onSnapshotsInSync(firestore: Firestore, onSync: () => void): Unsubscribe; -// @beta (undocumented) -export class Or extends FirestoreFunction implements FilterCondition { - constructor(conditions: (FilterCondition)[]); - // (undocumented) - filterable: true; -} - // @public export function or(...queryConstraints: QueryFilterConstraint[]): QueryCompositeFilterConstraint; @@ -1378,18 +478,6 @@ export function orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDir // @public export type OrderByDirection = 'desc' | 'asc'; -// @beta -export class Ordering { - constructor(expr: Expr, direction: 'ascending' | 'descending'); - // (undocumented) - readonly direction: 'ascending' | 'descending'; - // (undocumented) - readonly expr: Expr; -} - -// @beta -export function orFunction(left: FilterCondition, ...right: (FilterCondition)[]): Or; - // @public export type PartialWithFieldValue = Partial | (T extends Primitive ? T : T extends {} ? { [K in keyof T]?: PartialWithFieldValue | FieldValue; @@ -1446,78 +534,6 @@ export interface PersistentSingleTabManagerSettings { // @public export type PersistentTabManager = PersistentSingleTabManager | PersistentMultipleTabManager; -// @public (undocumented) -export class Pipeline { - /* Excluded from this release type: newPipeline */ - // Warning: (ae-incompatible-release-tags) The symbol "addFields" is marked as @public, but its signature references "Selectable" which is marked as @beta - addFields(...fields: Selectable[]): Pipeline; - // Warning: (ae-incompatible-release-tags) The symbol "aggregate" is marked as @public, but its signature references "AccumulatorTarget" which is marked as @beta - aggregate(...accumulators: AccumulatorTarget[]): Pipeline; - aggregate(options: { accumulators: AccumulatorTarget[]; groups?: Array; }): Pipeline; - // Warning: (ae-incompatible-release-tags) The symbol "distinct" is marked as @public, but its signature references "Selectable" which is marked as @beta - distinct(...groups: Array): Pipeline; - // Warning: (ae-incompatible-release-tags) The symbol "execute" is marked as @public, but its signature references "PipelineResult" which is marked as @beta - execute(): Promise; - // Warning: (ae-incompatible-release-tags) The symbol "findNearest" is marked as @public, but its signature references "FindNearestOptions" which is marked as @beta - // - // (undocumented) - findNearest(options: FindNearestOptions): Pipeline; - genericStage(name: string, params: any[]): Pipeline; - limit(limit: number): Pipeline; - offset(offset: number): Pipeline; - readUserData: any; - // Warning: (ae-incompatible-release-tags) The symbol "select" is marked as @public, but its signature references "Selectable" which is marked as @beta - select(...selections: Array): Pipeline; - // (undocumented) - selectablesToMap: any; - // Warning: (ae-incompatible-release-tags) The symbol "sort" is marked as @public, but its signature references "Ordering" which is marked as @beta - sort(...orderings: Ordering[]): Pipeline; - // (undocumented) - sort(options: { orderings: Ordering[]; }): Pipeline; - // (undocumented) - stages: any; - // (undocumented) - userDataReader: any; - // Warning: (ae-incompatible-release-tags) The symbol "where" is marked as @public, but its signature references "FilterCondition" which is marked as @beta - where(condition: FilterCondition): Pipeline; -} - -// Warning: (ae-incompatible-release-tags) The symbol "pipeline" is marked as @public, but its signature references "PipelineSource" which is marked as @beta -// -// @public -export function pipeline(firestore: Firestore): PipelineSource; - -// @public -export function pipeline(query: Query): Pipeline; - -// @beta -export class PipelineResult { - /* Excluded from this release type: _ref */ - /* Excluded from this release type: _fields */ - /* Excluded from this release type: __constructor */ - get createTime(): Timestamp | undefined; - data(): AppModelType | undefined; - get executionTime(): Timestamp; - get(fieldPath: string | FieldPath): any; - get id(): string | undefined; - get ref(): DocumentReference | undefined; - get updateTime(): Timestamp | undefined; -} - -// @beta -export class PipelineSource { - /* Excluded from this release type: _createPipeline */ - /* Excluded from this release type: __constructor */ - // (undocumented) - collection(collectionPath: string): PipelineType; - // (undocumented) - collectionGroup(collectionId: string): PipelineType; - // (undocumented) - database(): PipelineType; - // (undocumented) - documents(docs: DocumentReference[]): PipelineType; -} - // @public export type Primitive = string | number | boolean | undefined | null; @@ -1604,99 +620,9 @@ export class QueryStartAtConstraint extends QueryConstraint { // @public export function refEqual(left: DocumentReference | CollectionReference, right: DocumentReference | CollectionReference): boolean; -// @beta (undocumented) -export class RegexContains extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, pattern: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function regexContains(left: string, pattern: string): RegexContains; - -// @beta -export function regexContains(left: string, pattern: Expr): RegexContains; - -// @beta -export function regexContains(left: Expr, pattern: string): RegexContains; - -// @beta -export function regexContains(left: Expr, pattern: Expr): RegexContains; - -// @beta (undocumented) -export class RegexMatch extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, pattern: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function regexMatch(left: string, pattern: string): RegexMatch; - -// @beta -export function regexMatch(left: string, pattern: Expr): RegexMatch; - -// @beta -export function regexMatch(left: Expr, pattern: string): RegexMatch; - -// @beta -export function regexMatch(left: Expr, pattern: Expr): RegexMatch; - -// @beta (undocumented) -export class ReplaceAll extends FirestoreFunction { - constructor(value: Expr, find: Expr, replace: Expr); - } - -// @beta -export function replaceAll(value: Expr, find: string, replace: string): ReplaceAll; - -// @beta -export function replaceAll(value: Expr, find: Expr, replace: Expr): ReplaceAll; - -// @beta -export function replaceAll(field: string, find: string, replace: string): ReplaceAll; - -// @beta (undocumented) -export class ReplaceFirst extends FirestoreFunction { - constructor(value: Expr, find: Expr, replace: Expr); - } - -// @beta -export function replaceFirst(value: Expr, find: string, replace: string): ReplaceFirst; - -// @beta -export function replaceFirst(value: Expr, find: Expr, replace: Expr): ReplaceFirst; - -// @beta -export function replaceFirst(field: string, find: string, replace: string): ReplaceFirst; - -// @beta (undocumented) -export class Reverse extends FirestoreFunction { - constructor(value: Expr); - } - -// @beta -export function reverse(expr: Expr): Reverse; - -// @beta -export function reverse(field: string): Reverse; - // @public export function runTransaction(firestore: Firestore, updateFunction: (transaction: Transaction) => Promise, options?: TransactionOptions): Promise; -// @beta (undocumented) -export class Select implements Stage { - constructor(projections: Map); - // (undocumented) - name: string; - } - -// @beta -export interface Selectable extends Expr { - // (undocumented) - selectable: true; -} - // @public export function serverTimestamp(): FieldValue; @@ -1743,19 +669,6 @@ export interface SnapshotOptions { readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; } -// @beta (undocumented) -export class Sort implements Stage { - constructor(orders: Ordering[]); - // (undocumented) - name: string; - } - -// @beta (undocumented) -export interface Stage { - // (undocumented) - name: string; -} - // @public export function startAfter(snapshot: DocumentSnapshot): QueryStartAtConstraint; @@ -1768,88 +681,9 @@ export function startAt(snapshot // @public export function startAt(...fieldValues: unknown[]): QueryStartAtConstraint; -// @beta (undocumented) -export class StartsWith extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, prefix: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function startsWith(expr: string, prefix: string): StartsWith; - -// @beta -export function startsWith(expr: string, prefix: Expr): StartsWith; - -// @beta -export function startsWith(expr: Expr, prefix: string): StartsWith; - -// @beta -export function startsWith(expr: Expr, prefix: Expr): StartsWith; - -// @beta (undocumented) -export class StrConcat extends FirestoreFunction { - constructor(first: Expr, rest: Expr[]); - } - -// @beta -export function strConcat(first: string, ...elements: Array): StrConcat; - -// @beta -export function strConcat(first: Expr, ...elements: Array): StrConcat; - -// @beta (undocumented) -export class StrContains extends FirestoreFunction implements FilterCondition { - constructor(expr: Expr, substring: Expr); - // (undocumented) - filterable: true; - } - -// @beta -export function strContains(left: string, substring: string): StrContains; - -// @beta -export function strContains(left: string, substring: Expr): StrContains; - -// @beta -export function strContains(left: Expr, substring: string): StrContains; - -// @beta -export function strContains(left: Expr, substring: Expr): StrContains; - -// @beta (undocumented) -export class Subtract extends FirestoreFunction { - constructor(left: Expr, right: Expr); - } - -// @beta -export function subtract(left: Expr, right: Expr): Subtract; - -// @beta -export function subtract(left: Expr, right: any): Subtract; - -// @beta -export function subtract(left: string, right: Expr): Subtract; - -// @beta -export function subtract(left: string, right: any): Subtract; - -// @beta (undocumented) -export class Sum extends FirestoreFunction implements Accumulator { - constructor(value: Expr, distinct: boolean); - // (undocumented) - accumulator: true; - } - // @public export function sum(field: string | FieldPath): AggregateField; -// @beta -export function sumFunction(value: Expr): Sum; - -// @beta -export function sumFunction(value: string): Sum; - // @public export type TaskState = 'Error' | 'Running' | 'Success'; @@ -1877,89 +711,6 @@ export class Timestamp { valueOf(): string; } -// @beta (undocumented) -export class TimestampAdd extends FirestoreFunction { - constructor(timestamp: Expr, unit: Expr, amount: Expr); - } - -// @beta -export function timestampAdd(timestamp: Expr, unit: Expr, amount: Expr): TimestampAdd; - -// @beta -export function timestampAdd(timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - -// @beta -export function timestampAdd(field: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampAdd; - -// @beta (undocumented) -export class TimestampSub extends FirestoreFunction { - constructor(timestamp: Expr, unit: Expr, amount: Expr); - } - -// @beta -export function timestampSub(timestamp: Expr, unit: Expr, amount: Expr): TimestampSub; - -// @beta -export function timestampSub(timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - -// @beta -export function timestampSub(field: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number): TimestampSub; - -// @beta (undocumented) -export class TimestampToUnixMicros extends FirestoreFunction { - constructor(input: Expr); - } - -// @beta -export function timestampToUnixMicros(expr: Expr): TimestampToUnixMicros; - -// @beta -export function timestampToUnixMicros(field: string): TimestampToUnixMicros; - -// @beta (undocumented) -export class TimestampToUnixMillis extends FirestoreFunction { - constructor(input: Expr); - } - -// @beta -export function timestampToUnixMillis(expr: Expr): TimestampToUnixMillis; - -// @beta -export function timestampToUnixMillis(field: string): TimestampToUnixMillis; - -// @beta (undocumented) -export class TimestampToUnixSeconds extends FirestoreFunction { - constructor(input: Expr); - } - -// @beta -export function timestampToUnixSeconds(expr: Expr): TimestampToUnixSeconds; - -// @beta -export function timestampToUnixSeconds(field: string): TimestampToUnixSeconds; - -// @beta (undocumented) -export class ToLower extends FirestoreFunction { - constructor(expr: Expr); - } - -// @beta -export function toLower(expr: string): ToLower; - -// @beta -export function toLower(expr: Expr): ToLower; - -// @beta (undocumented) -export class ToUpper extends FirestoreFunction { - constructor(expr: Expr); - } - -// @beta -export function toUpper(expr: string): ToUpper; - -// @beta -export function toUpper(expr: Expr): ToUpper; - // @public export class Transaction { delete(documentRef: DocumentReference): this; @@ -1975,53 +726,9 @@ export interface TransactionOptions { readonly maxAttempts?: number; } -// @beta (undocumented) -export class Trim extends FirestoreFunction { - constructor(expr: Expr); - } - -// @beta -export function trim(expr: string): Trim; - -// @beta -export function trim(expr: Expr): Trim; - // @public export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; -// @beta (undocumented) -export class UnixMicrosToTimestamp extends FirestoreFunction { - constructor(input: Expr); - } - -// @beta -export function unixMicrosToTimestamp(expr: Expr): UnixMicrosToTimestamp; - -// @beta -export function unixMicrosToTimestamp(field: string): UnixMicrosToTimestamp; - -// @beta (undocumented) -export class UnixMillisToTimestamp extends FirestoreFunction { - constructor(input: Expr); - } - -// @beta -export function unixMillisToTimestamp(expr: Expr): UnixMillisToTimestamp; - -// @beta -export function unixMillisToTimestamp(field: string): UnixMillisToTimestamp; - -// @beta (undocumented) -export class UnixSecondsToTimestamp extends FirestoreFunction { - constructor(input: Expr); - } - -// @beta -export function unixSecondsToTimestamp(expr: Expr): UnixSecondsToTimestamp; - -// @beta -export function unixSecondsToTimestamp(field: string): UnixSecondsToTimestamp; - // @public export interface Unsubscribe { (): void; @@ -2041,17 +748,6 @@ export function updateDoc(refere // @public export function vector(values?: number[]): VectorValue; -// @beta (undocumented) -export class VectorLength extends FirestoreFunction { - constructor(value: Expr); - } - -// @beta -export function vectorLength(expr: Expr): VectorLength; - -// @beta -export function vectorLength(field: string): VectorLength; - // @public export class VectorValue { /* Excluded from this release type: __constructor */ @@ -2062,13 +758,6 @@ export class VectorValue { // @public export function waitForPendingWrites(firestore: Firestore): Promise; -// @beta (undocumented) -export class Where implements Stage { - constructor(condition: FilterCondition); - // (undocumented) - name: string; -} - // @public export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryFieldFilterConstraint; @@ -2093,21 +782,5 @@ export class WriteBatch { // @public export function writeBatch(firestore: Firestore): WriteBatch; -// @beta (undocumented) -export class Xor extends FirestoreFunction implements FilterCondition { - constructor(conditions: (FilterCondition)[]); - // (undocumented) - filterable: true; -} - -// @beta -export function xor(left: FilterCondition, ...right: (FilterCondition)[]): Xor; - - -// Warnings were encountered during analysis: -// -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/all_packages.d.ts:5962:26 - (ae-incompatible-release-tags) The symbol "accumulators" is marked as @public, but its signature references "AccumulatorTarget" which is marked as @beta -// /Users/markduckworth/projects/firebase-js-sdk/packages/firestore/dist/all_packages.d.ts:5962:61 - (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/all_packages.d.ts:5989:21 - (ae-incompatible-release-tags) The symbol "orderings" is marked as @public, but its signature references "Ordering" which is marked as @beta ``` diff --git a/packages/firestore/lite/pipelines/package.json b/packages/firestore/lite/pipelines/package.json index 349e25e3ac3..e7989c2ea97 100644 --- a/packages/firestore/lite/pipelines/package.json +++ b/packages/firestore/lite/pipelines/package.json @@ -6,7 +6,7 @@ "module": "../../dist/lite/pipelines.browser.esm2017.js", "browser": "../../dist/lite/pipelines.browser.esm2017.js", "react-native": "../../dist/lite/pipelines.rn.esm2017.js", - "typings": "../../dist/lite/pipelines.d.ts", + "typings": "./pipelines.d.ts", "private": true, "engines": { "node": ">=18.0.0" diff --git a/packages/firestore/lite/pipelines/pipelines.d.ts b/packages/firestore/lite/pipelines/pipelines.d.ts index e1243ebe907..81336456656 100644 --- a/packages/firestore/lite/pipelines/pipelines.d.ts +++ b/packages/firestore/lite/pipelines/pipelines.d.ts @@ -19,7 +19,7 @@ import { PipelineSource, Pipeline } from '../../dist/lite/pipelines'; // Augument the Firestore class with the pipeline() method. // This is stripped from dist/lite/pipelines.d.ts during the build // so it needs to be re-added here. -declare module '@firebase/firestore' { +declare module '@firebase/firestore/lite' { interface Firestore { pipeline(): PipelineSource; } diff --git a/packages/firestore/lite/pipelines/pipelines.ts b/packages/firestore/lite/pipelines/pipelines.ts index d3e31f5e52b..e03e5f4883b 100644 --- a/packages/firestore/lite/pipelines/pipelines.ts +++ b/packages/firestore/lite/pipelines/pipelines.ts @@ -50,11 +50,14 @@ export type { export { PipelineSource } from '../../src/lite-api/pipeline-source'; -export { PipelineResult } from '../../src/lite-api/pipeline-result'; +export { + PipelineResult, + PipelineSnapshot +} from '../../src/lite-api/pipeline-result'; export { Pipeline } from '../../src/lite-api/pipeline'; -export { pipeline, execute } from '../../src/lite-api/pipeline_impl'; +export { execute } from '../../src/lite-api/pipeline_impl'; export { Stage, @@ -76,14 +79,44 @@ export { } from '../../src/lite-api/stage'; export { + Expr, + field, + and, + array, + arrayOffset, + constant, add, subtract, multiply, + avg, + bitAnd, + substr, + constantVector, + bitLeftShift, + bitNot, + count, + mapMerge, + mapRemove, + bitOr, + ifError, + isAbsent, + isError, + or, + rand, + bitRightShift, + bitXor, divide, + isNotNan, + map, + isNotNull, + isNull, mod, + documentId, eq, neq, lt, + countIf, + currentContext, lte, gt, gte, @@ -132,79 +165,16 @@ export { timestampToUnixSeconds, timestampAdd, timestampSub, - genericFunction, ascending, descending, ExprWithAlias, Field, - Fields, Constant, - FirestoreFunction, - Add, - Subtract, - Multiply, - Divide, - Mod, - Eq, - Neq, - Lt, - Lte, - Gt, - Gte, - ArrayConcat, - ArrayReverse, - ArrayContains, - ArrayContainsAll, - ArrayContainsAny, - ArrayLength, - ArrayElement, - EqAny, - IsNan, - Exists, - Not, - And, - Or, - Xor, - Cond, - LogicalMaximum, - LogicalMinimum, - Reverse, - ReplaceFirst, - ReplaceAll, - CharLength, - ByteLength, - Like, - RegexContains, - RegexMatch, - StrContains, - StartsWith, - EndsWith, - ToLower, - ToUpper, - Trim, - StrConcat, - MapGet, - Count, - Sum, - Avg, - Minimum, - Maximum, - CosineDistance, - DotProduct, - EuclideanDistance, - VectorLength, - UnixMicrosToTimestamp, - TimestampToUnixMicros, - UnixMillisToTimestamp, - TimestampToUnixMillis, - UnixSecondsToTimestamp, - TimestampToUnixSeconds, - TimestampAdd, - TimestampSub, + FunctionExpr, Ordering, ExprType, - AccumulatorTarget, + AggregateWithAlias, Selectable, - FilterCondition, - Accumulator + BooleanExpr, + AggregateFunction } from '../../src/lite-api/expressions'; diff --git a/packages/firestore/package.json b/packages/firestore/package.json index 8dc84db4315..c11536d3c1b 100644 --- a/packages/firestore/package.json +++ b/packages/firestore/package.json @@ -48,12 +48,11 @@ "test:minified": "(cd ../../integration/firestore ; yarn test)", "trusted-type-check": "tsec -p tsconfig.json --noEmit", "api-report:main": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore --packageRoot . --typescriptDts ./dist/firestore/src/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/index.d.ts", - "api-report:all-packages": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore --packageRoot . --typescriptDts ./dist/firestore/src/all_packages.d.ts --rollupDts ./dist/private.all_packages.d.ts --untrimmedRollupDts ./dist/internal.all_packages.d.ts --publicDts ./dist/all_packages.d.ts", "api-report:pipelines": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore-pipelines --packageRoot . --typescriptDts ./dist/firestore/pipelines/pipelines.d.ts --rollupDts ./dist/private.pipelines.d.ts --untrimmedRollupDts ./dist/internal.pipelines.d.ts --publicDts ./dist/pipelines.d.ts --otherExportsPublicDtsFiles ./dist/index.d.ts", "api-report:lite": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore-lite --packageRoot . --typescriptDts ./dist/firestore/lite/index.d.ts --rollupDts ./dist/lite/private.d.ts --untrimmedRollupDts ./dist/lite/internal.d.ts --publicDts ./dist/lite/index.d.ts", - "api-report:lite:pipelines": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore-lite-pipelines --packageRoot . --typescriptDts ./dist/firestore/lite/pipelines/pipelines.d.ts --rollupDts ./dist/lite/private.pipelines.d.ts --untrimmedRollupDts ./dist/lite/internal.pipelines.d.ts --publicDts ./dist/lite/pipelines.d.ts --otherExportsPublicDtsFiles ./dist/index.d.ts", + "api-report:lite:pipelines": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package firestore-lite-pipelines --packageRoot . --typescriptDts ./dist/firestore/lite/pipelines/pipelines.d.ts --rollupDts ./dist/lite/private.pipelines.d.ts --untrimmedRollupDts ./dist/lite/internal.pipelines.d.ts --publicDts ./dist/lite/pipelines.d.ts --otherExportsPublicDtsFiles ./dist/lite/index.d.ts", "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", - "api-report": "run-s --npm-path npm api-report:main api-report:pipelines api-report:all-packages api-report:lite api-report:lite:pipelines && yarn api-report:api-json", + "api-report": "run-s --npm-path npm api-report:main api-report:pipelines api-report:lite api-report:lite:pipelines && yarn api-report:api-json", "doc": "api-documenter markdown --input temp --output docs", "typings:public": "node ../../scripts/build/use_typings.js ./dist/all-packages.d.ts" }, diff --git a/packages/firestore/pipelines/pipelines.d.ts b/packages/firestore/pipelines/pipelines.d.ts index 5e7944e5731..e7edb233991 100644 --- a/packages/firestore/pipelines/pipelines.d.ts +++ b/packages/firestore/pipelines/pipelines.d.ts @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {PipelineSource, Pipeline} from "../dist/pipelines"; +import { PipelineSource, Pipeline } from '../dist/pipelines'; // Augument the Firestore and Query classes with the pipeline() method. // This is stripped from dist/lite/pipelines.d.ts during the build @@ -23,9 +23,6 @@ declare module '@firebase/firestore' { interface Firestore { pipeline(): PipelineSource; } - interface Query { - pipeline(): Pipeline; - } } -export * from "../dist/pipelines"; +export * from '../dist/pipelines'; diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index 14390d57b49..ea969c6b94c 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -224,8 +224,7 @@ export { isBase64Available as _isBase64Available } from './platform/base64'; export { DatabaseId as _DatabaseId } from './core/database_info'; export { _internalQueryToProtoQueryTarget, - _internalAggregationQueryToProtoRunAggregationQueryRequest, - _internalPipelineToExecutePipelineRequestProto + _internalAggregationQueryToProtoRunAggregationQueryRequest } from './remote/internal_serializer'; export { cast as _cast, diff --git a/packages/firestore/src/api/parse_context.ts b/packages/firestore/src/api/parse_context.ts index ce3c221f66e..2381bcff4cd 100644 --- a/packages/firestore/src/api/parse_context.ts +++ b/packages/firestore/src/api/parse_context.ts @@ -16,8 +16,53 @@ */ import { DatabaseId } from '../core/database_info'; +import { UserDataSource } from '../lite-api/user_data_reader'; +import { DocumentKey } from '../model/document_key'; +import { FieldTransform } from '../model/mutation'; +import { FieldPath as InternalFieldPath } from '../model/path'; +import { JsonProtoSerializer } from '../remote/serializer'; +import { FirestoreError } from '../util/error'; + +/** Contains the settings that are mutated as we parse user data. */ +export interface ContextSettings { + /** Indicates what kind of API method this data came from. */ + readonly dataSource: UserDataSource; + /** The name of the method the user called to create the ParseContext. */ + readonly methodName: string; + /** The document the user is attempting to modify, if that applies. */ + readonly targetDoc?: DocumentKey; + /** + * A path within the object being parsed. This could be an empty path (in + * which case the context represents the root of the data being parsed), or a + * nonempty path (indicating the context represents a nested location within + * the data). + */ + readonly path?: InternalFieldPath; + /** + * Whether or not this context corresponds to an element of an array. + * If not set, elements are treated as if they were outside of arrays. + */ + readonly arrayElement?: boolean; + /** + * Whether or not a converter was specified in this context. If true, error + * messages will reference the converter when invalid data is provided. + */ + readonly hasConverter?: boolean; +} export interface ParseContext { + readonly settings: ContextSettings; readonly databaseId: DatabaseId; + readonly serializer: JsonProtoSerializer; readonly ignoreUndefinedProperties: boolean; + fieldTransforms: FieldTransform[]; + fieldMask: InternalFieldPath[]; + get path(): InternalFieldPath | undefined; + get dataSource(): UserDataSource; + contextWith(configuration: Partial): ParseContext; + childContextForField(field: string): ParseContext; + childContextForFieldPath(field: InternalFieldPath): ParseContext; + childContextForArray(index: number): ParseContext; + createError(reason: string): FirestoreError; + contains(fieldPath: InternalFieldPath): boolean; } diff --git a/packages/firestore/src/api/pipeline.ts b/packages/firestore/src/api/pipeline.ts index aaddf00274b..e7cd6e875eb 100644 --- a/packages/firestore/src/api/pipeline.ts +++ b/packages/firestore/src/api/pipeline.ts @@ -15,16 +15,12 @@ * limitations under the License. */ -import { firestoreClientExecutePipeline } from '../core/firestore_client'; import { Pipeline as LitePipeline } from '../lite-api/pipeline'; -import { PipelineResult } from '../lite-api/pipeline-result'; -import { DocumentReference } from '../lite-api/reference'; import { Stage } from '../lite-api/stage'; import { UserDataReader } from '../lite-api/user_data_reader'; import { AbstractUserDataWriter } from '../lite-api/user_data_writer'; -import { cast } from '../util/input_validation'; -import { ensureFirestoreConfigured, Firestore } from './database'; +import { Firestore } from './database'; export class Pipeline extends LitePipeline { /** @@ -41,66 +37,8 @@ export class Pipeline extends LitePipeline { db: Firestore, userDataReader: UserDataReader, userDataWriter: AbstractUserDataWriter, - stages: Stage[], - converter: unknown = {} + stages: Stage[] ): Pipeline { return new Pipeline(db, userDataReader, userDataWriter, stages); } - - /** - * Executes this pipeline and returns a Promise to represent the asynchronous operation. - * - *

The returned Promise can be used to track the progress of the pipeline execution - * and retrieve the results (or handle any errors) asynchronously. - * - *

The pipeline results are returned as a list of {@link PipelineResult} objects. Each {@link - * PipelineResult} typically represents a single key/value map that has passed through all the - * stages of the pipeline, however this might differ depending on the stages involved in the - * pipeline. For example: - * - *

    - *
  • If there are no stages or only transformation stages, each {@link PipelineResult} - * represents a single document.
  • - *
  • If there is an aggregation, only a single {@link PipelineResult} is returned, - * representing the aggregated results over the entire dataset .
  • - *
  • If there is an aggregation stage with grouping, each {@link PipelineResult} represents a - * distinct group and its associated aggregated values.
  • - *
- * - *

Example: - * - * ```typescript - * const futureResults = await firestore.pipeline().collection("books") - * .where(gt(Field.of("rating"), 4.5)) - * .select("title", "author", "rating") - * .execute(); - * ``` - * - * @return A Promise representing the asynchronous pipeline execution. - */ - execute(): Promise { - const firestore = cast(this._db, Firestore); - const client = ensureFirestoreConfigured(firestore); - return firestoreClientExecutePipeline(client, this).then(result => { - const docs = result - // Currently ignore any response from ExecutePipeline that does - // not contain any document data in the `fields` property. - .filter(element => !!element.fields) - .map( - element => - new PipelineResult( - this._userDataWriter, - element.key?.path - ? new DocumentReference(firestore, null, element.key) - : undefined, - element.fields, - element.executionTime?.toTimestamp(), - element.createTime?.toTimestamp(), - element.updateTime?.toTimestamp() - ) - ); - - return docs; - }); - } } diff --git a/packages/firestore/src/api/pipeline_impl.ts b/packages/firestore/src/api/pipeline_impl.ts index 9e7c25e69ab..ba6e08105bb 100644 --- a/packages/firestore/src/api/pipeline_impl.ts +++ b/packages/firestore/src/api/pipeline_impl.ts @@ -16,16 +16,16 @@ */ import { Pipeline } from '../api/pipeline'; -import { toPipeline } from '../core/pipeline-util'; +import { firestoreClientExecutePipeline } from '../core/firestore_client'; import { Pipeline as LitePipeline } from '../lite-api/pipeline'; -import { PipelineResult } from '../lite-api/pipeline-result'; +import { PipelineResult, PipelineSnapshot } from '../lite-api/pipeline-result'; import { PipelineSource } from '../lite-api/pipeline-source'; import { Stage } from '../lite-api/stage'; import { newUserDataReader } from '../lite-api/user_data_reader'; import { cast } from '../util/input_validation'; -import { Firestore } from './database'; -import { Query } from './reference'; +import { ensureFirestoreConfigured, Firestore } from './database'; +import { DocumentReference } from './reference'; import { ExpUserDataWriter } from './user_data_writer'; declare module './database' { @@ -35,49 +35,78 @@ declare module './database' { } /** - * Experimental Modular API for console testing. - * @param firestore - */ -export function pipeline(firestore: Firestore): PipelineSource; - -/** - * Experimental Modular API for console testing. - * @param query + * Executes this pipeline and returns a Promise to represent the asynchronous operation. + * + * The returned Promise can be used to track the progress of the pipeline execution + * and retrieve the results (or handle any errors) asynchronously. + * + * The pipeline results are returned as a {@link PipelineSnapshot} that contains + * a list of {@link PipelineResult} objects. Each {@link PipelineResult} typically + * represents a single key/value map that has passed through all the + * stages of the pipeline, however this might differ depending on the stages involved in the + * pipeline. For example: + * + *

    + *
  • If there are no stages or only transformation stages, each {@link PipelineResult} + * represents a single document.
  • + *
  • If there is an aggregation, only a single {@link PipelineResult} is returned, + * representing the aggregated results over the entire dataset .
  • + *
  • If there is an aggregation stage with grouping, each {@link PipelineResult} represents a + * distinct group and its associated aggregated values.
  • + *
+ * + *

Example: + * + * ```typescript + * const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + * .where(gt(field("rating"), 4.5)) + * .select("title", "author", "rating")); + * + * const results: PipelineResults = snapshot.results; + * ``` + * + * @param pipeline The pipeline to execute. + * @return A Promise representing the asynchronous pipeline execution. */ -export function pipeline(query: Query): Pipeline; +export function execute(pipeline: LitePipeline): Promise { + const firestore = cast(pipeline._db, Firestore); + const client = ensureFirestoreConfigured(firestore); + return firestoreClientExecutePipeline(client, pipeline).then(result => { + // Get the execution time from the first result. + // firestoreClientExecutePipeline returns at least one PipelineStreamElement + // even if the returned document set is empty. + const executionTime = + result.length > 0 ? result[0].executionTime?.toTimestamp() : undefined; -export function pipeline( - firestoreOrQuery: Firestore | Query -): PipelineSource | Pipeline { - if (firestoreOrQuery instanceof Firestore) { - const firestore = firestoreOrQuery; - return new PipelineSource((stages: Stage[]) => { - return new Pipeline( - firestore, - newUserDataReader(firestore), - new ExpUserDataWriter(firestore), - stages + const docs = result + // Currently ignore any response from ExecutePipeline that does + // not contain any document data in the `fields` property. + .filter(element => !!element.fields) + .map( + element => + new PipelineResult( + pipeline._userDataWriter, + element.key?.path + ? new DocumentReference(firestore, null, element.key) + : undefined, + element.fields, + element.createTime?.toTimestamp(), + element.updateTime?.toTimestamp() + ) ); - }); - } else { - const query = firestoreOrQuery; - const db = cast(query.firestore, Firestore); - const litePipeline: LitePipeline = toPipeline(query._query, db); - return cast(litePipeline, Pipeline); - } -} - -export function execute(pipeline: LitePipeline): Promise { - return pipeline.execute(); + return new PipelineSnapshot(pipeline, docs, executionTime); + }); } // Augment the Firestore class with the pipeline() factory method Firestore.prototype.pipeline = function (): PipelineSource { - return pipeline(this); -}; - -// Augment the Query class with the pipeline() factory method -Query.prototype.pipeline = function (): Pipeline { - return pipeline(this); + return new PipelineSource(this._databaseId, (stages: Stage[]) => { + return new Pipeline( + this, + newUserDataReader(this), + new ExpUserDataWriter(this), + stages + ); + }); }; diff --git a/packages/firestore/src/api_pipelines.ts b/packages/firestore/src/api_pipelines.ts index 90fe836932f..ad7815af3e4 100644 --- a/packages/firestore/src/api_pipelines.ts +++ b/packages/firestore/src/api_pipelines.ts @@ -17,11 +17,15 @@ export { PipelineSource } from './lite-api/pipeline-source'; -export { PipelineResult } from './lite-api/pipeline-result'; +export { + PipelineResult, + PipelineSnapshot, + pipelineResultEqual +} from './lite-api/pipeline-result'; export { Pipeline } from './api/pipeline'; -export { pipeline, execute } from './api/pipeline_impl'; +export { execute } from './api/pipeline_impl'; export { Stage, @@ -43,6 +47,8 @@ export { } from './lite-api/stage'; export { + field, + constant, add, subtract, multiply, @@ -85,11 +91,11 @@ export { strConcat, mapGet, countAll, - countFunction, - sumFunction, - avgFunction, - andFunction, - orFunction, + count, + sum, + avg, + and, + or, minimum, maximum, cosineDistance, @@ -104,84 +110,44 @@ export { timestampToUnixSeconds, timestampAdd, timestampSub, - genericFunction, ascending, descending, + countIf, + bitAnd, + bitOr, + bitXor, + bitNot, + bitLeftShift, + bitRightShift, + rand, + array, + arrayOffset, + currentContext, + isError, + ifError, + isAbsent, + isNull, + isNotNull, + isNotNan, + map, + mapRemove, + mapMerge, + documentId, + substr, Expr, ExprWithAlias, Field, - Fields, Constant, - FirestoreFunction, - Add, - Subtract, - Multiply, - Divide, - Mod, - Eq, - Neq, - Lt, - Lte, - Gt, - Gte, - ArrayConcat, - ArrayReverse, - ArrayContains, - ArrayContainsAll, - ArrayContainsAny, - ArrayLength, - ArrayElement, - EqAny, - NotEqAny, - IsNan, - Exists, - Not, - And, - Or, - Xor, - Cond, - LogicalMaximum, - LogicalMinimum, - Reverse, - ReplaceFirst, - ReplaceAll, - CharLength, - ByteLength, - Like, - RegexContains, - RegexMatch, - StrContains, - StartsWith, - EndsWith, - ToLower, - ToUpper, - Trim, - StrConcat, - MapGet, - Count, - Sum, - Avg, - Minimum, - Maximum, - CosineDistance, - DotProduct, - EuclideanDistance, - VectorLength, - UnixMicrosToTimestamp, - TimestampToUnixMicros, - UnixMillisToTimestamp, - TimestampToUnixMillis, - UnixSecondsToTimestamp, - TimestampToUnixSeconds, - TimestampAdd, - TimestampSub, + FunctionExpr, Ordering } from './lite-api/expressions'; export type { ExprType, - AccumulatorTarget, + AggregateWithAlias, Selectable, - FilterCondition, - Accumulator + BooleanExpr, + AggregateFunction } from './lite-api/expressions'; + +export { _internalPipelineToExecutePipelineRequestProto } from './remote/internal_serializer'; diff --git a/packages/firestore/src/core/pipeline-util.ts b/packages/firestore/src/core/pipeline-util.ts index 0800eba85ea..4409daffc2e 100644 --- a/packages/firestore/src/core/pipeline-util.ts +++ b/packages/firestore/src/core/pipeline-util.ts @@ -15,22 +15,20 @@ * limitations under the License. */ -import { Firestore } from '../api/database'; +import { Firestore } from '../lite-api/database'; import { Constant, Field, - FilterCondition, - not, - andFunction, - orFunction, + BooleanExpr, + and, + or, Ordering, - And, lt, gt, lte, gte, eq, - Or + field } from '../lite-api/expressions'; import { Pipeline } from '../lite-api/pipeline'; import { doc } from '../lite-api/reference'; @@ -56,56 +54,89 @@ import { /* eslint @typescript-eslint/no-explicit-any: 0 */ -export function toPipelineFilterCondition(f: FilterInternal): FilterCondition { +export function toPipelineBooleanExpr(f: FilterInternal): BooleanExpr { if (f instanceof FieldFilterInternal) { - const field = Field.of(f.field.toString()); + const fieldValue = field(f.field.toString()); if (isNanValue(f.value)) { if (f.op === Operator.EQUAL) { - return andFunction(field.exists(), field.isNaN()); + return and(fieldValue.exists(), fieldValue.isNan()); } else { - return andFunction(field.exists(), not(field.isNaN())); + return and(fieldValue.exists(), fieldValue.isNotNan()); } } else if (isNullValue(f.value)) { if (f.op === Operator.EQUAL) { - return andFunction(field.exists(), field.eq(null)); + return and(fieldValue.exists(), fieldValue.isNull()); } else { - return andFunction(field.exists(), not(field.eq(null))); + return and(fieldValue.exists(), fieldValue.isNotNull()); } } else { // Comparison filters const value = f.value; switch (f.op) { case Operator.LESS_THAN: - return andFunction(field.exists(), field.lt(value)); + return and( + fieldValue.exists(), + fieldValue.lt(Constant._fromProto(value)) + ); case Operator.LESS_THAN_OR_EQUAL: - return andFunction(field.exists(), field.lte(value)); + return and( + fieldValue.exists(), + fieldValue.lte(Constant._fromProto(value)) + ); case Operator.GREATER_THAN: - return andFunction(field.exists(), field.gt(value)); + return and( + fieldValue.exists(), + fieldValue.gt(Constant._fromProto(value)) + ); case Operator.GREATER_THAN_OR_EQUAL: - return andFunction(field.exists(), field.gte(value)); + return and( + fieldValue.exists(), + fieldValue.gte(Constant._fromProto(value)) + ); case Operator.EQUAL: - return andFunction(field.exists(), field.eq(value)); + return and( + fieldValue.exists(), + fieldValue.eq(Constant._fromProto(value)) + ); case Operator.NOT_EQUAL: - return andFunction(field.exists(), field.neq(value)); + return and( + fieldValue.exists(), + fieldValue.neq(Constant._fromProto(value)) + ); case Operator.ARRAY_CONTAINS: - return andFunction(field.exists(), field.arrayContains(value)); + return and( + fieldValue.exists(), + fieldValue.arrayContains(Constant._fromProto(value)) + ); case Operator.IN: { const values = value?.arrayValue?.values?.map((val: any) => - Constant.of(val) + Constant._fromProto(val) ); - return andFunction(field.exists(), field.eqAny(...values!)); + if (!values) { + return and(fieldValue.exists(), fieldValue.eqAny([])); + } else if (values.length === 1) { + return and(fieldValue.exists(), fieldValue.eq(values[0])); + } else { + return and(fieldValue.exists(), fieldValue.eqAny(values)); + } } case Operator.ARRAY_CONTAINS_ANY: { const values = value?.arrayValue?.values?.map((val: any) => - Constant.of(val) + Constant._fromProto(val) ); - return andFunction(field.exists(), field.arrayContainsAny(values!)); + return and(fieldValue.exists(), fieldValue.arrayContainsAny(values!)); } case Operator.NOT_IN: { const values = value?.arrayValue?.values?.map((val: any) => - Constant.of(val) + Constant._fromProto(val) ); - return andFunction(field.exists(), not(field.eqAny(...values!))); + if (!values) { + return and(fieldValue.exists(), fieldValue.notEqAny([])); + } else if (values.length === 1) { + return and(fieldValue.exists(), fieldValue.neq(values[0])); + } else { + return and(fieldValue.exists(), fieldValue.notEqAny(values)); + } } default: fail('Unexpected operator'); @@ -114,16 +145,12 @@ export function toPipelineFilterCondition(f: FilterInternal): FilterCondition { } else if (f instanceof CompositeFilterInternal) { switch (f.op) { case CompositeOperator.AND: { - const conditions = f - .getFilters() - .map(f => toPipelineFilterCondition(f)); - return andFunction(conditions[0], ...conditions.slice(1)); + const conditions = f.getFilters().map(f => toPipelineBooleanExpr(f)); + return and(conditions[0], conditions[1], ...conditions.slice(2)); } case CompositeOperator.OR: { - const conditions = f - .getFilters() - .map(f => toPipelineFilterCondition(f)); - return orFunction(conditions[0], ...conditions.slice(1)); + const conditions = f.getFilters().map(f => toPipelineBooleanExpr(f)); + return or(conditions[0], conditions[1], ...conditions.slice(2)); } default: fail('Unexpected operator'); @@ -155,17 +182,21 @@ export function toPipeline(query: Query, db: Firestore): Pipeline { // filters for (const filter of query.filters) { - pipeline = pipeline.where(toPipelineFilterCondition(filter)); + pipeline = pipeline.where(toPipelineBooleanExpr(filter)); } // orders const orders = queryNormalizedOrderBy(query); const existsConditions = orders.map(order => - Field.of(order.field.canonicalString()).exists() + field(order.field.canonicalString()).exists() ); if (existsConditions.length > 1) { pipeline = pipeline.where( - andFunction(existsConditions[0], ...existsConditions.slice(1)) + and( + existsConditions[0], + existsConditions[1], + ...existsConditions.slice(2) + ) ); } else { pipeline = pipeline.where(existsConditions[0]); @@ -173,42 +204,45 @@ export function toPipeline(query: Query, db: Firestore): Pipeline { const orderings = orders.map(order => order.dir === Direction.ASCENDING - ? Field.of(order.field.canonicalString()).ascending() - : Field.of(order.field.canonicalString()).descending() + ? field(order.field.canonicalString()).ascending() + : field(order.field.canonicalString()).descending() ); - if (query.limitType === LimitType.Last) { - pipeline = pipeline.sort(...reverseOrderings(orderings)); - // cursors - if (query.startAt !== null) { - pipeline = pipeline.where( - whereConditionsFromCursor(query.startAt, orderings, 'before') - ); - } + if (orderings.length > 0) { + if (query.limitType === LimitType.Last) { + const actualOrderings = reverseOrderings(orderings); + pipeline = pipeline.sort(actualOrderings[0], ...actualOrderings.slice(1)); + // cursors + if (query.startAt !== null) { + pipeline = pipeline.where( + whereConditionsFromCursor(query.startAt, orderings, 'after') + ); + } - if (query.endAt !== null) { - pipeline = pipeline.where( - whereConditionsFromCursor(query.endAt, orderings, 'after') - ); - } + if (query.endAt !== null) { + pipeline = pipeline.where( + whereConditionsFromCursor(query.endAt, orderings, 'before') + ); + } - pipeline = pipeline._limit(query.limit!, true); - pipeline = pipeline.sort(...orderings); - } else { - pipeline = pipeline.sort(...orderings); - if (query.startAt !== null) { - pipeline = pipeline.where( - whereConditionsFromCursor(query.startAt, orderings, 'after') - ); - } - if (query.endAt !== null) { - pipeline = pipeline.where( - whereConditionsFromCursor(query.endAt, orderings, 'before') - ); - } + pipeline = pipeline._limit(query.limit!, true); + pipeline = pipeline.sort(orderings[0], ...orderings.slice(1)); + } else { + pipeline = pipeline.sort(orderings[0], ...orderings.slice(1)); + if (query.startAt !== null) { + pipeline = pipeline.where( + whereConditionsFromCursor(query.startAt, orderings, 'after') + ); + } + if (query.endAt !== null) { + pipeline = pipeline.where( + whereConditionsFromCursor(query.endAt, orderings, 'before') + ); + } - if (query.limit !== null) { - pipeline = pipeline.limit(query.limit); + if (query.limit !== null) { + pipeline = pipeline.limit(query.limit); + } } } @@ -219,19 +253,19 @@ function whereConditionsFromCursor( bound: Bound, orderings: Ordering[], position: 'before' | 'after' -): FilterCondition { +): BooleanExpr { const cursors = bound.position.map(value => Constant._fromProto(value)); const filterFunc = position === 'before' ? lt : gt; const filterInclusiveFunc = position === 'before' ? lte : gte; - const orConditions = []; + const orConditions: BooleanExpr[] = []; for (let i = 1; i <= orderings.length; i++) { const cursorSubset = cursors.slice(0, i); - const conditions = cursorSubset.map((cursor, index) => { + const conditions: BooleanExpr[] = cursorSubset.map((cursor, index) => { if (index < cursorSubset.length - 1) { return eq(orderings[index].expr as Field, cursor); - } else if (!!bound.inclusive && i === orderings.length) { + } else if (bound.inclusive && i === orderings.length - 1) { return filterInclusiveFunc(orderings[index].expr as Field, cursor); } else { return filterFunc(orderings[index].expr as Field, cursor); @@ -241,13 +275,15 @@ function whereConditionsFromCursor( if (conditions.length === 1) { orConditions.push(conditions[0]); } else { - orConditions.push(new And(conditions)); + orConditions.push( + and(conditions[0], conditions[1], ...conditions.slice(2)) + ); } } if (orConditions.length === 1) { return orConditions[0]; } else { - return new Or(orConditions); + return or(orConditions[0], orConditions[1], ...orConditions.slice(2)); } } diff --git a/packages/firestore/src/lite-api/expressions.ts b/packages/firestore/src/lite-api/expressions.ts index 03e5c5e747e..6eaebf2c4f0 100644 --- a/packages/firestore/src/lite-api/expressions.ts +++ b/packages/firestore/src/lite-api/expressions.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -/* eslint @typescript-eslint/no-explicit-any: 0 */ - +import { ParseContext } from '../api/parse_context'; import { DOCUMENT_KEY_NAME, FieldPath as InternalFieldPath @@ -24,17 +23,19 @@ import { import { Value as ProtoValue } from '../protos/firestore_proto_api'; import { JsonProtoSerializer, - ProtoSerializable, + ProtoValueSerializable, + toMapValue, toStringValue, UserData } from '../remote/serializer'; import { hardAssert } from '../util/assert'; +import { isPlainObject } from '../util/input_validation'; import { isFirestoreValue } from '../util/proto'; +import { isString } from '../util/types'; import { Bytes } from './bytes'; -import { documentId, FieldPath } from './field_path'; +import { documentId as documentIdFieldPath, FieldPath } from './field_path'; import { GeoPoint } from './geo_point'; -import { Pipeline } from './pipeline'; import { DocumentReference } from './reference'; import { Timestamp } from './timestamp'; import { @@ -54,9 +55,72 @@ export type ExprType = | 'Field' | 'Constant' | 'Function' + | 'AggregateFunction' | 'ListOfExprs' | 'ExprWithAlias'; +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * + * @private + * @internal + * @param value + */ +function valueToDefaultExpr(value: unknown): Expr { + let result: Expr | undefined; + if (value instanceof Expr) { + return value; + } else if (isPlainObject(value)) { + result = map(value as Record); + } else if (value instanceof Array) { + result = array(value); + } else { + result = new Constant(value); + } + + result._createdFromLiteral = true; + return result; +} + +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * + * @private + * @internal + * @param value + */ +function vectorToExpr(value: VectorValue | number[] | Expr): Expr { + if (value instanceof Expr) { + return value; + } else { + const result = constantVector(value); + result._createdFromLiteral = true; + return result; + } +} + +/** + * Converts a value to an Expr, Returning either a Constant, MapFunction, + * ArrayFunction, or the input itself (if it's already an expression). + * If the input is a string, it is assumed to be a field name, and a + * field(value) is returned. + * + * @private + * @internal + * @param value + */ +function fieldOrExpression(value: unknown): Expr { + if (isString(value)) { + const result = field(value); + result._createdFromLiteral = true; + return result; + } else { + return valueToDefaultExpr(value); + } +} + /** * @beta * @@ -69,44 +133,55 @@ export type ExprType = * - **Field references:** Access values from document fields. * - **Literals:** Represent constant values (strings, numbers, booleans). * - **Function calls:** Apply functions to one or more expressions. - * - **Aggregations:** Calculate aggregate values (e.g., sum, average) over a set of documents. * * The `Expr` class provides a fluent API for building expressions. You can chain together * method calls to create complex expressions. */ -export abstract class Expr implements ProtoSerializable, UserData { - abstract exprType: ExprType; +export abstract class Expr implements ProtoValueSerializable, UserData { + abstract readonly exprType: ExprType; /** - * Creates an expression that adds this expression to another expression. - * - * ```typescript - * // Add the value of the 'quantity' field and the 'reserve' field. - * Field.of("quantity").add(Field.of("reserve")); - * ``` - * - * @param other The expression to add to this expression. - * @return A new `Expr` representing the addition operation. + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral: boolean = false; + + /** + * @private + * @internal + */ + abstract _toProto(serializer: JsonProtoSerializer): ProtoValue; + _protoValueType = 'ProtoValue' as const; + + /** + * @private + * @internal */ - add(other: Expr): Add; + abstract _readUserData( + dataReader: UserDataReader, + context?: ParseContext + ): void; /** - * Creates an expression that adds this expression to a constant value. + * Creates an expression that adds this expression to another expression. * * ```typescript - * // Add 5 to the value of the 'age' field - * Field.of("age").add(5); + * // Add the value of the 'quantity' field and the 'reserve' field. + * field("quantity").add(field("reserve")); * ``` * - * @param other The constant value to add. + * @param second The expression or literal to add to this expression. + * @param others Optional additional expressions or literals to add to this expression. * @return A new `Expr` representing the addition operation. */ - add(other: any): Add; - add(other: any): Add { - if (other instanceof Expr) { - return new Add(this, other); - } - return new Add(this, Constant.of(other)); + add(second: Expr | unknown, ...others: Array): FunctionExpr { + const values = [second, ...others]; + return new FunctionExpr('add', [ + this, + ...values.map(value => valueToDefaultExpr(value)) + ]); } /** @@ -114,31 +189,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Subtract the 'discount' field from the 'price' field - * Field.of("price").subtract(Field.of("discount")); + * field("price").subtract(field("discount")); * ``` * * @param other The expression to subtract from this expression. * @return A new `Expr` representing the subtraction operation. */ - subtract(other: Expr): Subtract; + subtract(other: Expr): FunctionExpr; /** * Creates an expression that subtracts a constant value from this expression. * * ```typescript * // Subtract 20 from the value of the 'total' field - * Field.of("total").subtract(20); + * field("total").subtract(20); * ``` * * @param other The constant value to subtract. * @return A new `Expr` representing the subtraction operation. */ - subtract(other: any): Subtract; - subtract(other: any): Subtract { - if (other instanceof Expr) { - return new Subtract(this, other); - } - return new Subtract(this, Constant.of(other)); + subtract(other: number): FunctionExpr; + subtract(other: number | Expr): FunctionExpr { + return new FunctionExpr('subtract', [this, valueToDefaultExpr(other)]); } /** @@ -146,31 +218,22 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Multiply the 'quantity' field by the 'price' field - * Field.of("quantity").multiply(Field.of("price")); - * ``` - * - * @param other The expression to multiply by. - * @return A new `Expr` representing the multiplication operation. - */ - multiply(other: Expr): Multiply; - - /** - * Creates an expression that multiplies this expression by a constant value. - * - * ```typescript - * // Multiply the 'value' field by 2 - * Field.of("value").multiply(2); + * field("quantity").multiply(field("price")); * ``` * - * @param other The constant value to multiply by. + * @param second The second expression or literal to multiply by. + * @param others Optional additional expressions or literals to multiply by. * @return A new `Expr` representing the multiplication operation. */ - multiply(other: any): Multiply; - multiply(other: any): Multiply { - if (other instanceof Expr) { - return new Multiply(this, other); - } - return new Multiply(this, Constant.of(other)); + multiply( + second: Expr | number, + ...others: Array + ): FunctionExpr { + return new FunctionExpr('multiply', [ + this, + valueToDefaultExpr(second), + ...others.map(value => valueToDefaultExpr(value)) + ]); } /** @@ -178,31 +241,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Divide the 'total' field by the 'count' field - * Field.of("total").divide(Field.of("count")); + * field("total").divide(field("count")); * ``` * * @param other The expression to divide by. * @return A new `Expr` representing the division operation. */ - divide(other: Expr): Divide; + divide(other: Expr): FunctionExpr; /** * Creates an expression that divides this expression by a constant value. * * ```typescript * // Divide the 'value' field by 10 - * Field.of("value").divide(10); + * field("value").divide(10); * ``` * * @param other The constant value to divide by. * @return A new `Expr` representing the division operation. */ - divide(other: any): Divide; - divide(other: any): Divide { - if (other instanceof Expr) { - return new Divide(this, other); - } - return new Divide(this, Constant.of(other)); + divide(other: number): FunctionExpr; + divide(other: number | Expr): FunctionExpr { + return new FunctionExpr('divide', [this, valueToDefaultExpr(other)]); } /** @@ -210,237 +270,57 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Calculate the remainder of dividing the 'value' field by the 'divisor' field - * Field.of("value").mod(Field.of("divisor")); + * field("value").mod(field("divisor")); * ``` * - * @param other The expression to divide by. + * @param expression The expression to divide by. * @return A new `Expr` representing the modulo operation. */ - mod(other: Expr): Mod; + mod(expression: Expr): FunctionExpr; /** * Creates an expression that calculates the modulo (remainder) of dividing this expression by a constant value. * * ```typescript * // Calculate the remainder of dividing the 'value' field by 10 - * Field.of("value").mod(10); + * field("value").mod(10); * ``` * - * @param other The constant value to divide by. + * @param value The constant value to divide by. * @return A new `Expr` representing the modulo operation. */ - mod(other: any): Mod; - mod(other: any): Mod { - if (other instanceof Expr) { - return new Mod(this, other); - } - return new Mod(this, Constant.of(other)); + mod(value: number): FunctionExpr; + mod(other: number | Expr): FunctionExpr { + return new FunctionExpr('mod', [this, valueToDefaultExpr(other)]); } - // /** - // * Creates an expression that applies a bitwise AND operation between this expression and another expression. - // * - // * ```typescript - // * // Calculate the bitwise AND of 'field1' and 'field2'. - // * Field.of("field1").bitAnd(Field.of("field2")); - // * ``` - // * - // * @param other The right operand expression. - // * @return A new {@code Expr} representing the bitwise AND operation. - // */ - // bitAnd(other: Expr): BitAnd; - // - // /** - // * Creates an expression that applies a bitwise AND operation between this expression and a constant value. - // * - // * ```typescript - // * // Calculate the bitwise AND of 'field1' and 0xFF. - // * Field.of("field1").bitAnd(0xFF); - // * ``` - // * - // * @param other The right operand constant. - // * @return A new {@code Expr} representing the bitwise AND operation. - // */ - // bitAnd(other: any): BitAnd; - // bitAnd(other: any): BitAnd { - // if (other instanceof Expr) { - // return new BitAnd(this, other); - // } - // return new BitAnd(this, Constant.of(other)); - // } - // - // /** - // * Creates an expression that applies a bitwise OR operation between this expression and another expression. - // * - // * ```typescript - // * // Calculate the bitwise OR of 'field1' and 'field2'. - // * Field.of("field1").bitOr(Field.of("field2")); - // * ``` - // * - // * @param other The right operand expression. - // * @return A new {@code Expr} representing the bitwise OR operation. - // */ - // bitOr(other: Expr): BitOr; - // - // /** - // * Creates an expression that applies a bitwise OR operation between this expression and a constant value. - // * - // * ```typescript - // * // Calculate the bitwise OR of 'field1' and 0xFF. - // * Field.of("field1").bitOr(0xFF); - // * ``` - // * - // * @param other The right operand constant. - // * @return A new {@code Expr} representing the bitwise OR operation. - // */ - // bitOr(other: any): BitOr; - // bitOr(other: any): BitOr { - // if (other instanceof Expr) { - // return new BitOr(this, other); - // } - // return new BitOr(this, Constant.of(other)); - // } - // - // /** - // * Creates an expression that applies a bitwise XOR operation between this expression and another expression. - // * - // * ```typescript - // * // Calculate the bitwise XOR of 'field1' and 'field2'. - // * Field.of("field1").bitXor(Field.of("field2")); - // * ``` - // * - // * @param other The right operand expression. - // * @return A new {@code Expr} representing the bitwise XOR operation. - // */ - // bitXor(other: Expr): BitXor; - // - // /** - // * Creates an expression that applies a bitwise XOR operation between this expression and a constant value. - // * - // * ```typescript - // * // Calculate the bitwise XOR of 'field1' and 0xFF. - // * Field.of("field1").bitXor(0xFF); - // * ``` - // * - // * @param other The right operand constant. - // * @return A new {@code Expr} representing the bitwise XOR operation. - // */ - // bitXor(other: any): BitXor; - // bitXor(other: any): BitXor { - // if (other instanceof Expr) { - // return new BitXor(this, other); - // } - // return new BitXor(this, Constant.of(other)); - // } - // - // /** - // * Creates an expression that applies a bitwise NOT operation to this expression. - // * - // * ```typescript - // * // Calculate the bitwise NOT of 'field1'. - // * Field.of("field1").bitNot(); - // * ``` - // * - // * @return A new {@code Expr} representing the bitwise NOT operation. - // */ - // bitNot(): BitNot { - // return new BitNot(this); - // } - // - // /** - // * Creates an expression that applies a bitwise left shift operation between this expression and another expression. - // * - // * ```typescript - // * // Calculate the bitwise left shift of 'field1' by 'field2' bits. - // * Field.of("field1").bitLeftShift(Field.of("field2")); - // * ``` - // * - // * @param other The right operand expression representing the number of bits to shift. - // * @return A new {@code Expr} representing the bitwise left shift operation. - // */ - // bitLeftShift(other: Expr): BitLeftShift; - // - // /** - // * Creates an expression that applies a bitwise left shift operation between this expression and a constant value. - // * - // * ```typescript - // * // Calculate the bitwise left shift of 'field1' by 2 bits. - // * Field.of("field1").bitLeftShift(2); - // * ``` - // * - // * @param other The right operand constant representing the number of bits to shift. - // * @return A new {@code Expr} representing the bitwise left shift operation. - // */ - // bitLeftShift(other: number): BitLeftShift; - // bitLeftShift(other: Expr | number): BitLeftShift { - // if (typeof other === 'number') { - // return new BitLeftShift(this, Constant.of(other)); - // } - // return new BitLeftShift(this, other as Expr); - // } - // - // /** - // * Creates an expression that applies a bitwise right shift operation between this expression and another expression. - // * - // * ```typescript - // * // Calculate the bitwise right shift of 'field1' by 'field2' bits. - // * Field.of("field1").bitRightShift(Field.of("field2")); - // * ``` - // * - // * @param other The right operand expression representing the number of bits to shift. - // * @return A new {@code Expr} representing the bitwise right shift operation. - // */ - // bitRightShift(other: Expr): BitRightShift; - // - // /** - // * Creates an expression that applies a bitwise right shift operation between this expression and a constant value. - // * - // * ```typescript - // * // Calculate the bitwise right shift of 'field1' by 2 bits. - // * Field.of("field1").bitRightShift(2); - // * ``` - // * - // * @param other The right operand constant representing the number of bits to shift. - // * @return A new {@code Expr} representing the bitwise right shift operation. - // */ - // bitRightShift(other: number): BitRightShift; - // bitRightShift(other: Expr | number): BitRightShift { - // if (typeof other === 'number') { - // return new BitRightShift(this, Constant.of(other)); - // } - // return new BitRightShift(this, other as Expr); - // } - /** * Creates an expression that checks if this expression is equal to another expression. * * ```typescript * // Check if the 'age' field is equal to 21 - * Field.of("age").eq(21); + * field("age").eq(21); * ``` * - * @param other The expression to compare for equality. + * @param expression The expression to compare for equality. * @return A new `Expr` representing the equality comparison. */ - eq(other: Expr): Eq; + eq(expression: Expr): BooleanExpr; /** * Creates an expression that checks if this expression is equal to a constant value. * * ```typescript * // Check if the 'city' field is equal to "London" - * Field.of("city").eq("London"); + * field("city").eq("London"); * ``` * - * @param other The constant value to compare for equality. + * @param value The constant value to compare for equality. * @return A new `Expr` representing the equality comparison. */ - eq(other: any): Eq; - eq(other: any): Eq { - if (other instanceof Expr) { - return new Eq(this, other); - } - return new Eq(this, Constant.of(other)); + eq(value: unknown): BooleanExpr; + eq(other: unknown): BooleanExpr { + return new BooleanExpr('eq', [this, valueToDefaultExpr(other)]); } /** @@ -448,31 +328,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'status' field is not equal to "completed" - * Field.of("status").neq("completed"); + * field("status").neq("completed"); * ``` * - * @param other The expression to compare for inequality. + * @param expression The expression to compare for inequality. * @return A new `Expr` representing the inequality comparison. */ - neq(other: Expr): Neq; + neq(expression: Expr): BooleanExpr; /** * Creates an expression that checks if this expression is not equal to a constant value. * * ```typescript * // Check if the 'country' field is not equal to "USA" - * Field.of("country").neq("USA"); + * field("country").neq("USA"); * ``` * - * @param other The constant value to compare for inequality. + * @param value The constant value to compare for inequality. * @return A new `Expr` representing the inequality comparison. */ - neq(other: any): Neq; - neq(other: any): Neq { - if (other instanceof Expr) { - return new Neq(this, other); - } - return new Neq(this, Constant.of(other)); + neq(value: unknown): BooleanExpr; + neq(other: unknown): BooleanExpr { + return new BooleanExpr('neq', [this, valueToDefaultExpr(other)]); } /** @@ -480,31 +357,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'age' field is less than 'limit' - * Field.of("age").lt(Field.of('limit')); + * field("age").lt(field('limit')); * ``` * - * @param other The expression to compare for less than. + * @param experession The expression to compare for less than. * @return A new `Expr` representing the less than comparison. */ - lt(other: Expr): Lt; + lt(experession: Expr): BooleanExpr; /** * Creates an expression that checks if this expression is less than a constant value. * * ```typescript * // Check if the 'price' field is less than 50 - * Field.of("price").lt(50); + * field("price").lt(50); * ``` * - * @param other The constant value to compare for less than. + * @param value The constant value to compare for less than. * @return A new `Expr` representing the less than comparison. */ - lt(other: any): Lt; - lt(other: any): Lt { - if (other instanceof Expr) { - return new Lt(this, other); - } - return new Lt(this, Constant.of(other)); + lt(value: unknown): BooleanExpr; + lt(other: unknown): BooleanExpr { + return new BooleanExpr('lt', [this, valueToDefaultExpr(other)]); } /** @@ -513,31 +387,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'quantity' field is less than or equal to 20 - * Field.of("quantity").lte(Constant.of(20)); + * field("quantity").lte(constant(20)); * ``` * - * @param other The expression to compare for less than or equal to. + * @param expression The expression to compare for less than or equal to. * @return A new `Expr` representing the less than or equal to comparison. */ - lte(other: Expr): Lte; + lte(expression: Expr): BooleanExpr; /** * Creates an expression that checks if this expression is less than or equal to a constant value. * * ```typescript * // Check if the 'score' field is less than or equal to 70 - * Field.of("score").lte(70); + * field("score").lte(70); * ``` * - * @param other The constant value to compare for less than or equal to. + * @param value The constant value to compare for less than or equal to. * @return A new `Expr` representing the less than or equal to comparison. */ - lte(other: any): Lte; - lte(other: any): Lte { - if (other instanceof Expr) { - return new Lte(this, other); - } - return new Lte(this, Constant.of(other)); + lte(value: unknown): BooleanExpr; + lte(other: unknown): BooleanExpr { + return new BooleanExpr('lte', [this, valueToDefaultExpr(other)]); } /** @@ -545,31 +416,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'age' field is greater than the 'limit' field - * Field.of("age").gt(Field.of("limit")); + * field("age").gt(field("limit")); * ``` * - * @param other The expression to compare for greater than. + * @param expression The expression to compare for greater than. * @return A new `Expr` representing the greater than comparison. */ - gt(other: Expr): Gt; + gt(expression: Expr): BooleanExpr; /** * Creates an expression that checks if this expression is greater than a constant value. * * ```typescript * // Check if the 'price' field is greater than 100 - * Field.of("price").gt(100); + * field("price").gt(100); * ``` * - * @param other The constant value to compare for greater than. + * @param value The constant value to compare for greater than. * @return A new `Expr` representing the greater than comparison. */ - gt(other: any): Gt; - gt(other: any): Gt { - if (other instanceof Expr) { - return new Gt(this, other); - } - return new Gt(this, Constant.of(other)); + gt(value: unknown): BooleanExpr; + gt(other: unknown): BooleanExpr { + return new BooleanExpr('gt', [this, valueToDefaultExpr(other)]); } /** @@ -578,13 +446,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'quantity' field is greater than or equal to field 'requirement' plus 1 - * Field.of("quantity").gte(Field.of('requirement').add(1)); + * field("quantity").gte(field('requirement').add(1)); * ``` * - * @param other The expression to compare for greater than or equal to. + * @param expression The expression to compare for greater than or equal to. * @return A new `Expr` representing the greater than or equal to comparison. */ - gte(other: Expr): Gte; + gte(expression: Expr): BooleanExpr; /** * Creates an expression that checks if this expression is greater than or equal to a constant @@ -592,18 +460,15 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'score' field is greater than or equal to 80 - * Field.of("score").gte(80); + * field("score").gte(80); * ``` * - * @param other The constant value to compare for greater than or equal to. + * @param value The constant value to compare for greater than or equal to. * @return A new `Expr` representing the greater than or equal to comparison. */ - gte(other: any): Gte; - gte(other: any): Gte { - if (other instanceof Expr) { - return new Gte(this, other); - } - return new Gte(this, Constant.of(other)); + gte(value: unknown): BooleanExpr; + gte(other: unknown): BooleanExpr { + return new BooleanExpr('gte', [this, valueToDefaultExpr(other)]); } /** @@ -611,31 +476,19 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Combine the 'items' array with another array field. - * Field.of("items").arrayConcat(Field.of("otherItems")); + * field("items").arrayConcat(field("otherItems")); * ``` - * - * @param arrays The array expressions to concatenate. + * @param secondArray Second array expression or array literal to concatenate. + * @param otherArrays Optional additional array expressions or array literals to concatenate. * @return A new `Expr` representing the concatenated array. */ - arrayConcat(...arrays: Expr[]): ArrayConcat; - - /** - * Creates an expression that concatenates an array with one or more other arrays. - * - * ```typescript - * // Combine the 'tags' array with a new array and an array field - * Field.of("tags").arrayConcat(Arrays.asList("newTag1", "newTag2"), Field.of("otherTag")); - * ``` - * - * @param arrays The arrays to concatenate. - * @return A new `Expr` representing the concatenated arrays. - */ - arrayConcat(...arrays: any[][]): ArrayConcat; - arrayConcat(...arrays: any[]): ArrayConcat { - const exprValues = arrays.map(value => - value instanceof Expr ? value : Constant.of(value) - ); - return new ArrayConcat(this, exprValues); + arrayConcat( + secondArray: Expr | unknown[], + ...otherArrays: Array + ): FunctionExpr { + const elements = [secondArray, ...otherArrays]; + const exprValues = elements.map(value => valueToDefaultExpr(value)); + return new FunctionExpr('array_concat', [this, ...exprValues]); } /** @@ -643,63 +496,63 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'sizes' array contains the value from the 'selectedSize' field - * Field.of("sizes").arrayContains(Field.of("selectedSize")); + * field("sizes").arrayContains(field("selectedSize")); * ``` * - * @param element The element to search for in the array. + * @param expression The element to search for in the array. * @return A new `Expr` representing the 'array_contains' comparison. */ - arrayContains(element: Expr): ArrayContains; + arrayContains(expression: Expr): BooleanExpr; /** * Creates an expression that checks if an array contains a specific value. * * ```typescript * // Check if the 'colors' array contains "red" - * Field.of("colors").arrayContains("red"); + * field("colors").arrayContains("red"); * ``` * - * @param element The element to search for in the array. + * @param value The element to search for in the array. * @return A new `Expr` representing the 'array_contains' comparison. */ - arrayContains(element: any): ArrayContains; - arrayContains(element: any): ArrayContains { - if (element instanceof Expr) { - return new ArrayContains(this, element); - } - return new ArrayContains(this, Constant.of(element)); + arrayContains(value: unknown): BooleanExpr; + arrayContains(element: unknown): BooleanExpr { + return new BooleanExpr('array_contains', [ + this, + valueToDefaultExpr(element) + ]); } /** * Creates an expression that checks if an array contains all the specified elements. * * ```typescript - * // Check if the 'tags' array contains both "news" and "sports" - * Field.of("tags").arrayContainsAll(Field.of("tag1"), Field.of("tag2")); + * // Check if the 'tags' array contains both the value in field "tag1" and the literal value "tag2" + * field("tags").arrayContainsAll([field("tag1"), "tag2"]); * ``` * * @param values The elements to check for in the array. * @return A new `Expr` representing the 'array_contains_all' comparison. */ - arrayContainsAll(...values: Expr[]): ArrayContainsAll; + arrayContainsAll(values: Array): BooleanExpr; /** * Creates an expression that checks if an array contains all the specified elements. * * ```typescript - * // Check if the 'tags' array contains both of the values from field 'tag1' and "tag2" - * Field.of("tags").arrayContainsAll(Field.of("tag1"), Field.of("tag2")); + * // Check if the 'tags' array contains both of the values from field "tag1" and the literal value "tag2" + * field("tags").arrayContainsAll(array([field("tag1"), "tag2"])); * ``` * - * @param values The elements to check for in the array. + * @param arrayExpression The elements to check for in the array. * @return A new `Expr` representing the 'array_contains_all' comparison. */ - arrayContainsAll(...values: any[]): ArrayContainsAll; - arrayContainsAll(...values: any[]): ArrayContainsAll { - const exprValues = values.map(value => - value instanceof Expr ? value : Constant.of(value) - ); - return new ArrayContainsAll(this, exprValues); + arrayContainsAll(arrayExpression: Expr): BooleanExpr; + arrayContainsAll(values: unknown[] | Expr): BooleanExpr { + const normalizedExpr = Array.isArray(values) + ? new ListOfExprs(values.map(valueToDefaultExpr)) + : values; + return new BooleanExpr('array_contains_all', [this, normalizedExpr]); } /** @@ -707,13 +560,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'categories' array contains either values from field "cate1" or "cate2" - * Field.of("categories").arrayContainsAny(Field.of("cate1"), Field.of("cate2")); + * field("categories").arrayContainsAny([field("cate1"), field("cate2")]); * ``` * * @param values The elements to check for in the array. * @return A new `Expr` representing the 'array_contains_any' comparison. */ - arrayContainsAny(...values: Expr[]): ArrayContainsAny; + arrayContainsAny(values: Array): BooleanExpr; /** * Creates an expression that checks if an array contains any of the specified elements. @@ -721,18 +574,18 @@ export abstract class Expr implements ProtoSerializable, UserData { * ```typescript * // Check if the 'groups' array contains either the value from the 'userGroup' field * // or the value "guest" - * Field.of("groups").arrayContainsAny(Field.of("userGroup"), "guest"); + * field("groups").arrayContainsAny(array([field("userGroup"), "guest"])); * ``` * - * @param values The elements to check for in the array. + * @param arrayExpression The elements to check for in the array. * @return A new `Expr` representing the 'array_contains_any' comparison. */ - arrayContainsAny(...values: any[]): ArrayContainsAny; - arrayContainsAny(...values: any[]): ArrayContainsAny { - const exprValues = values.map(value => - value instanceof Expr ? value : Constant.of(value) - ); - return new ArrayContainsAny(this, exprValues); + arrayContainsAny(arrayExpression: Expr): BooleanExpr; + arrayContainsAny(values: Array | Expr): BooleanExpr { + const normalizedExpr = Array.isArray(values) + ? new ListOfExprs(values.map(valueToDefaultExpr)) + : values; + return new BooleanExpr('array_contains_any', [this, normalizedExpr]); } /** @@ -740,13 +593,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Get the number of items in the 'cart' array - * Field.of("cart").arrayLength(); + * field("cart").arrayLength(); * ``` * * @return A new `Expr` representing the length of the array. */ - arrayLength(): ArrayLength { - return new ArrayLength(this); + arrayLength(): FunctionExpr { + return new FunctionExpr('array_length', [this]); } /** @@ -755,13 +608,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * Field.of("category").eqAny("Electronics", Field.of("primaryType")); + * field("category").eqAny("Electronics", field("primaryType")); * ``` * - * @param others The values or expressions to check against. + * @param values The values or expressions to check against. * @return A new `Expr` representing the 'IN' comparison. */ - eqAny(...others: Expr[]): EqAny; + eqAny(values: Array): BooleanExpr; /** * Creates an expression that checks if this expression is equal to any of the provided values or @@ -769,18 +622,18 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * Field.of("category").eqAny("Electronics", Field.of("primaryType")); + * field("category").eqAny(array(["Electronics", field("primaryType")])); * ``` * - * @param others The values or expressions to check against. + * @param arrayExpression An expression that evaluates to an array of values to check against. * @return A new `Expr` representing the 'IN' comparison. */ - eqAny(...others: any[]): EqAny; - eqAny(...others: any[]): EqAny { - const exprOthers = others.map(other => - other instanceof Expr ? other : Constant.of(other) - ); - return new EqAny(this, exprOthers); + eqAny(arrayExpression: Expr): BooleanExpr; + eqAny(others: unknown[] | Expr): BooleanExpr { + const exprOthers = Array.isArray(others) + ? new ListOfExprs(others.map(valueToDefaultExpr)) + : others; + return new BooleanExpr('eq_any', [this, exprOthers]); } /** @@ -789,32 +642,31 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * Field.of("status").notEqAny("pending", Field.of("rejectedStatus")); + * field("status").notEqAny(["pending", field("rejectedStatus")]); * ``` * - * @param others The values or expressions to check against. + * @param values The values or expressions to check against. * @return A new `Expr` representing the 'NotEqAny' comparison. */ - notEqAny(...others: Expr[]): NotEqAny; + notEqAny(values: Array): BooleanExpr; /** - * Creates an expression that checks if this expression is not equal to any of the provided values or - * expressions. + * Creates an expression that checks if this expression is not equal to any of the values in the evaluated expression. * * ```typescript - * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * Field.of("status").notEqAny("pending", Field.of("rejectedStatus")); + * // Check if the 'status' field is not equal to any value in the field 'rejectedStatuses' + * field("status").notEqAny(field('rejectedStatuses')); * ``` * - * @param others The values or expressions to check against. + * @param arrayExpression The values or expressions to check against. * @return A new `Expr` representing the 'NotEqAny' comparison. */ - notEqAny(...others: any[]): NotEqAny; - notEqAny(...others: any[]): NotEqAny { - const exprOthers = others.map(other => - other instanceof Expr ? other : Constant.of(other) - ); - return new NotEqAny(this, exprOthers); + notEqAny(arrayExpression: Expr): BooleanExpr; + notEqAny(others: unknown[] | Expr): BooleanExpr { + const exprOthers = Array.isArray(others) + ? new ListOfExprs(others.map(valueToDefaultExpr)) + : others; + return new BooleanExpr('not_eq_any', [this, exprOthers]); } /** @@ -822,13 +674,27 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the result of a calculation is NaN - * Field.of("value").divide(0).isNaN(); + * field("value").divide(0).isNaN(); * ``` * * @return A new `Expr` representing the 'isNaN' check. */ - isNaN(): IsNan { - return new IsNan(this); + isNan(): BooleanExpr { + return new BooleanExpr('is_nan', [this]); + } + + /** + * 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(): BooleanExpr { + return new BooleanExpr('is_null', [this]); } /** @@ -836,13 +702,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the document has a field named "phoneNumber" - * Field.of("phoneNumber").exists(); + * field("phoneNumber").exists(); * ``` * * @return A new `Expr` representing the 'exists' check. */ - exists(): Exists { - return new Exists(this); + exists(): BooleanExpr { + return new BooleanExpr('exists', [this]); } /** @@ -850,13 +716,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Get the character length of the 'name' field in its UTF-8 form. - * Field.of("name").charLength(); + * field("name").charLength(); * ``` * * @return A new `Expr` representing the length of the string. */ - charLength(): CharLength { - return new CharLength(this); + charLength(): FunctionExpr { + return new FunctionExpr('char_length', [this]); } /** @@ -864,31 +730,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'title' field contains the word "guide" (case-sensitive) - * Field.of("title").like("%guide%"); + * field("title").like("%guide%"); * ``` * * @param pattern The pattern to search for. You can use "%" as a wildcard character. * @return A new `Expr` representing the 'like' comparison. */ - like(pattern: string): Like; + like(pattern: string): FunctionExpr; /** * Creates an expression that performs a case-sensitive string comparison. * * ```typescript * // Check if the 'title' field contains the word "guide" (case-sensitive) - * Field.of("title").like("%guide%"); + * field("title").like("%guide%"); * ``` * * @param pattern The pattern to search for. You can use "%" as a wildcard character. * @return A new `Expr` representing the 'like' comparison. */ - like(pattern: Expr): Like; - like(stringOrExpr: string | Expr): Like { - if (typeof stringOrExpr === 'string') { - return new Like(this, Constant.of(stringOrExpr)); - } - return new Like(this, stringOrExpr as Expr); + like(pattern: Expr): FunctionExpr; + like(stringOrExpr: string | Expr): FunctionExpr { + return new FunctionExpr('like', [this, valueToDefaultExpr(stringOrExpr)]); } /** @@ -897,13 +760,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'description' field contains "example" (case-insensitive) - * Field.of("description").regexContains("(?i)example"); + * field("description").regexContains("(?i)example"); * ``` * * @param pattern The regular expression to use for the search. * @return A new `Expr` representing the 'contains' comparison. */ - regexContains(pattern: string): RegexContains; + regexContains(pattern: string): BooleanExpr; /** * Creates an expression that checks if a string contains a specified regular expression as a @@ -911,18 +774,18 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'description' field contains the regular expression stored in field 'regex' - * Field.of("description").regexContains(Field.of("regex")); + * field("description").regexContains(field("regex")); * ``` * * @param pattern The regular expression to use for the search. * @return A new `Expr` representing the 'contains' comparison. */ - regexContains(pattern: Expr): RegexContains; - regexContains(stringOrExpr: string | Expr): RegexContains { - if (typeof stringOrExpr === 'string') { - return new RegexContains(this, Constant.of(stringOrExpr)); - } - return new RegexContains(this, stringOrExpr as Expr); + regexContains(pattern: Expr): BooleanExpr; + regexContains(stringOrExpr: string | Expr): BooleanExpr { + return new BooleanExpr('regex_contains', [ + this, + valueToDefaultExpr(stringOrExpr) + ]); } /** @@ -930,31 +793,31 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'email' field matches a valid email pattern - * Field.of("email").regexMatch("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + * field("email").regexMatch("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); * ``` * * @param pattern The regular expression to use for the match. * @return A new `Expr` representing the regular expression match. */ - regexMatch(pattern: string): RegexMatch; + regexMatch(pattern: string): BooleanExpr; /** * Creates an expression that checks if a string matches a specified regular expression. * * ```typescript * // Check if the 'email' field matches a regular expression stored in field 'regex' - * Field.of("email").regexMatch(Field.of("regex")); + * field("email").regexMatch(field("regex")); * ``` * * @param pattern The regular expression to use for the match. * @return A new `Expr` representing the regular expression match. */ - regexMatch(pattern: Expr): RegexMatch; - regexMatch(stringOrExpr: string | Expr): RegexMatch { - if (typeof stringOrExpr === 'string') { - return new RegexMatch(this, Constant.of(stringOrExpr)); - } - return new RegexMatch(this, stringOrExpr as Expr); + regexMatch(pattern: Expr): BooleanExpr; + regexMatch(stringOrExpr: string | Expr): BooleanExpr { + return new BooleanExpr('regex_match', [ + this, + valueToDefaultExpr(stringOrExpr) + ]); } /** @@ -962,31 +825,31 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'description' field contains "example". - * Field.of("description").strContains("example"); + * field("description").strContains("example"); * ``` * * @param substring The substring to search for. * @return A new `Expr` representing the 'contains' comparison. */ - strContains(substring: string): StrContains; + strContains(substring: string): BooleanExpr; /** * Creates an expression that checks if a string contains the string represented by another expression. * * ```typescript * // Check if the 'description' field contains the value of the 'keyword' field. - * Field.of("description").strContains(Field.of("keyword")); + * field("description").strContains(field("keyword")); * ``` * * @param expr The expression representing the substring to search for. * @return A new `Expr` representing the 'contains' comparison. */ - strContains(expr: Expr): StrContains; - strContains(stringOrExpr: string | Expr): StrContains { - if (typeof stringOrExpr === 'string') { - return new StrContains(this, Constant.of(stringOrExpr)); - } - return new StrContains(this, stringOrExpr as Expr); + strContains(expr: Expr): BooleanExpr; + strContains(stringOrExpr: string | Expr): BooleanExpr { + return new BooleanExpr('str_contains', [ + this, + valueToDefaultExpr(stringOrExpr) + ]); } /** @@ -994,13 +857,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'name' field starts with "Mr." - * Field.of("name").startsWith("Mr."); + * field("name").startsWith("Mr."); * ``` * * @param prefix The prefix to check for. * @return A new `Expr` representing the 'starts with' comparison. */ - startsWith(prefix: string): StartsWith; + startsWith(prefix: string): BooleanExpr; /** * Creates an expression that checks if a string starts with a given prefix (represented as an @@ -1008,18 +871,18 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'fullName' field starts with the value of the 'firstName' field - * Field.of("fullName").startsWith(Field.of("firstName")); + * field("fullName").startsWith(field("firstName")); * ``` * * @param prefix The prefix expression to check for. * @return A new `Expr` representing the 'starts with' comparison. */ - startsWith(prefix: Expr): StartsWith; - startsWith(stringOrExpr: string | Expr): StartsWith { - if (typeof stringOrExpr === 'string') { - return new StartsWith(this, Constant.of(stringOrExpr)); - } - return new StartsWith(this, stringOrExpr as Expr); + startsWith(prefix: Expr): BooleanExpr; + startsWith(stringOrExpr: string | Expr): BooleanExpr { + return new BooleanExpr('starts_with', [ + this, + valueToDefaultExpr(stringOrExpr) + ]); } /** @@ -1027,13 +890,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'filename' field ends with ".txt" - * Field.of("filename").endsWith(".txt"); + * field("filename").endsWith(".txt"); * ``` * * @param suffix The postfix to check for. * @return A new `Expr` representing the 'ends with' comparison. */ - endsWith(suffix: string): EndsWith; + endsWith(suffix: string): BooleanExpr; /** * Creates an expression that checks if a string ends with a given postfix (represented as an @@ -1041,18 +904,18 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Check if the 'url' field ends with the value of the 'extension' field - * Field.of("url").endsWith(Field.of("extension")); + * field("url").endsWith(field("extension")); * ``` * * @param suffix The postfix expression to check for. * @return A new `Expr` representing the 'ends with' comparison. */ - endsWith(suffix: Expr): EndsWith; - endsWith(stringOrExpr: string | Expr): EndsWith { - if (typeof stringOrExpr === 'string') { - return new EndsWith(this, Constant.of(stringOrExpr)); - } - return new EndsWith(this, stringOrExpr as Expr); + endsWith(suffix: Expr): BooleanExpr; + endsWith(stringOrExpr: string | Expr): BooleanExpr { + return new BooleanExpr('ends_with', [ + this, + valueToDefaultExpr(stringOrExpr) + ]); } /** @@ -1060,13 +923,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Convert the 'name' field to lowercase - * Field.of("name").toLower(); + * field("name").toLower(); * ``` * * @return A new `Expr` representing the lowercase string. */ - toLower(): ToLower { - return new ToLower(this); + toLower(): FunctionExpr { + return new FunctionExpr('to_lower', [this]); } /** @@ -1074,13 +937,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Convert the 'title' field to uppercase - * Field.of("title").toUpper(); + * field("title").toUpper(); * ``` * * @return A new `Expr` representing the uppercase string. */ - toUpper(): ToUpper { - return new ToUpper(this); + toUpper(): FunctionExpr { + return new FunctionExpr('to_upper', [this]); } /** @@ -1088,13 +951,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Trim whitespace from the 'userInput' field - * Field.of("userInput").trim(); + * field("userInput").trim(); * ``` * * @return A new `Expr` representing the trimmed string. */ - trim(): Trim { - return new Trim(this); + trim(): FunctionExpr { + return new FunctionExpr('trim', [this]); } /** @@ -1102,17 +965,20 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Combine the 'firstName', " ", and 'lastName' fields into a single string - * Field.of("firstName").strConcat(Constant.of(" "), Field.of("lastName")); + * field("firstName").strConcat(constant(" "), field("lastName")); * ``` * - * @param elements The expressions (typically strings) to concatenate. + * @param secondString The additional expression or string literal to concatenate. + * @param otherStrings Optional additional expressions or string literals to concatenate. * @return A new `Expr` representing the concatenated string. */ - strConcat(...elements: Array): StrConcat { - const exprs = elements.map(e => - typeof e === 'string' ? Constant.of(e) : (e as Expr) - ); - return new StrConcat(this, exprs); + strConcat( + secondString: Expr | string, + ...otherStrings: Array + ): FunctionExpr { + const elements = [secondString, ...otherStrings]; + const exprs = elements.map(valueToDefaultExpr); + return new FunctionExpr('str_concat', [this, ...exprs]); } /** @@ -1120,13 +986,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Reverse the value of the 'myString' field. - * Field.of("myString").reverse(); + * field("myString").reverse(); * ``` * * @return A new {@code Expr} representing the reversed string. */ - reverse(): Reverse { - return new Reverse(this); + reverse(): FunctionExpr { + return new FunctionExpr('reverse', [this]); } /** @@ -1134,14 +1000,14 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Replace the first occurrence of "hello" with "hi" in the 'message' field - * Field.of("message").replaceFirst("hello", "hi"); + * field("message").replaceFirst("hello", "hi"); * ``` * * @param find The substring to search for. * @param replace The substring to replace the first occurrence of 'find' with. * @return A new {@code Expr} representing the string with the first occurrence replaced. */ - replaceFirst(find: string, replace: string): ReplaceFirst; + replaceFirst(find: string, replace: string): FunctionExpr; /** * Creates an expression that replaces the first occurrence of a substring within this string expression with another substring, @@ -1149,23 +1015,20 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Replace the first occurrence of the value in 'findField' with the value in 'replaceField' in the 'message' field - * Field.of("message").replaceFirst(Field.of("findField"), Field.of("replaceField")); + * field("message").replaceFirst(field("findField"), field("replaceField")); * ``` * * @param find The expression representing the substring to search for. * @param replace The expression representing the substring to replace the first occurrence of 'find' with. * @return A new {@code Expr} representing the string with the first occurrence replaced. */ - replaceFirst(find: Expr, replace: Expr): ReplaceFirst; - replaceFirst(find: Expr | string, replace: Expr | string): ReplaceFirst { - const normalizedFind = typeof find === 'string' ? Constant.of(find) : find; - const normalizedReplace = - typeof replace === 'string' ? Constant.of(replace) : replace; - return new ReplaceFirst( + replaceFirst(find: Expr, replace: Expr): FunctionExpr; + replaceFirst(find: Expr | string, replace: Expr | string): FunctionExpr { + return new FunctionExpr('replace_first', [ this, - normalizedFind as Expr, - normalizedReplace as Expr - ); + valueToDefaultExpr(find), + valueToDefaultExpr(replace) + ]); } /** @@ -1173,14 +1036,14 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Replace all occurrences of "hello" with "hi" in the 'message' field - * Field.of("message").replaceAll("hello", "hi"); + * field("message").replaceAll("hello", "hi"); * ``` * * @param find The substring to search for. * @param replace The substring to replace all occurrences of 'find' with. * @return A new {@code Expr} representing the string with all occurrences replaced. */ - replaceAll(find: string, replace: string): ReplaceAll; + replaceAll(find: string, replace: string): FunctionExpr; /** * Creates an expression that replaces all occurrences of a substring within this string expression with another substring, @@ -1188,23 +1051,20 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Replace all occurrences of the value in 'findField' with the value in 'replaceField' in the 'message' field - * Field.of("message").replaceAll(Field.of("findField"), Field.of("replaceField")); + * field("message").replaceAll(field("findField"), field("replaceField")); * ``` * * @param find The expression representing the substring to search for. * @param replace The expression representing the substring to replace all occurrences of 'find' with. * @return A new {@code Expr} representing the string with all occurrences replaced. */ - replaceAll(find: Expr, replace: Expr): ReplaceAll; - replaceAll(find: Expr | string, replace: Expr | string): ReplaceAll { - const normalizedFind = typeof find === 'string' ? Constant.of(find) : find; - const normalizedReplace = - typeof replace === 'string' ? Constant.of(replace) : replace; - return new ReplaceAll( + replaceAll(find: Expr, replace: Expr): FunctionExpr; + replaceAll(find: Expr | string, replace: Expr | string): FunctionExpr { + return new FunctionExpr('replace_all', [ this, - normalizedFind as Expr, - normalizedReplace as Expr - ); + valueToDefaultExpr(find), + valueToDefaultExpr(replace) + ]); } /** @@ -1212,13 +1072,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Calculate the length of the 'myString' field in bytes. - * Field.of("myString").byteLength(); + * field("myString").byteLength(); * ``` * * @return A new {@code Expr} representing the length of the string in bytes. */ - byteLength(): ByteLength { - return new ByteLength(this); + byteLength(): FunctionExpr { + return new FunctionExpr('byte_length', [this]); } /** @@ -1226,14 +1086,14 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Get the 'city' value from the 'address' map field - * Field.of("address").mapGet("city"); + * field("address").mapGet("city"); * ``` * * @param subfield The key to access in the map. * @return A new `Expr` representing the value associated with the given key in the map. */ - mapGet(subfield: string): MapGet { - return new MapGet(this, subfield); + mapGet(subfield: string): FunctionExpr { + return new FunctionExpr('map_get', [this, constant(subfield)]); } /** @@ -1242,13 +1102,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Count the total number of products - * Field.of("productId").count().as("totalProducts"); + * field("productId").count().as("totalProducts"); * ``` * - * @return A new `Accumulator` representing the 'count' aggregation. + * @return A new `AggregateFunction` representing the 'count' aggregation. */ - count(): Count { - return new Count(this, false); + count(): AggregateFunction { + return new AggregateFunction('count', [this]); } /** @@ -1256,13 +1116,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Calculate the total revenue from a set of orders - * Field.of("orderAmount").sum().as("totalRevenue"); + * field("orderAmount").sum().as("totalRevenue"); * ``` * - * @return A new `Accumulator` representing the 'sum' aggregation. + * @return A new `AggregateFunction` representing the 'sum' aggregation. */ - sum(): Sum { - return new Sum(this, false); + sum(): AggregateFunction { + return new AggregateFunction('sum', [this]); } /** @@ -1271,13 +1131,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Calculate the average age of users - * Field.of("age").avg().as("averageAge"); + * field("age").avg().as("averageAge"); * ``` * - * @return A new `Accumulator` representing the 'avg' aggregation. + * @return A new `AggregateFunction` representing the 'avg' aggregation. */ - avg(): Avg { - return new Avg(this, false); + avg(): AggregateFunction { + return new AggregateFunction('avg', [this]); } /** @@ -1285,13 +1145,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Find the lowest price of all products - * Field.of("price").minimum().as("lowestPrice"); + * field("price").minimum().as("lowestPrice"); * ``` * - * @return A new `Accumulator` representing the 'min' aggregation. + * @return A new `AggregateFunction` representing the 'min' aggregation. */ - minimum(): Minimum { - return new Minimum(this, false); + minimum(): AggregateFunction { + return new AggregateFunction('minimum', [this]); } /** @@ -1299,13 +1159,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Find the highest score in a leaderboard - * Field.of("score").maximum().as("highestScore"); + * field("score").maximum().as("highestScore"); * ``` * - * @return A new `Accumulator` representing the 'max' aggregation. + * @return A new `AggregateFunction` representing the 'max' aggregation. */ - maximum(): Maximum { - return new Maximum(this, false); + maximum(): AggregateFunction { + return new AggregateFunction('maximum', [this]); } /** @@ -1313,31 +1173,22 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Returns the larger value between the 'timestamp' field and the current timestamp. - * Field.of("timestamp").logicalMaximum(Function.currentTimestamp()); - * ``` - * - * @param other The expression to compare with. - * @return A new {@code Expr} representing the logical max operation. - */ - logicalMaximum(other: Expr): LogicalMaximum; - - /** - * Creates an expression that returns the larger value between this expression and a constant value, based on Firestore's value type ordering. - * - * ```typescript - * // Returns the larger value between the 'value' field and 10. - * Field.of("value").logicalMaximum(10); + * field("timestamp").logicalMaximum(Function.currentTimestamp()); * ``` * - * @param other The constant value to compare with. + * @param second The second expression or literal to compare with. + * @param others Optional additional expressions or literals to compare with. * @return A new {@code Expr} representing the logical max operation. */ - logicalMaximum(other: any): LogicalMaximum; - logicalMaximum(other: any): LogicalMaximum { - if (other instanceof Expr) { - return new LogicalMaximum(this, other as Expr); - } - return new LogicalMaximum(this, Constant.of(other)); + logicalMaximum( + second: Expr | unknown, + ...others: Array + ): FunctionExpr { + const values = [second, ...others]; + return new FunctionExpr('logical_maximum', [ + this, + ...values.map(valueToDefaultExpr) + ]); } /** @@ -1345,31 +1196,22 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Returns the smaller value between the 'timestamp' field and the current timestamp. - * Field.of("timestamp").logicalMinimum(Function.currentTimestamp()); - * ``` - * - * @param other The expression to compare with. - * @return A new {@code Expr} representing the logical min operation. - */ - logicalMinimum(other: Expr): LogicalMinimum; - - /** - * Creates an expression that returns the smaller value between this expression and a constant value, based on Firestore's value type ordering. - * - * ```typescript - * // Returns the smaller value between the 'value' field and 10. - * Field.of("value").logicalMinimum(10); + * field("timestamp").logicalMinimum(Function.currentTimestamp()); * ``` * - * @param other The constant value to compare with. + * @param second The second expression or literal to compare with. + * @param others Optional additional expressions or literals to compare with. * @return A new {@code Expr} representing the logical min operation. */ - logicalMinimum(other: any): LogicalMinimum; - logicalMinimum(other: any): LogicalMinimum { - if (other instanceof Expr) { - return new LogicalMinimum(this, other as Expr); - } - return new LogicalMinimum(this, Constant.of(other)); + logicalMinimum( + second: Expr | unknown, + ...others: Array + ): FunctionExpr { + const values = [second, ...others]; + return new FunctionExpr('logical_minimum', [ + this, + ...values.map(valueToDefaultExpr) + ]); } /** @@ -1377,13 +1219,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Get the vector length (dimension) of the field 'embedding'. - * Field.of("embedding").vectorLength(); + * field("embedding").vectorLength(); * ``` * * @return A new {@code Expr} representing the length of the vector. */ - vectorLength(): VectorLength { - return new VectorLength(this); + vectorLength(): FunctionExpr { + return new FunctionExpr('vector_length', [this]); } /** @@ -1391,46 +1233,27 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field - * Field.of("userVector").cosineDistance(Field.of("itemVector")); + * field("userVector").cosineDistance(field("itemVector")); * ``` * - * @param other The other vector (represented as an Expr) to compare against. + * @param vectorExpression The other vector (represented as an Expr) to compare against. * @return A new `Expr` representing the cosine distance between the two vectors. */ - cosineDistance(other: Expr): CosineDistance; + cosineDistance(vectorExpression: Expr): FunctionExpr; /** * Calculates the Cosine distance between two vectors. * * ```typescript * // Calculate the Cosine distance between the 'location' field and a target location - * Field.of("location").cosineDistance(new VectorValue([37.7749, -122.4194])); + * field("location").cosineDistance(new VectorValue([37.7749, -122.4194])); * ``` * - * @param other The other vector (as a VectorValue) to compare against. + * @param vector The other vector (as a VectorValue) to compare against. * @return A new `Expr` representing the Cosine* distance between the two vectors. */ - cosineDistance(other: VectorValue): CosineDistance; - /** - * Calculates the Cosine distance between two vectors. - * - * ```typescript - * // Calculate the Cosine distance between the 'location' field and a target location - * Field.of("location").cosineDistance([37.7749, -122.4194]); - * ``` - * - * @param other The other vector (as an array of numbers) to compare against. - * @return A new `Expr` representing the Cosine distance between the two vectors. - */ - cosineDistance(other: number[]): CosineDistance; - cosineDistance(other: Expr | VectorValue | number[]): CosineDistance { - if (other instanceof Expr) { - return new CosineDistance(this, other as Expr); - } else { - return new CosineDistance( - this, - Constant.vector(other as VectorValue | number[]) - ); - } + cosineDistance(vector: VectorValue | number[]): FunctionExpr; + cosineDistance(other: Expr | VectorValue | number[]): FunctionExpr { + return new FunctionExpr('cosine_distance', [this, vectorToExpr(other)]); } /** @@ -1438,48 +1261,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Calculate the dot product between a feature vector and a target vector - * Field.of("features").dotProduct([0.5, 0.8, 0.2]); + * field("features").dotProduct([0.5, 0.8, 0.2]); * ``` * - * @param other The other vector (as an array of numbers) to calculate with. + * @param vectorExpression The other vector (as an array of numbers) to calculate with. * @return A new `Expr` representing the dot product between the two vectors. */ - dotProduct(other: Expr): DotProduct; + dotProduct(vectorExpression: Expr): FunctionExpr; /** * Calculates the dot product between two vectors. * * ```typescript * // Calculate the dot product between a feature vector and a target vector - * Field.of("features").dotProduct(new VectorValue([0.5, 0.8, 0.2])); + * field("features").dotProduct(new VectorValue([0.5, 0.8, 0.2])); * ``` * - * @param other The other vector (as an array of numbers) to calculate with. + * @param vector The other vector (as an array of numbers) to calculate with. * @return A new `Expr` representing the dot product between the two vectors. */ - dotProduct(other: VectorValue): DotProduct; - - /** - * Calculates the dot product between two vectors. - * - * ```typescript - * // Calculate the dot product between a feature vector and a target vector - * Field.of("features").dotProduct([0.5, 0.8, 0.2]); - * ``` - * - * @param other The other vector (as an array of numbers) to calculate with. - * @return A new `Expr` representing the dot product between the two vectors. - */ - dotProduct(other: number[]): DotProduct; - dotProduct(other: Expr | VectorValue | number[]): DotProduct { - if (other instanceof Expr) { - return new DotProduct(this, other as Expr); - } else { - return new DotProduct( - this, - Constant.vector(other as VectorValue | number[]) - ); - } + dotProduct(vector: VectorValue | number[]): FunctionExpr; + dotProduct(other: Expr | VectorValue | number[]): FunctionExpr { + return new FunctionExpr('dot_product', [this, vectorToExpr(other)]); } /** @@ -1487,48 +1290,28 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Calculate the Euclidean distance between the 'location' field and a target location - * Field.of("location").euclideanDistance([37.7749, -122.4194]); - * ``` - * - * @param other The other vector (as an array of numbers) to calculate with. - * @return A new `Expr` representing the Euclidean distance between the two vectors. - */ - euclideanDistance(other: Expr): EuclideanDistance; - - /** - * Calculates the Euclidean distance between two vectors. - * - * ```typescript - * // Calculate the Euclidean distance between the 'location' field and a target location - * Field.of("location").euclideanDistance(new VectorValue([37.7749, -122.4194])); + * field("location").euclideanDistance([37.7749, -122.4194]); * ``` * - * @param other The other vector (as a VectorValue) to compare against. + * @param vectorExpression The other vector (as an array of numbers) to calculate with. * @return A new `Expr` representing the Euclidean distance between the two vectors. */ - euclideanDistance(other: VectorValue): EuclideanDistance; + euclideanDistance(vectorExpression: Expr): FunctionExpr; /** * Calculates the Euclidean distance between two vectors. * * ```typescript * // Calculate the Euclidean distance between the 'location' field and a target location - * Field.of("location").euclideanDistance([37.7749, -122.4194]); + * field("location").euclideanDistance(new VectorValue([37.7749, -122.4194])); * ``` * - * @param other The other vector (as an array of numbers) to compare against. + * @param vector The other vector (as a VectorValue) to compare against. * @return A new `Expr` representing the Euclidean distance between the two vectors. */ - euclideanDistance(other: number[]): EuclideanDistance; - euclideanDistance(other: Expr | VectorValue | number[]): EuclideanDistance { - if (other instanceof Expr) { - return new EuclideanDistance(this, other as Expr); - } else { - return new EuclideanDistance( - this, - Constant.vector(other as VectorValue | number[]) - ); - } + euclideanDistance(vector: VectorValue | number[]): FunctionExpr; + euclideanDistance(other: Expr | VectorValue | number[]): FunctionExpr { + return new FunctionExpr('euclidean_distance', [this, vectorToExpr(other)]); } /** @@ -1537,13 +1320,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Interpret the 'microseconds' field as microseconds since epoch. - * Field.of("microseconds").unixMicrosToTimestamp(); + * field("microseconds").unixMicrosToTimestamp(); * ``` * * @return A new {@code Expr} representing the timestamp. */ - unixMicrosToTimestamp(): UnixMicrosToTimestamp { - return new UnixMicrosToTimestamp(this); + unixMicrosToTimestamp(): FunctionExpr { + return new FunctionExpr('unix_micros_to_timestamp', [this]); } /** @@ -1551,13 +1334,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Convert the 'timestamp' field to microseconds since epoch. - * Field.of("timestamp").timestampToUnixMicros(); + * field("timestamp").timestampToUnixMicros(); * ``` * * @return A new {@code Expr} representing the number of microseconds since epoch. */ - timestampToUnixMicros(): TimestampToUnixMicros { - return new TimestampToUnixMicros(this); + timestampToUnixMicros(): FunctionExpr { + return new FunctionExpr('timestamp_to_unix_micros', [this]); } /** @@ -1566,13 +1349,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Interpret the 'milliseconds' field as milliseconds since epoch. - * Field.of("milliseconds").unixMillisToTimestamp(); + * field("milliseconds").unixMillisToTimestamp(); * ``` * * @return A new {@code Expr} representing the timestamp. */ - unixMillisToTimestamp(): UnixMillisToTimestamp { - return new UnixMillisToTimestamp(this); + unixMillisToTimestamp(): FunctionExpr { + return new FunctionExpr('unix_millis_to_timestamp', [this]); } /** @@ -1580,13 +1363,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Convert the 'timestamp' field to milliseconds since epoch. - * Field.of("timestamp").timestampToUnixMillis(); + * field("timestamp").timestampToUnixMillis(); * ``` * * @return A new {@code Expr} representing the number of milliseconds since epoch. */ - timestampToUnixMillis(): TimestampToUnixMillis { - return new TimestampToUnixMillis(this); + timestampToUnixMillis(): FunctionExpr { + return new FunctionExpr('timestamp_to_unix_millis', [this]); } /** @@ -1595,13 +1378,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Interpret the 'seconds' field as seconds since epoch. - * Field.of("seconds").unixSecondsToTimestamp(); + * field("seconds").unixSecondsToTimestamp(); * ``` * * @return A new {@code Expr} representing the timestamp. */ - unixSecondsToTimestamp(): UnixSecondsToTimestamp { - return new UnixSecondsToTimestamp(this); + unixSecondsToTimestamp(): FunctionExpr { + return new FunctionExpr('unix_seconds_to_timestamp', [this]); } /** @@ -1609,13 +1392,13 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Convert the 'timestamp' field to seconds since epoch. - * Field.of("timestamp").timestampToUnixSeconds(); + * field("timestamp").timestampToUnixSeconds(); * ``` * * @return A new {@code Expr} representing the number of seconds since epoch. */ - timestampToUnixSeconds(): TimestampToUnixSeconds { - return new TimestampToUnixSeconds(this); + timestampToUnixSeconds(): FunctionExpr { + return new FunctionExpr('timestamp_to_unix_seconds', [this]); } /** @@ -1623,21 +1406,21 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. - * Field.of("timestamp").timestampAdd(Field.of("unit"), Field.of("amount")); + * field("timestamp").timestampAdd(field("unit"), field("amount")); * ``` * * @param unit The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. * @param amount The expression evaluates to amount of the unit. * @return A new {@code Expr} representing the resulting timestamp. */ - timestampAdd(unit: Expr, amount: Expr): TimestampAdd; + timestampAdd(unit: Expr, amount: Expr): FunctionExpr; /** * Creates an expression that adds a specified amount of time to this timestamp expression. * * ```typescript * // Add 1 day to the 'timestamp' field. - * Field.of("timestamp").timestampAdd("day", 1); + * field("timestamp").timestampAdd("day", 1); * ``` * * @param unit The unit of time to add (e.g., "day", "hour"). @@ -1647,7 +1430,7 @@ export abstract class Expr implements ProtoSerializable, UserData { timestampAdd( unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number - ): TimestampAdd; + ): FunctionExpr; timestampAdd( unit: | Expr @@ -1658,15 +1441,12 @@ export abstract class Expr implements ProtoSerializable, UserData { | 'hour' | 'day', amount: Expr | number - ): TimestampAdd { - const normalizedUnit = typeof unit === 'string' ? Constant.of(unit) : unit; - const normalizedAmount = - typeof amount === 'number' ? Constant.of(amount) : amount; - return new TimestampAdd( + ): FunctionExpr { + return new FunctionExpr('timestamp_add', [ this, - normalizedUnit as Expr, - normalizedAmount as Expr - ); + valueToDefaultExpr(unit), + valueToDefaultExpr(amount) + ]); } /** @@ -1674,21 +1454,21 @@ export abstract class Expr implements ProtoSerializable, UserData { * * ```typescript * // Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. - * Field.of("timestamp").timestampSub(Field.of("unit"), Field.of("amount")); + * field("timestamp").timestampSub(field("unit"), field("amount")); * ``` * * @param unit The expression evaluates to unit of time, must be one of 'microsecond', 'millisecond', 'second', 'minute', 'hour', 'day'. * @param amount The expression evaluates to amount of the unit. * @return A new {@code Expr} representing the resulting timestamp. */ - timestampSub(unit: Expr, amount: Expr): TimestampSub; + timestampSub(unit: Expr, amount: Expr): FunctionExpr; /** * Creates an expression that subtracts a specified amount of time from this timestamp expression. * * ```typescript * // Subtract 1 day from the 'timestamp' field. - * Field.of("timestamp").timestampSub("day", 1); + * field("timestamp").timestampSub("day", 1); * ``` * * @param unit The unit of time to subtract (e.g., "day", "hour"). @@ -1698,7 +1478,7 @@ export abstract class Expr implements ProtoSerializable, UserData { timestampSub( unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number - ): TimestampSub; + ): FunctionExpr; timestampSub( unit: | Expr @@ -1709,129 +1489,557 @@ export abstract class Expr implements ProtoSerializable, UserData { | 'hour' | 'day', amount: Expr | number - ): TimestampSub { - const normalizedUnit = typeof unit === 'string' ? Constant.of(unit) : unit; - const normalizedAmount = - typeof amount === 'number' ? Constant.of(amount) : amount; - return new TimestampSub( + ): FunctionExpr { + return new FunctionExpr('timestamp_sub', [ this, - normalizedUnit as Expr, - normalizedAmount as Expr - ); + valueToDefaultExpr(unit), + valueToDefaultExpr(amount) + ]); } /** - * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * @beta + * + * Creates an expression that applies a bitwise AND operation between this expression and a constant. * * ```typescript - * // Sort documents by the 'name' field in ascending order - * pipeline().collection("users") - * .sort(Field.of("name").ascending()); + * // Calculate the bitwise AND of 'field1' and 0xFF. + * field("field1").bitAnd(0xFF); * ``` * - * @return A new `Ordering` for ascending sorting. + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise AND operation. */ - ascending(): Ordering { - return ascending(this); - } - + bitAnd(otherBits: number | Bytes): FunctionExpr; /** - * Creates an {@link Ordering} that sorts documents in descending order based on this expression. + * @beta + * + * Creates an expression that applies a bitwise AND operation between two expressions. * * ```typescript - * // Sort documents by the 'createdAt' field in descending order - * firestore.pipeline().collection("users") - * .sort(Field.of("createdAt").descending()); + * // Calculate the bitwise AND of 'field1' and 'field2'. + * field("field1").bitAnd(field("field2")); * ``` * - * @return A new `Ordering` for descending sorting. + * @param bitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise AND operation. */ - descending(): Ordering { - return descending(this); + bitAnd(bitsExpression: Expr): FunctionExpr; + bitAnd(bitsOrExpression: number | Expr | Bytes): FunctionExpr { + return new FunctionExpr('bit_and', [ + this, + valueToDefaultExpr(bitsOrExpression) + ]); } /** - * Assigns an alias to this expression. + * @beta * - * Aliases are useful for renaming fields in the output of a stage or for giving meaningful - * names to calculated values. + * Creates an expression that applies a bitwise OR operation between this expression and a constant. * * ```typescript - * // Calculate the total price and assign it the alias "totalPrice" and add it to the output. - * firestore.pipeline().collection("items") - * .addFields(Field.of("price").multiply(Field.of("quantity")).as("totalPrice")); + * // Calculate the bitwise OR of 'field1' and 0xFF. + * field("field1").bitOr(0xFF); * ``` * - * @param name The alias to assign to this expression. - * @return A new {@link ExprWithAlias} that wraps this - * expression and associates it with the provided alias. + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise OR operation. */ - as(name: string): ExprWithAlias { - return new ExprWithAlias(this, name); + bitOr(otherBits: number | Bytes): FunctionExpr; + /** + * @beta + * + * Creates an expression that applies a bitwise OR operation between two expressions. + * + * ```typescript + * // Calculate the bitwise OR of 'field1' and 'field2'. + * field("field1").bitOr(field("field2")); + * ``` + * + * @param bitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise OR operation. + */ + bitOr(bitsExpression: Expr): FunctionExpr; + bitOr(bitsOrExpression: number | Expr | Bytes): FunctionExpr { + return new FunctionExpr('bit_or', [ + this, + valueToDefaultExpr(bitsOrExpression) + ]); } /** - * @private - * @internal + * @beta + * + * Creates an expression that applies a bitwise XOR operation between this expression and a constant. + * + * ```typescript + * // Calculate the bitwise XOR of 'field1' and 0xFF. + * field("field1").bitXor(0xFF); + * ``` + * + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise XOR operation. */ - abstract _toProto(serializer: JsonProtoSerializer): ProtoValue; - + bitXor(otherBits: number | Bytes): FunctionExpr; /** - * @private - * @internal + * @beta + * + * Creates an expression that applies a bitwise XOR operation between two expressions. + * + * ```typescript + * // Calculate the bitwise XOR of 'field1' and 'field2'. + * field("field1").bitXor(field("field2")); + * ``` + * + * @param bitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise XOR operation. */ - abstract _readUserData(dataReader: UserDataReader): void; -} - -/** - * @beta - * - * An interface that represents a selectable expression. - */ -export abstract class Selectable extends Expr { - selectable: true = true; -} - -/** - * @beta - * - * An interface that represents a filter condition. - */ -export abstract class FilterCondition extends Expr { - filterable: true = true; -} - -/** - * @beta - * - * An interface that represents an accumulator. - */ -export abstract class Accumulator extends Expr { - accumulator: true = true; + bitXor(bitsExpression: Expr): FunctionExpr; + bitXor(bitsOrExpression: number | Expr | Bytes): FunctionExpr { + return new FunctionExpr('bit_xor', [ + this, + valueToDefaultExpr(bitsOrExpression) + ]); + } /** - * @private - * @internal + * @beta + * + * Creates an expression that applies a bitwise NOT operation to this expression. + * + * ```typescript + * // Calculate the bitwise NOT of 'field1'. + * field("field1").bitNot(); + * ``` + * + * @return A new {@code Expr} representing the bitwise NOT operation. */ - abstract _toProto(serializer: JsonProtoSerializer): ProtoValue; -} + bitNot(): FunctionExpr { + return new FunctionExpr('bit_not', [this]); + } -/** + /** + * @beta + * + * Creates an expression that applies a bitwise left shift operation to this expression. + * + * ```typescript + * // Calculate the bitwise left shift of 'field1' by 2 bits. + * field("field1").bitLeftShift(2); + * ``` + * + * @param y The operand constant representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise left shift operation. + */ + bitLeftShift(y: number): FunctionExpr; + /** + * @beta + * + * Creates an expression that applies a bitwise left shift operation to this expression. + * + * ```typescript + * // Calculate the bitwise left shift of 'field1' by 'field2' bits. + * field("field1").bitLeftShift(field("field2")); + * ``` + * + * @param numberExpr The operand expression representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise left shift operation. + */ + bitLeftShift(numberExpr: Expr): FunctionExpr; + bitLeftShift(numberExpr: number | Expr): FunctionExpr { + return new FunctionExpr('bit_left_shift', [ + this, + valueToDefaultExpr(numberExpr) + ]); + } + + /** + * @beta + * + * Creates an expression that applies a bitwise right shift operation to this expression. + * + * ```typescript + * // Calculate the bitwise right shift of 'field1' by 2 bits. + * field("field1").bitRightShift(2); + * ``` + * + * @param right The operand constant representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise right shift operation. + */ + bitRightShift(y: number): FunctionExpr; + /** + * @beta + * + * Creates an expression that applies a bitwise right shift operation to this expression. + * + * ```typescript + * // Calculate the bitwise right shift of 'field1' by 'field2' bits. + * field("field1").bitRightShift(field("field2")); + * ``` + * + * @param numberExpr The operand expression representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise right shift operation. + */ + bitRightShift(numberExpr: Expr): FunctionExpr; + bitRightShift(numberExpr: number | Expr): FunctionExpr { + return new FunctionExpr('bit_right_shift', [ + this, + valueToDefaultExpr(numberExpr) + ]); + } + + /** + * @beta + * + * Creates an expression that returns the document ID from a path. + * + * ```typescript + * // Get the document ID from a path. + * field("__path__").documentId(); + * ``` + * + * @return A new {@code Expr} representing the documentId operation. + */ + documentId(): FunctionExpr { + return new FunctionExpr('document_id', [this]); + } + + /** + * @beta + * + * Creates an expression that returns a substring of the results of this expression. + * + * @param position Index of the first character of the substring. + * @param length Length of the substring. If not provided, the substring will + * end at the end of the input. + */ + substr(position: number, length?: number): FunctionExpr; + + /** + * @beta + * + * Creates an expression that returns a substring of the results of this expression. + * + * @param position An expression returning the index of the first character of the substring. + * @param length An expression returning the length of the substring. If not provided the + * substring will end at the end of the input. + */ + substr(position: Expr, length?: Expr): FunctionExpr; + substr(position: Expr | number, length?: Expr | number): FunctionExpr { + const positionExpr = valueToDefaultExpr(position); + if (length === undefined) { + return new FunctionExpr('substr', [this, positionExpr]); + } else { + return new FunctionExpr('substr', [ + this, + positionExpr, + valueToDefaultExpr(length) + ]); + } + } + + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and returns the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the 'tags' field array at index `1`. + * field('tags').arrayOffset(1); + * ``` + * + * @param offset The index of the element to return. + * @return A new Expr representing the 'arrayOffset' operation. + */ + arrayOffset(offset: number): FunctionExpr; + + /** + * @beta + * Creates an expression that indexes into an array from the beginning or end + * and returns the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index specified by field + * // 'favoriteTag'. + * field('tags').arrayOffset(field('favoriteTag')); + * ``` + * + * @param offsetExpr An Expr evaluating to the index of the element to return. + * @return A new Expr representing the 'arrayOffset' operation. + */ + arrayOffset(offsetExpr: Expr): FunctionExpr; + arrayOffset(offset: Expr | number): FunctionExpr { + return new FunctionExpr('array_offset', [this, valueToDefaultExpr(offset)]); + } + + /** + * @beta + * + * Creates an expression that checks if a given expression produces an error. + * + * ```typescript + * // Check if the result of a calculation is an error + * field("title").arrayContains(1).isError(); + * ``` + * + * @return A new {@code BooleanExpr} representing the 'isError' check. + */ + isError(): BooleanExpr { + return new BooleanExpr('is_error', [this]); + } + + /** + * @beta + * + * Creates an expression that returns the result of the `catchExpr` argument + * if there is an error, else return the result of this expression. + * + * ```typescript + * // Returns the first item in the title field arrays, or returns + * // the entire title field if the array is empty or the field is another type. + * field("title").arrayOffset(0).ifError(field("title")); + * ``` + * + * @param catchExpr The catch expression that will be evaluated and + * returned if this expression produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ + ifError(catchExpr: Expr): FunctionExpr; + + /** + * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of this expression. + * + * ```typescript + * // Returns the first item in the title field arrays, or returns + * // "Default Title" + * field("title").arrayOffset(0).ifError("Default Title"); + * ``` + * + * @param catchValue The value that will be returned if this expression + * produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. + */ + ifError(catchValue: unknown): FunctionExpr; + ifError(catchValue: unknown): FunctionExpr { + return new FunctionExpr('if_error', [this, valueToDefaultExpr(catchValue)]); + } + + /** + * @beta + * + * Creates an expression that returns `true` if the result of this expression + * is absent. Otherwise, returns `false` even if the value is `null`. + * + * ```typescript + * // Check if the field `value` is absent. + * field("value").isAbsent(); + * ``` + * + * @return A new {@code BooleanExpr} representing the 'isAbsent' check. + */ + isAbsent(): BooleanExpr { + return new BooleanExpr('is_absent', [this]); + } + + /** + * @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(): BooleanExpr { + return new BooleanExpr('is_not_null', [this]); + } + + /** + * @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(): BooleanExpr { + return new BooleanExpr('is_not_nan', [this]); + } + + /** + * @beta + * + * Creates an expression that removes a key from the map produced by evaluating this expression. + * + * ``` + * // Removes the key 'baz' from the input map. + * map({foo: 'bar', baz: true}).mapRemove('baz'); + * ``` + * + * @param key The name of the key to remove from the input map. + * @returns A new {@code FirestoreFunction} representing the 'mapRemove' operation. + */ + mapRemove(key: string): FunctionExpr; + /** + * @beta + * + * Creates an expression that removes a key from the map produced by evaluating this expression. + * + * ``` + * // Removes the key 'baz' from the input map. + * map({foo: 'bar', baz: true}).mapRemove(constant('baz')); + * ``` + * + * @param keyExpr An expression that produces the name of the key to remove from the input map. + * @returns A new {@code FirestoreFunction} representing the 'mapRemove' operation. + */ + mapRemove(keyExpr: Expr): FunctionExpr; + mapRemove(stringExpr: Expr | string): FunctionExpr { + return new FunctionExpr('map_remove', [ + this, + valueToDefaultExpr(stringExpr) + ]); + } + + /** + * @beta + * + * Creates an expression that merges multiple map values. + * + * ``` + * // Merges the map in the settings field with, a map literal, and a map in + * // that is conditionally returned by another expression + * field('settings').mapMerge({ enabled: true }, cond(field('isAdmin'), { admin: true}, {}) + * ``` + * + * @param secondMap A required second map to merge. Represented as a literal or + * an expression that returns a map. + * @param otherMaps Optional additional maps to merge. Each map is represented + * as a literal or an expression that returns a map. + * + * @returns A new {@code FirestoreFunction} representing the 'mapMerge' operation. + */ + mapMerge( + secondMap: Record | Expr, + ...otherMaps: Array | Expr> + ): FunctionExpr { + const secondMapExpr = valueToDefaultExpr(secondMap); + const otherMapExprs = otherMaps.map(valueToDefaultExpr); + return new FunctionExpr('map_merge', [ + this, + secondMapExpr, + ...otherMapExprs + ]); + } + + /** + * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * pipeline().collection("users") + * .sort(field("name").ascending()); + * ``` + * + * @return A new `Ordering` for ascending sorting. + */ + ascending(): Ordering { + return ascending(this); + } + + /** + * Creates an {@link Ordering} that sorts documents in descending order based on this expression. + * + * ```typescript + * // Sort documents by the 'createdAt' field in descending order + * firestore.pipeline().collection("users") + * .sort(field("createdAt").descending()); + * ``` + * + * @return A new `Ordering` for descending sorting. + */ + descending(): Ordering { + return descending(this); + } + + /** + * Assigns an alias to this expression. + * + * Aliases are useful for renaming fields in the output of a stage or for giving meaningful + * names to calculated values. + * + * ```typescript + * // Calculate the total price and assign it the alias "totalPrice" and add it to the output. + * firestore.pipeline().collection("items") + * .addFields(field("price").multiply(field("quantity")).as("totalPrice")); + * ``` + * + * @param name The alias to assign to this expression. + * @return A new {@link ExprWithAlias} that wraps this + * expression and associates it with the provided alias. + */ + as(name: string): ExprWithAlias { + return new ExprWithAlias(this, name); + } +} + +/** * @beta * - * An accumulator target, which is an expression with an alias that also implements the Accumulator interface. + * An interface that represents a selectable expression. */ -export type AccumulatorTarget = ExprWithAlias; +export interface Selectable { + selectable: true; + readonly alias: string; + readonly expr: Expr; +} /** * @beta + * + * A class that represents an aggregate function. */ -export class ExprWithAlias extends Selectable { - exprType: ExprType = 'ExprWithAlias'; - selectable = true as const; +export class AggregateFunction implements ProtoValueSerializable, UserData { + exprType: ExprType = 'AggregateFunction'; - constructor(readonly expr: T, readonly alias: string) { - super(); + /** + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral: boolean = false; + + constructor(private name: string, private params: Expr[]) {} + + /** + * Assigns an alias to this AggregateFunction. The alias specifies the name that + * the aggregated value will have in the output document. + * + * ```typescript + * // Calculate the average price of all items and assign it the alias "averagePrice". + * firestore.pipeline().collection("items") + * .aggregate(field("price").avg().as("averagePrice")); + * ``` + * + * @param name The alias to assign to this AggregateFunction. + * @return A new {@link AggregateWithAlias} that wraps this + * AggregateFunction and associates it with the provided alias. + */ + as(name: string): AggregateWithAlias { + return new AggregateWithAlias(this, name); } /** @@ -1839,15 +2047,87 @@ export class ExprWithAlias extends Selectable { * @internal */ _toProto(serializer: JsonProtoSerializer): ProtoValue { - throw new Error('ExprWithAlias should not be serialized directly.'); + return { + functionValue: { + name: this.name, + args: this.params.map(p => p._toProto(serializer)) + } + }; } + _protoValueType = 'ProtoValue' as const; + /** * @private * @internal */ - _readUserData(dataReader: UserDataReader): void { - this.expr._readUserData(dataReader); + _readUserData(dataReader: UserDataReader, context?: ParseContext): void { + context = + this._createdFromLiteral && context + ? context + : dataReader.createContext(UserDataSource.Argument, this.name); + this.params.forEach(expr => { + return expr._readUserData(dataReader, context); + }); + } +} + +/** + * @beta + * + * An AggregateFunction with alias. + */ +export class AggregateWithAlias implements UserData { + constructor(readonly aggregate: AggregateFunction, readonly alias: string) {} + + /** + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral: boolean = false; + + /** + * @private + * @internal + */ + _readUserData(dataReader: UserDataReader, context?: ParseContext): void { + context = + this._createdFromLiteral && context + ? context + : dataReader.createContext(UserDataSource.Argument, 'as'); + this.aggregate._readUserData(dataReader, context); + } +} + +/** + * @beta + */ +export class ExprWithAlias implements Selectable, UserData { + exprType: ExprType = 'ExprWithAlias'; + selectable = true as const; + + /** + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral: boolean = false; + + constructor(readonly expr: Expr, readonly alias: string) {} + + /** + * @private + * @internal + */ + _readUserData(dataReader: UserDataReader, context?: ParseContext): void { + context = + this._createdFromLiteral && context + ? context + : dataReader.createContext(UserDataSource.Argument, 'as'); + this.expr._readUserData(dataReader, context); } } @@ -1856,6 +2136,7 @@ export class ExprWithAlias extends Selectable { */ class ListOfExprs extends Expr { exprType: ExprType = 'ListOfExprs'; + constructor(private exprs: Expr[]) { super(); } @@ -1893,106 +2174,36 @@ class ListOfExprs extends Expr { * * ```typescript * // Create a Field instance for the 'name' field - * const nameField = Field.of("name"); + * const nameField = field("name"); * * // Create a Field instance for a nested field 'address.city' - * const cityField = Field.of("address.city"); + * const cityField = field("address.city"); * ``` */ -export class Field extends Selectable { - exprType: ExprType = 'Field'; +export class Field extends Expr implements Selectable { + readonly exprType: ExprType = 'Field'; selectable = true as const; - private constructor( - private fieldPath: InternalFieldPath, - private pipeline: Pipeline | null = null - ) { - super(); - } - - /** - * Creates a {@code Field} instance representing the field at the given path. - * - * The path can be a simple field name (e.g., "name") or a dot-separated path to a nested field - * (e.g., "address.city"). - * - * ```typescript - * // Create a Field instance for the 'title' field - * const titleField = Field.of("title"); - * - * // Create a Field instance for a nested field 'author.firstName' - * const authorFirstNameField = Field.of("author.firstName"); - * ``` - * - * @param name The path to the field. - * @return A new {@code Field} instance representing the specified field. - */ - static of(name: string): Field; - static of(path: FieldPath): Field; - static of( - pipelineOrName: Pipeline | string | FieldPath, - name?: string - ): Field { - if (typeof pipelineOrName === 'string') { - if (DOCUMENT_KEY_NAME === pipelineOrName) { - return new Field(documentId()._internalPath); - } - return new Field(fieldPathFromArgument('of', pipelineOrName)); - } else if (pipelineOrName instanceof FieldPath) { - if (documentId().isEqual(pipelineOrName)) { - return new Field(documentId()._internalPath); - } - return new Field(pipelineOrName._internalPath); - } else { - return new Field( - fieldPathFromArgument('of', name!), - pipelineOrName as Pipeline - ); - } - } - - fieldName(): string { - return this.fieldPath.canonicalString(); - } - /** - * @private * @internal - */ - _toProto(serializer: JsonProtoSerializer): ProtoValue { - return { - fieldReferenceValue: this.fieldPath.canonicalString() - }; - } - - /** * @private - * @internal + * @hideconstructor + * @param fieldPath */ - _readUserData(dataReader: UserDataReader): void {} -} - -/** - * @beta - */ -export class Fields extends Selectable { - exprType: ExprType = 'Field'; - selectable = true as const; - - private constructor(private fields: Field[]) { + constructor(private fieldPath: InternalFieldPath) { super(); } - static of(name: string, ...others: string[]): Fields { - return new Fields([Field.of(name), ...others.map(Field.of)]); + fieldName(): string { + return this.fieldPath.canonicalString(); } - static ofAll(): Fields { - return new Fields([]); + get alias(): string { + return this.fieldName(); } - fieldList(): Field[] { - return this.fields.map(f => f); + get expr(): Expr { + return this; } /** @@ -2001,9 +2212,7 @@ export class Fields extends Selectable { */ _toProto(serializer: JsonProtoSerializer): ProtoValue { return { - arrayValue: { - values: this.fields.map(f => f._toProto(serializer)) - } + fieldReferenceValue: this.fieldPath.canonicalString() }; } @@ -2011,8 +2220,36 @@ export class Fields extends Selectable { * @private * @internal */ - _readUserData(dataReader: UserDataReader): void { - this.fields.forEach(expr => expr._readUserData(dataReader)); + _readUserData(dataReader: UserDataReader): void {} +} + +/** + * Creates a {@code Field} instance representing the field at the given path. + * + * The path can be a simple field name (e.g., "name") or a dot-separated path to a nested field + * (e.g., "address.city"). + * + * ```typescript + * // Create a Field instance for the 'title' field + * const titleField = field("title"); + * + * // Create a Field instance for a nested field 'author.firstName' + * const authorFirstNameField = field("author.firstName"); + * ``` + * + * @param name The path to the field. + * @return A new {@code Field} instance representing the specified field. + */ +export function field(name: string): Field; +export function field(path: FieldPath): Field; +export function field(nameOrPath: string | FieldPath): Field { + if (typeof nameOrPath === 'string') { + if (DOCUMENT_KEY_NAME === nameOrPath) { + return new Field(documentIdFieldPath()._internalPath); + } + return new Field(fieldPathFromArgument('of', nameOrPath)); + } else { + return new Field(nameOrPath._internalPath); } } @@ -2025,158 +2262,25 @@ export class Fields extends Selectable { * * ```typescript * // Create a Constant instance for the number 10 - * const ten = Constant.of(10); + * const ten = constant(10); * * // Create a Constant instance for the string "hello" - * const hello = Constant.of("hello"); + * const hello = constant("hello"); * ``` */ export class Constant extends Expr { - exprType: ExprType = 'Constant'; + readonly exprType: ExprType = 'Constant'; private _protoValue?: ProtoValue; - private constructor(private value: any) { - super(); - } - /** - * Creates a `Constant` instance for a number value. - * - * @param value The number value. - * @return A new `Constant` instance. - */ - static of(value: number): Constant; - - /** - * Creates a `Constant` instance for a string value. - * - * @param value The string value. - * @return A new `Constant` instance. - */ - static of(value: string): Constant; - - /** - * Creates a `Constant` instance for a boolean value. - * - * @param value The boolean value. - * @return A new `Constant` instance. - */ - static of(value: boolean): Constant; - - /** - * Creates a `Constant` instance for a null value. - * - * @param value The null value. - * @return A new `Constant` instance. - */ - static of(value: null): Constant; - - /** - * Creates a `Constant` instance for an undefined value. * @private * @internal - * - * @param value The undefined value. - * @return A new `Constant` instance. + * @hideconstructor + * @param value The value of the constant. */ - static of(value: undefined): Constant; - - /** - * Creates a `Constant` instance for a GeoPoint value. - * - * @param value The GeoPoint value. - * @return A new `Constant` instance. - */ - static of(value: GeoPoint): Constant; - - /** - * Creates a `Constant` instance for a Timestamp value. - * - * @param value The Timestamp value. - * @return A new `Constant` instance. - */ - static of(value: Timestamp): Constant; - - /** - * Creates a `Constant` instance for a Date value. - * - * @param value The Date value. - * @return A new `Constant` instance. - */ - static of(value: Date): Constant; - - /** - * Creates a `Constant` instance for a Bytes value. - * - * @param value The Bytes value. - * @return A new `Constant` instance. - */ - static of(value: Bytes): Constant; - - /** - * Creates a `Constant` instance for a DocumentReference value. - * - * @param value The DocumentReference value. - * @return A new `Constant` instance. - */ - static of(value: DocumentReference): Constant; - - /** - * Creates a `Constant` instance for a Firestore proto value. - * For internal use only. - * @private - * @internal - * @param value The Firestore proto value. - * @return A new `Constant` instance. - */ - static of(value: ProtoValue): Constant; - - /** - * Creates a `Constant` instance for an array value. - * - * @param value The array value. - * @return A new `Constant` instance. - */ - static of(value: any[]): Constant; - - /** - * Creates a `Constant` instance for a map value. - * - * @param value The map value. - * @return A new `Constant` instance. - */ - static of(value: Record): Constant; - - /** - * Creates a `Constant` instance for a VectorValue value. - * - * @param value The VectorValue value. - * @return A new `Constant` instance. - */ - static of(value: VectorValue): Constant; - - static of(value: any): Constant { - return new Constant(value); - } - - /** - * Creates a `Constant` instance for a VectorValue value. - * - * ```typescript - * // Create a Constant instance for a vector value - * const vectorConstant = Constant.ofVector([1, 2, 3]); - * ``` - * - * @param value The VectorValue value. - * @return A new `Constant` instance. - */ - static vector(value: number[] | VectorValue): Constant { - if (value instanceof VectorValue) { - return new Constant(value); - } else { - return new Constant(new VectorValue(value as number[])); - } + constructor(private value: unknown) { + super(); } /** @@ -2205,19 +2309,14 @@ export class Constant extends Expr { * @private * @internal */ - _readUserData(dataReader: UserDataReader): void { - const context = dataReader.createContext( - UserDataSource.Argument, - 'Constant.of' - ); - - if (isFirestoreValue(this.value)) { - // Special case where value is a proto value. - // This can occur when converting a Query to Pipeline. - this._protoValue = this.value; - } else if (this.value === undefined) { - // TODO(pipeline) how should we treat the value of `undefined`? - this._protoValue = parseData(null, context)!; + _readUserData(dataReader: UserDataReader, context?: ParseContext): void { + context = + this._createdFromLiteral && context + ? context + : dataReader.createContext(UserDataSource.Argument, 'constant'); + + if (isFirestoreValue(this._protoValue)) { + return; } else { this._protoValue = parseData(this.value, context)!; } @@ -2225,790 +2324,1208 @@ export class Constant extends Expr { } /** - * @beta + * Creates a `Constant` instance for a number value. * - * This class defines the base class for Firestore {@link Pipeline} functions, which can be evaluated within pipeline - * execution. + * @param value The number value. + * @return A new `Constant` instance. + */ +export function constant(value: number): Constant; + +/** + * Creates a `Constant` instance for a string value. * - * Typically, you would not use this class or its children directly. Use either the functions like {@link and}, {@link eq}, - * or the methods on {@link Expr} ({@link Expr#eq}, {@link Expr#lt}, etc) to construct new Function instances. + * @param value The string value. + * @return A new `Constant` instance. */ -export class FirestoreFunction extends Expr { - exprType: ExprType = 'Function'; - constructor(private name: string, private params: Expr[]) { - super(); - } +export function constant(value: string): Constant; - /** - * @private - * @internal - */ - _toProto(serializer: JsonProtoSerializer): ProtoValue { - return { - functionValue: { - name: this.name, - args: this.params.map(p => p._toProto(serializer)) - } - }; - } +/** + * Creates a `Constant` instance for a boolean value. + * + * @param value The boolean value. + * @return A new `Constant` instance. + */ +export function constant(value: boolean): Constant; - /** - * @private - * @internal - */ - _readUserData(dataReader: UserDataReader): void { - this.params.forEach(expr => expr._readUserData(dataReader)); - } -} +/** + * Creates a `Constant` instance for a null value. + * + * @param value The null value. + * @return A new `Constant` instance. + */ +export function constant(value: null): Constant; /** - * @beta + * Creates a `Constant` instance for a GeoPoint value. + * + * @param value The GeoPoint value. + * @return A new `Constant` instance. */ -export class Add extends FirestoreFunction { - constructor(private left: Expr, private right: Expr) { - super('add', [left, right]); - } -} +export function constant(value: GeoPoint): Constant; /** - * @beta + * Creates a `Constant` instance for a Timestamp value. + * + * @param value The Timestamp value. + * @return A new `Constant` instance. */ -export class Subtract extends FirestoreFunction { - constructor(private left: Expr, private right: Expr) { - super('subtract', [left, right]); - } -} +export function constant(value: Timestamp): Constant; /** - * @beta + * Creates a `Constant` instance for a Date value. + * + * @param value The Date value. + * @return A new `Constant` instance. */ -export class Multiply extends FirestoreFunction { - constructor(private left: Expr, private right: Expr) { - super('multiply', [left, right]); - } +export function constant(value: Date): Constant; + +/** + * Creates a `Constant` instance for a Bytes value. + * + * @param value The Bytes value. + * @return A new `Constant` instance. + */ +export function constant(value: Bytes): Constant; + +/** + * Creates a `Constant` instance for a DocumentReference value. + * + * @param value The DocumentReference value. + * @return A new `Constant` instance. + */ +export function constant(value: DocumentReference): Constant; + +/** + * Creates a `Constant` instance for a Firestore proto value. + * For internal use only. + * @private + * @internal + * @param value The Firestore proto value. + * @return A new `Constant` instance. + */ +export function constant(value: ProtoValue): Constant; + +/** + * Creates a `Constant` instance for a VectorValue value. + * + * @param value The VectorValue value. + * @return A new `Constant` instance. + */ +export function constant(value: VectorValue): Constant; + +export function constant(value: unknown): Constant { + return new Constant(value); } /** - * @beta + * Creates a `Constant` instance for a VectorValue value. + * + * ```typescript + * // Create a Constant instance for a vector value + * const vectorConstant = constantVector([1, 2, 3]); + * ``` + * + * @param value The VectorValue value. + * @return A new `Constant` instance. */ -export class Divide extends FirestoreFunction { - constructor(private left: Expr, private right: Expr) { - super('divide', [left, right]); +export function constantVector(value: number[] | VectorValue): Constant { + if (value instanceof VectorValue) { + return new Constant(value); + } else { + return new Constant(new VectorValue(value as number[])); } } /** - * @beta + * Internal only + * @internal + * @private */ -export class Mod extends FirestoreFunction { - constructor(private left: Expr, private right: Expr) { - super('mod', [left, right]); +export class MapValue extends Expr { + constructor(private plainObject: Map) { + super(); } -} -// /** -// * @beta -// */ -// export class BitAnd extends FirestoreFunction { -// constructor( -// private left: Expr, -// private right: Expr -// ) { -// super('bit_and', [left, right]); -// } -// } -// -// /** -// * @beta -// */ -// export class BitOr extends FirestoreFunction { -// constructor( -// private left: Expr, -// private right: Expr -// ) { -// super('bit_or', [left, right]); -// } -// } -// -// /** -// * @beta -// */ -// export class BitXor extends FirestoreFunction { -// constructor( -// private left: Expr, -// private right: Expr -// ) { -// super('bit_xor', [left, right]); -// } -// } -// -// /** -// * @beta -// */ -// export class BitNot extends FirestoreFunction { -// constructor(private operand: Expr) { -// super('bit_not', [operand]); -// } -// } -// -// /** -// * @beta -// */ -// export class BitLeftShift extends FirestoreFunction { -// constructor( -// private left: Expr, -// private right: Expr -// ) { -// super('bit_left_shift', [left, right]); -// } -// } -// -// /** -// * @beta -// */ -// export class BitRightShift extends FirestoreFunction { -// constructor( -// private left: Expr, -// private right: Expr -// ) { -// super('bit_right_shift', [left, right]); -// } -// } - -/** - * @beta - */ -export class Eq extends FirestoreFunction implements FilterCondition { - constructor(private left: Expr, private right: Expr) { - super('eq', [left, right]); + exprType: ExprType = 'Constant'; + + _readUserData(dataReader: UserDataReader, context?: ParseContext): void { + context = + this._createdFromLiteral && context + ? context + : dataReader.createContext(UserDataSource.Argument, '_map'); + + this.plainObject.forEach(expr => { + expr._readUserData(dataReader, context); + }); + } + + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return toMapValue(serializer, this.plainObject); } - filterable = true as const; } /** * @beta + * + * This class defines the base class for Firestore {@link Pipeline} functions, which can be evaluated within pipeline + * execution. + * + * Typically, you would not use this class or its children directly. Use either the functions like {@link and}, {@link eq}, + * or the methods on {@link Expr} ({@link Expr#eq}, {@link Expr#lt}, etc.) to construct new Function instances. */ -export class Neq extends FirestoreFunction implements FilterCondition { - constructor(private left: Expr, private right: Expr) { - super('neq', [left, right]); +export class FunctionExpr extends Expr { + readonly exprType: ExprType = 'Function'; + + constructor(private name: string, private params: Expr[]) { + super(); } - filterable = true as const; -} -/** - * @beta - */ -export class Lt extends FirestoreFunction implements FilterCondition { - constructor(private left: Expr, private right: Expr) { - super('lt', [left, right]); + /** + * @private + * @internal + */ + _toProto(serializer: JsonProtoSerializer): ProtoValue { + return { + functionValue: { + name: this.name, + args: this.params.map(p => p._toProto(serializer)) + } + }; } - filterable = true as const; -} -/** - * @beta - */ -export class Lte extends FirestoreFunction implements FilterCondition { - constructor(private left: Expr, private right: Expr) { - super('lte', [left, right]); + /** + * @private + * @internal + */ + _readUserData(dataReader: UserDataReader, context?: ParseContext): void { + context = + this._createdFromLiteral && context + ? context + : dataReader.createContext(UserDataSource.Argument, this.name); + this.params.forEach(expr => { + return expr._readUserData(dataReader, context); + }); } - filterable = true as const; } /** * @beta + * + * An interface that represents a filter condition. */ -export class Gt extends FirestoreFunction implements FilterCondition { - constructor(private left: Expr, private right: Expr) { - super('gt', [left, right]); +export class BooleanExpr extends FunctionExpr { + filterable: true = true; + + /** + * Creates an aggregation that finds the count of input documents satisfying + * this boolean expression. + * + * ```typescript + * // Find the count of documents with a score greater than 90 + * field("score").gt(90).countIf().as("highestScore"); + * ``` + * + * @return A new `AggregateFunction` representing the 'countIf' aggregation. + */ + countIf(): AggregateFunction { + return new AggregateFunction('count_if', [this]); } - filterable = true as const; -} -/** - * @beta - */ -export class Gte extends FirestoreFunction implements FilterCondition { - constructor(private left: Expr, private right: Expr) { - super('gte', [left, right]); + /** + * Creates an expression that negates this boolean expression. + * + * ```typescript + * // Find documents where the 'tags' field does not contain 'completed' + * field("tags").arrayContains("completed").not(); + * ``` + * + * @return A new {@code Expr} representing the negated filter condition. + */ + not(): BooleanExpr { + return new BooleanExpr('not', [this]); } - filterable = true as const; } /** * @beta + * Creates an aggregation that counts the number of stage inputs where the provided + * boolean expression evaluates to true. + * + * ```typescript + * // Count the number of documents where 'is_active' field equals true + * countIf(field("is_active").eq(true)).as("numActiveDocuments"); + * ``` + * + * @param booleanExpr - The boolean expression to evaluate on each input. + * @returns A new `AggregateFunction` representing the 'countIf' aggregation. */ -export class ArrayConcat extends FirestoreFunction { - constructor(private array: Expr, private elements: Expr[]) { - super('array_concat', [array, ...elements]); - } +export function countIf(booleanExpr: BooleanExpr): AggregateFunction { + return booleanExpr.countIf(); } /** * @beta + * Creates an expression that return a pseudo-random value of type double in the + * range of [0, 1), inclusive of 0 and exclusive of 1. + * + * @returns A new `Expr` representing the 'rand' function. */ -export class ArrayReverse extends FirestoreFunction { - constructor(private array: Expr) { - super('array_reverse', [array]); - } +export function rand(): FunctionExpr { + return new FunctionExpr('rand', []); } /** * @beta + * + * Creates an expression that applies a bitwise AND operation between a field and a constant. + * + * ```typescript + * // Calculate the bitwise AND of 'field1' and 0xFF. + * bitAnd("field1", 0xFF); + * ``` + * + * @param field The left operand field name. + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise AND operation. */ -export class ArrayContains - extends FirestoreFunction - implements FilterCondition -{ - constructor(private array: Expr, private element: Expr) { - super('array_contains', [array, element]); - } - filterable = true as const; -} - +export function bitAnd(field: string, otherBits: number | Bytes): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise AND operation between a field and an expression. + * + * ```typescript + * // Calculate the bitwise AND of 'field1' and 'field2'. + * bitAnd("field1", field("field2")); + * ``` + * + * @param field The left operand field name. + * @param bitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise AND operation. */ -export class ArrayContainsAll - extends FirestoreFunction - implements FilterCondition -{ - constructor(private array: Expr, private values: Expr[]) { - super('array_contains_all', [array, new ListOfExprs(values)]); - } - filterable = true as const; -} - +export function bitAnd(field: string, bitsExpression: Expr): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise AND operation between an expression and a constant. + * + * ```typescript + * // Calculate the bitwise AND of 'field1' and 0xFF. + * bitAnd(field("field1"), 0xFF); + * ``` + * + * @param bitsExpression An expression returning bits. + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise AND operation. */ -export class ArrayContainsAny - extends FirestoreFunction - implements FilterCondition -{ - constructor(private array: Expr, private values: Expr[]) { - super('array_contains_any', [array, new ListOfExprs(values)]); - } - filterable = true as const; -} - +export function bitAnd( + bitsExpression: Expr, + otherBits: number | Bytes +): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise AND operation between two expressions. + * + * ```typescript + * // Calculate the bitwise AND of 'field1' and 'field2'. + * bitAnd(field("field1"), field("field2")); + * ``` + * + * @param bitsExpression An expression that returns bits when evaluated. + * @param otherBitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise AND operation. */ -export class ArrayLength extends FirestoreFunction { - constructor(private array: Expr) { - super('array_length', [array]); - } +export function bitAnd( + bitsExpression: Expr, + otherBitsExpression: Expr +): FunctionExpr; +export function bitAnd( + bits: string | Expr, + bitsOrExpression: number | Expr | Bytes +): FunctionExpr { + return fieldOrExpression(bits).bitAnd(valueToDefaultExpr(bitsOrExpression)); } /** * @beta + * + * Creates an expression that applies a bitwise OR operation between a field and a constant. + * + * ```typescript + * // Calculate the bitwise OR of 'field1' and 0xFF. + * bitOr("field1", 0xFF); + * ``` + * + * @param field The left operand field name. + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise OR operation. */ -export class ArrayElement extends FirestoreFunction { - constructor() { - super('array_element', []); - } -} - +export function bitOr(field: string, otherBits: number | Bytes): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise OR operation between a field and an expression. + * + * ```typescript + * // Calculate the bitwise OR of 'field1' and 'field2'. + * bitOr("field1", field("field2")); + * ``` + * + * @param field The left operand field name. + * @param bitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise OR operation. */ -export class EqAny extends FirestoreFunction implements FilterCondition { - constructor(private left: Expr, private others: Expr[]) { - super('eq_any', [left, new ListOfExprs(others)]); - } - filterable = true as const; -} - +export function bitOr(field: string, bitsExpression: Expr): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise OR operation between an expression and a constant. + * + * ```typescript + * // Calculate the bitwise OR of 'field1' and 0xFF. + * bitOr(field("field1"), 0xFF); + * ``` + * + * @param bitsExpression An expression returning bits. + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise OR operation. */ -export class NotEqAny extends FirestoreFunction implements FilterCondition { - constructor(private left: Expr, private others: Expr[]) { - super('not_eq_any', [left, new ListOfExprs(others)]); - } - filterable = true as const; -} - +export function bitOr( + bitsExpression: Expr, + otherBits: number | Bytes +): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise OR operation between two expressions. + * + * ```typescript + * // Calculate the bitwise OR of 'field1' and 'field2'. + * bitOr(field("field1"), field("field2")); + * ``` + * + * @param bitsExpression An expression that returns bits when evaluated. + * @param otherBitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise OR operation. */ -export class IsNan extends FirestoreFunction implements FilterCondition { - constructor(private expr: Expr) { - super('is_nan', [expr]); - } - filterable = true as const; +export function bitOr( + bitsExpression: Expr, + otherBitsExpression: Expr +): FunctionExpr; +export function bitOr( + bits: string | Expr, + bitsOrExpression: number | Expr | Bytes +): FunctionExpr { + return fieldOrExpression(bits).bitOr(valueToDefaultExpr(bitsOrExpression)); } /** * @beta + * + * Creates an expression that applies a bitwise XOR operation between a field and a constant. + * + * ```typescript + * // Calculate the bitwise XOR of 'field1' and 0xFF. + * bitXor("field1", 0xFF); + * ``` + * + * @param field The left operand field name. + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise XOR operation. */ -export class Exists extends FirestoreFunction implements FilterCondition { - constructor(private expr: Expr) { - super('exists', [expr]); - } - filterable = true as const; -} - +export function bitXor(field: string, otherBits: number | Bytes): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise XOR operation between a field and an expression. + * + * ```typescript + * // Calculate the bitwise XOR of 'field1' and 'field2'. + * bitXor("field1", field("field2")); + * ``` + * + * @param field The left operand field name. + * @param bitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise XOR operation. */ -export class Not extends FirestoreFunction implements FilterCondition { - constructor(private expr: Expr) { - super('not', [expr]); - } - filterable = true as const; -} - +export function bitXor(field: string, bitsExpression: Expr): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise XOR operation between an expression and a constant. + * + * ```typescript + * // Calculate the bitwise XOR of 'field1' and 0xFF. + * bitXor(field("field1"), 0xFF); + * ``` + * + * @param bitsExpression An expression returning bits. + * @param otherBits A constant representing bits. + * @return A new {@code Expr} representing the bitwise XOR operation. */ -export class And extends FirestoreFunction implements FilterCondition { - constructor(private conditions: FilterCondition[]) { - super('and', conditions); - } - - filterable = true as const; -} - +export function bitXor( + bitsExpression: Expr, + otherBits: number | Bytes +): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise XOR operation between two expressions. + * + * ```typescript + * // Calculate the bitwise XOR of 'field1' and 'field2'. + * bitXor(field("field1"), field("field2")); + * ``` + * + * @param bitsExpression An expression that returns bits when evaluated. + * @param otherBitsExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise XOR operation. */ -export class Or extends FirestoreFunction implements FilterCondition { - constructor(private conditions: FilterCondition[]) { - super('or', conditions); - } - filterable = true as const; +export function bitXor( + bitsExpression: Expr, + otherBitsExpression: Expr +): FunctionExpr; +export function bitXor( + bits: string | Expr, + bitsOrExpression: number | Expr | Bytes +): FunctionExpr { + return fieldOrExpression(bits).bitXor(valueToDefaultExpr(bitsOrExpression)); } /** * @beta + * + * Creates an expression that applies a bitwise NOT operation to a field. + * + * ```typescript + * // Calculate the bitwise NOT of 'field1'. + * bitNot("field1"); + * ``` + * + * @param field The operand field name. + * @return A new {@code Expr} representing the bitwise NOT operation. */ -export class Xor extends FirestoreFunction implements FilterCondition { - constructor(private conditions: FilterCondition[]) { - super('xor', conditions); - } - filterable = true as const; -} - +export function bitNot(field: string): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise NOT operation to an expression. + * + * ```typescript + * // Calculate the bitwise NOT of 'field1'. + * bitNot(field("field1")); + * ``` + * + * @param bitsValueExpression An expression that returns bits when evaluated. + * @return A new {@code Expr} representing the bitwise NOT operation. */ -export class Cond extends FirestoreFunction { - constructor( - private condition: FilterCondition, - private thenExpr: Expr, - private elseExpr: Expr - ) { - super('cond', [condition, thenExpr, elseExpr]); - } - filterable = true as const; +export function bitNot(bitsValueExpression: Expr): FunctionExpr; +export function bitNot(bits: string | Expr): FunctionExpr { + return fieldOrExpression(bits).bitNot(); } /** * @beta + * + * Creates an expression that applies a bitwise left shift operation between a field and a constant. + * + * ```typescript + * // Calculate the bitwise left shift of 'field1' by 2 bits. + * bitLeftShift("field1", 2); + * ``` + * + * @param field The left operand field name. + * @param y The right operand constant representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise left shift operation. */ -export class LogicalMaximum extends FirestoreFunction { - constructor(private left: Expr, private right: Expr) { - super('logical_maximum', [left, right]); - } -} - +export function bitLeftShift(field: string, y: number): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise left shift operation between a field and an expression. + * + * ```typescript + * // Calculate the bitwise left shift of 'field1' by 'field2' bits. + * bitLeftShift("field1", field("field2")); + * ``` + * + * @param field The left operand field name. + * @param numberExpr The right operand expression representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise left shift operation. */ -export class LogicalMinimum extends FirestoreFunction { - constructor(private left: Expr, private right: Expr) { - super('logical_minimum', [left, right]); - } -} - +export function bitLeftShift(field: string, numberExpr: Expr): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise left shift operation between an expression and a constant. + * + * ```typescript + * // Calculate the bitwise left shift of 'field1' by 2 bits. + * bitLeftShift(field("field1"), 2); + * ``` + * + * @param xValue An expression returning bits. + * @param y The right operand constant representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise left shift operation. */ -export class Reverse extends FirestoreFunction { - constructor(private value: Expr) { - super('reverse', [value]); - } -} - +export function bitLeftShift(xValue: Expr, y: number): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise left shift operation between two expressions. + * + * ```typescript + * // Calculate the bitwise left shift of 'field1' by 'field2' bits. + * bitLeftShift(field("field1"), field("field2")); + * ``` + * + * @param xValue An expression returning bits. + * @param numberExpr The right operand expression representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise left shift operation. */ -export class ReplaceFirst extends FirestoreFunction { - constructor(private value: Expr, private find: Expr, private replace: Expr) { - super('replace_first', [value, find, replace]); - } +export function bitLeftShift(xValue: Expr, numberExpr: Expr): FunctionExpr; +export function bitLeftShift( + xValue: string | Expr, + numberExpr: number | Expr +): FunctionExpr { + return fieldOrExpression(xValue).bitLeftShift(valueToDefaultExpr(numberExpr)); } /** * @beta + * + * Creates an expression that applies a bitwise right shift operation between a field and a constant. + * + * ```typescript + * // Calculate the bitwise right shift of 'field1' by 2 bits. + * bitRightShift("field1", 2); + * ``` + * + * @param field The left operand field name. + * @param y The right operand constant representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise right shift operation. */ -export class ReplaceAll extends FirestoreFunction { - constructor(private value: Expr, private find: Expr, private replace: Expr) { - super('replace_all', [value, find, replace]); - } -} - +export function bitRightShift(field: string, y: number): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise right shift operation between a field and an expression. + * + * ```typescript + * // Calculate the bitwise right shift of 'field1' by 'field2' bits. + * bitRightShift("field1", field("field2")); + * ``` + * + * @param field The left operand field name. + * @param numberExpr The right operand expression representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise right shift operation. */ -export class CharLength extends FirestoreFunction { - constructor(private value: Expr) { - super('char_length', [value]); - } -} - +export function bitRightShift(field: string, numberExpr: Expr): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise right shift operation between an expression and a constant. + * + * ```typescript + * // Calculate the bitwise right shift of 'field1' by 2 bits. + * bitRightShift(field("field1"), 2); + * ``` + * + * @param xValue An expression returning bits. + * @param y The right operand constant representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise right shift operation. */ -export class ByteLength extends FirestoreFunction { - constructor(private value: Expr) { - super('byte_length', [value]); - } -} - +export function bitRightShift(xValue: Expr, y: number): FunctionExpr; /** * @beta + * + * Creates an expression that applies a bitwise right shift operation between two expressions. + * + * ```typescript + * // Calculate the bitwise right shift of 'field1' by 'field2' bits. + * bitRightShift(field("field1"), field("field2")); + * ``` + * + * @param xValue An expression returning bits. + * @param right The right operand expression representing the number of bits to shift. + * @return A new {@code Expr} representing the bitwise right shift operation. */ -export class Like extends FirestoreFunction implements FilterCondition { - constructor(private expr: Expr, private pattern: Expr) { - super('like', [expr, pattern]); - } - filterable = true as const; +export function bitRightShift(xValue: Expr, numberExpr: Expr): FunctionExpr; +export function bitRightShift( + xValue: string | Expr, + numberExpr: number | Expr +): FunctionExpr { + return fieldOrExpression(xValue).bitRightShift( + valueToDefaultExpr(numberExpr) + ); } /** * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index 1. + * arrayOffset('tags', 1); + * ``` + * + * @param arrayField The name of the array field. + * @param offset The index of the element to return. + * @return A new Expr representing the 'arrayOffset' operation. */ -export class RegexContains - extends FirestoreFunction - implements FilterCondition -{ - constructor(private expr: Expr, private pattern: Expr) { - super('regex_contains', [expr, pattern]); - } - filterable = true as const; -} +export function arrayOffset(arrayField: string, offset: number): FunctionExpr; /** * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index specified by field + * // 'favoriteTag'. + * arrayOffset('tags', field('favoriteTag')); + * ``` + * + * @param arrayField The name of the array field. + * @param offsetExpr An Expr evaluating to the index of the element to return. + * @return A new Expr representing the 'arrayOffset' operation. */ -export class RegexMatch extends FirestoreFunction implements FilterCondition { - constructor(private expr: Expr, private pattern: Expr) { - super('regex_match', [expr, pattern]); - } - filterable = true as const; -} +export function arrayOffset(arrayField: string, offsetExpr: Expr): FunctionExpr; /** * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index 1. + * arrayOffset(field('tags'), 1); + * ``` + * + * @param arrayExpression An Expr evaluating to an array. + * @param offset The index of the element to return. + * @return A new Expr representing the 'arrayOffset' operation. */ -export class StrContains extends FirestoreFunction implements FilterCondition { - constructor(private expr: Expr, private substring: Expr) { - super('str_contains', [expr, substring]); - } - filterable = true as const; -} +export function arrayOffset( + arrayExpression: Expr, + offset: number +): FunctionExpr; /** * @beta + * Creates an expression that indexes into an array from the beginning or end + * and return the element. If the offset exceeds the array length, an error is + * returned. A negative offset, starts from the end. + * + * ```typescript + * // Return the value in the tags field array at index specified by field + * // 'favoriteTag'. + * arrayOffset(field('tags'), field('favoriteTag')); + * ``` + * + * @param arrayExpression An Expr evaluating to an array. + * @param offsetExpr An Expr evaluating to the index of the element to return. + * @return A new Expr representing the 'arrayOffset' operation. */ -export class StartsWith extends FirestoreFunction implements FilterCondition { - constructor(private expr: Expr, private prefix: Expr) { - super('starts_with', [expr, prefix]); - } - filterable = true as const; +export function arrayOffset( + arrayExpression: Expr, + offsetExpr: Expr +): FunctionExpr; +export function arrayOffset( + array: Expr | string, + offset: Expr | number +): FunctionExpr { + return fieldOrExpression(array).arrayOffset(valueToDefaultExpr(offset)); } /** * @beta + * Creates an Expr that returns a map of all values in the current expression context. + * + * @return A new {@code Expr} representing the 'current_context' function. */ -export class EndsWith extends FirestoreFunction implements FilterCondition { - constructor(private expr: Expr, private suffix: Expr) { - super('ends_with', [expr, suffix]); - } - filterable = true as const; +export function currentContext(): FunctionExpr { + return new FunctionExpr('current_context', []); } /** * @beta + * + * Creates an expression that checks if a given expression produces an error. + * + * ```typescript + * // Check if the result of a calculation is an error + * isError(field("title").arrayContains(1)); + * ``` + * + * @param value The expression to check. + * @return A new {@code Expr} representing the 'isError' check. */ -export class ToLower extends FirestoreFunction { - constructor(private expr: Expr) { - super('to_lower', [expr]); - } +export function isError(value: Expr): BooleanExpr { + return value.isError(); } /** * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of the `try` argument evaluation. + * + * ```typescript + * // Returns the first item in the title field arrays, or returns + * // the entire title field if the array is empty or the field is another type. + * ifError(field("title").arrayOffset(0), field("title")); + * ``` + * + * @param tryExpr The try expression. + * @param catchExpr The catch expression that will be evaluated and + * returned if the tryExpr produces an error. + * @return A new {@code Expr} representing the 'ifError' operation. */ -export class ToUpper extends FirestoreFunction { - constructor(private expr: Expr) { - super('to_upper', [expr]); - } -} +export function ifError(tryExpr: Expr, catchExpr: Expr): FunctionExpr; /** * @beta + * + * Creates an expression that returns the `catch` argument if there is an + * error, else return the result of the `try` argument evaluation. + * + * ```typescript + * // Returns the first item in the title field arrays, or returns + * // "Default Title" + * ifError(field("title").arrayOffset(0), "Default Title"); + * ``` + * + * @param tryExpr The try expression. + * @param catchValue The value that will be returned if the tryExpr produces an + * error. + * @return A new {@code Expr} representing the 'ifError' operation. */ -export class Trim extends FirestoreFunction { - constructor(private expr: Expr) { - super('trim', [expr]); - } +export function ifError(tryExpr: Expr, catchValue: unknown): FunctionExpr; +export function ifError(tryExpr: Expr, catchValue: unknown): FunctionExpr { + return tryExpr.ifError(valueToDefaultExpr(catchValue)); } /** * @beta + * + * Creates an expression that returns `true` if a value is absent. Otherwise, + * returns `false` even if the value is `null`. + * + * ```typescript + * // Check if the field `value` is absent. + * isAbsent(field("value")); + * ``` + * + * @param value The expression to check. + * @return A new {@code Expr} representing the 'isAbsent' check. */ -export class StrConcat extends FirestoreFunction { - constructor(private first: Expr, private rest: Expr[]) { - super('str_concat', [first, ...rest]); - } -} +export function isAbsent(value: Expr): BooleanExpr; /** * @beta + * + * Creates an expression that returns `true` if a field is absent. Otherwise, + * returns `false` even if the field value is `null`. + * + * ```typescript + * // Check if the field `value` is absent. + * isAbsent("value"); + * ``` + * + * @param field The field to check. + * @return A new {@code Expr} representing the 'isAbsent' check. */ -export class MapGet extends FirestoreFunction { - constructor(map: Expr, name: string) { - super('map_get', [map, Constant.of(name)]); - } +export function isAbsent(field: string): BooleanExpr; +export function isAbsent(value: Expr | string): BooleanExpr { + 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 class Count extends FirestoreFunction implements Accumulator { - accumulator = true as const; - constructor(private value: Expr | undefined, private distinct: boolean) { - super('count', value === undefined ? [] : [value]); - } -} +export function isNull(value: Expr): BooleanExpr; /** * @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 class Sum extends FirestoreFunction implements Accumulator { - accumulator = true as const; - constructor(private value: Expr, private distinct: boolean) { - super('sum', [value]); - } +export function isNull(value: string): BooleanExpr; +export function isNull(value: Expr | string): BooleanExpr { + 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 class Avg extends FirestoreFunction implements Accumulator { - accumulator = true as const; - constructor(private value: Expr, private distinct: boolean) { - super('avg', [value]); - } -} +export function isNotNull(value: Expr): BooleanExpr; /** * @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 class Minimum extends FirestoreFunction implements Accumulator { - accumulator = true as const; - constructor(private value: Expr, private distinct: boolean) { - super('minimum', [value]); - } +export function isNotNull(value: string): BooleanExpr; +export function isNotNull(value: Expr | string): BooleanExpr { + 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 class Maximum extends FirestoreFunction implements Accumulator { - accumulator = true as const; - constructor(private value: Expr, private distinct: boolean) { - super('maximum', [value]); - } -} +export function isNotNan(value: Expr): BooleanExpr; /** * @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 class CosineDistance extends FirestoreFunction { - constructor(private vector1: Expr, private vector2: Expr) { - super('cosine_distance', [vector1, vector2]); - } +export function isNotNan(value: string): BooleanExpr; +export function isNotNan(value: Expr | string): BooleanExpr { + return fieldOrExpression(value).isNotNan(); } /** * @beta + * + * Creates an expression that removes a key from the map at the specified field name. + * + * ``` + * // Removes the key 'city' field from the map in the address field of the input document. + * mapRemove('address', 'city'); + * ``` + * + * @param mapField The name of a field containing a map value. + * @param key The name of the key to remove from the input map. */ -export class DotProduct extends FirestoreFunction { - constructor(private vector1: Expr, private vector2: Expr) { - super('dot_product', [vector1, vector2]); - } -} - +export function mapRemove(mapField: string, key: string): FunctionExpr; /** * @beta + * + * Creates an expression that removes a key from the map produced by evaluating an expression. + * + * ``` + * // Removes the key 'baz' from the input map. + * mapRemove(map({foo: 'bar', baz: true}), 'baz'); + * ``` + * + * @param mapExpr An expression return a map value. + * @param key The name of the key to remove from the input map. */ -export class EuclideanDistance extends FirestoreFunction { - constructor(private vector1: Expr, private vector2: Expr) { - super('euclidean_distance', [vector1, vector2]); - } -} - +export function mapRemove(mapExpr: Expr, key: string): FunctionExpr; /** * @beta + * + * Creates an expression that removes a key from the map at the specified field name. + * + * ``` + * // Removes the key 'city' field from the map in the address field of the input document. + * mapRemove('address', constant('city')); + * ``` + * + * @param mapField The name of a field containing a map value. + * @param keyExpr An expression that produces the name of the key to remove from the input map. */ -export class VectorLength extends FirestoreFunction { - constructor(private value: Expr) { - super('vector_length', [value]); - } -} - +export function mapRemove(mapField: string, keyExpr: Expr): FunctionExpr; /** * @beta + * + * Creates an expression that removes a key from the map produced by evaluating an expression. + * + * ``` + * // Removes the key 'baz' from the input map. + * mapRemove(map({foo: 'bar', baz: true}), constant('baz')); + * ``` + * + * @param mapExpr An expression return a map value. + * @param keyExpr An expression that produces the name of the key to remove from the input map. */ -export class UnixMicrosToTimestamp extends FirestoreFunction { - constructor(private input: Expr) { - super('unix_micros_to_timestamp', [input]); - } -} +export function mapRemove(mapExpr: Expr, keyExpr: Expr): FunctionExpr; -/** - * @beta - */ -export class TimestampToUnixMicros extends FirestoreFunction { - constructor(private input: Expr) { - super('timestamp_to_unix_micros', [input]); - } +export function mapRemove( + mapExpr: Expr | string, + stringExpr: Expr | string +): FunctionExpr { + return fieldOrExpression(mapExpr).mapRemove(valueToDefaultExpr(stringExpr)); } /** * @beta + * + * Creates an expression that merges multiple map values. + * + * ``` + * // Merges the map in the settings field with, a map literal, and a map in + * // that is conditionally returned by another expression + * mapMerge('settings', { enabled: true }, cond(field('isAdmin'), { admin: true}, {}) + * ``` + * + * @param mapField Name of a field containing a map value that will be merged. + * @param secondMap A required second map to merge. Represented as a literal or + * an expression that returns a map. + * @param otherMaps Optional additional maps to merge. Each map is represented + * as a literal or an expression that returns a map. */ -export class UnixMillisToTimestamp extends FirestoreFunction { - constructor(private input: Expr) { - super('unix_millis_to_timestamp', [input]); - } -} +export function mapMerge( + mapField: string, + secondMap: Record | Expr, + ...otherMaps: Array | Expr> +): FunctionExpr; /** * @beta + * + * Creates an expression that merges multiple map values. + * + * ``` + * // Merges the map in the settings field with, a map literal, and a map in + * // that is conditionally returned by another expression + * mapMerge(field('settings'), { enabled: true }, cond(field('isAdmin'), { admin: true}, {}) + * ``` + * + * @param firstMap An expression or literal map value that will be merged. + * @param secondMap A required second map to merge. Represented as a literal or + * an expression that returns a map. + * @param otherMaps Optional additional maps to merge. Each map is represented + * as a literal or an expression that returns a map. */ -export class TimestampToUnixMillis extends FirestoreFunction { - constructor(private input: Expr) { - super('timestamp_to_unix_millis', [input]); - } +export function mapMerge( + firstMap: Record | Expr, + secondMap: Record | Expr, + ...otherMaps: Array | Expr> +): FunctionExpr; + +export function mapMerge( + firstMap: string | Record | Expr, + secondMap: Record | Expr, + ...otherMaps: Array | Expr> +): FunctionExpr { + const secondMapExpr = valueToDefaultExpr(secondMap); + const otherMapExprs = otherMaps.map(valueToDefaultExpr); + return fieldOrExpression(firstMap).mapMerge(secondMapExpr, ...otherMapExprs); } /** * @beta + * + * Creates an expression that returns the document ID from a path. + * + * ```typescript + * // Get the document ID from a path. + * documentId(myDocumentReference); + * ``` + * + * @return A new {@code Expr} representing the documentId operation. */ -export class UnixSecondsToTimestamp extends FirestoreFunction { - constructor(private input: Expr) { - super('unix_seconds_to_timestamp', [input]); - } -} +export function documentId( + documentPath: string | DocumentReference +): FunctionExpr; /** * @beta + * + * Creates an expression that returns the document ID from a path. + * + * ```typescript + * // Get the document ID from a path. + * documentId(field("__path__")); + * ``` + * + * @return A new {@code Expr} representing the documentId operation. */ -export class TimestampToUnixSeconds extends FirestoreFunction { - constructor(private input: Expr) { - super('timestamp_to_unix_seconds', [input]); - } +export function documentId(documentPathExpr: Expr): FunctionExpr; + +export function documentId( + documentPath: Expr | string | DocumentReference +): FunctionExpr { + // @ts-ignore + const documentPathExpr = valueToDefaultExpr(documentPath); + return documentPathExpr.documentId(); } /** * @beta + * + * Creates an expression that returns a substring of a string or byte array. + * + * @param field The name of a field containing a string or byte array to compute the substring from. + * @param position Index of the first character of the substring. + * @param length Length of the substring. */ -export class TimestampAdd extends FirestoreFunction { - constructor( - private timestamp: Expr, - private unit: Expr, - private amount: Expr - ) { - super('timestamp_add', [timestamp, unit, amount]); - } -} +export function substr( + field: string, + position: number, + length?: number +): FunctionExpr; /** * @beta + * + * Creates an expression that returns a substring of a string or byte array. + * + * @param input An expression returning a string or byte array to compute the substring from. + * @param position Index of the first character of the substring. + * @param length Length of the substring. */ -export class TimestampSub extends FirestoreFunction { - constructor( - private timestamp: Expr, - private unit: Expr, - private amount: Expr - ) { - super('timestamp_sub', [timestamp, unit, amount]); - } -} +export function substr( + input: Expr, + position: number, + length?: number +): FunctionExpr; /** * @beta * - * Creates an expression that adds two expressions together. - * - * ```typescript - * // Add the value of the 'quantity' field and the 'reserve' field. - * add(Field.of("quantity"), Field.of("reserve")); - * ``` + * Creates an expression that returns a substring of a string or byte array. * - * @param left The first expression to add. - * @param right The second expression to add. - * @return A new {@code Expr} representing the addition operation. + * @param field The name of a field containing a string or byte array to compute the substring from. + * @param position An expression that returns the index of the first character of the substring. + * @param length An expression that returns the length of the substring. */ -export function add(left: Expr, right: Expr): Add; +export function substr( + field: string, + position: Expr, + length?: Expr +): FunctionExpr; /** * @beta * - * Creates an expression that adds an expression to a constant value. + * Creates an expression that returns a substring of a string or byte array. * - * ```typescript - * // Add 5 to the value of the 'age' field - * add(Field.of("age"), 5); - * ``` - * - * @param left The expression to add to. - * @param right The constant value to add. - * @return A new {@code Expr} representing the addition operation. + * @param input An expression returning a string or byte array to compute the substring from. + * @param position An expression that returns the index of the first character of the substring. + * @param length An expression that returns the length of the substring. */ -export function add(left: Expr, right: any): Add; +export function substr( + input: Expr, + position: Expr, + length?: Expr +): FunctionExpr; + +export function substr( + field: Expr | string, + position: Expr | number, + length?: Expr | number +): FunctionExpr { + const fieldExpr = fieldOrExpression(field); + const positionExpr = valueToDefaultExpr(position); + const lengthExpr = + length === undefined ? undefined : valueToDefaultExpr(length); + return fieldExpr.substr(positionExpr, lengthExpr); +} /** * @beta * - * Creates an expression that adds a field's value to an expression. + * Creates an expression that adds two expressions together. * * ```typescript * // Add the value of the 'quantity' field and the 'reserve' field. - * add("quantity", Field.of("reserve")); + * add(field("quantity"), field("reserve")); * ``` * - * @param left The field name to add to. - * @param right The expression to add. + * @param first The first expression to add. + * @param second The second expression or literal to add. + * @param others Optional other expressions or literals to add. * @return A new {@code Expr} representing the addition operation. */ -export function add(left: string, right: Expr): Add; +export function add( + first: Expr, + second: Expr | unknown, + ...others: Array +): FunctionExpr; /** * @beta * - * Creates an expression that adds a field's value to a constant value. + * Creates an expression that adds a field's value to an expression. * * ```typescript - * // Add 5 to the value of the 'age' field - * add("age", 5); + * // Add the value of the 'quantity' field and the 'reserve' field. + * add("quantity", field("reserve")); * ``` * - * @param left The field name to add to. - * @param right The constant value to add. + * @param fieldName The name of the field containing the value to add. + * @param second The second expression or literal to add. + * @param others Optional other expressions or literals to add. * @return A new {@code Expr} representing the addition operation. */ -export function add(left: string, right: any): Add; -export function add(left: Expr | string, right: Expr | any): Add { - const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; - const normalizedRight = right instanceof Expr ? right : Constant.of(right); - return new Add(normalizedLeft, normalizedRight); +export function add( + fieldName: string, + second: Expr | unknown, + ...others: Array +): FunctionExpr; + +export function add( + first: Expr | string, + second: Expr | unknown, + ...others: Array +): FunctionExpr { + return fieldOrExpression(first).add( + valueToDefaultExpr(second), + ...others.map(value => valueToDefaultExpr(value)) + ); } /** @@ -3018,14 +3535,14 @@ export function add(left: Expr | string, right: Expr | any): Add { * * ```typescript * // Subtract the 'discount' field from the 'price' field - * subtract(Field.of("price"), Field.of("discount")); + * subtract(field("price"), field("discount")); * ``` * * @param left The expression to subtract from. * @param right The expression to subtract. * @return A new {@code Expr} representing the subtraction operation. */ -export function subtract(left: Expr, right: Expr): Subtract; +export function subtract(left: Expr, right: Expr): FunctionExpr; /** * @beta @@ -3034,14 +3551,14 @@ export function subtract(left: Expr, right: Expr): Subtract; * * ```typescript * // Subtract the constant value 2 from the 'value' field - * subtract(Field.of("value"), 2); + * subtract(field("value"), 2); * ``` * - * @param left The expression to subtract from. - * @param right The constant value to subtract. + * @param expression The expression to subtract from. + * @param value The constant value to subtract. * @return A new {@code Expr} representing the subtraction operation. */ -export function subtract(left: Expr, right: any): Subtract; +export function subtract(expression: Expr, value: unknown): FunctionExpr; /** * @beta @@ -3050,14 +3567,14 @@ export function subtract(left: Expr, right: any): Subtract; * * ```typescript * // Subtract the 'discount' field from the 'price' field - * subtract("price", Field.of("discount")); + * subtract("price", field("discount")); * ``` * - * @param left The field name to subtract from. - * @param right The expression to subtract. + * @param fieldName The field name to subtract from. + * @param expression The expression to subtract. * @return A new {@code Expr} representing the subtraction operation. */ -export function subtract(left: string, right: Expr): Subtract; +export function subtract(fieldName: string, expression: Expr): FunctionExpr; /** * @beta @@ -3069,15 +3586,18 @@ export function subtract(left: string, right: Expr): Subtract; * subtract("total", 20); * ``` * - * @param left The field name to subtract from. - * @param right The constant value to subtract. + * @param fieldName The field name to subtract from. + * @param value The constant value to subtract. * @return A new {@code Expr} representing the subtraction operation. */ -export function subtract(left: string, right: any): Subtract; -export function subtract(left: Expr | string, right: Expr | any): Subtract { - const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; - const normalizedRight = right instanceof Expr ? right : Constant.of(right); - return new Subtract(normalizedLeft, normalizedRight); +export function subtract(fieldName: string, value: unknown): FunctionExpr; +export function subtract( + left: Expr | string, + right: Expr | unknown +): FunctionExpr { + const normalizedLeft = typeof left === 'string' ? field(left) : left; + const normalizedRight = valueToDefaultExpr(right); + return normalizedLeft.subtract(normalizedRight); } /** @@ -3087,30 +3607,19 @@ export function subtract(left: Expr | string, right: Expr | any): Subtract { * * ```typescript * // Multiply the 'quantity' field by the 'price' field - * multiply(Field.of("quantity"), Field.of("price")); - * ``` - * - * @param left The first expression to multiply. - * @param right The second expression to multiply. - * @return A new {@code Expr} representing the multiplication operation. - */ -export function multiply(left: Expr, right: Expr): Multiply; - -/** - * @beta - * - * Creates an expression that multiplies an expression by a constant value. - * - * ```typescript - * // Multiply the value of the 'price' field by 2 - * multiply(Field.of("price"), 2); + * multiply(field("quantity"), field("price")); * ``` * - * @param left The expression to multiply. - * @param right The constant value to multiply by. + * @param first The first expression to multiply. + * @param second The second expression or literal to multiply. + * @param others Optional additional expressions or literals to multiply. * @return A new {@code Expr} representing the multiplication operation. */ -export function multiply(left: Expr, right: any): Multiply; +export function multiply( + first: Expr, + second: Expr | unknown, + ...others: Array +): FunctionExpr; /** * @beta @@ -3119,34 +3628,29 @@ export function multiply(left: Expr, right: any): Multiply; * * ```typescript * // Multiply the 'quantity' field by the 'price' field - * multiply("quantity", Field.of("price")); - * ``` - * - * @param left The field name to multiply. - * @param right The expression to multiply by. - * @return A new {@code Expr} representing the multiplication operation. - */ -export function multiply(left: string, right: Expr): Multiply; - -/** - * @beta - * - * Creates an expression that multiplies a field's value by a constant value. - * - * ```typescript - * // Multiply the 'value' field by 2 - * multiply("value", 2); + * multiply("quantity", field("price")); * ``` * - * @param left The field name to multiply. - * @param right The constant value to multiply by. + * @param fieldName The name of the field containing the value to add. + * @param second The second expression or literal to add. + * @param others Optional other expressions or literals to add. * @return A new {@code Expr} representing the multiplication operation. */ -export function multiply(left: string, right: any): Multiply; -export function multiply(left: Expr | string, right: Expr | any): Multiply { - const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; - const normalizedRight = right instanceof Expr ? right : Constant.of(right); - return new Multiply(normalizedLeft, normalizedRight); +export function multiply( + fieldName: string, + second: Expr | unknown, + ...others: Array +): FunctionExpr; + +export function multiply( + first: Expr | string, + second: Expr | unknown, + ...others: Array +): FunctionExpr { + return fieldOrExpression(first).multiply( + valueToDefaultExpr(second), + ...others.map(valueToDefaultExpr) + ); } /** @@ -3156,14 +3660,14 @@ export function multiply(left: Expr | string, right: Expr | any): Multiply { * * ```typescript * // Divide the 'total' field by the 'count' field - * divide(Field.of("total"), Field.of("count")); + * divide(field("total"), field("count")); * ``` * * @param left The expression to be divided. * @param right The expression to divide by. * @return A new {@code Expr} representing the division operation. */ -export function divide(left: Expr, right: Expr): Divide; +export function divide(left: Expr, right: Expr): FunctionExpr; /** * @beta @@ -3172,14 +3676,14 @@ export function divide(left: Expr, right: Expr): Divide; * * ```typescript * // Divide the 'value' field by 10 - * divide(Field.of("value"), 10); + * divide(field("value"), 10); * ``` * - * @param left The expression to be divided. - * @param right The constant value to divide by. + * @param expression The expression to be divided. + * @param value The constant value to divide by. * @return A new {@code Expr} representing the division operation. */ -export function divide(left: Expr, right: any): Divide; +export function divide(expression: Expr, value: unknown): FunctionExpr; /** * @beta @@ -3188,14 +3692,14 @@ export function divide(left: Expr, right: any): Divide; * * ```typescript * // Divide the 'total' field by the 'count' field - * divide("total", Field.of("count")); + * divide("total", field("count")); * ``` * - * @param left The field name to be divided. - * @param right The expression to divide by. + * @param fieldName The field name to be divided. + * @param expressions The expression to divide by. * @return A new {@code Expr} representing the division operation. */ -export function divide(left: string, right: Expr): Divide; +export function divide(fieldName: string, expressions: Expr): FunctionExpr; /** * @beta @@ -3207,15 +3711,18 @@ export function divide(left: string, right: Expr): Divide; * divide("value", 10); * ``` * - * @param left The field name to be divided. - * @param right The constant value to divide by. + * @param fieldName The field name to be divided. + * @param value The constant value to divide by. * @return A new {@code Expr} representing the division operation. */ -export function divide(left: string, right: any): Divide; -export function divide(left: Expr | string, right: Expr | any): Divide { - const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; - const normalizedRight = right instanceof Expr ? right : Constant.of(right); - return new Divide(normalizedLeft, normalizedRight); +export function divide(fieldName: string, value: unknown): FunctionExpr; +export function divide( + left: Expr | string, + right: Expr | unknown +): FunctionExpr { + const normalizedLeft = typeof left === 'string' ? field(left) : left; + const normalizedRight = valueToDefaultExpr(right); + return normalizedLeft.divide(normalizedRight); } /** @@ -3225,14 +3732,14 @@ export function divide(left: Expr | string, right: Expr | any): Divide { * * ```typescript * // Calculate the remainder of dividing 'field1' by 'field2'. - * mod(Field.of("field1"), Field.of("field2")); + * mod(field("field1"), field("field2")); * ``` * * @param left The dividend expression. * @param right The divisor expression. * @return A new {@code Expr} representing the modulo operation. */ -export function mod(left: Expr, right: Expr): Mod; +export function mod(left: Expr, right: Expr): FunctionExpr; /** * @beta @@ -3241,14 +3748,14 @@ export function mod(left: Expr, right: Expr): Mod; * * ```typescript * // Calculate the remainder of dividing 'field1' by 5. - * mod(Field.of("field1"), 5); + * mod(field("field1"), 5); * ``` * - * @param left The dividend expression. - * @param right The divisor constant. + * @param expression The dividend expression. + * @param value The divisor constant. * @return A new {@code Expr} representing the modulo operation. */ -export function mod(left: Expr, right: any): Mod; +export function mod(expression: Expr, value: unknown): FunctionExpr; /** * @beta @@ -3257,14 +3764,14 @@ export function mod(left: Expr, right: any): Mod; * * ```typescript * // Calculate the remainder of dividing 'field1' by 'field2'. - * mod("field1", Field.of("field2")); + * mod("field1", field("field2")); * ``` * - * @param left The dividend field name. - * @param right The divisor expression. + * @param fieldName The dividend field name. + * @param expression The divisor expression. * @return A new {@code Expr} representing the modulo operation. */ -export function mod(left: string, right: Expr): Mod; +export function mod(fieldName: string, expression: Expr): FunctionExpr; /** * @beta @@ -3276,402 +3783,83 @@ export function mod(left: string, right: Expr): Mod; * mod("field1", 5); * ``` * - * @param left The dividend field name. - * @param right The divisor constant. + * @param fieldName The dividend field name. + * @param value The divisor constant. * @return A new {@code Expr} representing the modulo operation. */ -export function mod(left: string, right: any): Mod; -export function mod(left: Expr | string, right: Expr | any): Mod { - const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; - const normalizedRight = right instanceof Expr ? right : Constant.of(right); - return new Mod(normalizedLeft, normalizedRight); +export function mod(fieldName: string, value: unknown): FunctionExpr; +export function mod(left: Expr | string, right: Expr | unknown): FunctionExpr { + const normalizedLeft = typeof left === 'string' ? field(left) : left; + const normalizedRight = valueToDefaultExpr(right); + return normalizedLeft.mod(normalizedRight); +} + +/** + * @beta + * + * Creates an expression that creates a Firestore map value from an input object. + * + * ```typescript + * // Create a map from the input object and reference the 'baz' field value from the input document. + * map({foo: 'bar', baz: Field.of('baz')}).as('data'); + * ``` + * + * @param elements The input map to evaluate in the expression. + * @return A new {@code Expr} representing the map function. + */ +export function map(elements: Record): FunctionExpr { + const result: Expr[] = []; + for (const key in elements) { + if (Object.prototype.hasOwnProperty.call(elements, key)) { + const value = elements[key]; + result.push(constant(key)); + result.push(valueToDefaultExpr(value)); + } + } + return new FunctionExpr('map', result); +} + +/** + * Internal use only + * Converts a plainObject to a mapValue in the proto representation, + * rather than a functionValue+map that is the result of the map(...) function. + * This behaves different from constant(plainObject) because it + * traverses the input object, converts values in the object to expressions, + * and calls _readUserData on each of these expressions. + * @private + * @internal + * @param plainObject + */ +export function _mapValue(plainObject: Record): MapValue { + const result: Map = new Map(); + for (const key in plainObject) { + if (Object.prototype.hasOwnProperty.call(plainObject, key)) { + const value = plainObject[key]; + result.set(key, valueToDefaultExpr(value)); + } + } + return new MapValue(result); } -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise AND operation between two expressions. -// * -// * ```typescript -// * // Calculate the bitwise AND of 'field1' and 'field2'. -// * bitAnd(Field.of("field1"), Field.of("field2")); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand expression. -// * @return A new {@code Expr} representing the bitwise AND operation. -// */ -// export function bitAnd(left: Expr, right: Expr): BitAnd; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise AND operation between an expression and a constant. -// * -// * ```typescript -// * // Calculate the bitwise AND of 'field1' and 0xFF. -// * bitAnd(Field.of("field1"), 0xFF); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand constant. -// * @return A new {@code Expr} representing the bitwise AND operation. -// */ -// export function bitAnd(left: Expr, right: any): BitAnd; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise AND operation between a field and an expression. -// * -// * ```typescript -// * // Calculate the bitwise AND of 'field1' and 'field2'. -// * bitAnd("field1", Field.of("field2")); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand expression. -// * @return A new {@code Expr} representing the bitwise AND operation. -// */ -// export function bitAnd(left: string, right: Expr): BitAnd; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise AND operation between a field and a constant. -// * -// * ```typescript -// * // Calculate the bitwise AND of 'field1' and 0xFF. -// * bitAnd("field1", 0xFF); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand constant. -// * @return A new {@code Expr} representing the bitwise AND operation. -// */ -// export function bitAnd(left: string, right: any): BitAnd; -// export function bitAnd(left: Expr | string, right: Expr | any): BitAnd { -// const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; -// const normalizedRight = right instanceof Expr ? right : Constant.of(right); -// return new BitAnd(normalizedLeft, normalizedRight); -// } -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise OR operation between two expressions. -// * -// * ```typescript -// * // Calculate the bitwise OR of 'field1' and 'field2'. -// * bitOr(Field.of("field1"), Field.of("field2")); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand expression. -// * @return A new {@code Expr} representing the bitwise OR operation. -// */ -// export function bitOr(left: Expr, right: Expr): BitOr; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise OR operation between an expression and a constant. -// * -// * ```typescript -// * // Calculate the bitwise OR of 'field1' and 0xFF. -// * bitOr(Field.of("field1"), 0xFF); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand constant. -// * @return A new {@code Expr} representing the bitwise OR operation. -// */ -// export function bitOr(left: Expr, right: any): BitOr; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise OR operation between a field and an expression. -// * -// * ```typescript -// * // Calculate the bitwise OR of 'field1' and 'field2'. -// * bitOr("field1", Field.of("field2")); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand expression. -// * @return A new {@code Expr} representing the bitwise OR operation. -// */ -// export function bitOr(left: string, right: Expr): BitOr; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise OR operation between a field and a constant. -// * -// * ```typescript -// * // Calculate the bitwise OR of 'field1' and 0xFF. -// * bitOr("field1", 0xFF); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand constant. -// * @return A new {@code Expr} representing the bitwise OR operation. -// */ -// export function bitOr(left: string, right: any): BitOr; -// export function bitOr(left: Expr | string, right: Expr | any): BitOr { -// const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; -// const normalizedRight = right instanceof Expr ? right : Constant.of(right); -// return new BitOr(normalizedLeft, normalizedRight); -// } -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise XOR operation between two expressions. -// * -// * ```typescript -// * // Calculate the bitwise XOR of 'field1' and 'field2'. -// * bitXor(Field.of("field1"), Field.of("field2")); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand expression. -// * @return A new {@code Expr} representing the bitwise XOR operation. -// */ -// export function bitXor(left: Expr, right: Expr): BitXor; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise XOR operation between an expression and a constant. -// * -// * ```typescript -// * // Calculate the bitwise XOR of 'field1' and 0xFF. -// * bitXor(Field.of("field1"), 0xFF); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand constant. -// * @return A new {@code Expr} representing the bitwise XOR operation. -// */ -// export function bitXor(left: Expr, right: any): BitXor; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise XOR operation between a field and an expression. -// * -// * ```typescript -// * // Calculate the bitwise XOR of 'field1' and 'field2'. -// * bitXor("field1", Field.of("field2")); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand expression. -// * @return A new {@code Expr} representing the bitwise XOR operation. -// */ -// export function bitXor(left: string, right: Expr): BitXor; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise XOR operation between a field and a constant. -// * -// * ```typescript -// * // Calculate the bitwise XOR of 'field1' and 0xFF. -// * bitXor("field1", 0xFF); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand constant. -// * @return A new {@code Expr} representing the bitwise XOR operation. -// */ -// export function bitXor(left: string, right: any): BitXor; -// export function bitXor(left: Expr | string, right: Expr | any): BitXor { -// const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; -// const normalizedRight = right instanceof Expr ? right : Constant.of(right); -// return new BitXor(normalizedLeft, normalizedRight); -// } -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise NOT operation to an expression. -// * -// * ```typescript -// * // Calculate the bitwise NOT of 'field1'. -// * bitNot(Field.of("field1")); -// * ``` -// * -// * @param operand The operand expression. -// * @return A new {@code Expr} representing the bitwise NOT operation. -// */ -// export function bitNot(operand: Expr): BitNot; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise NOT operation to a field. -// * -// * ```typescript -// * // Calculate the bitwise NOT of 'field1'. -// * bitNot("field1"); -// * ``` -// * -// * @param operand The operand field name. -// * @return A new {@code Expr} representing the bitwise NOT operation. -// */ -// export function bitNot(operand: string): BitNot; -// export function bitNot(operand: Expr | string): BitNot { -// const normalizedOperand = -// typeof operand === 'string' ? Field.of(operand) : operand; -// return new BitNot(normalizedOperand); -// } -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise left shift operation between two expressions. -// * -// * ```typescript -// * // Calculate the bitwise left shift of 'field1' by 'field2' bits. -// * bitLeftShift(Field.of("field1"), Field.of("field2")); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand expression representing the number of bits to shift. -// * @return A new {@code Expr} representing the bitwise left shift operation. -// */ -// export function bitLeftShift(left: Expr, right: Expr): BitLeftShift; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise left shift operation between an expression and a constant. -// * -// * ```typescript -// * // Calculate the bitwise left shift of 'field1' by 2 bits. -// * bitLeftShift(Field.of("field1"), 2); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand constant representing the number of bits to shift. -// * @return A new {@code Expr} representing the bitwise left shift operation. -// */ -// export function bitLeftShift(left: Expr, right: any): BitLeftShift; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise left shift operation between a field and an expression. -// * -// * ```typescript -// * // Calculate the bitwise left shift of 'field1' by 'field2' bits. -// * bitLeftShift("field1", Field.of("field2")); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand expression representing the number of bits to shift. -// * @return A new {@code Expr} representing the bitwise left shift operation. -// */ -// export function bitLeftShift(left: string, right: Expr): BitLeftShift; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise left shift operation between a field and a constant. -// * -// * ```typescript -// * // Calculate the bitwise left shift of 'field1' by 2 bits. -// * bitLeftShift("field1", 2); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand constant representing the number of bits to shift. -// * @return A new {@code Expr} representing the bitwise left shift operation. -// */ -// export function bitLeftShift(left: string, right: any): BitLeftShift; -// export function bitLeftShift( -// left: Expr | string, -// right: Expr | any -// ): BitLeftShift { -// const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; -// const normalizedRight = right instanceof Expr ? right : Constant.of(right); -// return new BitLeftShift(normalizedLeft, normalizedRight); -// } -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise right shift operation between two expressions. -// * -// * ```typescript -// * // Calculate the bitwise right shift of 'field1' by 'field2' bits. -// * bitRightShift(Field.of("field1"), Field.of("field2")); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand expression representing the number of bits to shift. -// * @return A new {@code Expr} representing the bitwise right shift operation. -// */ -// export function bitRightShift(left: Expr, right: Expr): BitRightShift; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise right shift operation between an expression and a constant. -// * -// * ```typescript -// * // Calculate the bitwise right shift of 'field1' by 2 bits. -// * bitRightShift(Field.of("field1"), 2); -// * ``` -// * -// * @param left The left operand expression. -// * @param right The right operand constant representing the number of bits to shift. -// * @return A new {@code Expr} representing the bitwise right shift operation. -// */ -// export function bitRightShift(left: Expr, right: any): BitRightShift; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise right shift operation between a field and an expression. -// * -// * ```typescript -// * // Calculate the bitwise right shift of 'field1' by 'field2' bits. -// * bitRightShift("field1", Field.of("field2")); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand expression representing the number of bits to shift. -// * @return A new {@code Expr} representing the bitwise right shift operation. -// */ -// export function bitRightShift(left: string, right: Expr): BitRightShift; -// -// /** -// * @beta -// * -// * Creates an expression that applies a bitwise right shift operation between a field and a constant. -// * -// * ```typescript -// * // Calculate the bitwise right shift of 'field1' by 2 bits. -// * bitRightShift("field1", 2); -// * ``` -// * -// * @param left The left operand field name. -// * @param right The right operand constant representing the number of bits to shift. -// * @return A new {@code Expr} representing the bitwise right shift operation. -// */ -// export function bitRightShift(left: string, right: any): BitRightShift; -// export function bitRightShift( -// left: Expr | string, -// right: Expr | any -// ): BitRightShift { -// const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; -// const normalizedRight = right instanceof Expr ? right : Constant.of(right); -// return new BitRightShift(normalizedLeft, normalizedRight); -// } +/** + * @beta + * + * Creates an expression that creates a Firestore array value from an input array. + * + * ```typescript + * // Create an array value from the input array and reference the 'baz' field value from the input document. + * array(['bar', Field.of('baz')]).as('foo'); + * ``` + * + * @param elements The input array to evaluate in the expression. + * @return A new {@code Expr} representing the array function. + */ +export function array(elements: unknown[]): FunctionExpr { + return new FunctionExpr( + 'array', + elements.map(element => valueToDefaultExpr(element)) + ); +} /** * @beta @@ -3680,14 +3868,14 @@ export function mod(left: Expr | string, right: Expr | any): Mod { * * ```typescript * // Check if the 'age' field is equal to an expression - * eq(Field.of("age"), Field.of("minAge").add(10)); + * eq(field("age"), field("minAge").add(10)); * ``` * * @param left The first expression to compare. * @param right The second expression to compare. * @return A new `Expr` representing the equality comparison. */ -export function eq(left: Expr, right: Expr): Eq; +export function eq(left: Expr, right: Expr): BooleanExpr; /** * @beta @@ -3696,14 +3884,14 @@ export function eq(left: Expr, right: Expr): Eq; * * ```typescript * // Check if the 'age' field is equal to 21 - * eq(Field.of("age"), 21); + * eq(field("age"), 21); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. + * @param expression The expression to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the equality comparison. */ -export function eq(left: Expr, right: any): Eq; +export function eq(expression: Expr, value: unknown): BooleanExpr; /** * @beta @@ -3712,14 +3900,14 @@ export function eq(left: Expr, right: any): Eq; * * ```typescript * // Check if the 'age' field is equal to the 'limit' field - * eq("age", Field.of("limit")); + * eq("age", field("limit")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. + * @param fieldName The field name to compare. + * @param expression The expression to compare to. * @return A new `Expr` representing the equality comparison. */ -export function eq(left: string, right: Expr): Eq; +export function eq(fieldName: string, expression: Expr): BooleanExpr; /** * @beta @@ -3731,15 +3919,15 @@ export function eq(left: string, right: Expr): Eq; * eq("city", "London"); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. + * @param fieldName The field name to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the equality comparison. */ -export function eq(left: string, right: any): Eq; -export function eq(left: Expr | string, right: any): Eq { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const rightExpr = right instanceof Expr ? right : Constant.of(right); - return new Eq(leftExpr, rightExpr); +export function eq(fieldName: string, value: unknown): BooleanExpr; +export function eq(left: Expr | string, right: unknown): BooleanExpr { + const leftExpr = left instanceof Expr ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.eq(rightExpr); } /** @@ -3749,14 +3937,14 @@ export function eq(left: Expr | string, right: any): Eq { * * ```typescript * // Check if the 'status' field is not equal to field 'finalState' - * neq(Field.of("status"), Field.of("finalState")); + * neq(field("status"), field("finalState")); * ``` * * @param left The first expression to compare. * @param right The second expression to compare. * @return A new `Expr` representing the inequality comparison. */ -export function neq(left: Expr, right: Expr): Neq; +export function neq(left: Expr, right: Expr): BooleanExpr; /** * @beta @@ -3765,14 +3953,14 @@ export function neq(left: Expr, right: Expr): Neq; * * ```typescript * // Check if the 'status' field is not equal to "completed" - * neq(Field.of("status"), "completed"); + * neq(field("status"), "completed"); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. + * @param expression The expression to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the inequality comparison. */ -export function neq(left: Expr, right: any): Neq; +export function neq(expression: Expr, value: unknown): BooleanExpr; /** * @beta @@ -3781,14 +3969,14 @@ export function neq(left: Expr, right: any): Neq; * * ```typescript * // Check if the 'status' field is not equal to the value of 'expectedStatus' - * neq("status", Field.of("expectedStatus")); + * neq("status", field("expectedStatus")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. + * @param fieldName The field name to compare. + * @param expression The expression to compare to. * @return A new `Expr` representing the inequality comparison. */ -export function neq(left: string, right: Expr): Neq; +export function neq(fieldName: string, expression: Expr): BooleanExpr; /** * @beta @@ -3800,15 +3988,15 @@ export function neq(left: string, right: Expr): Neq; * neq("country", "USA"); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. + * @param fieldName The field name to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the inequality comparison. */ -export function neq(left: string, right: any): Neq; -export function neq(left: Expr | string, right: any): Neq { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const rightExpr = right instanceof Expr ? right : Constant.of(right); - return new Neq(leftExpr, rightExpr); +export function neq(fieldName: string, value: unknown): BooleanExpr; +export function neq(left: Expr | string, right: unknown): BooleanExpr { + const leftExpr = left instanceof Expr ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.neq(rightExpr); } /** @@ -3818,14 +4006,14 @@ export function neq(left: Expr | string, right: any): Neq { * * ```typescript * // Check if the 'age' field is less than 30 - * lt(Field.of("age"), Field.of("limit")); + * lt(field("age"), field("limit")); * ``` * * @param left The first expression to compare. * @param right The second expression to compare. * @return A new `Expr` representing the less than comparison. */ -export function lt(left: Expr, right: Expr): Lt; +export function lt(left: Expr, right: Expr): BooleanExpr; /** * @beta @@ -3834,14 +4022,14 @@ export function lt(left: Expr, right: Expr): Lt; * * ```typescript * // Check if the 'age' field is less than 30 - * lt(Field.of("age"), 30); + * lt(field("age"), 30); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. + * @param expression The expression to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the less than comparison. */ -export function lt(left: Expr, right: any): Lt; +export function lt(expression: Expr, value: unknown): BooleanExpr; /** * @beta @@ -3850,14 +4038,14 @@ export function lt(left: Expr, right: any): Lt; * * ```typescript * // Check if the 'age' field is less than the 'limit' field - * lt("age", Field.of("limit")); + * lt("age", field("limit")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. + * @param fieldName The field name to compare. + * @param expression The expression to compare to. * @return A new `Expr` representing the less than comparison. */ -export function lt(left: string, right: Expr): Lt; +export function lt(fieldName: string, expression: Expr): BooleanExpr; /** * @beta @@ -3869,15 +4057,15 @@ export function lt(left: string, right: Expr): Lt; * lt("price", 50); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. + * @param fieldName The field name to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the less than comparison. */ -export function lt(left: string, right: any): Lt; -export function lt(left: Expr | string, right: any): Lt { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const rightExpr = right instanceof Expr ? right : Constant.of(right); - return new Lt(leftExpr, rightExpr); +export function lt(fieldName: string, value: unknown): BooleanExpr; +export function lt(left: Expr | string, right: unknown): BooleanExpr { + const leftExpr = left instanceof Expr ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.lt(rightExpr); } /** @@ -3888,14 +4076,14 @@ export function lt(left: Expr | string, right: any): Lt { * * ```typescript * // Check if the 'quantity' field is less than or equal to 20 - * lte(Field.of("quantity"), Field.of("limit")); + * lte(field("quantity"), field("limit")); * ``` * * @param left The first expression to compare. * @param right The second expression to compare. * @return A new `Expr` representing the less than or equal to comparison. */ -export function lte(left: Expr, right: Expr): Lte; +export function lte(left: Expr, right: Expr): BooleanExpr; /** * @beta @@ -3904,28 +4092,28 @@ export function lte(left: Expr, right: Expr): Lte; * * ```typescript * // Check if the 'quantity' field is less than or equal to 20 - * lte(Field.of("quantity"), 20); + * lte(field("quantity"), 20); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. + * @param expression The expression to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the less than or equal to comparison. */ -export function lte(left: Expr, right: any): Lte; +export function lte(expression: Expr, value: unknown): BooleanExpr; /** * Creates an expression that checks if a field's value is less than or equal to an expression. * * ```typescript * // Check if the 'quantity' field is less than or equal to the 'limit' field - * lte("quantity", Field.of("limit")); + * lte("quantity", field("limit")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. + * @param fieldName The field name to compare. + * @param expression The expression to compare to. * @return A new `Expr` representing the less than or equal to comparison. */ -export function lte(left: string, right: Expr): Lte; +export function lte(fieldName: string, expression: Expr): BooleanExpr; /** * @beta @@ -3937,15 +4125,15 @@ export function lte(left: string, right: Expr): Lte; * lte("score", 70); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. + * @param fieldName The field name to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the less than or equal to comparison. */ -export function lte(left: string, right: any): Lte; -export function lte(left: Expr | string, right: any): Lte { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const rightExpr = right instanceof Expr ? right : Constant.of(right); - return new Lte(leftExpr, rightExpr); +export function lte(fieldName: string, value: unknown): BooleanExpr; +export function lte(left: Expr | string, right: unknown): BooleanExpr { + const leftExpr = left instanceof Expr ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.lte(rightExpr); } /** @@ -3956,14 +4144,14 @@ export function lte(left: Expr | string, right: any): Lte { * * ```typescript * // Check if the 'age' field is greater than 18 - * gt(Field.of("age"), Constant(9).add(9)); + * gt(field("age"), Constant(9).add(9)); * ``` * * @param left The first expression to compare. * @param right The second expression to compare. * @return A new `Expr` representing the greater than comparison. */ -export function gt(left: Expr, right: Expr): Gt; +export function gt(left: Expr, right: Expr): BooleanExpr; /** * @beta @@ -3972,14 +4160,14 @@ export function gt(left: Expr, right: Expr): Gt; * * ```typescript * // Check if the 'age' field is greater than 18 - * gt(Field.of("age"), 18); + * gt(field("age"), 18); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. + * @param expression The expression to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the greater than comparison. */ -export function gt(left: Expr, right: any): Gt; +export function gt(expression: Expr, value: unknown): BooleanExpr; /** * @beta @@ -3988,14 +4176,14 @@ export function gt(left: Expr, right: any): Gt; * * ```typescript * // Check if the value of field 'age' is greater than the value of field 'limit' - * gt("age", Field.of("limit")); + * gt("age", field("limit")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. + * @param fieldName The field name to compare. + * @param expression The expression to compare to. * @return A new `Expr` representing the greater than comparison. */ -export function gt(left: string, right: Expr): Gt; +export function gt(fieldName: string, expression: Expr): BooleanExpr; /** * @beta @@ -4007,15 +4195,15 @@ export function gt(left: string, right: Expr): Gt; * gt("price", 100); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. + * @param fieldName The field name to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the greater than comparison. */ -export function gt(left: string, right: any): Gt; -export function gt(left: Expr | string, right: any): Gt { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const rightExpr = right instanceof Expr ? right : Constant.of(right); - return new Gt(leftExpr, rightExpr); +export function gt(fieldName: string, value: unknown): BooleanExpr; +export function gt(left: Expr | string, right: unknown): BooleanExpr { + const leftExpr = left instanceof Expr ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.gt(rightExpr); } /** @@ -4026,14 +4214,14 @@ export function gt(left: Expr | string, right: any): Gt { * * ```typescript * // Check if the 'quantity' field is greater than or equal to the field "threshold" - * gte(Field.of("quantity"), Field.of("threshold")); + * gte(field("quantity"), field("threshold")); * ``` * * @param left The first expression to compare. * @param right The second expression to compare. * @return A new `Expr` representing the greater than or equal to comparison. */ -export function gte(left: Expr, right: Expr): Gte; +export function gte(left: Expr, right: Expr): BooleanExpr; /** * @beta @@ -4043,14 +4231,14 @@ export function gte(left: Expr, right: Expr): Gte; * * ```typescript * // Check if the 'quantity' field is greater than or equal to 10 - * gte(Field.of("quantity"), 10); + * gte(field("quantity"), 10); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. + * @param expression The expression to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the greater than or equal to comparison. */ -export function gte(left: Expr, right: any): Gte; +export function gte(expression: Expr, value: unknown): BooleanExpr; /** * @beta @@ -4059,14 +4247,14 @@ export function gte(left: Expr, right: any): Gte; * * ```typescript * // Check if the value of field 'age' is greater than or equal to the value of field 'limit' - * gte("age", Field.of("limit")); + * gte("age", field("limit")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. + * @param fieldName The field name to compare. + * @param value The expression to compare to. * @return A new `Expr` representing the greater than or equal to comparison. */ -export function gte(left: string, right: Expr): Gte; +export function gte(fieldName: string, value: Expr): BooleanExpr; /** * @beta @@ -4079,15 +4267,15 @@ export function gte(left: string, right: Expr): Gte; * gte("score", 80); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. + * @param fieldName The field name to compare. + * @param value The constant value to compare to. * @return A new `Expr` representing the greater than or equal to comparison. */ -export function gte(left: string, right: any): Gte; -export function gte(left: Expr | string, right: any): Gte { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const rightExpr = right instanceof Expr ? right : Constant.of(right); - return new Gte(leftExpr, rightExpr); +export function gte(fieldName: string, value: unknown): BooleanExpr; +export function gte(left: Expr | string, right: unknown): BooleanExpr { + const leftExpr = left instanceof Expr ? left : field(left); + const rightExpr = valueToDefaultExpr(right); + return leftExpr.gte(rightExpr); } /** @@ -4097,71 +4285,51 @@ export function gte(left: Expr | string, right: any): Gte { * * ```typescript * // Combine the 'items' array with two new item arrays - * arrayConcat(Field.of("items"), [Field.of("newItems"), Field.of("otherItems")]); - * ``` - * - * @param array The array expression to concatenate to. - * @param elements The array expressions to concatenate. - * @return A new {@code Expr} representing the concatenated array. - */ -export function arrayConcat(array: Expr, elements: Expr[]): ArrayConcat; - -/** - * @beta - * - * Creates an expression that concatenates an array expression with other arrays and/or values. - * - * ```typescript - * // Combine the 'tags' array with a new array - * arrayConcat(Field.of("tags"), ["newTag1", "newTag2"]); - * ``` - * - * @param array The array expression to concatenate to. - * @param elements The array expressions or single values to concatenate. - * @return A new {@code Expr} representing the concatenated array. - */ -export function arrayConcat(array: Expr, elements: any[]): ArrayConcat; - -/** - * @beta - * - * Creates an expression that concatenates a field's array value with other arrays. - * - * ```typescript - * // Combine the 'items' array with two new item arrays - * arrayConcat("items", [Field.of("newItems"), Field.of("otherItems")]); + * arrayConcat(field("items"), [field("newItems"), field("otherItems")]); * ``` * - * @param array The field name containing array values. - * @param elements The array expressions to concatenate. + * @param firstArray The first array expression to concatenate to. + * @param secondArray The second array expression or array literal to concatenate to. + * @param otherArrays Optional additional array expressions or array literals to concatenate. * @return A new {@code Expr} representing the concatenated array. */ -export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; +export function arrayConcat( + firstArray: Expr, + secondArray: Expr | unknown[], + ...otherArrays: Array +): FunctionExpr; /** * @beta * - * Creates an expression that concatenates a field's array value with other arrays and/or values. + * Creates an expression that concatenates a field's array value with other arrays. * * ```typescript - * // Combine the 'tags' array with a new array - * arrayConcat("tags", ["newTag1", "newTag2"]); + * // Combine the 'items' array with two new item arrays + * arrayConcat("items", [field("newItems"), field("otherItems")]); * ``` * - * @param array The field name containing array values. - * @param elements The array expressions or single values to concatenate. + * @param firstArrayField The first array to concatenate to. + * @param secondArray The second array expression or array literal to concatenate to. + * @param otherArrays Optional additional array expressions or array literals to concatenate. * @return A new {@code Expr} representing the concatenated array. */ -export function arrayConcat(array: string, elements: any[]): ArrayConcat; export function arrayConcat( - array: Expr | string, - elements: any[] -): ArrayConcat { - const arrayExpr = array instanceof Expr ? array : Field.of(array); - const exprValues = elements.map(element => - element instanceof Expr ? element : Constant.of(element) + firstArrayField: string, + secondArray: Expr | unknown[], + ...otherArrays: Array +): FunctionExpr; + +export function arrayConcat( + firstArray: Expr | string, + secondArray: Expr | unknown[], + ...otherArrays: Array +): FunctionExpr { + const exprValues = otherArrays.map(element => valueToDefaultExpr(element)); + return fieldOrExpression(firstArray).arrayConcat( + fieldOrExpression(secondArray), + ...exprValues ); - return new ArrayConcat(arrayExpr, exprValues); } /** @@ -4171,14 +4339,14 @@ export function arrayConcat( * * ```typescript * // Check if the 'colors' array contains the value of field 'selectedColor' - * arrayContains(Field.of("colors"), Field.of("selectedColor")); + * arrayContains(field("colors"), field("selectedColor")); * ``` * * @param array The array expression to check. * @param element The element to search for in the array. * @return A new {@code Expr} representing the 'array_contains' comparison. */ -export function arrayContains(array: Expr, element: Expr): ArrayContains; +export function arrayContains(array: Expr, element: Expr): FunctionExpr; /** * @beta @@ -4187,14 +4355,14 @@ export function arrayContains(array: Expr, element: Expr): ArrayContains; * * ```typescript * // Check if the 'colors' array contains "red" - * arrayContains(Field.of("colors"), "red"); + * arrayContains(field("colors"), "red"); * ``` * * @param array The array expression to check. * @param element The element to search for in the array. * @return A new {@code Expr} representing the 'array_contains' comparison. */ -export function arrayContains(array: Expr, element: any): ArrayContains; +export function arrayContains(array: Expr, element: unknown): FunctionExpr; /** * @beta @@ -4203,14 +4371,14 @@ export function arrayContains(array: Expr, element: any): ArrayContains; * * ```typescript * // Check if the 'colors' array contains the value of field 'selectedColor' - * arrayContains("colors", Field.of("selectedColor")); + * arrayContains("colors", field("selectedColor")); * ``` * - * @param array The field name to check. + * @param fieldName The field name to check. * @param element The element to search for in the array. * @return A new {@code Expr} representing the 'array_contains' comparison. */ -export function arrayContains(array: string, element: Expr): ArrayContains; +export function arrayContains(fieldName: string, element: Expr): FunctionExpr; /** * @beta @@ -4222,18 +4390,18 @@ export function arrayContains(array: string, element: Expr): ArrayContains; * arrayContains("colors", "red"); * ``` * - * @param array The field name to check. + * @param fieldName The field name to check. * @param element The element to search for in the array. * @return A new {@code Expr} representing the 'array_contains' comparison. */ -export function arrayContains(array: string, element: any): ArrayContains; +export function arrayContains(fieldName: string, element: unknown): BooleanExpr; export function arrayContains( array: Expr | string, - element: any -): ArrayContains { - const arrayExpr = array instanceof Expr ? array : Field.of(array); - const elementExpr = element instanceof Expr ? element : Constant.of(element); - return new ArrayContains(arrayExpr, elementExpr); + element: unknown +): BooleanExpr { + const arrayExpr = fieldOrExpression(array); + const elementExpr = valueToDefaultExpr(element); + return arrayExpr.arrayContains(elementExpr); } /** @@ -4244,52 +4412,55 @@ export function arrayContains( * * ```typescript * // Check if the 'categories' array contains either values from field "cate1" or "Science" - * arrayContainsAny(Field.of("categories"), [Field.of("cate1"), "Science"]); + * arrayContainsAny(field("categories"), [field("cate1"), "Science"]); * ``` * * @param array The array expression to check. * @param values The elements to check for in the array. * @return A new {@code Expr} representing the 'array_contains_any' comparison. */ -export function arrayContainsAny(array: Expr, values: Expr[]): ArrayContainsAny; +export function arrayContainsAny( + array: Expr, + values: Array +): BooleanExpr; /** * @beta * - * Creates an expression that checks if an array expression contains any of the specified + * Creates an expression that checks if a field's array value contains any of the specified * elements. * * ```typescript - * // Check if the 'categories' array contains either values from field "cate1" or "Science" - * arrayContainsAny(Field.of("categories"), [Field.of("cate1"), "Science"]); + * // Check if the 'groups' array contains either the value from the 'userGroup' field + * // or the value "guest" + * arrayContainsAny("categories", [field("cate1"), "Science"]); * ``` * - * @param array The array expression to check. + * @param fieldName The field name to check. * @param values The elements to check for in the array. * @return A new {@code Expr} representing the 'array_contains_any' comparison. */ -export function arrayContainsAny(array: Expr, values: any[]): ArrayContainsAny; +export function arrayContainsAny( + fieldName: string, + values: Array +): BooleanExpr; /** * @beta * - * Creates an expression that checks if a field's array value contains any of the specified + * Creates an expression that checks if an array expression contains any of the specified * elements. * * ```typescript - * // Check if the 'groups' array contains either the value from the 'userGroup' field - * // or the value "guest" - * arrayContainsAny("categories", [Field.of("cate1"), "Science"]); + * // Check if the 'categories' array contains either values from field "cate1" or "Science" + * arrayContainsAny(field("categories"), array([field("cate1"), "Science"])); * ``` * - * @param array The field name to check. - * @param values The elements to check for in the array. + * @param array The array expression to check. + * @param values An expression that evaluates to an array, whose elements to check for in the array. * @return A new {@code Expr} representing the 'array_contains_any' comparison. */ -export function arrayContainsAny( - array: string, - values: Expr[] -): ArrayContainsAny; +export function arrayContainsAny(array: Expr, values: Expr): BooleanExpr; /** * @beta @@ -4300,26 +4471,20 @@ export function arrayContainsAny( * ```typescript * // Check if the 'groups' array contains either the value from the 'userGroup' field * // or the value "guest" - * arrayContainsAny("categories", [Field.of("cate1"), "Science"]); + * arrayContainsAny("categories", array([field("cate1"), "Science"])); * ``` * - * @param array The field name to check. - * @param values The elements to check for in the array. + * @param fieldName The field name to check. + * @param values An expression that evaluates to an array, whose elements to check for in the array field. * @return A new {@code Expr} representing the 'array_contains_any' comparison. */ -export function arrayContainsAny( - array: string, - values: any[] -): ArrayContainsAny; +export function arrayContainsAny(fieldName: string, values: Expr): BooleanExpr; export function arrayContainsAny( array: Expr | string, - values: any[] -): ArrayContainsAny { - const arrayExpr = array instanceof Expr ? array : Field.of(array); - const exprValues = values.map(value => - value instanceof Expr ? value : Constant.of(value) - ); - return new ArrayContainsAny(arrayExpr, exprValues); + values: unknown[] | Expr +): BooleanExpr { + // @ts-ignore implementation accepts both types + return fieldOrExpression(array).arrayContainsAny(values); } /** @@ -4329,50 +4494,56 @@ export function arrayContainsAny( * * ```typescript * // Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" - * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), Constant.of("SciFi"), Constant.of("Adventure")]); + * arrayContainsAll(field("tags"), [field("tag1"), constant("SciFi"), "Adventure"]); * ``` * * @param array The array expression to check. * @param values The elements to check for in the array. * @return A new {@code Expr} representing the 'array_contains_all' comparison. */ -export function arrayContainsAll(array: Expr, values: Expr[]): ArrayContainsAll; +export function arrayContainsAll( + array: Expr, + values: Array +): BooleanExpr; /** * @beta * - * Creates an expression that checks if an array expression contains all the specified elements. + * Creates an expression that checks if a field's array value contains all the specified values or + * expressions. * * ```typescript - * // Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" - * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "SciFi", "Adventure"]); + * // Check if the 'tags' array contains both of the values from field 'tag1', the value "SciFi", and "Adventure" + * arrayContainsAll("tags", [field("tag1"), "SciFi", "Adventure"]); * ``` * - * @param array The array expression to check. + * @param fieldName The field name to check. * @param values The elements to check for in the array. * @return A new {@code Expr} representing the 'array_contains_all' comparison. */ -export function arrayContainsAll(array: Expr, values: any[]): ArrayContainsAll; +export function arrayContainsAll( + fieldName: string, + values: Array +): BooleanExpr; /** * @beta * - * Creates an expression that checks if a field's array value contains all the specified values or - * expressions. + * Creates an expression that checks if an array expression contains all the specified elements. * * ```typescript - * // Check if the 'tags' array contains both of the values from field 'tag1' and "tag2" - * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * // Check if the "tags" array contains all of the values: "SciFi", "Adventure", and the value from field "tag1" + * arrayContainsAll(field("tags"), [field("tag1"), constant("SciFi"), "Adventure"]); * ``` * - * @param array The field name to check. - * @param values The elements to check for in the array. + * @param array The array expression to check. + * @param arrayExpression The elements to check for in the array. * @return A new {@code Expr} representing the 'array_contains_all' comparison. */ export function arrayContainsAll( - array: string, - values: Expr[] -): ArrayContainsAll; + array: Expr, + arrayExpression: Expr +): BooleanExpr; /** * @beta @@ -4381,29 +4552,41 @@ export function arrayContainsAll( * expressions. * * ```typescript - * // Check if the 'tags' array contains both of the values from field 'tag1' and "tag2" - * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * // Check if the 'tags' array contains both of the values from field 'tag1', the value "SciFi", and "Adventure" + * arrayContainsAll("tags", [field("tag1"), "SciFi", "Adventure"]); * ``` * - * @param array The field name to check. - * @param values The elements to check for in the array. + * @param fieldName The field name to check. + * @param arrayExpression The elements to check for in the array. * @return A new {@code Expr} representing the 'array_contains_all' comparison. */ export function arrayContainsAll( - array: string, - values: any[] -): ArrayContainsAll; + fieldName: string, + arrayExpression: Expr +): BooleanExpr; export function arrayContainsAll( array: Expr | string, - values: any[] -): ArrayContainsAll { - const arrayExpr = array instanceof Expr ? array : Field.of(array); - const exprValues = values.map(value => - value instanceof Expr ? value : Constant.of(value) - ); - return new ArrayContainsAll(arrayExpr, exprValues); + values: unknown[] | Expr +): BooleanExpr { + // @ts-ignore implementation accepts both types + return fieldOrExpression(array).arrayContainsAll(values); } +/** + * @beta + * + * Creates an expression that calculates the length of an array in a specified field. + * + * ```typescript + * // Get the number of items in field 'cart' + * arrayLength('cart'); + * ``` + * + * @param fieldName The name of the field containing an array to calculate the length of. + * @return A new {@code Expr} representing the length of the array. + */ +export function arrayLength(fieldName: string): FunctionExpr; + /** * @beta * @@ -4411,49 +4594,52 @@ export function arrayContainsAll( * * ```typescript * // Get the number of items in the 'cart' array - * arrayLength(Field.of("cart")); + * arrayLength(field("cart")); * ``` * * @param array The array expression to calculate the length of. * @return A new {@code Expr} representing the length of the array. */ -export function arrayLength(array: Expr): ArrayLength { - return new ArrayLength(array); +export function arrayLength(array: Expr): FunctionExpr; +export function arrayLength(array: Expr | string): FunctionExpr { + return fieldOrExpression(array).arrayLength(); } /** * @beta * - * Creates an expression that checks if an expression is equal to any of the provided values or + * Creates an expression that checks if an expression, when evaluated, is equal to any of the provided values or * expressions. * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * eqAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); + * eqAny(field("category"), [constant("Electronics"), field("primaryType")]); * ``` * - * @param element The expression to compare. - * @param others The values to check against. + * @param expression The expression whose results to compare. + * @param values The values to check against. * @return A new {@code Expr} representing the 'IN' comparison. */ -export function eqAny(element: Expr, others: Expr[]): EqAny; +export function eqAny( + expression: Expr, + values: Array +): BooleanExpr; /** * @beta * - * Creates an expression that checks if an expression is equal to any of the provided values or - * expressions. + * Creates an expression that checks if an expression is equal to any of the provided values. * * ```typescript - * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * eqAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); + * // Check if the 'category' field is set to a value in the disabledCategories field + * eqAny(field("category"), field('disabledCategories')); * ``` * - * @param element The expression to compare. - * @param others The values to check against. + * @param expression The expression whose results to compare. + * @param arrayExpression An expression that evaluates to an array, whose elements to check for equality to the input. * @return A new {@code Expr} representing the 'IN' comparison. */ -export function eqAny(element: Expr, others: any[]): EqAny; +export function eqAny(expression: Expr, arrayExpression: Expr): BooleanExpr; /** * @beta @@ -4463,14 +4649,17 @@ export function eqAny(element: Expr, others: any[]): EqAny; * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * eqAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); + * eqAny("category", [constant("Electronics"), field("primaryType")]); * ``` * - * @param element The field to compare. - * @param others The values to check against. + * @param fieldName The field to compare. + * @param values The values to check against. * @return A new {@code Expr} representing the 'IN' comparison. */ -export function eqAny(element: string, others: Expr[]): EqAny; +export function eqAny( + fieldName: string, + values: Array +): BooleanExpr; /** * @beta @@ -4480,20 +4669,20 @@ export function eqAny(element: string, others: Expr[]): EqAny; * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * eqAny("category", ["Electronics", Field.of("primaryType")]); + * eqAny("category", ["Electronics", field("primaryType")]); * ``` * - * @param element The field to compare. - * @param others The values to check against. + * @param fieldName The field to compare. + * @param arrayExpression An expression that evaluates to an array, whose elements to check for equality to the input field. * @return A new {@code Expr} representing the 'IN' comparison. */ -export function eqAny(element: string, others: any[]): EqAny; -export function eqAny(element: Expr | string, others: any[]): EqAny { - const elementExpr = element instanceof Expr ? element : Field.of(element); - const exprOthers = others.map(other => - other instanceof Expr ? other : Constant.of(other) - ); - return new EqAny(elementExpr, exprOthers); +export function eqAny(fieldName: string, arrayExpression: Expr): BooleanExpr; +export function eqAny( + element: Expr | string, + values: unknown[] | Expr +): BooleanExpr { + // @ts-ignore implementation accepts both types + return fieldOrExpression(element).eqAny(values); } /** @@ -4504,78 +4693,83 @@ export function eqAny(element: Expr | string, others: any[]): EqAny { * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notEqAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); + * notEqAny(field("status"), ["pending", field("rejectedStatus")]); * ``` * * @param element The expression to compare. - * @param others The values to check against. + * @param values The values to check against. * @return A new {@code Expr} representing the 'NOT IN' comparison. */ -export function notEqAny(element: Expr, others: Expr[]): NotEqAny; +export function notEqAny( + element: Expr, + values: Array +): BooleanExpr; /** * @beta * - * Creates an expression that checks if an expression is not equal to any of the provided values + * Creates an expression that checks if a field's value is not equal to any of the provided values * or expressions. * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notEqAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); + * notEqAny("status", [constant("pending"), field("rejectedStatus")]); * ``` * - * @param element The expression to compare. - * @param others The values to check against. + * @param fieldName The field name to compare. + * @param values The values to check against. * @return A new {@code Expr} representing the 'NOT IN' comparison. */ -export function notEqAny(element: Expr, others: any[]): NotEqAny; +export function notEqAny( + fieldName: string, + values: Array +): BooleanExpr; /** * @beta * - * Creates an expression that checks if a field's value is not equal to any of the provided values + * Creates an expression that checks if an expression is not equal to any of the provided values * or expressions. * * ```typescript - * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notEqAny("status", [Constant.of("pending"), Field.of("rejectedStatus")]); + * // Check if the 'status' field is neither "pending" nor the value of the field 'rejectedStatus' + * notEqAny(field("status"), ["pending", field("rejectedStatus")]); * ``` * - * @param element The field name to compare. - * @param others The values to check against. + * @param element The expression to compare. + * @param arrayExpression The values to check against. * @return A new {@code Expr} representing the 'NOT IN' comparison. */ -export function notEqAny(element: string, others: Expr[]): NotEqAny; +export function notEqAny(element: Expr, arrayExpression: Expr): BooleanExpr; /** * @beta * - * Creates an expression that checks if a field's value is not equal to any of the provided values - * or expressions. + * Creates an expression that checks if a field's value is not equal to any of the values in the evaluated expression. * * ```typescript - * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notEqAny("status", ["pending", Field.of("rejectedStatus")]); + * // Check if the 'status' field is not equal to any value in the field 'rejectedStatuses' + * notEqAny("status", field("rejectedStatuses")); * ``` * - * @param element The field name to compare. - * @param others The values to check against. + * @param fieldName The field name to compare. + * @param arrayExpression The values to check against. * @return A new {@code Expr} representing the 'NOT IN' comparison. */ -export function notEqAny(element: string, others: any[]): NotEqAny; -export function notEqAny(element: Expr | string, others: any[]): NotEqAny { - const elementExpr = element instanceof Expr ? element : Field.of(element); - const exprOthers = others.map(other => - other instanceof Expr ? other : Constant.of(other) - ); - return new NotEqAny(elementExpr, exprOthers); +export function notEqAny(fieldName: string, arrayExpression: Expr): BooleanExpr; + +export function notEqAny( + element: Expr | string, + values: unknown[] | Expr +): BooleanExpr { + // @ts-ignore implementation accepts both types + return fieldOrExpression(element).notEqAny(values); } /** * @beta * - * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple filter - * conditions. + * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple BooleanExpressions. * * ```typescript * // Check if only one of the conditions is true: 'age' greater than 18, 'city' is "London", @@ -4586,12 +4780,17 @@ export function notEqAny(element: Expr | string, others: any[]): NotEqAny { * eq("status", "active")); * ``` * - * @param left The first filter condition. - * @param right Additional filter conditions to 'XOR' together. + * @param first The first condition. + * @param second The second condition. + * @param additionalConditions Additional conditions to 'XOR' together. * @return A new {@code Expr} representing the logical 'XOR' operation. */ -export function xor(left: FilterCondition, ...right: FilterCondition[]): Xor { - return new Xor([left, ...right]); +export function xor( + first: BooleanExpr, + second: BooleanExpr, + ...additionalConditions: BooleanExpr[] +): BooleanExpr { + return new BooleanExpr('xor', [first, second, ...additionalConditions]); } /** @@ -4603,7 +4802,7 @@ export function xor(left: FilterCondition, ...right: FilterCondition[]): Xor { * ```typescript * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". * cond( - * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); + * gt("age", 18), constant("Adult"), constant("Minor")); * ``` * * @param condition The condition to evaluate. @@ -4612,11 +4811,11 @@ export function xor(left: FilterCondition, ...right: FilterCondition[]): Xor { * @return A new {@code Expr} representing the conditional expression. */ export function cond( - condition: FilterCondition, + condition: BooleanExpr, thenExpr: Expr, elseExpr: Expr -): Cond { - return new Cond(condition, thenExpr, elseExpr); +): FunctionExpr { + return new FunctionExpr('cond', [condition, thenExpr, elseExpr]); } /** @@ -4629,155 +4828,126 @@ export function cond( * not(eq("completed", true)); * ``` * - * @param filter The filter condition to negate. + * @param booleanExpr The filter condition to negate. * @return A new {@code Expr} representing the negated filter condition. */ -export function not(filter: FilterCondition): Not { - return new Not(filter); +export function not(booleanExpr: BooleanExpr): BooleanExpr { + return booleanExpr.not(); } /** * @beta * - * Creates an expression that returns the larger value between two expressions, based on Firestore's value type ordering. - * - * ```typescript - * // Returns the larger value between the 'field1' field and the 'field2' field. - * logicalMaximum(Field.of("field1"), Field.of("field2")); - * ``` - * - * @param left The left operand expression. - * @param right The right operand expression. - * @return A new {@code Expr} representing the logical max operation. - */ -export function logicalMaximum(left: Expr, right: Expr): LogicalMaximum; - -/** - * @beta - * - * Creates an expression that returns the larger value between an expression and a constant value, based on Firestore's value type ordering. + * Creates an expression that returns the largest value between multiple input + * expressions or literal values. Based on Firestore's value type ordering. * * ```typescript - * // Returns the larger value between the 'value' field and 10. - * logicalMaximum(Field.of("value"), 10); + * // Returns the largest value between the 'field1' field, the 'field2' field, + * // and 1000 + * logicalMaximum(field("field1"), field("field2"), 1000); * ``` * - * @param left The left operand expression. - * @param right The right operand constant. + * @param first The first operand expression. + * @param second The second expression or literal. + * @param others Optional additional expressions or literals. * @return A new {@code Expr} representing the logical max operation. */ -export function logicalMaximum(left: Expr, right: any): LogicalMaximum; +export function logicalMaximum( + first: Expr, + second: Expr | unknown, + ...others: Array +): FunctionExpr; /** * @beta * - * Creates an expression that returns the larger value between a field and an expression, based on Firestore's value type ordering. + * Creates an expression that returns the largest value between multiple input + * expressions or literal values. Based on Firestore's value type ordering. * * ```typescript - * // Returns the larger value between the 'field1' field and the 'field2' field. - * logicalMaximum("field1", Field.of('field2')); + * // Returns the largest value between the 'field1' field, the 'field2' field, + * // and 1000. + * logicalMaximum("field1", field("field2"), 1000); * ``` * - * @param left The left operand field name. - * @param right The right operand expression. + * @param fieldName The first operand field name. + * @param second The second expression or literal. + * @param others Optional additional expressions or literals. * @return A new {@code Expr} representing the logical max operation. */ -export function logicalMaximum(left: string, right: Expr): LogicalMaximum; +export function logicalMaximum( + fieldName: string, + second: Expr | unknown, + ...others: Array +): FunctionExpr; -/** - * @beta - * - * Creates an expression that returns the larger value between a field and a constant value, based on Firestore's value type ordering. - * - * ```typescript - * // Returns the larger value between the 'value' field and 10. - * logicalMaximum("value", 10); - * ``` - * - * @param left The left operand field name. - * @param right The right operand constant. - * @return A new {@code Expr} representing the logical max operation. - */ -export function logicalMaximum(left: string, right: any): LogicalMaximum; export function logicalMaximum( - left: Expr | string, - right: Expr | any -): LogicalMaximum { - const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; - const normalizedRight = right instanceof Expr ? right : Constant.of(right); - return new LogicalMaximum(normalizedLeft, normalizedRight); + first: Expr | string, + second: Expr | unknown, + ...others: Array +): FunctionExpr { + return fieldOrExpression(first).logicalMaximum( + valueToDefaultExpr(second), + ...others.map(value => valueToDefaultExpr(value)) + ); } /** * @beta * - * Creates an expression that returns the smaller value between two expressions, based on Firestore's value type ordering. - * - * ```typescript - * // Returns the smaller value between the 'field1' field and the 'field2' field. - * logicalMinimum(Field.of("field1"), Field.of("field2")); - * ``` - * - * @param left The left operand expression. - * @param right The right operand expression. - * @return A new {@code Expr} representing the logical min operation. - */ -export function logicalMinimum(left: Expr, right: Expr): LogicalMinimum; - -/** - * @beta - * - * Creates an expression that returns the smaller value between an expression and a constant value, based on Firestore's value type ordering. + * Creates an expression that returns the smallest value between multiple input + * expressions and literal values. Based on Firestore's value type ordering. * * ```typescript - * // Returns the smaller value between the 'value' field and 10. - * logicalMinimum(Field.of("value"), 10); + * // Returns the smallest value between the 'field1' field, the 'field2' field, + * // and 1000. + * logicalMinimum(field("field1"), field("field2"), 1000); * ``` * - * @param left The left operand expression. - * @param right The right operand constant. + * @param first The first operand expression. + * @param second The second expression or literal. + * @param others Optional additional expressions or literals. * @return A new {@code Expr} representing the logical min operation. */ -export function logicalMinimum(left: Expr, right: any): LogicalMinimum; +export function logicalMinimum( + first: Expr, + second: Expr | unknown, + ...others: Array +): FunctionExpr; /** * @beta * - * Creates an expression that returns the smaller value between a field and an expression, based on Firestore's value type ordering. + * Creates an expression that returns the smallest value between a field's value + * and other input expressions or literal values. + * Based on Firestore's value type ordering. * * ```typescript - * // Returns the smaller value between the 'field1' field and the 'field2' field. - * logicalMinimum("field1", Field.of("field2")); + * // Returns the smallest value between the 'field1' field, the 'field2' field, + * // and 1000. + * logicalMinimum("field1", field("field2"), 1000); * ``` * - * @param left The left operand field name. - * @param right The right operand expression. + * @param fieldName The first operand field name. + * @param second The second expression or literal. + * @param others Optional additional expressions or literals. * @return A new {@code Expr} representing the logical min operation. */ -export function logicalMinimum(left: string, right: Expr): LogicalMinimum; +export function logicalMinimum( + fieldName: string, + second: Expr | unknown, + ...others: Array +): FunctionExpr; -/** - * @beta - * - * Creates an expression that returns the smaller value between a field and a constant value, based on Firestore's value type ordering. - * - * ```typescript - * // Returns the smaller value between the 'value' field and 10. - * logicalMinimum("value", 10); - * ``` - * - * @param left The left operand field name. - * @param right The right operand constant. - * @return A new {@code Expr} representing the logical min operation. - */ -export function logicalMinimum(left: string, right: any): LogicalMinimum; export function logicalMinimum( - left: Expr | string, - right: Expr | any -): LogicalMinimum { - const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; - const normalizedRight = right instanceof Expr ? right : Constant.of(right); - return new LogicalMinimum(normalizedLeft, normalizedRight); + first: Expr | string, + second: Expr | unknown, + ...others: Array +): FunctionExpr { + return fieldOrExpression(first).logicalMinimum( + valueToDefaultExpr(second), + ...others.map(value => valueToDefaultExpr(value)) + ); } /** @@ -4787,13 +4957,13 @@ export function logicalMinimum( * * ```typescript * // Check if the document has a field named "phoneNumber" - * exists(Field.of("phoneNumber")); + * exists(field("phoneNumber")); * ``` * * @param value An expression evaluates to the name of the field to check. * @return A new {@code Expr} representing the 'exists' check. */ -export function exists(value: Expr): Exists; +export function exists(value: Expr): BooleanExpr; /** * @beta @@ -4805,14 +4975,12 @@ export function exists(value: Expr): Exists; * exists("phoneNumber"); * ``` * - * @param field The field name to check. + * @param fieldName The field name to check. * @return A new {@code Expr} representing the 'exists' check. */ -export function exists(field: string): Exists; -export function exists(valueOrField: Expr | string): Exists { - const valueExpr = - valueOrField instanceof Expr ? valueOrField : Field.of(valueOrField); - return new Exists(valueExpr); +export function exists(fieldName: string): BooleanExpr; +export function exists(valueOrField: Expr | string): BooleanExpr { + return fieldOrExpression(valueOrField).exists(); } /** @@ -4822,13 +4990,13 @@ export function exists(valueOrField: Expr | string): Exists { * * ```typescript * // Check if the result of a calculation is NaN - * isNaN(Field.of("value").divide(0)); + * isNaN(field("value").divide(0)); * ``` * * @param value The expression to check. * @return A new {@code Expr} representing the 'isNaN' check. */ -export function isNan(value: Expr): IsNan; +export function isNan(value: Expr): BooleanExpr; /** * @beta @@ -4840,13 +5008,12 @@ export function isNan(value: Expr): IsNan; * isNaN("value"); * ``` * - * @param value The name of the field to check. + * @param fieldName The name of the field to check. * @return A new {@code Expr} representing the 'isNaN' check. */ -export function isNan(value: string): IsNan; -export function isNan(value: Expr | string): IsNan { - const valueExpr = value instanceof Expr ? value : Field.of(value); - return new IsNan(valueExpr); +export function isNan(fieldName: string): BooleanExpr; +export function isNan(value: Expr | string): BooleanExpr { + return fieldOrExpression(value).isNan(); } /** @@ -4856,18 +5023,18 @@ export function isNan(value: Expr | string): IsNan { * * ```typescript * // Reverse the value of the 'myString' field. - * reverse(Field.of("myString")); + * reverse(field("myString")); * ``` * - * @param expr The expression representing the string to reverse. + * @param stringExpression An expression evaluating to a string value, which will be reversed. * @return A new {@code Expr} representing the reversed string. */ -export function reverse(expr: Expr): Reverse; +export function reverse(stringExpression: Expr): FunctionExpr; /** * @beta * - * Creates an expression that reverses a string represented by a field. + * Creates an expression that reverses a string value in the specified field. * * ```typescript * // Reverse the value of the 'myString' field. @@ -4877,10 +5044,9 @@ export function reverse(expr: Expr): Reverse; * @param field The name of the field representing the string to reverse. * @return A new {@code Expr} representing the reversed string. */ -export function reverse(field: string): Reverse; -export function reverse(expr: Expr | string): Reverse { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new Reverse(normalizedExpr); +export function reverse(field: string): FunctionExpr; +export function reverse(expr: Expr | string): FunctionExpr { + return fieldOrExpression(expr).reverse(); } /** @@ -4890,7 +5056,7 @@ export function reverse(expr: Expr | string): Reverse { * * ```typescript * // Replace the first occurrence of "hello" with "hi" in the 'message' field. - * replaceFirst(Field.of("message"), "hello", "hi"); + * replaceFirst(field("message"), "hello", "hi"); * ``` * * @param value The expression representing the string to perform the replacement on. @@ -4902,7 +5068,7 @@ export function replaceFirst( value: Expr, find: string, replace: string -): ReplaceFirst; +): FunctionExpr; /** * @beta @@ -4912,7 +5078,7 @@ export function replaceFirst( * * ```typescript * // Replace the first occurrence of the value in 'findField' with the value in 'replaceField' in the 'message' field. - * replaceFirst(Field.of("message"), Field.of("findField"), Field.of("replaceField")); + * replaceFirst(field("message"), field("findField"), field("replaceField")); * ``` * * @param value The expression representing the string to perform the replacement on. @@ -4924,7 +5090,7 @@ export function replaceFirst( value: Expr, find: Expr, replace: Expr -): ReplaceFirst; +): FunctionExpr; /** * @beta @@ -4936,26 +5102,25 @@ export function replaceFirst( * replaceFirst("message", "hello", "hi"); * ``` * - * @param field The name of the field representing the string to perform the replacement on. + * @param fieldName The name of the field representing the string to perform the replacement on. * @param find The substring to search for. * @param replace The substring to replace the first occurrence of 'find' with. * @return A new {@code Expr} representing the string with the first occurrence replaced. */ export function replaceFirst( - field: string, + fieldName: string, find: string, replace: string -): ReplaceFirst; +): FunctionExpr; export function replaceFirst( value: Expr | string, find: Expr | string, replace: Expr | string -): ReplaceFirst { - const normalizedValue = typeof value === 'string' ? Field.of(value) : value; - const normalizedFind = typeof find === 'string' ? Constant.of(find) : find; - const normalizedReplace = - typeof replace === 'string' ? Constant.of(replace) : replace; - return new ReplaceFirst(normalizedValue, normalizedFind, normalizedReplace); +): FunctionExpr { + const normalizedValue = fieldOrExpression(value); + const normalizedFind = valueToDefaultExpr(find); + const normalizedReplace = valueToDefaultExpr(replace); + return normalizedValue.replaceFirst(normalizedFind, normalizedReplace); } /** @@ -4965,7 +5130,7 @@ export function replaceFirst( * * ```typescript * // Replace all occurrences of "hello" with "hi" in the 'message' field. - * replaceAll(Field.of("message"), "hello", "hi"); + * replaceAll(field("message"), "hello", "hi"); * ``` * * @param value The expression representing the string to perform the replacement on. @@ -4977,7 +5142,7 @@ export function replaceAll( value: Expr, find: string, replace: string -): ReplaceAll; +): FunctionExpr; /** * @beta @@ -4987,7 +5152,7 @@ export function replaceAll( * * ```typescript * // Replace all occurrences of the value in 'findField' with the value in 'replaceField' in the 'message' field. - * replaceAll(Field.of("message"), Field.of("findField"), Field.of("replaceField")); + * replaceAll(field("message"), field("findField"), field("replaceField")); * ``` * * @param value The expression representing the string to perform the replacement on. @@ -4995,7 +5160,11 @@ export function replaceAll( * @param replace The expression representing the substring to replace all occurrences of 'find' with. * @return A new {@code Expr} representing the string with all occurrences replaced. */ -export function replaceAll(value: Expr, find: Expr, replace: Expr): ReplaceAll; +export function replaceAll( + value: Expr, + find: Expr, + replace: Expr +): FunctionExpr; /** * @beta @@ -5007,26 +5176,25 @@ export function replaceAll(value: Expr, find: Expr, replace: Expr): ReplaceAll; * replaceAll("message", "hello", "hi"); * ``` * - * @param field The name of the field representing the string to perform the replacement on. + * @param fieldName The name of the field representing the string to perform the replacement on. * @param find The substring to search for. * @param replace The substring to replace all occurrences of 'find' with. * @return A new {@code Expr} representing the string with all occurrences replaced. */ export function replaceAll( - field: string, + fieldName: string, find: string, replace: string -): ReplaceAll; +): FunctionExpr; export function replaceAll( value: Expr | string, find: Expr | string, replace: Expr | string -): ReplaceAll { - const normalizedValue = typeof value === 'string' ? Field.of(value) : value; - const normalizedFind = typeof find === 'string' ? Constant.of(find) : find; - const normalizedReplace = - typeof replace === 'string' ? Constant.of(replace) : replace; - return new ReplaceAll(normalizedValue, normalizedFind, normalizedReplace); +): FunctionExpr { + const normalizedValue = fieldOrExpression(value); + const normalizedFind = valueToDefaultExpr(find); + const normalizedReplace = valueToDefaultExpr(replace); + return normalizedValue.replaceAll(normalizedFind, normalizedReplace); } /** @@ -5036,13 +5204,13 @@ export function replaceAll( * * ```typescript * // Calculate the length of the 'myString' field in bytes. - * byteLength(Field.of("myString")); + * byteLength(field("myString")); * ``` * * @param expr The expression representing the string. * @return A new {@code Expr} representing the length of the string in bytes. */ -export function byteLength(expr: Expr): ByteLength; +export function byteLength(expr: Expr): FunctionExpr; /** * @beta @@ -5054,13 +5222,13 @@ export function byteLength(expr: Expr): ByteLength; * byteLength("myString"); * ``` * - * @param field The name of the field representing the string. + * @param fieldName The name of the field containing the string. * @return A new {@code Expr} representing the length of the string in bytes. */ -export function byteLength(field: string): ByteLength; -export function byteLength(expr: Expr | string): ByteLength { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new ByteLength(normalizedExpr); +export function byteLength(fieldName: string): FunctionExpr; +export function byteLength(expr: Expr | string): FunctionExpr { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.byteLength(); } /** @@ -5073,10 +5241,10 @@ export function byteLength(expr: Expr | string): ByteLength { * strLength("name"); * ``` * - * @param field The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @return A new {@code Expr} representing the length of the string. */ -export function charLength(field: string): CharLength; +export function charLength(fieldName: string): FunctionExpr; /** * @beta @@ -5085,16 +5253,16 @@ export function charLength(field: string): CharLength; * * ```typescript * // Get the character length of the 'name' field in UTF-8. - * strLength(Field.of("name")); + * strLength(field("name")); * ``` * - * @param expr The expression representing the string to calculate the length of. + * @param stringExpression The expression representing the string to calculate the length of. * @return A new {@code Expr} representing the length of the string. */ -export function charLength(expr: Expr): CharLength; -export function charLength(value: Expr | string): CharLength { - const valueExpr = value instanceof Expr ? value : Field.of(value); - return new CharLength(valueExpr); +export function charLength(stringExpression: Expr): FunctionExpr; +export function charLength(value: Expr | string): FunctionExpr { + const valueExpr = fieldOrExpression(value); + return valueExpr.charLength(); } /** @@ -5108,11 +5276,11 @@ export function charLength(value: Expr | string): CharLength { * like("title", "%guide%"); * ``` * - * @param left The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @param pattern The pattern to search for. You can use "%" as a wildcard character. * @return A new {@code Expr} representing the 'like' comparison. */ -export function like(left: string, pattern: string): Like; +export function like(fieldName: string, pattern: string): BooleanExpr; /** * @beta @@ -5122,14 +5290,14 @@ export function like(left: string, pattern: string): Like; * * ```typescript * // Check if the 'title' field contains the string "guide" - * like("title", Field.of("pattern")); + * like("title", field("pattern")); * ``` * - * @param left The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @param pattern The pattern to search for. You can use "%" as a wildcard character. * @return A new {@code Expr} representing the 'like' comparison. */ -export function like(left: string, pattern: Expr): Like; +export function like(fieldName: string, pattern: Expr): BooleanExpr; /** * @beta @@ -5138,14 +5306,14 @@ export function like(left: string, pattern: Expr): Like; * * ```typescript * // Check if the 'title' field contains the string "guide" - * like(Field.of("title"), "%guide%"); + * like(field("title"), "%guide%"); * ``` * - * @param left The expression representing the string to perform the comparison on. + * @param stringExpression The expression representing the string to perform the comparison on. * @param pattern The pattern to search for. You can use "%" as a wildcard character. * @return A new {@code Expr} representing the 'like' comparison. */ -export function like(left: Expr, pattern: string): Like; +export function like(stringExpression: Expr, pattern: string): BooleanExpr; /** * @beta @@ -5154,18 +5322,21 @@ export function like(left: Expr, pattern: string): Like; * * ```typescript * // Check if the 'title' field contains the string "guide" - * like(Field.of("title"), Field.of("pattern")); + * like(field("title"), field("pattern")); * ``` * - * @param left The expression representing the string to perform the comparison on. + * @param stringExpression The expression representing the string to perform the comparison on. * @param pattern The pattern to search for. You can use "%" as a wildcard character. * @return A new {@code Expr} representing the 'like' comparison. */ -export function like(left: Expr, pattern: Expr): Like; -export function like(left: Expr | string, pattern: Expr | string): Like { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const patternExpr = pattern instanceof Expr ? pattern : Constant.of(pattern); - return new Like(leftExpr, patternExpr); +export function like(stringExpression: Expr, pattern: Expr): BooleanExpr; +export function like( + left: Expr | string, + pattern: Expr | string +): FunctionExpr { + const leftExpr = fieldOrExpression(left); + const patternExpr = valueToDefaultExpr(pattern); + return leftExpr.like(patternExpr); } /** @@ -5179,11 +5350,11 @@ export function like(left: Expr | string, pattern: Expr | string): Like { * regexContains("description", "(?i)example"); * ``` * - * @param left The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @param pattern The regular expression to use for the search. * @return A new {@code Expr} representing the 'contains' comparison. */ -export function regexContains(left: string, pattern: string): RegexContains; +export function regexContains(fieldName: string, pattern: string): BooleanExpr; /** * @beta @@ -5193,14 +5364,14 @@ export function regexContains(left: string, pattern: string): RegexContains; * * ```typescript * // Check if the 'description' field contains "example" (case-insensitive) - * regexContains("description", Field.of("pattern")); + * regexContains("description", field("pattern")); * ``` * - * @param left The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @param pattern The regular expression to use for the search. * @return A new {@code Expr} representing the 'contains' comparison. */ -export function regexContains(left: string, pattern: Expr): RegexContains; +export function regexContains(fieldName: string, pattern: Expr): BooleanExpr; /** * @beta @@ -5210,14 +5381,17 @@ export function regexContains(left: string, pattern: Expr): RegexContains; * * ```typescript * // Check if the 'description' field contains "example" (case-insensitive) - * regexContains(Field.of("description"), "(?i)example"); + * regexContains(field("description"), "(?i)example"); * ``` * - * @param left The expression representing the string to perform the comparison on. + * @param stringExpression The expression representing the string to perform the comparison on. * @param pattern The regular expression to use for the search. * @return A new {@code Expr} representing the 'contains' comparison. */ -export function regexContains(left: Expr, pattern: string): RegexContains; +export function regexContains( + stringExpression: Expr, + pattern: string +): BooleanExpr; /** * @beta @@ -5227,21 +5401,24 @@ export function regexContains(left: Expr, pattern: string): RegexContains; * * ```typescript * // Check if the 'description' field contains "example" (case-insensitive) - * regexContains(Field.of("description"), Field.of("pattern")); + * regexContains(field("description"), field("pattern")); * ``` * - * @param left The expression representing the string to perform the comparison on. + * @param stringExpression The expression representing the string to perform the comparison on. * @param pattern The regular expression to use for the search. * @return A new {@code Expr} representing the 'contains' comparison. */ -export function regexContains(left: Expr, pattern: Expr): RegexContains; +export function regexContains( + stringExpression: Expr, + pattern: Expr +): BooleanExpr; export function regexContains( left: Expr | string, pattern: Expr | string -): RegexContains { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const patternExpr = pattern instanceof Expr ? pattern : Constant.of(pattern); - return new RegexContains(leftExpr, patternExpr); +): BooleanExpr { + const leftExpr = fieldOrExpression(left); + const patternExpr = valueToDefaultExpr(pattern); + return leftExpr.regexContains(patternExpr); } /** @@ -5254,11 +5431,11 @@ export function regexContains( * regexMatch("email", "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); * ``` * - * @param left The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @param pattern The regular expression to use for the match. * @return A new {@code Expr} representing the regular expression match. */ -export function regexMatch(left: string, pattern: string): RegexMatch; +export function regexMatch(fieldName: string, pattern: string): BooleanExpr; /** * @beta @@ -5267,14 +5444,14 @@ export function regexMatch(left: string, pattern: string): RegexMatch; * * ```typescript * // Check if the 'email' field matches a valid email pattern - * regexMatch("email", Field.of("pattern")); + * regexMatch("email", field("pattern")); * ``` * - * @param left The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @param pattern The regular expression to use for the match. * @return A new {@code Expr} representing the regular expression match. */ -export function regexMatch(left: string, pattern: Expr): RegexMatch; +export function regexMatch(fieldName: string, pattern: Expr): BooleanExpr; /** * @beta @@ -5284,14 +5461,17 @@ export function regexMatch(left: string, pattern: Expr): RegexMatch; * * ```typescript * // Check if the 'email' field matches a valid email pattern - * regexMatch(Field.of("email"), "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); + * regexMatch(field("email"), "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"); * ``` * - * @param left The expression representing the string to match against. + * @param stringExpression The expression representing the string to match against. * @param pattern The regular expression to use for the match. * @return A new {@code Expr} representing the regular expression match. */ -export function regexMatch(left: Expr, pattern: string): RegexMatch; +export function regexMatch( + stringExpression: Expr, + pattern: string +): BooleanExpr; /** * @beta @@ -5301,21 +5481,21 @@ export function regexMatch(left: Expr, pattern: string): RegexMatch; * * ```typescript * // Check if the 'email' field matches a valid email pattern - * regexMatch(Field.of("email"), Field.of("pattern")); + * regexMatch(field("email"), field("pattern")); * ``` * - * @param left The expression representing the string to match against. + * @param stringExpression The expression representing the string to match against. * @param pattern The regular expression to use for the match. * @return A new {@code Expr} representing the regular expression match. */ -export function regexMatch(left: Expr, pattern: Expr): RegexMatch; +export function regexMatch(stringExpression: Expr, pattern: Expr): BooleanExpr; export function regexMatch( left: Expr | string, pattern: Expr | string -): RegexMatch { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const patternExpr = pattern instanceof Expr ? pattern : Constant.of(pattern); - return new RegexMatch(leftExpr, patternExpr); +): BooleanExpr { + const leftExpr = fieldOrExpression(left); + const patternExpr = valueToDefaultExpr(pattern); + return leftExpr.regexMatch(patternExpr); } /** @@ -5328,11 +5508,11 @@ export function regexMatch( * strContains("description", "example"); * ``` * - * @param left The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @param substring The substring to search for. * @return A new {@code Expr} representing the 'contains' comparison. */ -export function strContains(left: string, substring: string): StrContains; +export function strContains(fieldName: string, substring: string): BooleanExpr; /** * @beta @@ -5341,14 +5521,14 @@ export function strContains(left: string, substring: string): StrContains; * * ```typescript * // Check if the 'description' field contains the value of the 'keyword' field. - * strContains("description", Field.of("keyword")); + * strContains("description", field("keyword")); * ``` * - * @param left The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @param substring The expression representing the substring to search for. * @return A new {@code Expr} representing the 'contains' comparison. */ -export function strContains(left: string, substring: Expr): StrContains; +export function strContains(fieldName: string, substring: Expr): BooleanExpr; /** * @beta @@ -5357,14 +5537,17 @@ export function strContains(left: string, substring: Expr): StrContains; * * ```typescript * // Check if the 'description' field contains "example". - * strContains(Field.of("description"), "example"); + * strContains(field("description"), "example"); * ``` * - * @param left The expression representing the string to perform the comparison on. + * @param stringExpression The expression representing the string to perform the comparison on. * @param substring The substring to search for. * @return A new {@code Expr} representing the 'contains' comparison. */ -export function strContains(left: Expr, substring: string): StrContains; +export function strContains( + stringExpression: Expr, + substring: string +): BooleanExpr; /** * @beta @@ -5373,22 +5556,24 @@ export function strContains(left: Expr, substring: string): StrContains; * * ```typescript * // Check if the 'description' field contains the value of the 'keyword' field. - * strContains(Field.of("description"), Field.of("keyword")); + * strContains(field("description"), field("keyword")); * ``` * - * @param left The expression representing the string to perform the comparison on. + * @param stringExpression The expression representing the string to perform the comparison on. * @param substring The expression representing the substring to search for. * @return A new {@code Expr} representing the 'contains' comparison. */ -export function strContains(left: Expr, substring: Expr): StrContains; +export function strContains( + stringExpression: Expr, + substring: Expr +): BooleanExpr; export function strContains( left: Expr | string, substring: Expr | string -): StrContains { - const leftExpr = left instanceof Expr ? left : Field.of(left); - const substringExpr = - substring instanceof Expr ? substring : Constant.of(substring); - return new StrContains(leftExpr, substringExpr); +): BooleanExpr { + const leftExpr = fieldOrExpression(left); + const substringExpr = valueToDefaultExpr(substring); + return leftExpr.strContains(substringExpr); } /** @@ -5401,11 +5586,11 @@ export function strContains( * startsWith("name", "Mr."); * ``` * - * @param expr The field name to check. + * @param fieldName The field name to check. * @param prefix The prefix to check for. * @return A new {@code Expr} representing the 'starts with' comparison. */ -export function startsWith(expr: string, prefix: string): StartsWith; +export function startsWith(fieldName: string, prefix: string): BooleanExpr; /** * @beta @@ -5414,14 +5599,14 @@ export function startsWith(expr: string, prefix: string): StartsWith; * * ```typescript * // Check if the 'fullName' field starts with the value of the 'firstName' field - * startsWith("fullName", Field.of("firstName")); + * startsWith("fullName", field("firstName")); * ``` * - * @param expr The field name to check. + * @param fieldName The field name to check. * @param prefix The expression representing the prefix. * @return A new {@code Expr} representing the 'starts with' comparison. */ -export function startsWith(expr: string, prefix: Expr): StartsWith; +export function startsWith(fieldName: string, prefix: Expr): BooleanExpr; /** * @beta @@ -5430,14 +5615,14 @@ export function startsWith(expr: string, prefix: Expr): StartsWith; * * ```typescript * // Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." - * startsWith(Field.of("fullName"), "Mr."); + * startsWith(field("fullName"), "Mr."); * ``` * - * @param expr The expression to check. + * @param stringExpression The expression to check. * @param prefix The prefix to check for. * @return A new {@code Expr} representing the 'starts with' comparison. */ -export function startsWith(expr: Expr, prefix: string): StartsWith; +export function startsWith(stringExpression: Expr, prefix: string): BooleanExpr; /** * @beta @@ -5446,21 +5631,19 @@ export function startsWith(expr: Expr, prefix: string): StartsWith; * * ```typescript * // Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." - * startsWith(Field.of("fullName"), Field.of("prefix")); + * startsWith(field("fullName"), field("prefix")); * ``` * - * @param expr The expression to check. + * @param stringExpression The expression to check. * @param prefix The prefix to check for. * @return A new {@code Expr} representing the 'starts with' comparison. */ -export function startsWith(expr: Expr, prefix: Expr): StartsWith; +export function startsWith(stringExpression: Expr, prefix: Expr): BooleanExpr; export function startsWith( expr: Expr | string, prefix: Expr | string -): StartsWith { - const exprLeft = expr instanceof Expr ? expr : Field.of(expr); - const prefixExpr = prefix instanceof Expr ? prefix : Constant.of(prefix); - return new StartsWith(exprLeft, prefixExpr); +): BooleanExpr { + return fieldOrExpression(expr).startsWith(valueToDefaultExpr(prefix)); } /** @@ -5473,11 +5656,11 @@ export function startsWith( * endsWith("filename", ".txt"); * ``` * - * @param expr The field name to check. + * @param fieldName The field name to check. * @param suffix The postfix to check for. * @return A new {@code Expr} representing the 'ends with' comparison. */ -export function endsWith(expr: string, suffix: string): EndsWith; +export function endsWith(fieldName: string, suffix: string): BooleanExpr; /** * @beta @@ -5486,14 +5669,14 @@ export function endsWith(expr: string, suffix: string): EndsWith; * * ```typescript * // Check if the 'url' field ends with the value of the 'extension' field - * endsWith("url", Field.of("extension")); + * endsWith("url", field("extension")); * ``` * - * @param expr The field name to check. + * @param fieldName The field name to check. * @param suffix The expression representing the postfix. * @return A new {@code Expr} representing the 'ends with' comparison. */ -export function endsWith(expr: string, suffix: Expr): EndsWith; +export function endsWith(fieldName: string, suffix: Expr): BooleanExpr; /** * @beta @@ -5502,14 +5685,14 @@ export function endsWith(expr: string, suffix: Expr): EndsWith; * * ```typescript * // Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." - * endsWith(Field.of("fullName"), "Jr."); + * endsWith(field("fullName"), "Jr."); * ``` * - * @param expr The expression to check. + * @param stringExpression The expression to check. * @param suffix The postfix to check for. * @return A new {@code Expr} representing the 'ends with' comparison. */ -export function endsWith(expr: Expr, suffix: string): EndsWith; +export function endsWith(stringExpression: Expr, suffix: string): BooleanExpr; /** * @beta @@ -5518,18 +5701,19 @@ export function endsWith(expr: Expr, suffix: string): EndsWith; * * ```typescript * // Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." - * endsWith(Field.of("fullName"), Constant.of("Jr.")); + * endsWith(field("fullName"), constant("Jr.")); * ``` * - * @param expr The expression to check. + * @param stringExpression The expression to check. * @param suffix The postfix to check for. * @return A new {@code Expr} representing the 'ends with' comparison. */ -export function endsWith(expr: Expr, suffix: Expr): EndsWith; -export function endsWith(expr: Expr | string, suffix: Expr | string): EndsWith { - const exprLeft = expr instanceof Expr ? expr : Field.of(expr); - const suffixExpr = suffix instanceof Expr ? suffix : Constant.of(suffix); - return new EndsWith(exprLeft, suffixExpr); +export function endsWith(stringExpression: Expr, suffix: Expr): BooleanExpr; +export function endsWith( + expr: Expr | string, + suffix: Expr | string +): BooleanExpr { + return fieldOrExpression(expr).endsWith(valueToDefaultExpr(suffix)); } /** @@ -5542,10 +5726,10 @@ export function endsWith(expr: Expr | string, suffix: Expr | string): EndsWith { * toLower("name"); * ``` * - * @param expr The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @return A new {@code Expr} representing the lowercase string. */ -export function toLower(expr: string): ToLower; +export function toLower(fieldName: string): FunctionExpr; /** * @beta @@ -5554,15 +5738,15 @@ export function toLower(expr: string): ToLower; * * ```typescript * // Convert the 'name' field to lowercase - * toLower(Field.of("name")); + * toLower(field("name")); * ``` * - * @param expr The expression representing the string to convert to lowercase. + * @param stringExpression The expression representing the string to convert to lowercase. * @return A new {@code Expr} representing the lowercase string. */ -export function toLower(expr: Expr): ToLower; -export function toLower(expr: Expr | string): ToLower { - return new ToLower(expr instanceof Expr ? expr : Field.of(expr)); +export function toLower(stringExpression: Expr): FunctionExpr; +export function toLower(expr: Expr | string): FunctionExpr { + return fieldOrExpression(expr).toLower(); } /** @@ -5575,10 +5759,10 @@ export function toLower(expr: Expr | string): ToLower { * toUpper("title"); * ``` * - * @param expr The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @return A new {@code Expr} representing the uppercase string. */ -export function toUpper(expr: string): ToUpper; +export function toUpper(fieldName: string): FunctionExpr; /** * @beta @@ -5587,15 +5771,15 @@ export function toUpper(expr: string): ToUpper; * * ```typescript * // Convert the 'title' field to uppercase - * toUppercase(Field.of("title")); + * toUppercase(field("title")); * ``` * - * @param expr The expression representing the string to convert to uppercase. + * @param stringExpression The expression representing the string to convert to uppercase. * @return A new {@code Expr} representing the uppercase string. */ -export function toUpper(expr: Expr): ToUpper; -export function toUpper(expr: Expr | string): ToUpper { - return new ToUpper(expr instanceof Expr ? expr : Field.of(expr)); +export function toUpper(stringExpression: Expr): FunctionExpr; +export function toUpper(expr: Expr | string): FunctionExpr { + return fieldOrExpression(expr).toUpper(); } /** @@ -5608,10 +5792,10 @@ export function toUpper(expr: Expr | string): ToUpper { * trim("userInput"); * ``` * - * @param expr The name of the field containing the string. + * @param fieldName The name of the field containing the string. * @return A new {@code Expr} representing the trimmed string. */ -export function trim(expr: string): Trim; +export function trim(fieldName: string): FunctionExpr; /** * @beta @@ -5620,15 +5804,15 @@ export function trim(expr: string): Trim; * * ```typescript * // Trim whitespace from the 'userInput' field - * trim(Field.of("userInput")); + * trim(field("userInput")); * ``` * - * @param expr The expression representing the string to trim. + * @param stringExpression The expression representing the string to trim. * @return A new {@code Expr} representing the trimmed string. */ -export function trim(expr: Expr): Trim; -export function trim(expr: Expr | string): Trim { - return new Trim(expr instanceof Expr ? expr : Field.of(expr)); +export function trim(stringExpression: Expr): FunctionExpr; +export function trim(expr: Expr | string): FunctionExpr { + return fieldOrExpression(expr).trim(); } /** @@ -5638,17 +5822,19 @@ export function trim(expr: Expr | string): Trim { * * ```typescript * // Combine the 'firstName', " ", and 'lastName' fields into a single string - * strConcat("firstName", " ", Field.of("lastName")); + * strConcat("firstName", " ", field("lastName")); * ``` * - * @param first The field name containing the initial string value. - * @param elements The expressions (typically strings) to concatenate. + * @param fieldName The field name containing the initial string value. + * @param secondString An expression or string literal to concatenate. + * @param otherStrings Optional additional expressions or literals (typically strings) to concatenate. * @return A new {@code Expr} representing the concatenated string. */ export function strConcat( - first: string, - ...elements: Array -): StrConcat; + fieldName: string, + secondString: Expr | string, + ...otherStrings: Array +): FunctionExpr; /** * @beta @@ -5656,23 +5842,28 @@ export function strConcat( * * ```typescript * // Combine the 'firstName', " ", and 'lastName' fields into a single string - * strConcat(Field.of("firstName"), " ", Field.of("lastName")); + * strConcat(field("firstName"), " ", field("lastName")); * ``` * - * @param first The initial string expression to concatenate to. - * @param elements The expressions (typically strings) to concatenate. + * @param firstString The initial string expression to concatenate to. + * @param secondString An expression or string literal to concatenate. + * @param otherStrings Optional additional expressions or literals (typically strings) to concatenate. * @return A new {@code Expr} representing the concatenated string. */ export function strConcat( - first: Expr, - ...elements: Array -): StrConcat; + firstString: Expr, + secondString: Expr | string, + ...otherStrings: Array +): FunctionExpr; export function strConcat( first: string | Expr, + second: string | Expr, ...elements: Array -): StrConcat { - const exprs = elements.map(e => (e instanceof Expr ? e : Constant.of(e))); - return new StrConcat(first instanceof Expr ? first : Field.of(first), exprs); +): FunctionExpr { + return fieldOrExpression(first).strConcat( + valueToDefaultExpr(second), + ...elements.map(valueToDefaultExpr) + ); } /** @@ -5685,11 +5876,11 @@ export function strConcat( * mapGet("address", "city"); * ``` * - * @param mapField The field name of the map field. + * @param fieldName The field name of the map field. * @param subField The key to access in the map. * @return A new {@code Expr} representing the value associated with the given key in the map. */ -export function mapGet(mapField: string, subField: string): MapGet; +export function mapGet(fieldName: string, subField: string): FunctionExpr; /** * @beta @@ -5698,19 +5889,19 @@ export function mapGet(mapField: string, subField: string): MapGet; * * ```typescript * // Get the 'city' value from the 'address' map field - * mapGet(Field.of("address"), "city"); + * mapGet(field("address"), "city"); * ``` * - * @param mapExpr The expression representing the map. + * @param mapExpression The expression representing the map. * @param subField The key to access in the map. * @return A new {@code Expr} representing the value associated with the given key in the map. */ -export function mapGet(mapExpr: Expr, subField: string): MapGet; -export function mapGet(fieldOrExpr: string | Expr, subField: string): MapGet { - return new MapGet( - typeof fieldOrExpr === 'string' ? Field.of(fieldOrExpr) : fieldOrExpr, - subField - ); +export function mapGet(mapExpression: Expr, subField: string): FunctionExpr; +export function mapGet( + fieldOrExpr: string | Expr, + subField: string +): FunctionExpr { + return fieldOrExpression(fieldOrExpr).mapGet(subField); } /** @@ -5719,14 +5910,14 @@ export function mapGet(fieldOrExpr: string | Expr, subField: string): MapGet { * Creates an aggregation that counts the total number of stage inputs. * * ```typescript - * // Count the total number of users - * countAll().as("totalUsers"); + * // Count the total number of input documents + * countAll().as("totalDocument"); * ``` * - * @return A new {@code Accumulator} representing the 'countAll' aggregation. + * @return A new {@code AggregateFunction} representing the 'countAll' aggregation. */ -export function countAll(): Count { - return new Count(undefined, false); +export function countAll(): AggregateFunction { + return new AggregateFunction('count', []); } /** @@ -5737,30 +5928,28 @@ export function countAll(): Count { * * ```typescript * // Count the number of items where the price is greater than 10 - * count(Field.of("price").gt(10)).as("expensiveItemCount"); + * count(field("price").gt(10)).as("expensiveItemCount"); * ``` * - * @param value The expression to count. - * @return A new {@code Accumulator} representing the 'count' aggregation. + * @param expression The expression to count. + * @return A new {@code AggregateFunction} representing the 'count' aggregation. */ -export function countFunction(value: Expr): Count; +export function count(expression: Expr): AggregateFunction; /** - * Creates an aggregation that counts the number of stage inputs with valid evaluations of the - * provided field. + * Creates an aggregation that counts the number of stage inputs where the input field exists. * * ```typescript * // Count the total number of products * count("productId").as("totalProducts"); * ``` * - * @param value The name of the field to count. - * @return A new {@code Accumulator} representing the 'count' aggregation. + * @param fieldName The name of the field to count. + * @return A new {@code AggregateFunction} representing the 'count' aggregation. */ -export function countFunction(value: string): Count; -export function countFunction(value: Expr | string): Count { - const exprValue = value instanceof Expr ? value : Field.of(value); - return new Count(exprValue, false); +export function count(fieldName: string): AggregateFunction; +export function count(value: Expr | string): AggregateFunction { + return fieldOrExpression(value).count(); } /** @@ -5771,13 +5960,13 @@ export function countFunction(value: Expr | string): Count { * * ```typescript * // Calculate the total revenue from a set of orders - * sum(Field.of("orderAmount")).as("totalRevenue"); + * sum(field("orderAmount")).as("totalRevenue"); * ``` * - * @param value The expression to sum up. - * @return A new {@code Accumulator} representing the 'sum' aggregation. + * @param expression The expression to sum up. + * @return A new {@code AggregateFunction} representing the 'sum' aggregation. */ -export function sumFunction(value: Expr): Sum; +export function sum(expression: Expr): AggregateFunction; /** * @beta @@ -5790,13 +5979,12 @@ export function sumFunction(value: Expr): Sum; * sum("orderAmount").as("totalRevenue"); * ``` * - * @param value The name of the field containing numeric values to sum up. - * @return A new {@code Accumulator} representing the 'sum' aggregation. + * @param fieldName The name of the field containing numeric values to sum up. + * @return A new {@code AggregateFunction} representing the 'sum' aggregation. */ -export function sumFunction(value: string): Sum; -export function sumFunction(value: Expr | string): Sum { - const exprValue = value instanceof Expr ? value : Field.of(value); - return new Sum(exprValue, false); +export function sum(fieldName: string): AggregateFunction; +export function sum(value: Expr | string): AggregateFunction { + return fieldOrExpression(value).sum(); } /** @@ -5807,13 +5995,13 @@ export function sumFunction(value: Expr | string): Sum { * * ```typescript * // Calculate the average age of users - * avg(Field.of("age")).as("averageAge"); + * avg(field("age")).as("averageAge"); * ``` * - * @param value The expression representing the values to average. - * @return A new {@code Accumulator} representing the 'avg' aggregation. + * @param expression The expression representing the values to average. + * @return A new {@code AggregateFunction} representing the 'avg' aggregation. */ -export function avgFunction(value: Expr): Avg; +export function avg(expression: Expr): AggregateFunction; /** * @beta @@ -5826,13 +6014,12 @@ export function avgFunction(value: Expr): Avg; * avg("age").as("averageAge"); * ``` * - * @param value The name of the field containing numeric values to average. - * @return A new {@code Accumulator} representing the 'avg' aggregation. + * @param fieldName The name of the field containing numeric values to average. + * @return A new {@code AggregateFunction} representing the 'avg' aggregation. */ -export function avgFunction(value: string): Avg; -export function avgFunction(value: Expr | string): Avg { - const exprValue = value instanceof Expr ? value : Field.of(value); - return new Avg(exprValue, false); +export function avg(fieldName: string): AggregateFunction; +export function avg(value: Expr | string): AggregateFunction { + return fieldOrExpression(value).avg(); } /** @@ -5843,13 +6030,13 @@ export function avgFunction(value: Expr | string): Avg { * * ```typescript * // Find the lowest price of all products - * minimum(Field.of("price")).as("lowestPrice"); + * minimum(field("price")).as("lowestPrice"); * ``` * - * @param value The expression to find the minimum value of. - * @return A new {@code Accumulator} representing the 'min' aggregation. + * @param expression The expression to find the minimum value of. + * @return A new {@code AggregateFunction} representing the 'min' aggregation. */ -export function minimum(value: Expr): Minimum; +export function minimum(expression: Expr): AggregateFunction; /** * @beta @@ -5861,13 +6048,12 @@ export function minimum(value: Expr): Minimum; * minimum("price").as("lowestPrice"); * ``` * - * @param value The name of the field to find the minimum value of. - * @return A new {@code Accumulator} representing the 'min' aggregation. + * @param fieldName The name of the field to find the minimum value of. + * @return A new {@code AggregateFunction} representing the 'min' aggregation. */ -export function minimum(value: string): Minimum; -export function minimum(value: Expr | string): Minimum { - const exprValue = value instanceof Expr ? value : Field.of(value); - return new Minimum(exprValue, false); +export function minimum(fieldName: string): AggregateFunction; +export function minimum(value: Expr | string): AggregateFunction { + return fieldOrExpression(value).minimum(); } /** @@ -5878,13 +6064,13 @@ export function minimum(value: Expr | string): Minimum { * * ```typescript * // Find the highest score in a leaderboard - * maximum(Field.of("score")).as("highestScore"); + * maximum(field("score")).as("highestScore"); * ``` * - * @param value The expression to find the maximum value of. - * @return A new {@code Accumulator} representing the 'max' aggregation. + * @param expression The expression to find the maximum value of. + * @return A new {@code AggregateFunction} representing the 'max' aggregation. */ -export function maximum(value: Expr): Maximum; +export function maximum(expression: Expr): AggregateFunction; /** * @beta @@ -5896,49 +6082,32 @@ export function maximum(value: Expr): Maximum; * maximum("score").as("highestScore"); * ``` * - * @param value The name of the field to find the maximum value of. - * @return A new {@code Accumulator} representing the 'max' aggregation. + * @param fieldName The name of the field to find the maximum value of. + * @return A new {@code AggregateFunction} representing the 'max' aggregation. */ -export function maximum(value: string): Maximum; -export function maximum(value: Expr | string): Maximum { - const exprValue = value instanceof Expr ? value : Field.of(value); - return new Maximum(exprValue, false); +export function maximum(fieldName: string): AggregateFunction; +export function maximum(value: Expr | string): AggregateFunction { + return fieldOrExpression(value).maximum(); } /** * @beta * - * Calculates the Cosine distance between a field's vector value and a double array. + * Calculates the Cosine distance between a field's vector value and a literal vector value. * * ```typescript * // Calculate the Cosine distance between the 'location' field and a target location * cosineDistance("location", [37.7749, -122.4194]); * ``` * - * @param expr The name of the field containing the first vector. - * @param other The other vector (as an array of doubles) to compare against. - * @return A new {@code Expr} representing the Cosine distance between the two vectors. - */ -export function cosineDistance(expr: string, other: number[]): CosineDistance; - -/** - * @beta - * - * Calculates the Cosine distance between a field's vector value and a VectorValue. - * - * ```typescript - * // Calculate the Cosine distance between the 'location' field and a target location - * cosineDistance("location", new VectorValue([37.7749, -122.4194])); - * ``` - * - * @param expr The name of the field containing the first vector. - * @param other The other vector (as a VectorValue) to compare against. + * @param fieldName The name of the field containing the first vector. + * @param vector The other vector (as an array of doubles) or {@link VectorValue} to compare against. * @return A new {@code Expr} representing the Cosine distance between the two vectors. */ export function cosineDistance( - expr: string, - other: VectorValue -): CosineDistance; + fieldName: string, + vector: number[] | VectorValue +): FunctionExpr; /** * @beta @@ -5947,46 +6116,36 @@ export function cosineDistance( * * ```typescript * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field - * cosineDistance("userVector", Field.of("itemVector")); - * ``` - * - * @param expr The name of the field containing the first vector. - * @param other The other vector (represented as an Expr) to compare against. - * @return A new {@code Expr} representing the cosine distance between the two vectors. - */ -export function cosineDistance(expr: string, other: Expr): CosineDistance; - -/** - * @beta - * - * Calculates the Cosine distance between a vector expression and a double array. - * - * ```typescript - * // Calculate the cosine distance between the 'location' field and a target location - * cosineDistance(Field.of("location"), [37.7749, -122.4194]); + * cosineDistance("userVector", field("itemVector")); * ``` * - * @param expr The first vector (represented as an Expr) to compare against. - * @param other The other vector (as an array of doubles) to compare against. + * @param fieldName The name of the field containing the first vector. + * @param vectorExpression The other vector (represented as an Expr) to compare against. * @return A new {@code Expr} representing the cosine distance between the two vectors. */ -export function cosineDistance(expr: Expr, other: number[]): CosineDistance; +export function cosineDistance( + fieldName: string, + vectorExpression: Expr +): FunctionExpr; /** * @beta * - * Calculates the Cosine distance between a vector expression and a VectorValue. + * Calculates the Cosine distance between a vector expression and a vector literal. * * ```typescript * // Calculate the cosine distance between the 'location' field and a target location - * cosineDistance(Field.of("location"), new VectorValue([37.7749, -122.4194])); + * cosineDistance(field("location"), [37.7749, -122.4194]); * ``` * - * @param expr The first vector (represented as an Expr) to compare against. - * @param other The other vector (as a VectorValue) to compare against. + * @param vectorExpression The first vector (represented as an Expr) to compare against. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. * @return A new {@code Expr} representing the cosine distance between the two vectors. */ -export function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; +export function cosineDistance( + vectorExpression: Expr, + vector: number[] | Expr +): FunctionExpr; /** * @beta @@ -5995,21 +6154,24 @@ export function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; * * ```typescript * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field - * cosineDistance(Field.of("userVector"), Field.of("itemVector")); + * cosineDistance(field("userVector"), field("itemVector")); * ``` * - * @param expr The first vector (represented as an Expr) to compare against. - * @param other The other vector (represented as an Expr) to compare against. + * @param vectorExpression The first vector (represented as an Expr) to compare against. + * @param otherVectorExpression The other vector (represented as an Expr) to compare against. * @return A new {@code Expr} representing the cosine distance between the two vectors. */ -export function cosineDistance(expr: Expr, other: Expr): CosineDistance; +export function cosineDistance( + vectorExpression: Expr, + otherVectorExpression: Expr +): FunctionExpr; export function cosineDistance( expr: Expr | string, other: Expr | number[] | VectorValue -): CosineDistance { - const expr1 = expr instanceof Expr ? expr : Field.of(expr); - const expr2 = other instanceof Expr ? other : Constant.vector(other); - return new CosineDistance(expr1, expr2); +): FunctionExpr { + const expr1 = fieldOrExpression(expr); + const expr2 = vectorToExpr(other); + return expr1.cosineDistance(expr2); } /** @@ -6022,27 +6184,14 @@ export function cosineDistance( * dotProduct("features", [0.5, 0.8, 0.2]); * ``` * - * @param expr The name of the field containing the first vector. - * @param other The other vector (as an array of doubles) to calculate with. - * @return A new {@code Expr} representing the dot product between the two vectors. - */ -export function dotProduct(expr: string, other: number[]): DotProduct; - -/** - * @beta - * - * Calculates the dot product between a field's vector value and a VectorValue. - * - * ```typescript - * // Calculate the dot product distance between a feature vector and a target vector - * dotProduct("features", new VectorValue([0.5, 0.8, 0.2])); - * ``` - * - * @param expr The name of the field containing the first vector. - * @param other The other vector (as a VectorValue) to calculate with. + * @param fieldName The name of the field containing the first vector. + * @param vector The other vector (as an array of doubles or VectorValue) to calculate with. * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProduct(expr: string, other: VectorValue): DotProduct; +export function dotProduct( + fieldName: string, + vector: number[] | VectorValue +): FunctionExpr; /** * @beta @@ -6051,14 +6200,17 @@ export function dotProduct(expr: string, other: VectorValue): DotProduct; * * ```typescript * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' - * dotProduct("docVector1", Field.of("docVector2")); + * dotProduct("docVector1", field("docVector2")); * ``` * - * @param expr The name of the field containing the first vector. - * @param other The other vector (represented as an Expr) to calculate with. + * @param fieldName The name of the field containing the first vector. + * @param vectorExpression The other vector (represented as an Expr) to calculate with. * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProduct(expr: string, other: Expr): DotProduct; +export function dotProduct( + fieldName: string, + vectorExpression: Expr +): FunctionExpr; /** * @beta @@ -6067,30 +6219,17 @@ export function dotProduct(expr: string, other: Expr): DotProduct; * * ```typescript * // Calculate the dot product between a feature vector and a target vector - * dotProduct(Field.of("features"), [0.5, 0.8, 0.2]); - * ``` - * - * @param expr The first vector (represented as an Expr) to calculate with. - * @param other The other vector (as an array of doubles) to calculate with. - * @return A new {@code Expr} representing the dot product between the two vectors. - */ -export function dotProduct(expr: Expr, other: number[]): DotProduct; - -/** - * @beta - * - * Calculates the dot product between a vector expression and a VectorValue. - * - * ```typescript - * // Calculate the dot product between a feature vector and a target vector - * dotProduct(Field.of("features"), new VectorValue([0.5, 0.8, 0.2])); + * dotProduct(field("features"), [0.5, 0.8, 0.2]); * ``` * - * @param expr The first vector (represented as an Expr) to calculate with. - * @param other The other vector (as a VectorValue) to calculate with. + * @param vectorExpression The first vector (represented as an Expr) to calculate with. + * @param vector The other vector (as an array of doubles or VectorValue) to calculate with. * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProduct(expr: Expr, other: VectorValue): DotProduct; +export function dotProduct( + vectorExpression: Expr, + vector: number[] | VectorValue +): FunctionExpr; /** * @beta @@ -6099,21 +6238,24 @@ export function dotProduct(expr: Expr, other: VectorValue): DotProduct; * * ```typescript * // Calculate the dot product between two document vectors: 'docVector1' and 'docVector2' - * dotProduct(Field.of("docVector1"), Field.of("docVector2")); + * dotProduct(field("docVector1"), field("docVector2")); * ``` * - * @param expr The first vector (represented as an Expr) to calculate with. - * @param other The other vector (represented as an Expr) to calculate with. + * @param vectorExpression The first vector (represented as an Expr) to calculate with. + * @param otherVectorExpression The other vector (represented as an Expr) to calculate with. * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProduct(expr: Expr, other: Expr): DotProduct; +export function dotProduct( + vectorExpression: Expr, + otherVectorExpression: Expr +): FunctionExpr; export function dotProduct( expr: Expr | string, other: Expr | number[] | VectorValue -): DotProduct { - const expr1 = expr instanceof Expr ? expr : Field.of(expr); - const expr2 = other instanceof Expr ? other : Constant.vector(other); - return new DotProduct(expr1, expr2); +): FunctionExpr { + const expr1 = fieldOrExpression(expr); + const expr2 = vectorToExpr(other); + return expr1.dotProduct(expr2); } /** @@ -6126,33 +6268,14 @@ export function dotProduct( * euclideanDistance("location", [37.7749, -122.4194]); * ``` * - * @param expr The name of the field containing the first vector. - * @param other The other vector (as an array of doubles) to compare against. - * @return A new {@code Expr} representing the Euclidean distance between the two vectors. - */ -export function euclideanDistance( - expr: string, - other: number[] -): EuclideanDistance; - -/** - * @beta - * - * Calculates the Euclidean distance between a field's vector value and a VectorValue. - * - * ```typescript - * // Calculate the Euclidean distance between the 'location' field and a target location - * euclideanDistance("location", new VectorValue([37.7749, -122.4194])); - * ``` - * - * @param expr The name of the field containing the first vector. - * @param other The other vector (as a VectorValue) to compare against. + * @param fieldName The name of the field containing the first vector. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. * @return A new {@code Expr} representing the Euclidean distance between the two vectors. */ export function euclideanDistance( - expr: string, - other: VectorValue -): EuclideanDistance; + fieldName: string, + vector: number[] | VectorValue +): FunctionExpr; /** * @beta @@ -6161,14 +6284,17 @@ export function euclideanDistance( * * ```typescript * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' - * euclideanDistance("pointA", Field.of("pointB")); + * euclideanDistance("pointA", field("pointB")); * ``` * - * @param expr The name of the field containing the first vector. - * @param other The other vector (represented as an Expr) to compare against. + * @param fieldName The name of the field containing the first vector. + * @param vectorExpression The other vector (represented as an Expr) to compare against. * @return A new {@code Expr} representing the Euclidean distance between the two vectors. */ -export function euclideanDistance(expr: string, other: Expr): EuclideanDistance; +export function euclideanDistance( + fieldName: string, + vectorExpression: Expr +): FunctionExpr; /** * @beta @@ -6178,36 +6304,17 @@ export function euclideanDistance(expr: string, other: Expr): EuclideanDistance; * ```typescript * // Calculate the Euclidean distance between the 'location' field and a target location * - * euclideanDistance(Field.of("location"), [37.7749, -122.4194]); - * ``` - * - * @param expr The first vector (represented as an Expr) to compare against. - * @param other The other vector (as an array of doubles) to compare against. - * @return A new {@code Expr} representing the Euclidean distance between the two vectors. - */ -export function euclideanDistance( - expr: Expr, - other: number[] -): EuclideanDistance; - -/** - * @beta - * - * Calculates the Euclidean distance between a vector expression and a VectorValue. - * - * ```typescript - * // Calculate the Euclidean distance between the 'location' field and a target location - * euclideanDistance(Field.of("location"), new VectorValue([37.7749, -122.4194])); + * euclideanDistance(field("location"), [37.7749, -122.4194]); * ``` * - * @param expr The first vector (represented as an Expr) to compare against. - * @param other The other vector (as a VectorValue) to compare against. + * @param vectorExpression The first vector (represented as an Expr) to compare against. + * @param vector The other vector (as an array of doubles or VectorValue) to compare against. * @return A new {@code Expr} representing the Euclidean distance between the two vectors. */ export function euclideanDistance( - expr: Expr, - other: VectorValue -): EuclideanDistance; + vectorExpression: Expr, + vector: number[] | VectorValue +): FunctionExpr; /** * @beta @@ -6216,21 +6323,24 @@ export function euclideanDistance( * * ```typescript * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' - * euclideanDistance(Field.of("pointA"), Field.of("pointB")); + * euclideanDistance(field("pointA"), field("pointB")); * ``` * - * @param expr The first vector (represented as an Expr) to compare against. - * @param other The other vector (represented as an Expr) to compare against. + * @param vectorExpression The first vector (represented as an Expr) to compare against. + * @param otherVectorExpression The other vector (represented as an Expr) to compare against. * @return A new {@code Expr} representing the Euclidean distance between the two vectors. */ -export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; +export function euclideanDistance( + vectorExpression: Expr, + otherVectorExpression: Expr +): FunctionExpr; export function euclideanDistance( expr: Expr | string, other: Expr | number[] | VectorValue -): EuclideanDistance { - const expr1 = expr instanceof Expr ? expr : Field.of(expr); - const expr2 = other instanceof Expr ? other : Constant.vector(other); - return new EuclideanDistance(expr1, expr2); +): FunctionExpr { + const expr1 = fieldOrExpression(expr); + const expr2 = vectorToExpr(other); + return expr1.euclideanDistance(expr2); } /** @@ -6240,13 +6350,13 @@ export function euclideanDistance( * * ```typescript * // Get the vector length (dimension) of the field 'embedding'. - * vectorLength(Field.of("embedding")); + * vectorLength(field("embedding")); * ``` * - * @param expr The expression representing the Firestore Vector. + * @param vectorExpression The expression representing the Firestore Vector. * @return A new {@code Expr} representing the length of the array. */ -export function vectorLength(expr: Expr): VectorLength; +export function vectorLength(vectorExpression: Expr): FunctionExpr; /** * @beta @@ -6258,13 +6368,12 @@ export function vectorLength(expr: Expr): VectorLength; * vectorLength("embedding"); * ``` * - * @param field The name of the field representing the Firestore Vector. + * @param fieldName The name of the field representing the Firestore Vector. * @return A new {@code Expr} representing the length of the array. */ -export function vectorLength(field: string): VectorLength; -export function vectorLength(expr: Expr | string): VectorLength { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new VectorLength(normalizedExpr); +export function vectorLength(fieldName: string): FunctionExpr; +export function vectorLength(expr: Expr | string): FunctionExpr { + return fieldOrExpression(expr).vectorLength(); } /** @@ -6275,13 +6384,13 @@ export function vectorLength(expr: Expr | string): VectorLength { * * ```typescript * // Interpret the 'microseconds' field as microseconds since epoch. - * unixMicrosToTimestamp(Field.of("microseconds")); + * unixMicrosToTimestamp(field("microseconds")); * ``` * * @param expr The expression representing the number of microseconds since epoch. * @return A new {@code Expr} representing the timestamp. */ -export function unixMicrosToTimestamp(expr: Expr): UnixMicrosToTimestamp; +export function unixMicrosToTimestamp(expr: Expr): FunctionExpr; /** * @beta @@ -6294,15 +6403,12 @@ export function unixMicrosToTimestamp(expr: Expr): UnixMicrosToTimestamp; * unixMicrosToTimestamp("microseconds"); * ``` * - * @param field The name of the field representing the number of microseconds since epoch. + * @param fieldName The name of the field representing the number of microseconds since epoch. * @return A new {@code Expr} representing the timestamp. */ -export function unixMicrosToTimestamp(field: string): UnixMicrosToTimestamp; -export function unixMicrosToTimestamp( - expr: Expr | string -): UnixMicrosToTimestamp { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new UnixMicrosToTimestamp(normalizedExpr); +export function unixMicrosToTimestamp(fieldName: string): FunctionExpr; +export function unixMicrosToTimestamp(expr: Expr | string): FunctionExpr { + return fieldOrExpression(expr).unixMicrosToTimestamp(); } /** @@ -6312,13 +6418,13 @@ export function unixMicrosToTimestamp( * * ```typescript * // Convert the 'timestamp' field to microseconds since epoch. - * timestampToUnixMicros(Field.of("timestamp")); + * timestampToUnixMicros(field("timestamp")); * ``` * * @param expr The expression representing the timestamp. * @return A new {@code Expr} representing the number of microseconds since epoch. */ -export function timestampToUnixMicros(expr: Expr): TimestampToUnixMicros; +export function timestampToUnixMicros(expr: Expr): FunctionExpr; /** * @beta @@ -6330,15 +6436,12 @@ export function timestampToUnixMicros(expr: Expr): TimestampToUnixMicros; * timestampToUnixMicros("timestamp"); * ``` * - * @param field The name of the field representing the timestamp. + * @param fieldName The name of the field representing the timestamp. * @return A new {@code Expr} representing the number of microseconds since epoch. */ -export function timestampToUnixMicros(field: string): TimestampToUnixMicros; -export function timestampToUnixMicros( - expr: Expr | string -): TimestampToUnixMicros { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new TimestampToUnixMicros(normalizedExpr); +export function timestampToUnixMicros(fieldName: string): FunctionExpr; +export function timestampToUnixMicros(expr: Expr | string): FunctionExpr { + return fieldOrExpression(expr).timestampToUnixMicros(); } /** @@ -6349,13 +6452,13 @@ export function timestampToUnixMicros( * * ```typescript * // Interpret the 'milliseconds' field as milliseconds since epoch. - * unixMillisToTimestamp(Field.of("milliseconds")); + * unixMillisToTimestamp(field("milliseconds")); * ``` * * @param expr The expression representing the number of milliseconds since epoch. * @return A new {@code Expr} representing the timestamp. */ -export function unixMillisToTimestamp(expr: Expr): UnixMillisToTimestamp; +export function unixMillisToTimestamp(expr: Expr): FunctionExpr; /** * @beta @@ -6368,15 +6471,13 @@ export function unixMillisToTimestamp(expr: Expr): UnixMillisToTimestamp; * unixMillisToTimestamp("milliseconds"); * ``` * - * @param field The name of the field representing the number of milliseconds since epoch. + * @param fieldName The name of the field representing the number of milliseconds since epoch. * @return A new {@code Expr} representing the timestamp. */ -export function unixMillisToTimestamp(field: string): UnixMillisToTimestamp; -export function unixMillisToTimestamp( - expr: Expr | string -): UnixMillisToTimestamp { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new UnixMillisToTimestamp(normalizedExpr); +export function unixMillisToTimestamp(fieldName: string): FunctionExpr; +export function unixMillisToTimestamp(expr: Expr | string): FunctionExpr { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.unixMillisToTimestamp(); } /** @@ -6386,13 +6487,13 @@ export function unixMillisToTimestamp( * * ```typescript * // Convert the 'timestamp' field to milliseconds since epoch. - * timestampToUnixMillis(Field.of("timestamp")); + * timestampToUnixMillis(field("timestamp")); * ``` * * @param expr The expression representing the timestamp. * @return A new {@code Expr} representing the number of milliseconds since epoch. */ -export function timestampToUnixMillis(expr: Expr): TimestampToUnixMillis; +export function timestampToUnixMillis(expr: Expr): FunctionExpr; /** * @beta @@ -6404,15 +6505,13 @@ export function timestampToUnixMillis(expr: Expr): TimestampToUnixMillis; * timestampToUnixMillis("timestamp"); * ``` * - * @param field The name of the field representing the timestamp. + * @param fieldName The name of the field representing the timestamp. * @return A new {@code Expr} representing the number of milliseconds since epoch. */ -export function timestampToUnixMillis(field: string): TimestampToUnixMillis; -export function timestampToUnixMillis( - expr: Expr | string -): TimestampToUnixMillis { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new TimestampToUnixMillis(normalizedExpr); +export function timestampToUnixMillis(fieldName: string): FunctionExpr; +export function timestampToUnixMillis(expr: Expr | string): FunctionExpr { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.timestampToUnixMillis(); } /** @@ -6423,13 +6522,13 @@ export function timestampToUnixMillis( * * ```typescript * // Interpret the 'seconds' field as seconds since epoch. - * unixSecondsToTimestamp(Field.of("seconds")); + * unixSecondsToTimestamp(field("seconds")); * ``` * * @param expr The expression representing the number of seconds since epoch. * @return A new {@code Expr} representing the timestamp. */ -export function unixSecondsToTimestamp(expr: Expr): UnixSecondsToTimestamp; +export function unixSecondsToTimestamp(expr: Expr): FunctionExpr; /** * @beta @@ -6442,15 +6541,13 @@ export function unixSecondsToTimestamp(expr: Expr): UnixSecondsToTimestamp; * unixSecondsToTimestamp("seconds"); * ``` * - * @param field The name of the field representing the number of seconds since epoch. + * @param fieldName The name of the field representing the number of seconds since epoch. * @return A new {@code Expr} representing the timestamp. */ -export function unixSecondsToTimestamp(field: string): UnixSecondsToTimestamp; -export function unixSecondsToTimestamp( - expr: Expr | string -): UnixSecondsToTimestamp { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new UnixSecondsToTimestamp(normalizedExpr); +export function unixSecondsToTimestamp(fieldName: string): FunctionExpr; +export function unixSecondsToTimestamp(expr: Expr | string): FunctionExpr { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.unixSecondsToTimestamp(); } /** @@ -6460,13 +6557,13 @@ export function unixSecondsToTimestamp( * * ```typescript * // Convert the 'timestamp' field to seconds since epoch. - * timestampToUnixSeconds(Field.of("timestamp")); + * timestampToUnixSeconds(field("timestamp")); * ``` * * @param expr The expression representing the timestamp. * @return A new {@code Expr} representing the number of seconds since epoch. */ -export function timestampToUnixSeconds(expr: Expr): TimestampToUnixSeconds; +export function timestampToUnixSeconds(expr: Expr): FunctionExpr; /** * @beta @@ -6478,15 +6575,13 @@ export function timestampToUnixSeconds(expr: Expr): TimestampToUnixSeconds; * timestampToUnixSeconds("timestamp"); * ``` * - * @param field The name of the field representing the timestamp. + * @param fieldName The name of the field representing the timestamp. * @return A new {@code Expr} representing the number of seconds since epoch. */ -export function timestampToUnixSeconds(field: string): TimestampToUnixSeconds; -export function timestampToUnixSeconds( - expr: Expr | string -): TimestampToUnixSeconds { - const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; - return new TimestampToUnixSeconds(normalizedExpr); +export function timestampToUnixSeconds(fieldName: string): FunctionExpr; +export function timestampToUnixSeconds(expr: Expr | string): FunctionExpr { + const normalizedExpr = fieldOrExpression(expr); + return normalizedExpr.timestampToUnixSeconds(); } /** @@ -6496,7 +6591,7 @@ export function timestampToUnixSeconds( * * ```typescript * // Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. - * timestampAdd(Field.of("timestamp"), Field.of("unit"), Field.of("amount")); + * timestampAdd(field("timestamp"), field("unit"), field("amount")); * ``` * * @param timestamp The expression representing the timestamp. @@ -6508,7 +6603,7 @@ export function timestampAdd( timestamp: Expr, unit: Expr, amount: Expr -): TimestampAdd; +): FunctionExpr; /** * @beta @@ -6517,7 +6612,7 @@ export function timestampAdd( * * ```typescript * // Add 1 day to the 'timestamp' field. - * timestampAdd(Field.of("timestamp"), "day", 1); + * timestampAdd(field("timestamp"), "day", 1); * ``` * * @param timestamp The expression representing the timestamp. @@ -6529,7 +6624,7 @@ export function timestampAdd( timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number -): TimestampAdd; +): FunctionExpr; /** * @beta @@ -6541,16 +6636,16 @@ export function timestampAdd( * timestampAdd("timestamp", "day", 1); * ``` * - * @param field The name of the field representing the timestamp. + * @param fieldName The name of the field representing the timestamp. * @param unit The unit of time to add (e.g., "day", "hour"). * @param amount The amount of time to add. * @return A new {@code Expr} representing the resulting timestamp. */ export function timestampAdd( - field: string, + fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number -): TimestampAdd; +): FunctionExpr; export function timestampAdd( timestamp: Expr | string, unit: @@ -6562,17 +6657,11 @@ export function timestampAdd( | 'hour' | 'day', amount: Expr | number -): TimestampAdd { - const normalizedTimestamp = - typeof timestamp === 'string' ? Field.of(timestamp) : timestamp; - const normalizedUnit = unit instanceof Expr ? unit : Constant.of(unit); - const normalizedAmount = - typeof amount === 'number' ? Constant.of(amount) : amount; - return new TimestampAdd( - normalizedTimestamp, - normalizedUnit, - normalizedAmount - ); +): FunctionExpr { + const normalizedTimestamp = fieldOrExpression(timestamp); + const normalizedUnit = valueToDefaultExpr(unit); + const normalizedAmount = valueToDefaultExpr(amount); + return normalizedTimestamp.timestampAdd(normalizedUnit, normalizedAmount); } /** @@ -6582,7 +6671,7 @@ export function timestampAdd( * * ```typescript * // Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. - * timestampSub(Field.of("timestamp"), Field.of("unit"), Field.of("amount")); + * timestampSub(field("timestamp"), field("unit"), field("amount")); * ``` * * @param timestamp The expression representing the timestamp. @@ -6594,7 +6683,7 @@ export function timestampSub( timestamp: Expr, unit: Expr, amount: Expr -): TimestampSub; +): FunctionExpr; /** * @beta @@ -6603,7 +6692,7 @@ export function timestampSub( * * ```typescript * // Subtract 1 day from the 'timestamp' field. - * timestampSub(Field.of("timestamp"), "day", 1); + * timestampSub(field("timestamp"), "day", 1); * ``` * * @param timestamp The expression representing the timestamp. @@ -6615,7 +6704,7 @@ export function timestampSub( timestamp: Expr, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number -): TimestampSub; +): FunctionExpr; /** * @beta @@ -6627,16 +6716,16 @@ export function timestampSub( * timestampSub("timestamp", "day", 1); * ``` * - * @param field The name of the field representing the timestamp. + * @param fieldName The name of the field representing the timestamp. * @param unit The unit of time to subtract (e.g., "day", "hour"). * @param amount The amount of time to subtract. * @return A new {@code Expr} representing the resulting timestamp. */ export function timestampSub( - field: string, + fieldName: string, unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', amount: number -): TimestampSub; +): FunctionExpr; export function timestampSub( timestamp: Expr | string, unit: @@ -6648,39 +6737,11 @@ export function timestampSub( | 'hour' | 'day', amount: Expr | number -): TimestampSub { - const normalizedTimestamp = - typeof timestamp === 'string' ? Field.of(timestamp) : timestamp; - const normalizedUnit = unit instanceof Expr ? unit : Constant.of(unit); - const normalizedAmount = - typeof amount === 'number' ? Constant.of(amount) : amount; - return new TimestampSub( - normalizedTimestamp, - normalizedUnit, - normalizedAmount - ); -} - -/** - * @beta - * - * Creates functions that work on the backend but do not exist in the SDK yet. - * - * ```typescript - * // Call a user defined function named "myFunc" with the arguments 10 and 20 - * // This is the same of the 'sum(Field.of("price"))', if it did not exist - * genericFunction("sum", [Field.of("price")]); - * ``` - * - * @param name The name of the user defined function. - * @param params The arguments to pass to the function. - * @return A new {@code Function} representing the function call. - */ -export function genericFunction( - name: string, - params: Expr[] -): FirestoreFunction { - return new FirestoreFunction(name, params); +): FunctionExpr { + const normalizedTimestamp = fieldOrExpression(timestamp); + const normalizedUnit = valueToDefaultExpr(unit); + const normalizedAmount = valueToDefaultExpr(amount); + return normalizedTimestamp.timestampSub(normalizedUnit, normalizedAmount); } /** @@ -6694,15 +6755,17 @@ export function genericFunction( * const condition = and(gt("age", 18), eq("city", "London"), eq("status", "active")); * ``` * - * @param left The first filter condition. - * @param right Additional filter conditions to 'AND' together. + * @param first The first filter condition. + * @param second The second filter condition. + * @param more Additional filter conditions to 'AND' together. * @return A new {@code Expr} representing the logical 'AND' operation. */ -export function andFunction( - left: FilterCondition, - ...right: FilterCondition[] -): And { - return new And([left, ...right]); +export function and( + first: BooleanExpr, + second: BooleanExpr, + ...more: BooleanExpr[] +): BooleanExpr { + return new BooleanExpr('and', [first, second, ...more]); } /** @@ -6716,51 +6779,87 @@ export function andFunction( * const condition = or(gt("age", 18), eq("city", "London"), eq("status", "active")); * ``` * - * @param left The first filter condition. - * @param right Additional filter conditions to 'OR' together. + * @param first The first filter condition. + * @param second The second filter condition. + * @param more Additional filter conditions to 'OR' together. * @return A new {@code Expr} representing the logical 'OR' operation. */ -export function orFunction( - left: FilterCondition, - ...right: FilterCondition[] -): Or { - return new Or([left, ...right]); +export function or( + first: BooleanExpr, + second: BooleanExpr, + ...more: BooleanExpr[] +): BooleanExpr { + return new BooleanExpr('or', [first, second, ...more]); } /** * @beta * - * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * Creates an {@link Ordering} that sorts documents in ascending order based on an expression. * * ```typescript - * // Sort documents by the 'name' field in ascending order + * // Sort documents by the 'name' field in lowercase in ascending order * firestore.pipeline().collection("users") - * .sort(ascending(Field.of("name"))); + * .sort(ascending(field("name").toLower())); * ``` * * @param expr The expression to create an ascending ordering for. * @return A new `Ordering` for ascending sorting. */ -export function ascending(expr: Expr): Ordering { - return new Ordering(expr, 'ascending'); +export function ascending(expr: Expr): Ordering; + +/** + * @beta + * + * Creates an {@link Ordering} that sorts documents in ascending order based on a field. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * firestore.pipeline().collection("users") + * .sort(ascending("name")); + * ``` + * + * @param fieldName The field to create an ascending ordering for. + * @return A new `Ordering` for ascending sorting. + */ +export function ascending(fieldName: string): Ordering; +export function ascending(field: Expr | string): Ordering { + return new Ordering(fieldOrExpression(field), 'ascending'); } /** * @beta * - * Creates an {@link Ordering} that sorts documents in descending order based on this expression. + * Creates an {@link Ordering} that sorts documents in descending order based on an expression. * * ```typescript - * // Sort documents by the 'createdAt' field in descending order + * // Sort documents by the 'name' field in lowercase in descending order * firestore.pipeline().collection("users") - * .sort(descending(Field.of("createdAt"))); + * .sort(descending(field("name").toLower())); * ``` * * @param expr The expression to create a descending ordering for. * @return A new `Ordering` for descending sorting. */ -export function descending(expr: Expr): Ordering { - return new Ordering(expr, 'descending'); +export function descending(expr: Expr): Ordering; + +/** + * @beta + * + * Creates an {@link Ordering} that sorts documents in descending order based on a field. + * + * ```typescript + * // Sort documents by the 'name' field in descending order + * firestore.pipeline().collection("users") + * .sort(descending("name")); + * ``` + * + * @param fieldName The field to create a descending ordering for. + * @return A new `Ordering` for descending sorting. + */ +export function descending(fieldName: string): Ordering; +export function descending(field: Expr | string): Ordering { + return new Ordering(fieldOrExpression(field), 'descending'); } /** @@ -6770,12 +6869,20 @@ export function descending(expr: Expr): Ordering { * * You create `Ordering` instances using the `ascending` and `descending` helper functions. */ -export class Ordering { +export class Ordering implements ProtoValueSerializable, UserData { constructor( readonly expr: Expr, readonly direction: 'ascending' | 'descending' ) {} + /** + * @internal + * @private + * Indicates if this expression was created from a literal value passed + * by the caller. + */ + _createdFromLiteral: boolean = false; + /** * @private * @internal @@ -6795,7 +6902,13 @@ export class Ordering { * @private * @internal */ - _readUserData(dataReader: UserDataReader): void { + _readUserData(dataReader: UserDataReader, context?: ParseContext): void { + context = + this._createdFromLiteral && context + ? context + : dataReader.createContext(UserDataSource.Argument, 'constant'); this.expr._readUserData(dataReader); } + + _protoValueType: 'ProtoValue' = 'ProtoValue'; } diff --git a/packages/firestore/src/lite-api/pipeline-result.ts b/packages/firestore/src/lite-api/pipeline-result.ts index dc0a6412481..635636ac46b 100644 --- a/packages/firestore/src/lite-api/pipeline-result.ts +++ b/packages/firestore/src/lite-api/pipeline-result.ts @@ -18,12 +18,58 @@ import { ObjectValue } from '../model/object_value'; import { isOptionalEqual } from '../util/misc'; +import { Field } from './expressions'; import { FieldPath } from './field_path'; +import { Pipeline } from './pipeline'; import { DocumentData, DocumentReference, refEqual } from './reference'; import { fieldPathFromArgument } from './snapshot'; import { Timestamp } from './timestamp'; import { AbstractUserDataWriter } from './user_data_writer'; +export class PipelineSnapshot { + private readonly _pipeline: Pipeline; + private readonly _executionTime: Timestamp | undefined; + private readonly _results: PipelineResult[]; + constructor( + pipeline: Pipeline, + results: PipelineResult[], + executionTime?: Timestamp + ) { + this._pipeline = pipeline; + this._executionTime = executionTime; + this._results = results; + } + + /** + * The Pipeline on which you called `execute()` in order to get this + * `PipelineSnapshot`. + */ + get pipeline(): Pipeline { + return this._pipeline; + } + + /** An array of all the results in the `PipelineSnapshot`. */ + get results(): PipelineResult[] { + return this._results; + } + + /** + * The time at which the pipeline producing this result is executed. + * + * @type {Timestamp} + * @readonly + * + */ + get executionTime(): Timestamp { + if (this._executionTime === undefined) { + throw new Error( + "'executionTime' is expected to exist, but it is undefined" + ); + } + return this._executionTime; + } +} + /** * @beta * @@ -36,7 +82,6 @@ import { AbstractUserDataWriter } from './user_data_writer'; export class PipelineResult { private readonly _userDataWriter: AbstractUserDataWriter; - private readonly _executionTime: Timestamp | undefined; private readonly _createTime: Timestamp | undefined; private readonly _updateTime: Timestamp | undefined; @@ -58,7 +103,7 @@ export class PipelineResult { * * @param userDataWriter The serializer used to encode/decode protobuf. * @param ref The reference to the document. - * @param _fieldsProto The fields of the Firestore `Document` Protobuf backing + * @param fields The fields of the Firestore `Document` Protobuf backing * this document (or undefined if the document does not exist). * @param readTime The time when this result was read (or undefined if * the document exists only locally). @@ -69,13 +114,11 @@ export class PipelineResult { userDataWriter: AbstractUserDataWriter, ref?: DocumentReference, fields?: ObjectValue, - executionTime?: Timestamp, createTime?: Timestamp, updateTime?: Timestamp ) { this._ref = ref; this._userDataWriter = userDataWriter; - this._executionTime = executionTime; this._createTime = createTime; this._updateTime = updateTime; this._fields = fields; @@ -120,22 +163,6 @@ export class PipelineResult { return this._updateTime; } - /** - * The time at which the pipeline producing this result is executed. - * - * @type {Timestamp} - * @readonly - * - */ - get executionTime(): Timestamp { - if (this._executionTime === undefined) { - throw new Error( - "'executionTime' is expected to exist, but it is undefined" - ); - } - return this._executionTime; - } - /** * Retrieves all fields in the result as an object. Returns 'undefined' if * the document doesn't exist. @@ -166,7 +193,7 @@ export class PipelineResult { /** * Retrieves the field specified by `field`. * - * @param {string|FieldPath} field The field path + * @param {string|FieldPath|Field} field The field path * (e.g. 'foo' or 'foo.bar') to a specific field. * @returns {*} The data at the specified field location or undefined if no * such field exists. @@ -184,7 +211,7 @@ export class PipelineResult { // We deliberately use `any` in the external API to not impose type-checking // on end users. // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(fieldPath: string | FieldPath): any { + get(fieldPath: string | FieldPath | Field): any { if (this._fields === undefined) { return undefined; } diff --git a/packages/firestore/src/lite-api/pipeline-source.ts b/packages/firestore/src/lite-api/pipeline-source.ts index 856096037f8..421fc759bfb 100644 --- a/packages/firestore/src/lite-api/pipeline-source.ts +++ b/packages/firestore/src/lite-api/pipeline-source.ts @@ -15,7 +15,12 @@ * limitations under the License. */ -import { DocumentReference } from './reference'; +import { DatabaseId } from '../core/database_info'; +import { toPipeline } from '../core/pipeline-util'; +import { FirestoreError, Code } from '../util/error'; + +import { Pipeline } from './pipeline'; +import { CollectionReference, DocumentReference, Query } from './reference'; import { CollectionGroupSource, CollectionSource, @@ -35,6 +40,7 @@ export class PipelineSource { * @param _createPipeline */ constructor( + private databaseId: DatabaseId, /** * @internal * @private @@ -42,19 +48,89 @@ export class PipelineSource { public _createPipeline: (stages: Stage[]) => PipelineType ) {} - collection(collectionPath: string): PipelineType { - return this._createPipeline([new CollectionSource(collectionPath)]); + /** + * Set the pipeline's source to the collection specified by the given path. + * + * @param collectionPath A path to a collection that will be the source of this pipeline. + */ + collection(collectionPath: string): PipelineType; + + /** + * Set the pipeline's source to the collection specified by the given CollectionReference. + * + * @param collectionReference A CollectionReference for a collection that will be the source of this pipeline. + * The converter for this CollectionReference will be ignored and not have an effect on this pipeline. + * + * @throws {@FirestoreError} Thrown if the provided CollectionReference targets a different project or database than the pipeline. + */ + collection(collectionReference: CollectionReference): PipelineType; + collection(collection: CollectionReference | string): PipelineType { + if (collection instanceof CollectionReference) { + this._validateReference(collection); + return this._createPipeline([new CollectionSource(collection.path)]); + } else { + return this._createPipeline([new CollectionSource(collection)]); + } } + /** + * Set the pipeline's source to the collection group with the given id. + * + * @param collectionid The id of a collection group that will be the source of this pipeline. + */ collectionGroup(collectionId: string): PipelineType { return this._createPipeline([new CollectionGroupSource(collectionId)]); } + /** + * Set the pipeline's source to be all documents in this database. + */ database(): PipelineType { return this._createPipeline([new DatabaseSource()]); } - documents(docs: DocumentReference[]): PipelineType { + /** + * Set the pipeline's source to the documents specified by the given paths and DocumentReferences. + * + * @param docs An array of paths and DocumentReferences specifying the individual documents that will be the source of this pipeline. + * The converters for these DocumentReferences will be ignored and not have an effect on this pipeline. + * + * @throws {@FirestoreError} Thrown if any of the provided DocumentReferences target a different project or database than the pipeline. + */ + documents(docs: Array): PipelineType { + docs.forEach(doc => { + if (doc instanceof DocumentReference) { + this._validateReference(doc); + } + }); + return this._createPipeline([DocumentsSource.of(docs)]); } + + /** + * Convert the given Query into an equivalent Pipeline. + * + * @param query A Query to be converted into a Pipeline. + * + * @throws {@FirestoreError} Thrown if any of the provided DocumentReferences target a different project or database than the pipeline. + */ + createFrom(query: Query): Pipeline { + return toPipeline(query._query, query.firestore); + } + + _validateReference(reference: CollectionReference | DocumentReference): void { + const refDbId = reference.firestore._databaseId; + if (!refDbId.isEqual(this.databaseId)) { + throw new FirestoreError( + Code.INVALID_ARGUMENT, + `Invalid ${ + reference instanceof CollectionReference + ? 'CollectionReference' + : 'DocumentReference' + }. ` + + `The project ID ("${refDbId.projectId}") or the database ("${refDbId.database}") does not match ` + + `the project ID ("${this.databaseId.projectId}") and database ("${this.databaseId.database}") of the target database of this Pipeline.` + ); + } + } } diff --git a/packages/firestore/src/lite-api/pipeline.ts b/packages/firestore/src/lite-api/pipeline.ts index 2145952c004..e07c7a37b9f 100644 --- a/packages/firestore/src/lite-api/pipeline.ts +++ b/packages/firestore/src/lite-api/pipeline.ts @@ -15,36 +15,28 @@ * limitations under the License. */ -/* eslint @typescript-eslint/no-explicit-any: 0 */ - import { ObjectValue } from '../model/object_value'; import { - ExecutePipelineRequest, - StructuredPipeline, + Pipeline as ProtoPipeline, Stage as ProtoStage } from '../protos/firestore_proto_api'; -import { invokeExecutePipeline } from '../remote/datastore'; -import { - getEncodedDatabaseId, - JsonProtoSerializer, - ProtoSerializable -} from '../remote/serializer'; +import { JsonProtoSerializer, ProtoSerializable } from '../remote/serializer'; +import { isPlainObject } from '../util/input_validation'; -import { getDatastore } from './components'; import { Firestore } from './database'; import { - Accumulator, - AccumulatorTarget, + _mapValue, + AggregateFunction, + AggregateWithAlias, Expr, ExprWithAlias, Field, - Fields, - FilterCondition, + BooleanExpr, Ordering, - Selectable + Selectable, + field, + Constant } from './expressions'; -import { PipelineResult } from './pipeline-result'; -import { DocumentReference } from './reference'; import { AddFields, Aggregate, @@ -54,8 +46,13 @@ import { GenericStage, Limit, Offset, + RemoveFields, + Replace, Select, Sort, + Sample, + Union, + Unnest, Stage, Where } from './stage'; @@ -70,7 +67,7 @@ interface ReadableUserData { _readUserData(dataReader: UserDataReader): void; } -function isReadableUserData(value: any): value is ReadableUserData { +function isReadableUserData(value: unknown): value is ReadableUserData { return typeof (value as ReadableUserData)._readUserData === 'function'; } @@ -97,30 +94,27 @@ function isReadableUserData(value: any): value is ReadableUserData { * const db: Firestore; // Assumes a valid firestore instance. * * // Example 1: Select specific fields and rename 'rating' to 'bookRating' - * const results1 = await db.pipeline() + * const results1 = await execute(db.pipeline() * .collection("books") - * .select("title", "author", Field.of("rating").as("bookRating")) - * .execute(); + * .select("title", "author", field("rating").as("bookRating"))); * * // Example 2: Filter documents where 'genre' is "Science Fiction" and 'published' is after 1950 - * const results2 = await db.pipeline() + * const results2 = await execute(db.pipeline() * .collection("books") - * .where(and(Field.of("genre").eq("Science Fiction"), Field.of("published").gt(1950))) - * .execute(); + * .where(and(field("genre").eq("Science Fiction"), field("published").gt(1950)))); * * // Example 3: Calculate the average rating of books published after 1980 - * const results3 = await db.pipeline() + * const results3 = await execute(db.pipeline() * .collection("books") - * .where(Field.of("published").gt(1980)) - * .aggregate(avg(Field.of("rating")).as("averageRating")) - * .execute(); + * .where(field("published").gt(1980)) + * .aggregate(avg(field("rating")).as("averageRating"))); * ``` */ /** * Base-class implementation */ -export class Pipeline implements ProtoSerializable { +export class Pipeline implements ProtoSerializable { /** * @internal * @private @@ -162,27 +156,53 @@ export class Pipeline implements ProtoSerializable { * ```typescript * firestore.pipeline().collection("books") * .addFields( - * Field.of("rating").as("bookRating"), // Rename 'rating' to 'bookRating' - * add(5, Field.of("quantity")).as("totalCost") // Calculate 'totalCost' + * field("rating").as("bookRating"), // Rename 'rating' to 'bookRating' + * add(5, field("quantity")).as("totalCost") // Calculate 'totalCost' * ); * ``` * - * @param fields The fields to add to the documents, specified as {@link Selectable}s. + * @param field The first field to add to the documents, specified as a {@link Selectable}. + * @param additionalFields Optional additional fields to add to the documents, specified as {@link Selectable}s. * @return A new Pipeline object with this stage appended to the stage list. */ - addFields(...fields: Selectable[]): Pipeline { - const copy = this.stages.map(s => s); - copy.push( + addFields(field: Selectable, ...additionalFields: Selectable[]): Pipeline { + return this._addStage( new AddFields( - this.readUserData('addFields', this.selectablesToMap(fields)) + this.readUserData( + 'addFields', + this.selectablesToMap([field, ...additionalFields]) + ) ) ); - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy + } + + /** + * Remove fields from outputs of previous stages. + * + * Example: + * + * ```typescript + * firestore.pipeline().collection('books') + * // removes field 'rating' and 'cost' from the previous stage outputs. + * .removeFields( + * field('rating'), + * 'cost' + * ); + * ``` + * + * @param fieldValue The first field to remove. + * @param additionalFields Optional additional fields to remove. + * @return A new Pipeline object with this stage appended to the stage list. + */ + removeFields( + fieldValue: Field | string, + ...additionalFields: Array + ): Pipeline { + const fieldExpressions = [fieldValue, ...additionalFields].map(f => + typeof f === 'string' ? field(f) : (f as Field) ); + this.readUserData('removeFields', fieldExpressions); + return this._addStage(new RemoveFields(fieldExpressions)); } /** @@ -198,7 +218,7 @@ export class Pipeline implements ProtoSerializable { * * *

If no selections are provided, the output of this stage is empty. Use {@link - * com.google.cloud.firestore.Pipeline#addFields} instead if only additions are + * Pipeline#addFields} instead if only additions are * desired. * *

Example: @@ -207,101 +227,36 @@ export class Pipeline implements ProtoSerializable { * firestore.pipeline().collection("books") * .select( * "firstName", - * Field.of("lastName"), - * Field.of("address").toUppercase().as("upperAddress"), + * field("lastName"), + * field("address").toUppercase().as("upperAddress"), * ); * ``` * - * @param selections The fields to include in the output documents, specified as {@link + * @param selection The first field to include in the output documents, specified as {@link + * Selectable} expression or string value representing the field name. + * @param additionalSelections Optional additional fields to include in the output documents, specified as {@link * Selectable} expressions or {@code string} values representing field names. * @return A new Pipeline object with this stage appended to the stage list. */ - select(...selections: Array): Pipeline { - const copy = this.stages.map(s => s); - let projections: Map = this.selectablesToMap(selections); - projections = this.readUserData('select', projections); - copy.push(new Select(projections)); - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); - } - - private selectablesToMap( - selectables: Array - ): Map { - const result = new Map(); - for (const selectable of selectables) { - if (typeof selectable === 'string') { - result.set(selectable as string, Field.of(selectable)); - } else if (selectable instanceof Field) { - result.set((selectable as Field).fieldName(), selectable); - } else if (selectable instanceof Fields) { - const fields = selectable as Fields; - for (const field of fields.fieldList()) { - result.set(field.fieldName(), field); - } - } else if (selectable instanceof ExprWithAlias) { - const expr = selectable as ExprWithAlias; - result.set(expr.alias, expr.expr); - } - } - return result; - } - - /** - * Reads user data for each expression in the expressionMap. - * @param name Name of the calling function. Used for error messages when invalid user data is encountered. - * @param expressionMap - * @return the expressionMap argument. - * @private - */ - private readUserData< - T extends - | Map - | ReadableUserData[] - | ReadableUserData - >(name: string, expressionMap: T): T { - if (isReadableUserData(expressionMap)) { - expressionMap._readUserData(this.userDataReader); - } else if (Array.isArray(expressionMap)) { - expressionMap.forEach(readableData => - readableData._readUserData(this.userDataReader) - ); - } else { - expressionMap.forEach(expr => expr._readUserData(this.userDataReader)); - } - return expressionMap; - } - - /** - * @internal - * @private - * @param db - * @param userDataReader - * @param userDataWriter - * @param stages - * @protected - */ - protected newPipeline( - db: Firestore, - userDataReader: UserDataReader, - userDataWriter: AbstractUserDataWriter, - stages: Stage[], - converter: unknown = {} + select( + selection: Selectable | string, + ...additionalSelections: Array ): Pipeline { - return new Pipeline(db, userDataReader, userDataWriter, stages); + let projections: Map = this.selectablesToMap([ + selection, + ...additionalSelections + ]); + projections = this.readUserData('select', projections); + return this._addStage(new Select(projections)); } /** * Filters the documents from previous stages to only include those matching the specified {@link - * FilterCondition}. + * BooleanExpr}. * *

This stage allows you to apply conditions to the data, similar to a "WHERE" clause in SQL. * You can filter documents based on their field values, using implementations of {@link - * FilterCondition}, typically including but not limited to: + * BooleanExpr}, typically including but not limited to: * *

    *
  • field comparators: {@link Function#eq}, {@link Function#lt} (less than), {@link @@ -317,25 +272,18 @@ export class Pipeline implements ProtoSerializable { * firestore.pipeline().collection("books") * .where( * and( - * gt(Field.of("rating"), 4.0), // Filter for ratings greater than 4.0 - * Field.of("genre").eq("Science Fiction") // Equivalent to gt("genre", "Science Fiction") + * gt(field("rating"), 4.0), // Filter for ratings greater than 4.0 + * field("genre").eq("Science Fiction") // Equivalent to gt("genre", "Science Fiction") * ) * ); * ``` * - * @param condition The {@link FilterCondition} to apply. + * @param condition The {@link BooleanExpr} to apply. * @return A new Pipeline object with this stage appended to the stage list. */ - where(condition: FilterCondition): Pipeline { - const copy = this.stages.map(s => s); + where(condition: BooleanExpr): Pipeline { this.readUserData('where', condition); - copy.push(new Where(condition)); - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); + return this._addStage(new Where(condition)); } /** @@ -350,7 +298,7 @@ export class Pipeline implements ProtoSerializable { * ```typescript * // Retrieve the second page of 20 results * firestore.pipeline().collection("books") - * .sort(Field.of("published").descending()) + * .sort(field("published").descending()) * .offset(20) // Skip the first 20 results * .limit(20); // Take the next 20 results * ``` @@ -359,14 +307,7 @@ export class Pipeline implements ProtoSerializable { * @return A new Pipeline object with this stage appended to the stage list. */ offset(offset: number): Pipeline { - const copy = this.stages.map(s => s); - copy.push(new Offset(offset)); - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); + return this._addStage(new Offset(offset)); } /** @@ -387,7 +328,7 @@ export class Pipeline implements ProtoSerializable { * ```typescript * // Limit the results to the top 10 highest-rated books * firestore.pipeline().collection("books") - * .sort(Field.of("rating").descending()) + * .sort(field("rating").descending()) * .limit(10); * ``` * @@ -395,32 +336,28 @@ export class Pipeline implements ProtoSerializable { * @return A new Pipeline object with this stage appended to the stage list. */ limit(limit: number): Pipeline { - const copy = this.stages.map(s => s); - copy.push(new Limit(limit)); - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); + return this._addStage(new Limit(limit)); } - _limit(limit: number, convertedFromLimitTolast: boolean): Pipeline { - const copy = this.stages.map(s => s); - copy.push(new Limit(limit, convertedFromLimitTolast)); - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); + /** + * Internal use only. + * Helper to add a limit stage when converting from a Query. + * + * @internal + * @private + * + * @param limit + * @param convertedFromLimitToLast + */ + _limit(limit: number, convertedFromLimitToLast: boolean): Pipeline { + return this._addStage(new Limit(limit, convertedFromLimitToLast)); } /** * Returns a set of distinct {@link Expr} values from the inputs to this stage. * *

    This stage run through the results from previous stages to include only results with unique - * combinations of {@link Expr} values ({@link Field}, {@link Function}, etc). + * combinations of {@link Expr} values ({@link Field}, {@link Function}, etc.). * *

    The parameters to this stage are defined using {@link Selectable} expressions or {@code string}s: * @@ -436,35 +373,36 @@ export class Pipeline implements ProtoSerializable { * ```typescript * // Get a list of unique author names in uppercase and genre combinations. * firestore.pipeline().collection("books") - * .distinct(toUppercase(Field.of("author")).as("authorName"), Field.of("genre"), "publishedAt") + * .distinct(toUppercase(field("author")).as("authorName"), field("genre"), "publishedAt") * .select("authorName"); * ``` * - * @param selectables The {@link Selectable} expressions to consider when determining distinct - * value combinations or {@code string}s representing field names. + * @param group The first {@link Selectable} expression to consider when determining distinct + * value combinations or strings representing field names. + * @param additionalGroups Optional additional {@link Selectable} expressions to consider when determining distinct + * value combinations or strings representing field names. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ - distinct(...groups: Array): Pipeline { - const copy = this.stages.map(s => s); - copy.push( + distinct( + group: string | Selectable, + ...additionalGroups: Array + ): Pipeline { + return this._addStage( new Distinct( - this.readUserData('distinct', this.selectablesToMap(groups || [])) + this.readUserData( + 'distinct', + this.selectablesToMap([group, ...additionalGroups]) + ) ) ); - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); } /** * Performs aggregation operations on the documents from previous stages. * *

    This stage allows you to calculate aggregate values over a set of documents. You define the - * aggregations to perform using {@link AccumulatorTarget} expressions which are typically results of - * calling {@link Expr#as} on {@link Accumulator} instances. + * aggregations to perform using {@link AggregateWithAlias} expressions which are typically results of + * calling {@link Expr#as} on {@link AggregateFunction} instances. * *

    Example: * @@ -472,16 +410,21 @@ export class Pipeline implements ProtoSerializable { * // Calculate the average rating and the total number of books * firestore.pipeline().collection("books") * .aggregate( - * Field.of("rating").avg().as("averageRating"), + * field("rating").avg().as("averageRating"), * countAll().as("totalBooks") * ); * ``` * - * @param accumulators The {@link AccumulatorTarget} expressions, each wrapping an {@link Accumulator} + * @param accumulator The first {@link AggregateWithAlias}, wrapping an {@link AggregateFunction} + * and provide a name for the accumulated results. + * @param additionalAccumulators Optional additional {@link AggregateWithAlias}, each wrapping an {@link AggregateFunction} * and provide a name for the accumulated results. * @return A new Pipeline object with this stage appended to the stage list. */ - aggregate(...accumulators: AccumulatorTarget[]): Pipeline; + aggregate( + accumulator: AggregateWithAlias, + ...additionalAccumulators: AggregateWithAlias[] + ): Pipeline; /** * Performs optionally grouped aggregation operations on the documents from previous stages. * @@ -494,8 +437,8 @@ export class Pipeline implements ProtoSerializable { * If no grouping fields are provided, a single group containing all documents is used. Not * specifying groups is the same as putting the entire inputs into one group.

  • *
  • **Accumulators:** One or more accumulation operations to perform within each group. These - * are defined using {@link AccumulatorTarget} expressions, which are typically created by - * calling {@link Expr#as} on {@link Accumulator} instances. Each aggregation + * are defined using {@link AggregateWithAlias} expressions, which are typically created by + * calling {@link Expr#as} on {@link AggregateFunction} instances. Each aggregation * calculates a value (e.g., sum, average, count) based on the documents within its group.
  • *
* @@ -505,40 +448,43 @@ export class Pipeline implements ProtoSerializable { * // Calculate the average rating for each genre. * firestore.pipeline().collection("books") * .aggregate({ - * accumulators: [avg(Field.of("rating")).as("avg_rating")] + * accumulators: [avg(field("rating")).as("avg_rating")] * groups: ["genre"] * }); * ``` * - * @param aggregate An {@link Aggregate} object that specifies the grouping fields (if any) and - * the aggregation operations to perform. - * @return A new {@code Pipeline} object with this stage appended to the stage list. + * @param options An object that specifies the accumulators + * and optional grouping fields to perform. + * @return A new {@code Pipeline} object with this stage appended to the stage + * list. */ aggregate(options: { - accumulators: AccumulatorTarget[]; + accumulators: AggregateWithAlias[]; groups?: Array; }): Pipeline; aggregate( optionsOrTarget: - | AccumulatorTarget + | AggregateWithAlias | { - accumulators: AccumulatorTarget[]; + accumulators: AggregateWithAlias[]; groups?: Array; }, - ...rest: AccumulatorTarget[] + ...rest: AggregateWithAlias[] ): Pipeline { - const copy = this.stages.map(s => s); if ('accumulators' in optionsOrTarget) { - copy.push( + return this._addStage( new Aggregate( - new Map( - optionsOrTarget.accumulators.map((target: AccumulatorTarget) => [ - (target as unknown as AccumulatorTarget).alias, + new Map( + optionsOrTarget.accumulators.map((target: AggregateWithAlias) => { this.readUserData( 'aggregate', - (target as unknown as AccumulatorTarget).expr - ) - ]) + target as unknown as AggregateWithAlias + ); + return [ + (target as unknown as AggregateWithAlias).alias, + (target as unknown as AggregateWithAlias).aggregate + ]; + }) ), this.readUserData( 'aggregate', @@ -547,14 +493,14 @@ export class Pipeline implements ProtoSerializable { ) ); } else { - copy.push( + return this._addStage( new Aggregate( - new Map( + new Map( [optionsOrTarget, ...rest].map(target => [ - (target as unknown as AccumulatorTarget).alias, + (target as unknown as AggregateWithAlias).alias, this.readUserData( 'aggregate', - (target as unknown as AccumulatorTarget).expr + (target as unknown as AggregateWithAlias).aggregate ) ]) ), @@ -562,37 +508,24 @@ export class Pipeline implements ProtoSerializable { ) ); } - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); } findNearest(options: FindNearestOptions): Pipeline { - const copy = this.stages.map(s => s); const parseContext = this.userDataReader.createContext( UserDataSource.Argument, 'findNearest' ); const value = parseVectorValue(options.vectorValue, parseContext); const vectorObjectValue = new ObjectValue(value); - copy.push( + return this._addStage( new FindNearest( - options.field, + options.field instanceof Field ? options.field : field(options.field), vectorObjectValue, options.distanceMeasure, options.limit, options.distanceField ) ); - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); } /** @@ -611,15 +544,16 @@ export class Pipeline implements ProtoSerializable { * // with the same rating * firestore.pipeline().collection("books") * .sort( - * Ordering.of(Field.of("rating")).descending(), - * Ordering.of(Field.of("title")) // Ascending order is the default + * Ordering.of(field("rating")).descending(), + * Ordering.of(field("title")) // Ascending order is the default * ); * ``` * - * @param orders One or more {@link Ordering} instances specifying the sorting criteria. + * @param ordering The first {@link Ordering} instance specifying the sorting criteria. + * @param additionalOrderings Optional additional {@link Ordering} instances specifying the additional sorting criteria. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ - sort(...orderings: Ordering[]): Pipeline; + sort(ordering: Ordering, ...additionalOrderings: Ordering[]): Pipeline; sort( optionsOrOrderings: | Ordering @@ -628,10 +562,9 @@ export class Pipeline implements ProtoSerializable { }, ...rest: Ordering[] ): Pipeline { - const copy = this.stages.map(s => s); // Option object - if ('orderings' in optionsOrOrderings) { - copy.push( + if (optionsOrOrderings && 'orderings' in optionsOrOrderings) { + return this._addStage( new Sort( this.readUserData( 'sort', @@ -641,17 +574,205 @@ export class Pipeline implements ProtoSerializable { ); } else { // Ordering object - copy.push( + return this._addStage( new Sort(this.readUserData('sort', [optionsOrOrderings, ...rest])) ); } + } - return this.newPipeline( - this._db, - this.userDataReader, - this._userDataWriter, - copy - ); + /** + * Fully overwrites all fields in a document with those coming from a nested map. + * + *

This stage allows you to emit a map value as a document. Each key of the map becomes a field + * on the document that contains the corresponding value. + * + *

Example: + * + * ```typescript + * // Input. + * // { + * // 'name': 'John Doe Jr.', + * // 'parents': { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * // } + * + * // Emit parents as document. + * firestore.pipeline().collection('people').replaceWith('parents'); + * + * // Output + * // { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * ``` + * + * @param fieldName The {@link Field} field containing the nested map. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + replaceWith(fieldName: string): Pipeline; + + /** + * Fully overwrites all fields in a document with those coming from a map. + * + *

This stage allows you to emit a map value as a document. Each key of the map becomes a field + * on the document that contains the corresponding value. + * + *

Example: + * + * ```typescript + * // Input. + * // { + * // 'name': 'John Doe Jr.', + * // 'parents': { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * // } + * + * // Emit parents as document. + * firestore.pipeline().collection('people').replaceWith(map({ + * foo: 'bar', + * info: { + * name: field('name') + * } + * })); + * + * // Output + * // { + * // 'father': 'John Doe Sr.', + * // 'mother': 'Jane Doe' + * // } + * ``` + * + * @param expr An {@link Expr} that when returned evaluates to a map. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + replaceWith(expr: Expr): Pipeline; + replaceWith(value: Expr | string): Pipeline { + const fieldExpr = typeof value === 'string' ? field(value) : value; + this.readUserData('replaceWith', fieldExpr); + return this._addStage(new Replace(fieldExpr, 'full_replace')); + } + + /** + * Performs a pseudo-random sampling of the documents from the previous stage. + * + *

This stage will filter documents pseudo-randomly. The parameter specifies how number of + * documents to be returned. + * + *

Examples: + * + * ```typescript + * // Sample 25 books, if available. + * firestore.pipeline().collection('books') + * .sample(25); + * ``` + * + * @param documents The number of documents to sample. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + sample(documents: number): Pipeline; + + /** + * Performs a pseudo-random sampling of the documents from the previous stage. + * + *

This stage will filter documents pseudo-randomly. The 'options' parameter specifies how + * sampling will be performed. See {@code SampleOptions} for more information. + * + *

Examples: + * + * // Sample 10 books, if available. + * firestore.pipeline().collection("books") + * .sample({ documents: 10 }); + * + * // Sample 50% of books. + * firestore.pipeline().collection("books") + * .sample({ percentage: 0.5 }); + * + * @param options The {@code SampleOptions} specifies how sampling is performed. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + sample(options: { percentage: number } | { documents: number }): Pipeline; + sample( + documentsOrOptions: number | { percentage: number } | { documents: number } + ): Pipeline { + if (typeof documentsOrOptions === 'number') { + return this._addStage(new Sample(documentsOrOptions, 'documents')); + } else if ('percentage' in documentsOrOptions) { + return this._addStage( + new Sample(documentsOrOptions.percentage, 'percent') + ); + } else { + return this._addStage( + new Sample(documentsOrOptions.documents, 'documents') + ); + } + } + + /** + * Performs union of all documents from two pipelines, including duplicates. + * + *

This stage will pass through documents from previous stage, and also pass through documents + * from previous stage of the `other` {@code Pipeline} given in parameter. The order of documents + * emitted from this stage is undefined. + * + *

Example: + * + * ```typescript + * // Emit documents from books collection and magazines collection. + * firestore.pipeline().collection('books') + * .union(firestore.pipeline().collection('magazines')); + * ``` + * + * @param other The other {@code Pipeline} that is part of union. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + union(other: Pipeline): Pipeline { + return this._addStage(new Union(other)); + } + + /** + * Produces a document for each element in array found in previous stage document. + * + * For each previous stage document, this stage will emit zero or more augmented documents. The + * input array found in the previous stage document field specified by the `selectable` parameter, + * will emit an augmented document for each input array element. The input array element will + * augment the previous stage document by setting the `alias` field with the array element value. + * + * When `selectable` evaluates to a non-array value (ex: number, null, absent), then the stage becomes a no-op for + * the current input document, returning it as is with the `alias` field absent. + * + * No documents are emitted when `selectable` evaluates to an empty array. + * + * Example: + * + * ```typescript + * // Input: + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tags": [ "comedy", "space", "adventure" ], ... } + * + * // Emit a book document for each tag of the book. + * firestore.pipeline().collection("books") + * .unnest(field("tags").as('tag'), 'tagIndex'); + * + * // Output: + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "comedy", "tagIndex": 0, ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "space", "tagIndex": 1, ... } + * // { "title": "The Hitchhiker's Guide to the Galaxy", "tag": "adventure", "tagIndex": 2, ... } + * ``` + * + * @param selectable A selectable expression defining the field to unnest and the alias to use for each un-nested element in the output documents. + * @param indexField An optional string value specifying the field path to write the offset (starting at zero) into the array the un-nested element is from + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + unnest(selectable: Selectable, indexField?: string): Pipeline { + this.readUserData('unnest', selectable.expr); + + const alias = field(selectable.alias); + this.readUserData('unnest', alias); + + return this._addStage(new Unnest(selectable.expr, alias, indexField)); } /** @@ -666,7 +787,7 @@ export class Pipeline implements ProtoSerializable { * ```typescript * // Assume we don't have a built-in "where" stage * firestore.pipeline().collection("books") - * .genericStage("where", [Field.of("published").lt(1900)]) // Custom "where" stage + * .genericStage("where", [field("published").lt(1900)]) // Custom "where" stage * .select("title", "author"); * ``` * @@ -674,14 +795,45 @@ export class Pipeline implements ProtoSerializable { * @param params A list of parameters to configure the generic stage's behavior. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ - genericStage(name: string, params: any[]): Pipeline { - const copy = this.stages.map(s => s); - params.forEach(param => { + genericStage(name: string, params: unknown[]): Pipeline { + // Convert input values to Expressions. + // We treat objects as mapValues and arrays as arrayValues, + // this is unlike the default conversion for objects and arrays + // passed to an expression. + const expressionParams = params.map((value: unknown) => { + if (value instanceof Expr) { + return value; + } else if (value instanceof AggregateFunction) { + return value; + } else if (isPlainObject(value)) { + return _mapValue(value as Record); + } else { + return new Constant(value); + } + }); + + expressionParams.forEach(param => { if (isReadableUserData(param)) { param._readUserData(this.userDataReader); } }); - copy.push(new GenericStage(name, params)); + return this._addStage(new GenericStage(name, expressionParams)); + } + + /** + * @internal + * @private + */ + _toProto(jsonProtoSerializer: JsonProtoSerializer): ProtoPipeline { + const stages: ProtoStage[] = this.stages.map(stage => + stage._toProto(jsonProtoSerializer) + ); + return { stages }; + } + + private _addStage(stage: Stage): Pipeline { + const copy = this.stages.map(s => s); + copy.push(stage); return this.newPipeline( this._db, this.userDataReader, @@ -690,74 +842,62 @@ export class Pipeline implements ProtoSerializable { ); } + private selectablesToMap( + selectables: Array + ): Map { + const result = new Map(); + for (const selectable of selectables) { + if (typeof selectable === 'string') { + result.set(selectable as string, field(selectable)); + } else if (selectable instanceof Field) { + result.set(selectable.alias, selectable.expr); + } else if (selectable instanceof ExprWithAlias) { + result.set(selectable.alias, selectable.expr); + } + } + return result; + } + /** - * Executes this pipeline and returns a Promise to represent the asynchronous operation. - * - *

The returned Promise can be used to track the progress of the pipeline execution - * and retrieve the results (or handle any errors) asynchronously. - * - *

The pipeline results are returned as a list of {@link PipelineResult} objects. Each {@link - * PipelineResult} typically represents a single key/value map that has passed through all the - * stages of the pipeline, however this might differ depending on the stages involved in the - * pipeline. For example: - * - *

    - *
  • If there are no stages or only transformation stages, each {@link PipelineResult} - * represents a single document.
  • - *
  • If there is an aggregation, only a single {@link PipelineResult} is returned, - * representing the aggregated results over the entire dataset .
  • - *
  • If there is an aggregation stage with grouping, each {@link PipelineResult} represents a - * distinct group and its associated aggregated values.
  • - *
- * - *

Example: - * - * ```typescript - * const futureResults = await firestore.pipeline().collection("books") - * .where(gt(Field.of("rating"), 4.5)) - * .select("title", "author", "rating") - * .execute(); - * ``` - * - * @return A Promise representing the asynchronous pipeline execution. + * Reads user data for each expression in the expressionMap. + * @param name Name of the calling function. Used for error messages when invalid user data is encountered. + * @param expressionMap + * @return the expressionMap argument. + * @private */ - execute(): Promise { - const datastore = getDatastore(this._db); - return invokeExecutePipeline(datastore, this).then(result => { - const docs = result - // Currently ignore any response from ExecutePipeline that does - // not contain any document data in the `fields` property. - .filter(element => !!element.fields) - .map( - element => - new PipelineResult( - this._userDataWriter, - element.key?.path - ? new DocumentReference(this._db, null, element.key) - : undefined, - element.fields, - element.executionTime?.toTimestamp(), - element.createTime?.toTimestamp(), - element.updateTime?.toTimestamp() - ) - ); - - return docs; - }); + private readUserData< + T extends + | Map + | ReadableUserData[] + | ReadableUserData + >(name: string, expressionMap: T): T { + if (isReadableUserData(expressionMap)) { + expressionMap._readUserData(this.userDataReader); + } else if (Array.isArray(expressionMap)) { + expressionMap.forEach(readableData => + readableData._readUserData(this.userDataReader) + ); + } else { + expressionMap.forEach(expr => expr._readUserData(this.userDataReader)); + } + return expressionMap; } /** * @internal * @private + * @param db + * @param userDataReader + * @param userDataWriter + * @param stages + * @protected */ - _toProto(jsonProtoSerializer: JsonProtoSerializer): ExecutePipelineRequest { - const stages: ProtoStage[] = this.stages.map(stage => - stage._toProto(jsonProtoSerializer) - ); - const structuredPipeline: StructuredPipeline = { pipeline: { stages } }; - return { - database: getEncodedDatabaseId(jsonProtoSerializer), - structuredPipeline - }; + protected newPipeline( + db: Firestore, + userDataReader: UserDataReader, + userDataWriter: AbstractUserDataWriter, + stages: Stage[] + ): Pipeline { + return new Pipeline(db, userDataReader, userDataWriter, stages); } } diff --git a/packages/firestore/src/lite-api/pipeline_impl.ts b/packages/firestore/src/lite-api/pipeline_impl.ts index 98b121ad485..c1ca940a56b 100644 --- a/packages/firestore/src/lite-api/pipeline_impl.ts +++ b/packages/firestore/src/lite-api/pipeline_impl.ts @@ -15,11 +15,14 @@ * limitations under the License. */ +import { invokeExecutePipeline } from '../remote/datastore'; + +import { getDatastore } from './components'; import { Firestore } from './database'; import { Pipeline } from './pipeline'; -import { PipelineResult } from './pipeline-result'; +import { PipelineResult, PipelineSnapshot } from './pipeline-result'; import { PipelineSource } from './pipeline-source'; -import { Query } from './reference'; +import { DocumentReference } from './reference'; import { LiteUserDataWriter } from './reference_impl'; import { Stage } from './stage'; import { newUserDataReader } from './user_data_reader'; @@ -30,67 +33,74 @@ declare module './database' { } } -declare module './reference' { - interface Query { - pipeline(): Pipeline; - } -} - -/** - * Modular API for console experimentation. - * @param pipeline Execute this pipeline. - * @beta - */ -export function execute(pipeline: Pipeline): Promise { - return pipeline.execute(); -} - -/** - * Experimental Modular API for console testing. - * @param firestore - */ -export function pipeline(firestore: Firestore): PipelineSource; - /** - * Experimental Modular API for console testing. - * @param query + * Executes this pipeline and returns a Promise to represent the asynchronous operation. + * + * The returned Promise can be used to track the progress of the pipeline execution + * and retrieve the results (or handle any errors) asynchronously. + * + * The pipeline results are returned as a {@link PipelineSnapshot} that contains + * a list of {@link PipelineResult} objects. Each {@link PipelineResult} typically + * represents a single key/value map that has passed through all the + * stages of the pipeline, however this might differ depending on the stages involved in the + * pipeline. For example: + * + *

    + *
  • If there are no stages or only transformation stages, each {@link PipelineResult} + * represents a single document.
  • + *
  • If there is an aggregation, only a single {@link PipelineResult} is returned, + * representing the aggregated results over the entire dataset .
  • + *
  • If there is an aggregation stage with grouping, each {@link PipelineResult} represents a + * distinct group and its associated aggregated values.
  • + *
+ * + *

Example: + * + * ```typescript + * const snapshot: PipelineSnapshot = await execute(firestore.pipeline().collection("books") + * .where(gt(field("rating"), 4.5)) + * .select("title", "author", "rating")); + * + * const results: PipelineResults = snapshot.results; + * ``` + * + * @param pipeline The pipeline to execute. + * @return A Promise representing the asynchronous pipeline execution. */ -export function pipeline(query: Query): Pipeline; - -export function pipeline( - firestoreOrQuery: Firestore | Query -): PipelineSource | Pipeline { - if (firestoreOrQuery instanceof Firestore) { - const db = firestoreOrQuery; - const userDataWriter = new LiteUserDataWriter(db); - const userDataReader = newUserDataReader(db); - return new PipelineSource((stages: Stage[]) => { - return new Pipeline(db, userDataReader, userDataWriter, stages); - }); - } else { - let pipeline; - const query = firestoreOrQuery; - if (query._query.collectionGroup) { - pipeline = query.firestore - .pipeline() - .collectionGroup(query._query.collectionGroup); - } else { - pipeline = query.firestore - .pipeline() - .collection(query._query.path.canonicalString()); - } +export function execute(pipeline: Pipeline): Promise { + const datastore = getDatastore(pipeline._db); + return invokeExecutePipeline(datastore, pipeline).then(result => { + // Get the execution time from the first result. + // firestoreClientExecutePipeline returns at least one PipelineStreamElement + // even if the returned document set is empty. + const executionTime = + result.length > 0 ? result[0].executionTime?.toTimestamp() : undefined; - // TODO(pipeline) convert existing query filters, limits, etc into - // pipeline stages + const docs = result + // Currently ignore any response from ExecutePipeline that does + // not contain any document data in the `fields` property. + .filter(element => !!element.fields) + .map( + element => + new PipelineResult( + pipeline._userDataWriter, + element.key?.path + ? new DocumentReference(pipeline._db, null, element.key) + : undefined, + element.fields, + element.createTime?.toTimestamp(), + element.updateTime?.toTimestamp() + ) + ); - return pipeline; - } + return new PipelineSnapshot(pipeline, docs, executionTime); + }); } Firestore.prototype.pipeline = function (): PipelineSource { - return pipeline(this); -}; - -Query.prototype.pipeline = function (): Pipeline { - return pipeline(this); + const userDataWriter = new LiteUserDataWriter(this); + const userDataReader = newUserDataReader(this); + return new PipelineSource(this._databaseId, (stages: Stage[]) => { + return new Pipeline(this, userDataReader, userDataWriter, stages); + }); }; diff --git a/packages/firestore/src/lite-api/reference.ts b/packages/firestore/src/lite-api/reference.ts index 26ae2fbd433..e9f5685e2b1 100644 --- a/packages/firestore/src/lite-api/reference.ts +++ b/packages/firestore/src/lite-api/reference.ts @@ -581,7 +581,7 @@ export function doc( ) { throw new FirestoreError( Code.INVALID_ARGUMENT, - 'Expected first argument to collection() to be a CollectionReference, ' + + 'Expected first argument to doc() to be a CollectionReference, ' + 'a DocumentReference or FirebaseFirestore' ); } diff --git a/packages/firestore/src/lite-api/snapshot.ts b/packages/firestore/src/lite-api/snapshot.ts index 3024e2e9db0..66c3a1422e9 100644 --- a/packages/firestore/src/lite-api/snapshot.ts +++ b/packages/firestore/src/lite-api/snapshot.ts @@ -23,6 +23,7 @@ import { FieldPath as InternalFieldPath } from '../model/path'; import { arrayEquals } from '../util/misc'; import { Firestore } from './database'; +import { Field } from './expressions'; import { FieldPath } from './field_path'; import { DocumentData, @@ -515,12 +516,14 @@ export function snapshotEqual( */ export function fieldPathFromArgument( methodName: string, - arg: string | FieldPath | Compat + arg: string | FieldPath | Compat | Field ): InternalFieldPath { if (typeof arg === 'string') { return fieldPathFromDotSeparatedString(methodName, arg); } else if (arg instanceof FieldPath) { return arg._internalPath; + } else if (arg instanceof Field) { + return fieldPathFromDotSeparatedString(methodName, arg.fieldName()); } else { return arg._delegate._internalPath; } diff --git a/packages/firestore/src/lite-api/stage.ts b/packages/firestore/src/lite-api/stage.ts index 46f8fe60654..1d8ae06eaf6 100644 --- a/packages/firestore/src/lite-api/stage.ts +++ b/packages/firestore/src/lite-api/stage.ts @@ -25,17 +25,20 @@ import { JsonProtoSerializer, ProtoSerializable, toMapValue, + toPipelineValue, toStringValue } from '../remote/serializer'; import { hardAssert } from '../util/assert'; import { - Accumulator, + AggregateFunction, Expr, Field, - FilterCondition, - Ordering + BooleanExpr, + Ordering, + field } from './expressions'; +import { Pipeline } from './pipeline'; import { DocumentReference } from './reference'; import { VectorValue } from './vector_value'; @@ -66,6 +69,26 @@ export class AddFields implements Stage { } } +/** + * @beta + */ +export class RemoveFields implements Stage { + name = 'remove_fields'; + + constructor(private fields: Field[]) {} + + /** + * @internal + * @private + */ + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + name: this.name, + args: this.fields.map(f => f._toProto(serializer)) + }; + } +} + /** * @beta */ @@ -73,7 +96,7 @@ export class Aggregate implements Stage { name = 'aggregate'; constructor( - private accumulators: Map, + private accumulators: Map, private groups: Map ) {} @@ -181,8 +204,16 @@ export class DocumentsSource implements Stage { constructor(private docPaths: string[]) {} - static of(refs: DocumentReference[]): DocumentsSource { - return new DocumentsSource(refs.map(ref => '/' + ref.path)); + static of(refs: Array): DocumentsSource { + return new DocumentsSource( + refs.map(ref => + ref instanceof DocumentReference + ? '/' + ref.path + : ref.startsWith('/') + ? ref + : '/' + ref + ) + ); } /** @@ -205,7 +236,7 @@ export class DocumentsSource implements Stage { export class Where implements Stage { name = 'where'; - constructor(private condition: FilterCondition) {} + constructor(private condition: BooleanExpr) {} /** * @internal @@ -223,7 +254,7 @@ export class Where implements Stage { * @beta */ export interface FindNearestOptions { - field: Field; + field: Field | string; vectorValue: VectorValue | number[]; distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; limit?: number; @@ -267,9 +298,7 @@ export class FindNearest implements Stage { if (this._distanceField) { // eslint-disable-next-line camelcase - options.distance_field = Field.of(this._distanceField)._toProto( - serializer - ); + options.distance_field = field(this._distanceField)._toProto(serializer); } return { @@ -372,18 +401,108 @@ export class Sort implements Stage { } } +/** + * @beta + */ +export class Sample implements Stage { + name = 'sample'; + + constructor(private limit: number, private mode: string) {} + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + name: this.name, + args: [toNumber(serializer, this.limit)!, toStringValue(this.mode)!] + }; + } +} + +/** + * @beta + */ +export class Union implements Stage { + name = 'union'; + + constructor(private _other: Pipeline) {} + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + name: this.name, + args: [toPipelineValue(this._other._toProto(serializer))] + }; + } +} + +/** + * @beta + */ +export class Unnest implements Stage { + name = 'unnest'; + constructor( + private expr: Expr, + private alias: Field, + private indexField?: string + ) {} + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + const stageProto: ProtoStage = { + name: this.name, + args: [this.expr._toProto(serializer), this.alias._toProto(serializer)] + }; + + if (this.indexField) { + stageProto.options = { + indexField: toStringValue(this.indexField) + }; + } + + return stageProto; + } +} + +/** + * @beta + */ +export class Replace implements Stage { + name = 'replace_with'; + + constructor( + private field: Expr, + private mode: + | 'full_replace' + | 'merge_prefer_nest' + | 'merge_prefer_parent' = 'full_replace' + ) {} + + _toProto(serializer: JsonProtoSerializer): ProtoStage { + return { + name: this.name, + args: [this.field._toProto(serializer), toStringValue(this.mode)] + }; + } +} + /** * @beta */ export class GenericStage implements Stage { - constructor(public name: string, params: unknown[]) {} + /** + * @private + * @internal + */ + constructor( + public name: string, + private params: Array + ) {} /** * @internal * @private */ _toProto(serializer: JsonProtoSerializer): ProtoStage { - // TODO support generic stage - return {}; + return { + name: this.name, + args: this.params.map(o => o._toProto(serializer)) + }; } } diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index 42f905d5cab..e3e0deaa479 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -22,7 +22,7 @@ import { } from '@firebase/firestore-types'; import { Compat, deepEqual, getModularInstance } from '@firebase/util'; -import { ParseContext } from '../api/parse_context'; +import { ContextSettings, ParseContext } from '../api/parse_context'; import { DatabaseId } from '../core/database_info'; import { DocumentKey } from '../model/document_key'; import { FieldMask } from '../model/field_mask'; @@ -56,7 +56,8 @@ import { JsonProtoSerializer, toBytes, toResourceName, - toTimestamp + toTimestamp, + isProtoValueSerializable } from '../remote/serializer'; import { debugAssert, fail } from '../util/assert'; import { Code, FirestoreError } from '../util/error'; @@ -179,33 +180,6 @@ function isWrite(dataSource: UserDataSource): boolean { } } -/** Contains the settings that are mutated as we parse user data. */ -interface ContextSettings { - /** Indicates what kind of API method this data came from. */ - readonly dataSource: UserDataSource; - /** The name of the method the user called to create the ParseContext. */ - readonly methodName: string; - /** The document the user is attempting to modify, if that applies. */ - readonly targetDoc?: DocumentKey; - /** - * A path within the object being parsed. This could be an empty path (in - * which case the context represents the root of the data being parsed), or a - * nonempty path (indicating the context represents a nested location within - * the data). - */ - readonly path?: InternalFieldPath; - /** - * Whether or not this context corresponds to an element of an array. - * If not set, elements are treated as if they were outside of arrays. - */ - readonly arrayElement?: boolean; - /** - * Whether or not a converter was specified in this context. If true, error - * messages will reference the converter when invalid data is provided. - */ - readonly hasConverter?: boolean; -} - /** A "context" object passed around while parsing user data. */ class ParseContextImpl implements ParseContext { readonly fieldTransforms: FieldTransform[]; @@ -729,7 +703,7 @@ export function parseQueryValue( */ export function parseData( input: unknown, - context: ParseContextImpl + context: ParseContext ): ProtoValue | null { // Unwrap the API type from the Compat SDK. This will return the API type // from firestore-exp. @@ -780,7 +754,7 @@ export function parseData( function parseObject( obj: Dict, - context: ParseContextImpl + context: ParseContext ): { mapValue: ProtoMapValue } { const fields: Dict = {}; @@ -802,7 +776,7 @@ function parseObject( return { mapValue: { fields } }; } -function parseArray(array: unknown[], context: ParseContextImpl): ProtoValue { +function parseArray(array: unknown[], context: ParseContext): ProtoValue { const values: ProtoValue[] = []; let entryIndex = 0; for (const entry of array) { @@ -827,7 +801,7 @@ function parseArray(array: unknown[], context: ParseContextImpl): ProtoValue { */ function parseSentinelFieldValue( value: FieldValue, - context: ParseContextImpl + context: ParseContext ): void { // Sentinels are only supported with writes, and not within arrays. if (!isWrite(context.dataSource)) { @@ -854,7 +828,7 @@ function parseSentinelFieldValue( */ export function parseScalarValue( value: unknown, - context: ParseContextImpl + context: ParseContext ): ProtoValue | null { value = getModularInstance(value); @@ -909,6 +883,8 @@ export function parseScalarValue( }; } else if (value instanceof VectorValue) { return parseVectorValue(value, context); + } else if (isProtoValueSerializable(value)) { + return value._toProto(context.serializer); } else { throw context.createError( `Unsupported field value: ${valueDescription(value)}` @@ -921,7 +897,7 @@ export function parseScalarValue( */ export function parseVectorValue( value: VectorValue | number[], - context: ParseContextImpl + context: ParseContext ): { mapValue: ProtoMapValue } { const values = value instanceof VectorValue ? value.toArray() : value; const mapValue: ProtoMapValue = { @@ -955,7 +931,7 @@ export function parseVectorValue( * GeoPoints, etc. are not considered to look like JSON objects since they map * to specific FieldValue types other than ObjectValue. */ -function looksLikeJsonObject(input: unknown): boolean { +export function looksLikeJsonObject(input: unknown): boolean { return ( typeof input === 'object' && input !== null && @@ -966,13 +942,14 @@ function looksLikeJsonObject(input: unknown): boolean { !(input instanceof Bytes) && !(input instanceof DocumentReference) && !(input instanceof FieldValue) && - !(input instanceof VectorValue) + !(input instanceof VectorValue) && + !isProtoValueSerializable(input) ); } function validatePlainObject( message: string, - context: ParseContextImpl, + context: ParseContext, input: unknown ): asserts input is Dict { if (!looksLikeJsonObject(input) || !isPlainObject(input)) { diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index 00c1e7fca9e..32666feeea1 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -59,7 +59,8 @@ import { toQueryTarget, toResourcePath, toRunAggregationQueryRequest, - fromPipelineResponse + fromPipelineResponse, + getEncodedDatabaseId } from './serializer'; /** @@ -244,7 +245,12 @@ export async function invokeExecutePipeline( pipeline: Pipeline ): Promise { const datastoreImpl = debugCast(datastore, DatastoreImpl); - const executePipelineRequest = pipeline._toProto(datastoreImpl.serializer); + const executePipelineRequest: ProtoExecutePipelineRequest = { + database: getEncodedDatabaseId(datastoreImpl.serializer), + structuredPipeline: { + pipeline: pipeline._toProto(datastoreImpl.serializer) + } + }; const response = await datastoreImpl.invokeStreamingRPC< ProtoExecutePipelineRequest, diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 4759571b4a5..1ed2c7cd381 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { ParseContext } from '../api/parse_context'; import { Aggregate } from '../core/aggregate'; import { Bound } from '../core/bound'; import { DatabaseId } from '../core/database_info'; @@ -94,7 +95,8 @@ import { WriteResult as ProtoWriteResult, Value as ProtoValue, MapValue as ProtoMapValue, - ExecutePipelineResponse as ProtoExecutePipelineResponse + ExecutePipelineResponse as ProtoExecutePipelineResponse, + Pipeline } from '../protos/firestore_proto_api'; import { debugAssert, fail, hardAssert } from '../util/assert'; import { ByteString } from '../util/byte_string'; @@ -1433,8 +1435,24 @@ export interface ProtoSerializable { _toProto(serializer: JsonProtoSerializer): ProtoType; } +export interface ProtoValueSerializable extends ProtoSerializable { + // Supports runtime identification of the ProtoSerializable type. + _protoValueType: 'ProtoValue'; +} + +export function isProtoValueSerializable( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any +): value is ProtoValueSerializable { + return ( + !!value && + typeof value._toProto === 'function' && + value._protoValueType === 'ProtoValue' + ); +} + export interface UserData { - _readUserData(dataReader: UserDataReader): void; + _readUserData(dataReader: UserDataReader, context?: ParseContext): void; } export function toMapValue( @@ -1466,6 +1484,10 @@ export function toStringValue(value: string): ProtoValue { return { stringValue: value }; } +export function toPipelineValue(value: Pipeline): ProtoValue { + return { pipelineValue: value }; +} + export function dateToTimestampValue( serializer: JsonProtoSerializer, value: Date diff --git a/packages/firestore/src/util/types.ts b/packages/firestore/src/util/types.ts index c298bfe2131..361ebda1935 100644 --- a/packages/firestore/src/util/types.ts +++ b/packages/firestore/src/util/types.ts @@ -51,6 +51,10 @@ export function isSafeInteger(value: unknown): boolean { ); } +export function isString(value: unknown): value is string { + return typeof value === 'string'; +} + /** The subset of the browser's Window interface used by the SDK. */ export interface WindowLike { readonly localStorage: Storage; diff --git a/packages/firestore/test/integration/api/pipeline.test.ts b/packages/firestore/test/integration/api/pipeline.test.ts index 48441c26065..7fc26ce58cd 100644 --- a/packages/firestore/test/integration/api/pipeline.test.ts +++ b/packages/firestore/test/integration/api/pipeline.test.ts @@ -18,312 +18,914 @@ import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import { Bytes, vector } from '../../../src/api'; -import { GeoPoint } from '../../../src/lite-api/geo_point'; -import { Timestamp } from '../../../src/lite-api/timestamp'; +import { + AggregateFunction, + ascending, + BooleanExpr, + byteLength, + constantVector, + FunctionExpr, + timestampAdd, + timestampToUnixMicros, + timestampToUnixMillis, + timestampToUnixSeconds, + toLower, + unixMicrosToTimestamp, + unixMillisToTimestamp, + vectorLength +} from '../../../src/lite-api/expressions'; +import { PipelineSnapshot } from '../../../src/lite-api/pipeline-result'; import { addEqualityMatcher } from '../../util/equality_matcher'; import { Deferred } from '../../util/promise'; import { - pipeline, + GeoPoint, + Timestamp, + Bytes, + getFirestore, + terminate, + vector, + CollectionReference, + doc, + DocumentData, + Firestore, + setDoc, + setLogLevel, + collection, + documentId as documentIdFieldPath, + writeBatch, + addDoc +} from '../util/firebase_export'; +import { apiDescribe, withTestCollection, itIf } from '../util/helpers'; +import { + array, + mod, + pipelineResultEqual, + sum, + replaceFirst, + replaceAll, + descending, + isNan, + map, execute, - _internalPipelineToExecutePipelineRequestProto, add, - andFunction, + arrayContainsAll, + unixSecondsToTimestamp, + and, arrayContains, arrayContainsAny, - avgFunction, - CollectionReference, - Constant, + count, + avg, cosineDistance, + not, countAll, - doc, - DocumentData, dotProduct, endsWith, eq, + reverse, + toUpper, euclideanDistance, - Field, - Firestore, gt, like, lt, + strContains, + divide, lte, + arrayLength, + arrayConcat, mapGet, neq, - not, - orFunction, - PipelineResult, + or, regexContains, regexMatch, - setDoc, startsWith, + strConcat, subtract, - setLogLevel, cond, eqAny, logicalMaximum, - logicalMinimum, notEqAny, - query, - where, - FieldPath, - orderBy, - limit, - limitToLast, - startAt, - startAfter, - endAt, - endBefore, - collectionGroup, - collection, - and, + multiply, + countIf, + bitAnd, + bitOr, + bitXor, + bitNot, + exists, + bitLeftShift, + charLength, + bitRightShift, + rand, + arrayOffset, + minimum, + maximum, + currentContext, + isError, + ifError, + trim, + isAbsent, + isNull, + isNotNull, + isNotNan, + timestampSub, + mapRemove, + mapMerge, documentId, - addDoc, - getDoc -} from '../util/firebase_export'; -import { - apiDescribe, - PERSISTENCE_MODE_UNSPECIFIED, - withTestCollection -} from '../util/helpers'; + substr, + logicalMinimum, + xor, + field, + constant, + _internalPipelineToExecutePipelineRequestProto, + FindNearestOptions +} from '../util/pipeline_export'; use(chaiAsPromised); setLogLevel('debug'); +const testUnsupportedFeatures = false; + apiDescribe.only('Pipelines', persistence => { addEqualityMatcher(); - describe('books tests', () => { - let firestore: Firestore; - let randomCol: CollectionReference; - - async function testCollectionWithDocs(docs: { - [id: string]: DocumentData; - }): Promise> { - for (const id in docs) { - if (docs.hasOwnProperty(id)) { - const ref = doc(randomCol, id); - await setDoc(ref, docs[id]); - } + let firestore: Firestore; + let randomCol: CollectionReference; + let beginDocCreation: number = 0; + let endDocCreation: number = 0; + + const timestampDeltaMS = 1000; + + async function testCollectionWithDocs(docs: { + [id: string]: DocumentData; + }): Promise> { + beginDocCreation = new Date().valueOf(); + for (const id in docs) { + if (docs.hasOwnProperty(id)) { + const ref = doc(randomCol, id); + await setDoc(ref, docs[id]); } - return randomCol; } - - function expectResults( - result: Array>, - ...docs: string[] - ): void; - function expectResults( - result: Array>, - ...data: DocumentData[] - ): void; - - function expectResults( - result: Array>, - ...data: DocumentData[] | string[] - ): void { - expect(result.length).to.equal(data.length); - - if (data.length > 0) { - if (typeof data[0] === 'string') { - const actualIds = result.map(result => result.ref?.id); - expect(actualIds).to.deep.equal(data); - } else { - result.forEach(r => { - expect(r.data()).to.deep.equal(data.shift()); - }); - } + endDocCreation = new Date().valueOf(); + return randomCol; + } + + function expectResults(snapshot: PipelineSnapshot, ...docs: string[]): void; + function expectResults( + snapshot: PipelineSnapshot, + ...data: DocumentData[] + ): void; + + function expectResults( + snapshot: PipelineSnapshot, + ...data: DocumentData[] | string[] + ): void { + const docs = snapshot.results; + + expect(docs.length).to.equal(data.length); + + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = docs.map(doc => doc.id); + expect(actualIds).to.deep.equal(data); + } else { + docs.forEach(r => { + expect(r.data()).to.deep.equal(data.shift()); + }); } } - - async function setupBookDocs(): Promise> { - const bookDocs: { [id: string]: DocumentData } = { - book1: { - title: "The Hitchhiker's Guide to the Galaxy", - author: 'Douglas Adams', - genre: 'Science Fiction', - published: 1979, - rating: 4.2, - tags: ['comedy', 'space', 'adventure'], - awards: { - hugo: true, - nebula: false, - others: { unknown: { year: 1980 } } - }, - nestedField: { 'level.1': { 'level.2': true } } - }, - book2: { - title: 'Pride and Prejudice', - author: 'Jane Austen', - genre: 'Romance', - published: 1813, - rating: 4.5, - tags: ['classic', 'social commentary', 'love'], - awards: { none: true } - }, - book3: { - title: 'One Hundred Years of Solitude', - author: 'Gabriel García Márquez', - genre: 'Magical Realism', - published: 1967, - rating: 4.3, - tags: ['family', 'history', 'fantasy'], - awards: { nobel: true, nebula: false } - }, - book4: { - title: 'The Lord of the Rings', - author: 'J.R.R. Tolkien', - genre: 'Fantasy', - published: 1954, - rating: 4.7, - tags: ['adventure', 'magic', 'epic'], - awards: { hugo: false, nebula: false } - }, - book5: { - title: "The Handmaid's Tale", - author: 'Margaret Atwood', - genre: 'Dystopian', - published: 1985, - rating: 4.1, - tags: ['feminism', 'totalitarianism', 'resistance'], - awards: { 'arthur c. clarke': true, 'booker prize': false } - }, - book6: { - title: 'Crime and Punishment', - author: 'Fyodor Dostoevsky', - genre: 'Psychological Thriller', - published: 1866, - rating: 4.3, - tags: ['philosophy', 'crime', 'redemption'], - awards: { none: true } - }, - book7: { - title: 'To Kill a Mockingbird', - author: 'Harper Lee', - genre: 'Southern Gothic', - published: 1960, - rating: 4.2, - tags: ['racism', 'injustice', 'coming-of-age'], - awards: { pulitzer: true } - }, - book8: { - title: '1984', - author: 'George Orwell', - genre: 'Dystopian', - published: 1949, - rating: 4.2, - tags: ['surveillance', 'totalitarianism', 'propaganda'], - awards: { prometheus: true } - }, - book9: { - title: 'The Great Gatsby', - author: 'F. Scott Fitzgerald', - genre: 'Modernist', - published: 1925, - rating: 4.0, - tags: ['wealth', 'american dream', 'love'], - awards: { none: true } + } + + async function setupBookDocs(): Promise> { + const bookDocs: { [id: string]: DocumentData } = { + book1: { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } }, - book10: { - title: 'Dune', - author: 'Frank Herbert', - genre: 'Science Fiction', - published: 1965, - rating: 4.6, - tags: ['politics', 'desert', 'ecology'], - awards: { hugo: true, nebula: true } - } - }; - return testCollectionWithDocs(bookDocs); - } + nestedField: { 'level.1': { 'level.2': true } }, + embedding: vector([10, 1, 1, 1, 1, 1, 1, 1, 1, 1]) + }, + book2: { + title: 'Pride and Prejudice', + author: 'Jane Austen', + genre: 'Romance', + published: 1813, + rating: 4.5, + tags: ['classic', 'social commentary', 'love'], + awards: { none: true }, + embedding: vector([1, 10, 1, 1, 1, 1, 1, 1, 1, 1]) + }, + book3: { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + genre: 'Magical Realism', + published: 1967, + rating: 4.3, + tags: ['family', 'history', 'fantasy'], + awards: { nobel: true, nebula: false }, + embedding: vector([1, 1, 10, 1, 1, 1, 1, 1, 1, 1]) + }, + book4: { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: { hugo: false, nebula: false }, + remarks: null, + cost: NaN, + embedding: vector([1, 1, 1, 10, 1, 1, 1, 1, 1, 1]) + }, + book5: { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + genre: 'Dystopian', + published: 1985, + rating: 4.1, + tags: ['feminism', 'totalitarianism', 'resistance'], + awards: { 'arthur c. clarke': true, 'booker prize': false }, + embedding: vector([1, 1, 1, 1, 10, 1, 1, 1, 1, 1]) + }, + book6: { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + genre: 'Psychological Thriller', + published: 1866, + rating: 4.3, + tags: ['philosophy', 'crime', 'redemption'], + awards: { none: true }, + embedding: vector([1, 1, 1, 1, 1, 10, 1, 1, 1, 1]) + }, + book7: { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + genre: 'Southern Gothic', + published: 1960, + rating: 4.2, + tags: ['racism', 'injustice', 'coming-of-age'], + awards: { pulitzer: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 10, 1, 1, 1]) + }, + book8: { + title: '1984', + author: 'George Orwell', + genre: 'Dystopian', + published: 1949, + rating: 4.2, + tags: ['surveillance', 'totalitarianism', 'propaganda'], + awards: { prometheus: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 10, 1, 1]) + }, + book9: { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + genre: 'Modernist', + published: 1925, + rating: 4.0, + tags: ['wealth', 'american dream', 'love'], + awards: { none: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 1, 10, 1]) + }, + book10: { + title: 'Dune', + author: 'Frank Herbert', + genre: 'Science Fiction', + published: 1965, + rating: 4.6, + tags: ['politics', 'desert', 'ecology'], + awards: { hugo: true, nebula: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 1, 1, 10]) + } + }; + return testCollectionWithDocs(bookDocs); + } + + let testDeferred: Deferred | undefined; + let withTestCollectionPromise: Promise | undefined; + + beforeEach(async () => { + const setupDeferred = new Deferred(); + testDeferred = new Deferred(); + withTestCollectionPromise = withTestCollection( + persistence, + {}, + async (collectionRef, firestoreInstance) => { + randomCol = collectionRef; + firestore = firestoreInstance; + await setupBookDocs(); + setupDeferred.resolve(); + + return testDeferred?.promise; + } + ); - let testDeferred: Deferred | undefined; - let withTestCollectionPromise: Promise | undefined; - - beforeEach(async () => { - const setupDeferred = new Deferred(); - testDeferred = new Deferred(); - withTestCollectionPromise = withTestCollection( - persistence, - {}, - async (collectionRef, firestoreInstance) => { - randomCol = collectionRef; - firestore = firestoreInstance; - await setupBookDocs(); - setupDeferred.resolve(); - - return testDeferred?.promise; - } + await setupDeferred.promise; + }); + + afterEach(async () => { + testDeferred?.resolve(); + await withTestCollectionPromise; + }); + + describe('pipeline results', () => { + it('empty snapshot as expected', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol.path).limit(0) + ); + expect(snapshot.results.length).to.equal(0); + }); + + // Skipping because __name__ is not currently working in DBE + itIf(testUnsupportedFeatures)('full snapshot as expected', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('__name__')); + const snapshot = await execute(ppl); + expect(snapshot.results.length).to.equal(10); + expect(snapshot.pipeline).to.equal(ppl); + expectResults( + snapshot, + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9' + ); + }); + + it('result equals works', async () => { + const ppl = firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('title')) + .limit(1); + const snapshot1 = await execute(ppl); + const snapshot2 = await execute(ppl); + expect(snapshot1.results.length).to.equal(1); + expect(snapshot2.results.length).to.equal(1); + expect(pipelineResultEqual(snapshot1.results[0], snapshot2.results[0])).to + .be.true; + }); + + it('returns execution time', async () => { + const start = new Date().valueOf(); + const pipeline = firestore.pipeline().collection(randomCol.path); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS + ); + }); + + it('returns execution time for an empty query', async () => { + const start = new Date().valueOf(); + const pipeline = firestore.pipeline().collection(randomCol.path).limit(0); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.results.length).to.equal(0); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS + ); + }); + + it('returns create and update time for each document', async () => { + const pipeline = firestore.pipeline().collection(randomCol.path); + + let snapshot = await execute(pipeline); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(doc => { + expect(doc.createTime).to.not.be.null; + expect(doc.updateTime).to.not.be.null; + + expect(doc.createTime!.toDate().valueOf()).to.approximately( + (beginDocCreation + endDocCreation) / 2, + timestampDeltaMS + ); + expect(doc.updateTime!.toDate().valueOf()).to.approximately( + (beginDocCreation + endDocCreation) / 2, + timestampDeltaMS + ); + expect(doc.createTime?.valueOf()).to.equal(doc.updateTime?.valueOf()); + }); + + const wb = writeBatch(firestore); + snapshot.results.forEach(doc => { + wb.update(doc.ref!, { newField: 'value' }); + }); + await wb.commit(); + + snapshot = await execute(pipeline); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(doc => { + expect(doc.createTime).to.not.be.null; + expect(doc.updateTime).to.not.be.null; + expect(doc.createTime!.toDate().valueOf()).to.be.lessThan( + doc.updateTime!.toDate().valueOf() + ); + }); + }); + + it('returns execution time for an aggregate query', async () => { + const start = new Date().valueOf(); + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .aggregate(avg('rating').as('avgRating')); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.results.length).to.equal(1); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + timestampDeltaMS ); + }); + + it('returns undefined create and update time for each result in an aggregate query', async () => { + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [avg('rating').as('avgRating')], + groups: ['genre'] + }); + + const snapshot = await execute(pipeline); - await setupDeferred.promise; + expect(snapshot.results.length).to.equal(8); + + snapshot.results.forEach(doc => { + expect(doc.updateTime).to.be.undefined; + expect(doc.createTime).to.be.undefined; + }); }); + }); - afterEach(async () => { - testDeferred?.resolve(); - await withTestCollectionPromise; + describe('pipeline sources', () => { + it('supports CollectionReference as source', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol) + ); + expect(snapshot.results.length).to.equal(10); }); - describe('fluent API', () => { - it('empty results as expected', async () => { - const result = await firestore + it('supports list of documents as source', async () => { + const collName = randomCol.id; + + const snapshot = await execute( + firestore .pipeline() - .collection(randomCol.path) - .limit(0) - .execute(); - expect(result.length).to.equal(0); + .documents([ + `${collName}/book1`, + doc(randomCol, 'book2'), + doc(randomCol, 'book3').path + ]) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('reject CollectionReference for another DB', async () => { + const db2 = getFirestore(firestore.app, 'notDefault'); + + expect(() => { + firestore.pipeline().collection(collection(db2, 'foo')); + }).to.throw(/Invalid CollectionReference/); + + await terminate(db2); + }); + + it('reject DocumentReference for another DB', async () => { + const db2 = getFirestore(firestore.app, 'notDefault'); + + expect(() => { + firestore.pipeline().documents([doc(db2, 'foo/bar')]); + }).to.throw(/Invalid DocumentReference/); + + 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); + } + ); + + // subcollections not currently supported in dbe + itIf(testUnsupportedFeatures)('supports database as source', async () => { + const randomId = Math.random().toString(16).slice(2); + const doc1 = await addDoc(collection(randomCol, 'book1', 'sub'), { + order: 1, + randomId }); + const doc2 = await addDoc(collection(randomCol, 'book2', 'sub'), { + order: 2, + randomId + }); + const snapshot = await execute( + firestore + .pipeline() + .database() + .where(eq('randomId', randomId)) + .sort(ascending('order')) + ); + expectResults(snapshot, doc1.id, doc2.id); + }); + }); + + describe('supported data types', () => { + it('accepts and returns all data types', async () => { + const refDate = new Date(); + const refTimestamp = Timestamp.now(); + const constants = [ + constant(1).as('number'), + constant('a string').as('string'), + constant(true).as('boolean'), + constant(null).as('null'), + constant(new GeoPoint(0.1, 0.2)).as('geoPoint'), + constant(refTimestamp).as('timestamp'), + constant(refDate).as('date'), + constant( + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) + ).as('bytes'), + constant(doc(firestore, 'foo', 'bar')).as('documentReference'), + constantVector(vector([1, 2, 3])).as('vectorValue'), + constantVector([1, 2, 3]).as('vectorValue2'), + map({ + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': refDate, + 'uint8Array': Bytes.fromUint8Array( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) + ), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 2, + 'string': 'b string' + }, + 'array': [1, 'c string'] + }).as('map'), + array([ + 1, + 'a string', + true, + null, + new GeoPoint(0.1, 0.2), + refTimestamp, + refDate, + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + doc(firestore, 'foo', 'bar'), + vector([1, 2, 3]), + { + 'number': 2, + 'string': 'b string' + } + ]).as('array') + ]; - it('full results as expected', async () => { - const result = await firestore + const snapshots = await execute( + firestore .pipeline() .collection(randomCol.path) - .execute(); - expect(result.length).to.equal(10); + .limit(1) + .select(constants[0], ...constants.slice(1)) + ); + + expectResults(snapshots, { + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': Timestamp.fromDate(refDate), + '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', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': Timestamp.fromDate(refDate), + 'uint8Array': Bytes.fromUint8Array( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) + ), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 2, + 'string': 'b string' + }, + 'array': [1, 'c string'] + }, + 'array': [ + 1, + 'a string', + true, + null, + new GeoPoint(0.1, 0.2), + refTimestamp, + Timestamp.fromDate(refDate), + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + doc(firestore, 'foo', 'bar'), + vector([1, 2, 3]), + { + 'number': 2, + 'string': 'b string' + } + ] }); + }); + + it('throws on undefined in a map', async () => { + expect(() => { + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + map({ + 'number': 1, + undefined + }).as('foo') + ); + }).to.throw( + 'Function map() called with invalid data. Unsupported field value: undefined' + ); + }); - it('returns aggregate results as expected', async () => { - let result = await firestore + it('throws on undefined in an array', async () => { + expect(() => { + firestore .pipeline() .collection(randomCol.path) - .aggregate(countAll().as('count')) - .execute(); - expectResults(result, { count: 10 }); + .limit(1) + .select(array([1, undefined]).as('foo')); + }).to.throw( + 'Function array() called with invalid data. Unsupported field value: undefined' + ); + }); - result = await randomCol + it('converts arrays and plain objects to functionValues if the customer intent is unspecified', async () => { + const snapshot = await execute( + firestore .pipeline() - .where(eq('genre', 'Science Fiction')) - .aggregate( - countAll().as('count'), - avgFunction('rating').as('avgRating'), - Field.of('rating').maximum().as('maxRating') + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + 'title', + 'author', + 'genre', + 'rating', + 'published', + 'tags', + 'awards' + ) + .addFields( + array([ + 1, + 2, + field('genre'), + multiply('rating', 10), + [field('title')], + { + published: field('published') + } + ]).as('metadataArray'), + map({ + genre: field('genre'), + rating: multiply('rating', 10), + nestedArray: [field('title')], + nestedMap: { + published: field('published') + } + }).as('metadata') ) - .execute(); - expectResults(result, { count: 2, avgRating: 4.4, maxRating: 4.6 }); + .where( + and( + eq('metadataArray', [ + 1, + 2, + field('genre'), + multiply('rating', 10), + [field('title')], + { + published: field('published') + } + ]), + eq('metadata', { + genre: field('genre'), + rating: multiply('rating', 10), + nestedArray: [field('title')], + nestedMap: { + published: field('published') + } + }) + ) + ) + ); + + expect(snapshot.results.length).to.equal(1); + + expectResults(snapshot, { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: { hugo: false, nebula: false }, + metadataArray: [ + 1, + 2, + 'Fantasy', + 47, + ['The Lord of the Rings'], + { + published: 1954 + } + ], + metadata: { + genre: 'Fantasy', + rating: 47, + nestedArray: ['The Lord of the Rings'], + nestedMap: { + published: 1954 + } + } + }); + }); + }); + + describe('stages', () => { + describe('aggregate stage', () => { + it('supports aggregate', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countAll().as('count')) + ); + expectResults(snapshot, { count: 10 }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('genre', 'Science Fiction')) + .aggregate( + countAll().as('count'), + avg('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( - randomCol + execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(lt('published', 1900)) + .aggregate({ + accumulators: [], + groups: ['genre'] + }) + ) + ).to.be.rejected; + }); + + it('returns group and accumulate results', async () => { + const snapshot = await execute( + firestore .pipeline() - .where(lt('published', 1900)) + .collection(randomCol.path) + .where(lt(field('published'), 1984)) .aggregate({ - accumulators: [], + accumulators: [avg('rating').as('avgRating')], groups: ['genre'] }) - .execute() - ).to.be.rejected; + .where(gt('avgRating', 4.3)) + .sort(field('avgRating').descending()) + ); + expectResults( + snapshot, + { avgRating: 4.7, genre: 'Fantasy' }, + { avgRating: 4.5, genre: 'Romance' }, + { avgRating: 4.4, genre: 'Science Fiction' } + ); + }); + + it('returns min, max, count, and countAll accumulations', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + count('cost').as('booksWithCost'), + countAll().as('count'), + maximum('rating').as('maxRating'), + minimum('published').as('minPublished') + ) + ); + expectResults(snapshot, { + booksWithCost: 1, + count: 10, + maxRating: 4.7, + minPublished: 1813 + }); + }); + + it('returns countif accumulation', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countIf(field('rating').gt(4.3)).as('count')) + ); + const expectedResults = { + count: 3 + }; + expectResults(snapshot, expectedResults); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(field('rating').gt(4.3).countIf().as('count')) + ); + expectResults(snapshot, expectedResults); }); + }); + describe('distinct stage', () => { it('returns distinct values as expected', async () => { - const results = await randomCol - .pipeline() - .distinct('genre', 'author') - .sort(Field.of('genre').ascending(), Field.of('author').ascending()) - .execute(); + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort(field('genre').ascending(), field('author').ascending()) + ); expectResults( - results, + snapshot, { genre: 'Dystopian', author: 'George Orwell' }, { genre: 'Dystopian', author: 'Margaret Atwood' }, { genre: 'Fantasy', author: 'J.R.R. Tolkien' }, @@ -336,51 +938,19 @@ apiDescribe.only('Pipelines', persistence => { { genre: 'Southern Gothic', author: 'Harper Lee' } ); }); + }); - it('returns group and accumulate results', async () => { - const results = await randomCol - .pipeline() - .where(lt(Field.of('published'), 1984)) - .aggregate({ - accumulators: [avgFunction('rating').as('avgRating')], - groups: ['genre'] - }) - .where(gt('avgRating', 4.3)) - .sort(Field.of('avgRating').descending()) - .execute(); - expectResults( - results, - { avgRating: 4.7, genre: 'Fantasy' }, - { avgRating: 4.5, genre: 'Romance' }, - { avgRating: 4.4, genre: 'Science Fiction' } - ); - }); - - it('returns min and max accumulations', async () => { - const results = await randomCol - .pipeline() - .aggregate( - countAll().as('count'), - Field.of('rating').maximum().as('maxRating'), - Field.of('published').minimum().as('minPublished') - ) - .execute(); - expectResults(results, { - count: 10, - maxRating: 4.7, - minPublished: 1813 - }); - }); - + describe('select stage', () => { it('can select fields', async () => { - const results = await firestore - .pipeline() - .collection(randomCol.path) - .select('title', 'author') - .sort(Field.of('author').ascending()) - .execute(); + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + ); expectResults( - results, + snapshot, { title: "The Hitchhiker's Guide to the Galaxy", author: 'Douglas Adams' @@ -399,1331 +969,2116 @@ apiDescribe.only('Pipelines', persistence => { { title: "The Handmaid's Tale", author: 'Margaret Atwood' } ); }); + }); - it('where with and', async () => { - const results = await randomCol - .pipeline() - .where(andFunction(gt('rating', 4.5), eq('genre', 'Science Fiction'))) - .execute(); - expectResults(results, 'book10'); - }); - - it('where with or', async () => { - const results = await randomCol - .pipeline() - .where(orFunction(eq('genre', 'Romance'), eq('genre', 'Dystopian'))) - .select('title') - .execute(); + describe('addField stage', () => { + it('can add fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields(constant('bar').as('foo')) + .sort(field('author').ascending()) + ); expectResults( - results, - { title: 'Pride and Prejudice' }, - { title: "The Handmaid's Tale" }, - { title: '1984' } + 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' + } ); }); + }); - it('offset and limits', async () => { - const results = await firestore - .pipeline() - .collection(randomCol.path) - .sort(Field.of('author').ascending()) - .offset(5) - .limit(3) - .select('title', 'author') - .execute(); + describe('removeFields stage', () => { + it('can remove fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + .sort(field('author').ascending()) + ); expectResults( - results, - { title: '1984', author: 'George Orwell' }, - { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, - { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' } + 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('logical min works', async () => { - const results = await randomCol - .pipeline() - .select( - 'title', - logicalMinimum(Constant.of(1960), Field.of('published')).as( - 'published-safe' + describe('where stage', () => { + it('where with and (2 conditions)', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + gt('rating', 4.5), + eqAny('genre', ['Science Fiction', 'Romance', 'Fantasy']) + ) ) - ) - .sort(Field.of('title').ascending()) - .limit(3) - .execute(); - expectResults( - results, - { title: '1984', 'published-safe': 1949 }, - { title: 'Crime and Punishment', 'published-safe': 1866 }, - { title: 'Dune', 'published-safe': 1960 } ); + expectResults(snapshot, 'book10', 'book4'); }); - - it('logical max works', async () => { - const results = await randomCol - .pipeline() - .select( - 'title', - logicalMaximum(Constant.of(1960), Field.of('published')).as( - 'published-safe' + it('where with and (3 conditions)', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + gt('rating', 4.5), + eqAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), + lt('published', 1965) + ) ) - ) - .sort(Field.of('title').ascending()) - .limit(3) - .execute(); - expectResults( - results, - { title: '1984', 'published-safe': 1960 }, - { title: 'Crime and Punishment', 'published-safe': 1960 }, - { title: 'Dune', 'published-safe': 1965 } - ); - }); - - it('accepts and returns all data types', async () => { - const refDate = new Date(); - const refTimestamp = Timestamp.now(); - const constants = [ - Constant.of(1).as('number'), - Constant.of('a string').as('string'), - Constant.of(true).as('boolean'), - Constant.of(null).as('null'), - Constant.of(new GeoPoint(0.1, 0.2)).as('geoPoint'), - Constant.of(refTimestamp).as('timestamp'), - Constant.of(refDate).as('date'), - Constant.of( - Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) - ).as('bytes'), - Constant.of(doc(firestore, 'foo', 'bar')).as('documentReference'), - Constant.of(vector([1, 2, 3])).as('vectorValue'), - Constant.of({ - 'number': 1, - 'string': 'a string', - 'boolean': true, - 'null': null, - 'geoPoint': new GeoPoint(0.1, 0.2), - 'timestamp': refTimestamp, - 'date': refDate, - 'uint8Array': Bytes.fromUint8Array( - new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) - ), - 'documentReference': doc(firestore, 'foo', 'bar'), - 'vectorValue': vector([1, 2, 3]), - 'map': { - 'number': 2, - 'string': 'b string' - }, - 'array': [1, 'c string'] - }).as('map'), - Constant.of([ - 1, - 'a string', - true, - null, - new GeoPoint(0.1, 0.2), - refTimestamp, - refDate, - Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), - doc(firestore, 'foo', 'bar'), - vector([1, 2, 3]), - { - 'number': 2, - 'string': 'b string' - } - ]).as('array') - ]; - - const results = await randomCol - .pipeline() - .limit(1) - .select(...constants) - .execute(); - - expectResults(results, { - 'number': 1, - 'string': 'a string', - 'boolean': true, - 'null': null, - 'geoPoint': new GeoPoint(0.1, 0.2), - 'timestamp': refTimestamp, - 'date': Timestamp.fromDate(refDate), - 'bytes': Bytes.fromUint8Array( - new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) - ), - 'documentReference': doc(firestore, 'foo', 'bar'), - 'vectorValue': vector([1, 2, 3]), - 'map': { - 'number': 1, - 'string': 'a string', - 'boolean': true, - 'null': null, - 'geoPoint': new GeoPoint(0.1, 0.2), - 'timestamp': refTimestamp, - 'date': Timestamp.fromDate(refDate), - 'uint8Array': Bytes.fromUint8Array( - new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) - ), - 'documentReference': doc(firestore, 'foo', 'bar'), - 'vectorValue': vector([1, 2, 3]), - 'map': { - 'number': 2, - 'string': 'b string' - }, - 'array': [1, 'c string'] - }, - 'array': [ - 1, - 'a string', - true, - null, - new GeoPoint(0.1, 0.2), - refTimestamp, - Timestamp.fromDate(refDate), - Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), - doc(firestore, 'foo', 'bar'), - vector([1, 2, 3]), - { - 'number': 2, - 'string': 'b string' - } - ] - }); + ); + expectResults(snapshot, 'book4'); }); - - it('cond works', async () => { - const results = await randomCol - .pipeline() - .select( - 'title', - cond( - lt(Field.of('published'), 1960), - Constant.of(1960), - Field.of('published') - ).as('published-safe') - ) - .sort(Field.of('title').ascending()) - .limit(3) - .execute(); + it('where with or', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + eq('genre', 'Romance'), + eq('genre', 'Dystopian'), + eq('genre', 'Fantasy') + ) + ) + .sort(ascending('title')) + .select('title') + ); expectResults( - results, - { title: '1984', 'published-safe': 1960 }, - { title: 'Crime and Punishment', 'published-safe': 1960 }, - { title: 'Dune', 'published-safe': 1965 } + snapshot, + { title: '1984' }, + { title: 'Pride and Prejudice' }, + { title: "The Handmaid's Tale" }, + { title: 'The Lord of the Rings' } ); }); - it('eqAny works', async () => { - const results = await randomCol - .pipeline() - .where(eqAny('published', [1979, 1999, 1967])) - .select('title') - .execute(); + it('where with xor', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + xor( + eq('genre', 'Romance'), + eq('genre', 'Dystopian'), + eq('genre', 'Fantasy'), + eq('published', 1949) + ) + ) + .select('title') + ); expectResults( - results, - { title: "The Hitchhiker's Guide to the Galaxy" }, - { title: 'One Hundred Years of Solitude' } + snapshot, + { title: 'Pride and Prejudice' }, + { title: 'The Lord of the Rings' }, + { title: "The Handmaid's Tale" } ); }); + }); - it('notEqAny works', async () => { - const results = await randomCol - .pipeline() - .where( - notEqAny( - 'published', - [1965, 1925, 1949, 1960, 1866, 1985, 1954, 1967, 1979] - ) - ) - .select('title') - .execute(); - expectResults(results, { title: 'Pride and Prejudice' }); + describe('sort, offset, and limit stages', () => { + it('supports sort, offset, and limits', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .offset(5) + .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' } + ); }); + }); - it('arrayContains works', async () => { - const results = await randomCol - .pipeline() - .where(arrayContains('tags', 'comedy')) - .select('title') - .execute(); - expectResults(results, { - title: "The Hitchhiker's Guide to the Galaxy" + describe('generic stage', () => { + it('can select fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .genericStage('select', [ + { + title: field('title'), + metadata: { + 'author': field('author') + } + } + ]) + .sort(field('author').ascending()) + .limit(1) + ); + expectResults(snapshot, { + metadata: { + author: 'Frank Herbert' + }, + title: 'Dune' }); }); - it('arrayContainsAny works', async () => { - const results = await randomCol - .pipeline() - .where(arrayContainsAny('tags', ['comedy', 'classic'])) - .select('title') - .execute(); - expectResults( - results, - { title: "The Hitchhiker's Guide to the Galaxy" }, - { title: 'Pride and Prejudice' } + it('can add fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .limit(1) + .select('title', 'author') + .genericStage('add_fields', [ + { + display: strConcat('title', ' - ', field('author')) + } + ]) ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + display: "The Hitchhiker's Guide to the Galaxy - Douglas Adams" + }); }); - it('arrayContainsAll works', async () => { - const results = await randomCol - .pipeline() - .where(Field.of('tags').arrayContainsAll('adventure', 'magic')) - .select('title') - .execute(); - expectResults(results, { title: 'The Lord of the Rings' }); - }); - - it('arrayLength works', async () => { - const results = await randomCol - .pipeline() - .select(Field.of('tags').arrayLength().as('tagsCount')) - .where(eq('tagsCount', 3)) - .execute(); - expect(results.length).to.equal(10); - }); - - // skip: arrayConcat not supported - // it.skip('arrayConcat works', async () => { - // const results = await randomCol - // .pipeline() - // .select( - // Field.of('tags').arrayConcat(['newTag1', 'newTag2']).as('modifiedTags') - // ) - // .limit(1) - // .execute(); - // expectResults(results, { - // modifiedTags: ['comedy', 'space', 'adventure', 'newTag1', 'newTag2'] - // }); - // }); - - it('testStrConcat', async () => { - const results = await randomCol - .pipeline() - .select( - Field.of('author') - .strConcat(' - ', Field.of('title')) - .as('bookInfo') - ) - .limit(1) - .execute(); - expectResults(results, { - bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy" + it('can filter with where', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .genericStage('where', [field('author').eq('Douglas Adams')]) + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams' }); }); - it('testStartsWith', async () => { - const results = await randomCol - .pipeline() - .where(startsWith('title', 'The')) - .select('title') - .sort(Field.of('title').ascending()) - .execute(); - expectResults( - results, - { title: 'The Great Gatsby' }, - { title: "The Handmaid's Tale" }, - { title: "The Hitchhiker's Guide to the Galaxy" }, - { title: 'The Lord of the Rings' } + it('can limit, offset, and sort', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .genericStage('sort', [ + { + direction: 'ascending', + expression: field('author') + } + ]) + .genericStage('offset', [3]) + .genericStage('limit', [1]) ); + expectResults(snapshot, { + author: 'Fyodor Dostoevsky', + title: 'Crime and Punishment' + }); }); - it('testEndsWith', async () => { - const results = await randomCol - .pipeline() - .where(endsWith('title', 'y')) - .select('title') - .sort(Field.of('title').descending()) - .execute(); - expectResults( - results, - { title: "The Hitchhiker's Guide to the Galaxy" }, - { title: 'The Great Gatsby' } + it('can perform aggregate query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .genericStage('aggregate', [ + { averageRating: field('rating').avg() }, + {} + ]) ); + expectResults(snapshot, { + averageRating: 4.3100000000000005 + }); }); - it('testLength', async () => { - const results = await randomCol - .pipeline() - .select( - Field.of('title').charLength().as('titleLength'), - Field.of('title') - ) - .where(gt('titleLength', 20)) - .sort(Field.of('title').ascending()) - .execute(); - + it('can perform distinct query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .genericStage('distinct', [{ rating: field('rating') }]) + .sort(field('rating').descending()) + ); expectResults( - results, - + snapshot, { - titleLength: 29, - title: 'One Hundred Years of Solitude' + rating: 4.7 }, { - titleLength: 36, - title: "The Hitchhiker's Guide to the Galaxy" + rating: 4.6 }, { - titleLength: 21, - title: 'The Lord of the Rings' + rating: 4.5 }, { - titleLength: 21, - title: 'To Kill a Mockingbird' + rating: 4.3 + }, + { + rating: 4.2 + }, + { + rating: 4.1 + }, + { + rating: 4.0 } ); }); + }); - // skip: toLower not supported - // it.skip('testToLowercase', async () => { - // const results = await randomCol - // .pipeline() - // .select(Field.of('title').toLower().as('lowercaseTitle')) - // .limit(1) - // .execute(); - // expectResults(results, { - // lowercaseTitle: "the hitchhiker's guide to the galaxy" - // }); - // }); - - // skip: toUpper not supported - // it.skip('testToUppercase', async () => { - // const results = await randomCol - // .pipeline() - // .select(Field.of('author').toUpper().as('uppercaseAuthor')) - // .limit(1) - // .execute(); - // expectResults(results, { uppercaseAuthor: 'DOUGLAS ADAMS' }); - // }); - - // skip: trim not supported - // it.skip('testTrim', async () => { - // const results = await randomCol - // .pipeline() - // .addFields(strConcat(' ', Field.of('title'), ' ').as('spacedTitle')) - // .select( - // Field.of('spacedTitle').trim().as('trimmedTitle'), - // Field.of('spacedTitle') - // ) - // .limit(1) - // .execute(); - // expectResults(results, { - // spacedTitle: " The Hitchhiker's Guide to the Galaxy ", - // trimmedTitle: "The Hitchhiker's Guide to the Galaxy" - // }); - // }); - - it('testLike', async () => { - const results = await randomCol - .pipeline() - .where(like('title', '%Guide%')) - .select('title') - .execute(); - expectResults(results, { - title: "The Hitchhiker's Guide to the Galaxy" + describe('replaceWith stage', () => { + it('run pipeline with replaceWith field name', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith('awards') + ); + expectResults(snapshot, { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } }); }); - it('testRegexContains', async () => { - const results = await randomCol - .pipeline() - .where(regexContains('title', '(?i)(the|of)')) - .execute(); - expect(results.length).to.equal(5); + it('run pipeline with replaceWith Expr result', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith( + map({ + foo: 'bar', + baz: { + title: field('title') + } + }) + ) + ); + expectResults(snapshot, { + foo: 'bar', + baz: { title: "The Hitchhiker's Guide to the Galaxy" } + }); }); + }); - it('testRegexMatches', async () => { - const results = await randomCol - .pipeline() - .where(regexMatch('title', '.*(?i)(the|of).*')) - .execute(); - expect(results.length).to.equal(5); + describe('sample stage', () => { + it('run pipeline with sample limit of 3', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol.path).sample(3) + ); + expect(snapshot.results.length).to.equal(3); }); - it('testArithmeticOperations', async () => { - const results = await randomCol - .pipeline() - .select( - add(Field.of('rating'), 1).as('ratingPlusOne'), - subtract(Field.of('published'), 1900).as('yearsSince1900'), - Field.of('rating').multiply(10).as('ratingTimesTen'), - Field.of('rating').divide(2).as('ratingDividedByTwo') - ) - .limit(1) - .execute(); - expectResults(results, { - ratingPlusOne: 5.2, - yearsSince1900: 79, - ratingTimesTen: 42, - ratingDividedByTwo: 2.1 - }); + it('run pipeline with sample limit of {documents: 3}', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sample({ documents: 3 }) + ); + expect(snapshot.results.length).to.equal(3); }); - it('testComparisonOperators', async () => { - const results = await randomCol - .pipeline() - .where( - andFunction( - gt('rating', 4.2), - lte(Field.of('rating'), 4.5), - neq('genre', 'Science Fiction') - ) - ) - .select('rating', 'title') - .sort(Field.of('title').ascending()) - .execute(); - expectResults( - results, - { rating: 4.3, title: 'Crime and Punishment' }, - { - rating: 4.3, - title: 'One Hundred Years of Solitude' - }, - { rating: 4.5, title: 'Pride and Prejudice' } - ); + it('run pipeline with sample limit of {percentage: 0.6}', async () => { + let avgSize = 0; + const numIterations = 20; + for (let i = 0; i < numIterations; i++) { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sample({ percentage: 0.6 }) + ); + + avgSize += snapshot.results.length; + } + avgSize /= numIterations; + expect(avgSize).to.be.closeTo(6, 1); }); + }); - it('testLogicalOperators', async () => { - const results = await randomCol - .pipeline() - .where( - orFunction( - andFunction(gt('rating', 4.5), eq('genre', 'Science Fiction')), - lt('published', 1900) - ) - ) - .select('title') - .sort(Field.of('title').ascending()) - .execute(); + describe('union stage', () => { + // __name__ not currently supported by dbe + itIf(testUnsupportedFeatures)('run pipeline with union', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .union(firestore.pipeline().collection(randomCol.path)) + .sort(field(documentIdFieldPath()).ascending()) + ); expectResults( - results, - { title: 'Crime and Punishment' }, - { title: 'Dune' }, - { title: 'Pride and Prejudice' } + snapshot, + 'book1', + 'book1', + 'book10', + 'book10', + 'book2', + 'book2', + 'book3', + 'book3', + 'book4', + 'book4', + 'book5', + 'book5', + 'book6', + 'book6', + 'book7', + 'book7', + 'book8', + 'book8', + 'book9', + 'book9' ); }); + }); - it('testChecks', async () => { - const results = await randomCol - .pipeline() - .where(not(Field.of('rating').isNaN())) - .select( - Field.of('rating').eq(null).as('ratingIsNull'), - not(Field.of('rating').isNaN()).as('ratingIsNotNaN') - ) - .limit(1) - .execute(); - expectResults(results, { ratingIsNull: false, ratingIsNotNaN: true }); - }); - - it('testMapGet', async () => { - const results = await randomCol - .pipeline() - .select( - Field.of('awards').mapGet('hugo').as('hugoAward'), - Field.of('awards').mapGet('others').as('others'), - Field.of('title') - ) - .where(eq('hugoAward', true)) - .execute(); + describe('unnest stage', () => { + it('run pipeline with unnest', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(field('tags').as('tag')) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + 'awards', + 'nestedField' + ) + ); expectResults( - results, + snapshot, { - hugoAward: true, title: "The Hitchhiker's Guide to the Galaxy", - others: { unknown: { year: 1980 } } + 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 } } }, - { hugoAward: true, title: 'Dune', others: null } - ); - }); - - // it('testParent', async () => { - // const results = await randomCol - // .pipeline() - // .select( - // parent(randomCol.doc('chile').collection('subCollection').path).as( - // 'parent' - // ) - // ) - // .limit(1) - // .execute(); - // expect(results[0].data().parent.endsWith('/books')).to.be.true; - // }); - // - // it('testCollectionId', async () => { - // const results = await randomCol - // .pipeline() - // .select(collectionId(randomCol.doc('chile')).as('collectionId')) - // .limit(1) - // .execute(); - // expectResults(results, {collectionId: 'books'}); - // }); - - it('testDistanceFunctions', async () => { - const sourceVector = [0.1, 0.1]; - const targetVector = [0.5, 0.8]; - const results = await randomCol - .pipeline() - .select( - cosineDistance(Constant.vector(sourceVector), targetVector).as( - 'cosineDistance' - ), - dotProduct(Constant.vector(sourceVector), targetVector).as( - 'dotProductDistance' - ), - euclideanDistance(Constant.vector(sourceVector), targetVector).as( - 'euclideanDistance' - ) - ) - .limit(1) - .execute(); - - expectResults(results, { - cosineDistance: 0.02560880430538015, - dotProductDistance: 0.13, - euclideanDistance: 0.806225774829855 - }); - }); - - it('testNestedFields', async () => { - const results = await randomCol - .pipeline() - .where(eq('awards.hugo', true)) - .select('title', 'awards.hugo') - .execute(); - expectResults( - results, { title: "The Hitchhiker's Guide to the Galaxy", - 'awards.hugo': true + 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 } } }, - { title: 'Dune', 'awards.hugo': true } + { + 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 } } + } ); }); - - it('test mapGet with field name including . notation', async () => { - const results = await randomCol - .pipeline() - .where(eq('awards.hugo', true)) - .select( - 'title', - Field.of('nestedField.level.1'), - mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') - ) - .execute(); + it('unnest an expr', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('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( - results, + 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", - 'nestedField.level.`1`': null, - nested: true + 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: 'Dune', 'nestedField.level.`1`': null, nested: null } + { + 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 internal serialization to proto', async () => { - const pipeline = firestore - .pipeline() - .collection('books') - .where(eq('awards.hugo', true)) - .select( - 'title', - Field.of('nestedField.level.1'), - mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') - ); - - const proto = _internalPipelineToExecutePipelineRequestProto(pipeline); - expect(proto).not.to.be.null; - }); - - describe('pagination', () => { - async function addBooks( - collection: CollectionReference - ): Promise { - await setDoc(doc(randomCol, 'book11'), { - title: 'Jonathan Strange & Mr Norrell', - author: 'Susanna Clarke', - genre: 'Fantasy', - published: 2004, - rating: 4.6, - tags: [ - 'historical fantasy', - 'magic', - 'alternate history', - 'england' - ], - awards: { hugo: false, nebula: false } - }); - await setDoc(doc(randomCol, 'book12'), { - title: 'The Master and Margarita', - author: 'Mikhail Bulgakov', - genre: 'Satire', - published: 1967, // Though written much earlier - rating: 4.6, - tags: [ - 'russian literature', - 'supernatural', - 'philosophy', - 'dark comedy' - ], - awards: {} - }); - await setDoc(doc(randomCol, 'book13'), { - title: 'A Long Way to a Small, Angry Planet', - author: 'Becky Chambers', - genre: 'Science Fiction', - published: 2014, - rating: 4.6, - tags: [ - 'space opera', - 'found family', - 'character-driven', - 'optimistic' - ], - awards: { hugo: false, nebula: false, kitschies: true } - }); - } - - it('supports pagination with filters', async () => { - await addBooks(randomCol); - const pageSize = 2; - const pipeline = randomCol - .pipeline() - .select('title', 'rating', '__name__') - .sort( - Field.of('rating').descending(), - Field.of('__name__').ascending() - ); - - let results = await pipeline.limit(pageSize).execute(); - expectResults( - results, - { title: 'The Lord of the Rings', rating: 4.7 }, - { title: 'Jonathan Strange & Mr Norrell', rating: 4.6 } - ); - - const lastDoc = results[results.length - 1]; - - results = await pipeline - .where( - orFunction( - andFunction( - Field.of('rating').eq(lastDoc.get('rating')), - Field.of('__path__').gt(lastDoc.ref?.path) - ), - Field.of('rating').lt(lastDoc.get('rating')) - ) - ) - .limit(pageSize) - .execute(); - expectResults( - results, - { title: 'Pride and Prejudice', rating: 4.5 }, - { title: 'Crime and Punishment', rating: 4.3 } - ); - }); - - it('supports pagination with offsets', async () => { - await addBooks(randomCol); - - const secondFilterField = '__path__'; - - const pipeline = randomCol - .pipeline() - .select('title', 'rating', secondFilterField) - .sort( - Field.of('rating').descending(), - Field.of(secondFilterField).ascending() - ); - - const pageSize = 2; - let currPage = 0; - - let results = await pipeline - .offset(currPage++ * pageSize) - .limit(pageSize) - .execute(); - - expectResults( - results, - { - title: 'The Lord of the Rings', - rating: 4.7 - }, - { title: 'Dune', rating: 4.6 } + 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') ); - - results = await pipeline - .offset(currPage++ * pageSize) - .limit(pageSize) - .execute(); expectResults( - results, + snapshot, { - title: 'Jonathan Strange & Mr Norrell', - rating: 4.6 + title: "The Hitchhiker's Guide to the Galaxy" }, - { title: 'The Master and Margarita', rating: 4.6 } - ); - - results = await pipeline - .offset(currPage++ * pageSize) - .limit(pageSize) - .execute(); - expectResults( - results, { - title: 'A Long Way to a Small, Angry Planet', - rating: 4.6 + title: 'One Hundred Years of Solitude' }, { - title: 'Pride and Prejudice', - rating: 4.5 + title: "The Handmaid's Tale" } ); - }); + } }); - }); - describe('modular API', () => { - it('works when creating a pipeline from a Firestore instance', async () => { - const myPipeline = pipeline(firestore) - .collection(randomCol.path) - .where(lt(Field.of('published'), 1984)) - .aggregate({ - accumulators: [avgFunction('rating').as('avgRating')], - groups: ['genre'] - }) - .where(gt('avgRating', 4.3)) - .sort(Field.of('avgRating').descending()); - - const results = await execute(myPipeline); - - expectResults( - results, - { avgRating: 4.7, genre: 'Fantasy' }, - { avgRating: 4.5, genre: 'Romance' }, - { avgRating: 4.4, genre: 'Science Fiction' } + 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') ); - }); - - it('works when creating a pipeline from a collection', async () => { - const myPipeline = pipeline(randomCol) - .where(lt(Field.of('published'), 1984)) - .aggregate({ - accumulators: [avgFunction('rating').as('avgRating')], - groups: ['genre'] - }) - .where(gt('avgRating', 4.3)) - .sort(Field.of('avgRating').descending()); - - const results = await execute(myPipeline); - expectResults( - results, - { avgRating: 4.7, genre: 'Fantasy' }, - { avgRating: 4.5, genre: 'Romance' }, - { avgRating: 4.4, genre: 'Science Fiction' } + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + computedDistance: 1 + }, + { + title: 'One Hundred Years of Solitude', + computedDistance: 12.041594578792296 + } ); }); }); }); - // This is the Query integration tests from the lite API (no cache support) - // with some additional test cases added for more complete coverage. - describe('Query to Pipeline', () => { - function verifyResults( - actual: Array>, - ...expected: DocumentData[] - ): void { - expect(actual.length).to.equal(expected.length); - - for (let i = 0; i < expected.length; ++i) { - expect(actual[i].data()).to.deep.equal(expected[i]); - } - } - - it('supports default query', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { 1: { foo: 1 } }, - async collRef => { - const result = await collRef.pipeline().execute(); - verifyResults(result, { foo: 1 }); - } + describe('function expressions', () => { + it('logical max works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMaximum(constant(1960), field('published'), 1961).as( + 'published-safe' + ) + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1961 }, + { title: 'Crime and Punishment', 'published-safe': 1961 }, + { title: 'Dune', 'published-safe': 1965 } ); }); - it('supports filtered query', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, where('foo', '==', 1)); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 1 }); - } + it('logical min works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMinimum(constant(1960), field('published'), 1961).as( + 'published-safe' + ) + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1949 }, + { title: 'Crime and Punishment', 'published-safe': 1866 }, + { title: 'Dune', 'published-safe': 1960 } ); }); - it('supports filtered query (with FieldPath)', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, where(new FieldPath('foo'), '==', 1)); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 1 }); - } + it('cond works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + cond( + lt(field('published'), 1960), + constant(1960), + field('published') + ).as('published-safe') + ) + .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 } ); }); - it('supports ordered query (with default order)', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, orderBy('foo')); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 1 }, { foo: 2 }); - } + it('eqAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eqAny('published', [1979, 1999, 1967])) + .sort(descending('title')) + .select('title') + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'One Hundred Years of Solitude' } ); }); - it('supports ordered query (with asc)', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, orderBy('foo', 'asc')); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 1 }, { foo: 2 }); - } + it('notEqAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + notEqAny( + 'published', + [1965, 1925, 1949, 1960, 1866, 1985, 1954, 1967, 1979] + ) + ) + .select('title') ); + expectResults(snapshot, { title: 'Pride and Prejudice' }); }); - it('supports ordered query (with desc)', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, orderBy('foo', 'desc')); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 2 }, { foo: 1 }); - } + it('arrayContains works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContains('tags', 'comedy')) + .select('title') ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy" + }); }); - it('supports limit query', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, orderBy('foo'), limit(1)); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 1 }); - } + it('arrayContainsAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAny('tags', ['comedy', 'classic'])) + .sort(descending('title')) + .select('title') + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'Pride and Prejudice' } ); }); - it('supports limitToLast query', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 }, - 3: { foo: 3 } - }, - async collRef => { - const query1 = query(collRef, orderBy('foo'), limitToLast(2)); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 2 }, { foo: 3 }); - } + it('arrayContainsAll works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAll('tags', ['adventure', 'magic'])) + .select('title') ); + expectResults(snapshot, { title: 'The Lord of the Rings' }); }); - it('supports startAt', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, orderBy('foo'), startAt(2)); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 2 }); - } + it('arrayLength works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(arrayLength('tags').as('tagsCount')) + .where(eq('tagsCount', 3)) ); + expect(snapshot.results.length).to.equal(10); }); - it('supports startAfter (with DocumentReference)', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { id: 1, foo: 1, bar: 1, baz: 1 }, - 2: { id: 2, foo: 1, bar: 1, baz: 2 }, - 3: { id: 3, foo: 1, bar: 1, baz: 2 }, - 4: { id: 4, foo: 1, bar: 2, baz: 1 }, - 5: { id: 5, foo: 1, bar: 2, baz: 2 }, - 6: { id: 6, foo: 1, bar: 2, baz: 2 }, - 7: { id: 7, foo: 2, bar: 1, baz: 1 }, - 8: { id: 8, foo: 2, bar: 1, baz: 2 }, - 9: { id: 9, foo: 2, bar: 1, baz: 2 }, - 10: { id: 10, foo: 2, bar: 2, baz: 1 }, - 11: { id: 11, foo: 2, bar: 2, baz: 2 }, - 12: { id: 12, foo: 2, bar: 2, baz: 2 } - }, - async collRef => { - let docRef = await getDoc(doc(collRef, '2')); - let query1 = query( - collRef, - orderBy('foo'), - orderBy('bar'), - orderBy('baz'), - startAfter(docRef) - ); - let result = await query1.pipeline().execute(); - verifyResults( - result, - { id: 3, foo: 1, bar: 1, baz: 2 }, - { id: 4, foo: 1, bar: 2, baz: 1 }, - { id: 5, foo: 1, bar: 2, baz: 2 }, - { id: 6, foo: 1, bar: 2, baz: 2 }, - { id: 7, foo: 2, bar: 1, baz: 1 }, - { id: 8, foo: 2, bar: 1, baz: 2 }, - { id: 9, foo: 2, bar: 1, baz: 2 }, - { id: 10, foo: 2, bar: 2, baz: 1 }, - { id: 11, foo: 2, bar: 2, baz: 2 }, - { id: 12, foo: 2, bar: 2, baz: 2 } - ); - - docRef = await getDoc(doc(collRef, '3')); - query1 = query( - collRef, - orderBy('foo'), - orderBy('bar'), - orderBy('baz'), - startAfter(docRef) - ); - result = await query1.pipeline().execute(); - verifyResults( - result, - { id: 4, foo: 1, bar: 2, baz: 1 }, - { id: 5, foo: 1, bar: 2, baz: 2 }, - { id: 6, foo: 1, bar: 2, baz: 2 }, - { id: 7, foo: 2, bar: 1, baz: 1 }, - { id: 8, foo: 2, bar: 1, baz: 2 }, - { id: 9, foo: 2, bar: 1, baz: 2 }, - { id: 10, foo: 2, bar: 2, baz: 1 }, - { id: 11, foo: 2, bar: 2, baz: 2 }, - { id: 12, foo: 2, bar: 2, baz: 2 } - ); - } + it('testStrConcat', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(ascending('author')) + .select( + field('author').strConcat(' - ', field('title')).as('bookInfo') + ) + .limit(1) ); + expectResults(snapshot, { + bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy" + }); }); - it('supports startAt (with DocumentReference)', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { id: 1, foo: 1, bar: 1, baz: 1 }, - 2: { id: 2, foo: 1, bar: 1, baz: 2 }, - 3: { id: 3, foo: 1, bar: 1, baz: 2 }, - 4: { id: 4, foo: 1, bar: 2, baz: 1 }, - 5: { id: 5, foo: 1, bar: 2, baz: 2 }, - 6: { id: 6, foo: 1, bar: 2, baz: 2 }, - 7: { id: 7, foo: 2, bar: 1, baz: 1 }, - 8: { id: 8, foo: 2, bar: 1, baz: 2 }, - 9: { id: 9, foo: 2, bar: 1, baz: 2 }, - 10: { id: 10, foo: 2, bar: 2, baz: 1 }, - 11: { id: 11, foo: 2, bar: 2, baz: 2 }, - 12: { id: 12, foo: 2, bar: 2, baz: 2 } - }, - async collRef => { - let docRef = await getDoc(doc(collRef, '2')); - let query1 = query( - collRef, - orderBy('foo'), - orderBy('bar'), - orderBy('baz'), - startAt(docRef) - ); - let result = await query1.pipeline().execute(); - verifyResults( - result, - { id: 2, foo: 1, bar: 1, baz: 2 }, - { id: 3, foo: 1, bar: 1, baz: 2 }, - { id: 4, foo: 1, bar: 2, baz: 1 }, - { id: 5, foo: 1, bar: 2, baz: 2 }, - { id: 6, foo: 1, bar: 2, baz: 2 }, - { id: 7, foo: 2, bar: 1, baz: 1 }, - { id: 8, foo: 2, bar: 1, baz: 2 }, - { id: 9, foo: 2, bar: 1, baz: 2 }, - { id: 10, foo: 2, bar: 2, baz: 1 }, - { id: 11, foo: 2, bar: 2, baz: 2 }, - { id: 12, foo: 2, bar: 2, baz: 2 } - ); - - docRef = await getDoc(doc(collRef, '3')); - query1 = query( - collRef, - orderBy('foo'), - orderBy('bar'), - orderBy('baz'), - startAt(docRef) - ); - result = await query1.pipeline().execute(); - verifyResults( - result, - { id: 3, foo: 1, bar: 1, baz: 2 }, - { id: 4, foo: 1, bar: 2, baz: 1 }, - { id: 5, foo: 1, bar: 2, baz: 2 }, - { id: 6, foo: 1, bar: 2, baz: 2 }, - { id: 7, foo: 2, bar: 1, baz: 1 }, - { id: 8, foo: 2, bar: 1, baz: 2 }, - { id: 9, foo: 2, bar: 1, baz: 2 }, - { id: 10, foo: 2, bar: 2, baz: 1 }, - { id: 11, foo: 2, bar: 2, baz: 2 }, - { id: 12, foo: 2, bar: 2, baz: 2 } - ); - } + it('testStartsWith', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(startsWith('title', 'The')) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: 'The Great Gatsby' }, + { title: "The Handmaid's Tale" }, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'The Lord of the Rings' } ); }); - it('supports startAfter', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, orderBy('foo'), startAfter(1)); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 2 }); - } + it('testEndsWith', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(endsWith('title', 'y')) + .select('title') + .sort(field('title').descending()) + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'The Great Gatsby' } ); }); - it('supports endAt', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - const query1 = query(collRef, orderBy('foo'), endAt(1)); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 1 }); - } + it('testStrContains', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(strContains('title', "'s")) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: "The Handmaid's Tale" }, + { title: "The Hitchhiker's Guide to the Galaxy" } ); }); - it('supports endBefore', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, + it('testLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(charLength('title').as('titleLength'), field('title')) + .where(gt('titleLength', 20)) + .sort(field('title').ascending()) + ); + + expectResults( + snapshot, + + { + titleLength: 29, + title: 'One Hundred Years of Solitude' + }, + { + titleLength: 36, + title: "The Hitchhiker's Guide to the Galaxy" + }, { - 1: { foo: 1 }, - 2: { foo: 2 } + titleLength: 21, + title: 'The Lord of the Rings' }, - async collRef => { - const query1 = query(collRef, orderBy('foo'), endBefore(2)); - const result = await query1.pipeline().execute(); - verifyResults(result, { foo: 1 }); + { + titleLength: 21, + title: 'To Kill a Mockingbird' } ); }); - it('supports pagination', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - { - 1: { foo: 1 }, - 2: { foo: 2 } - }, - async collRef => { - let query1 = query(collRef, orderBy('foo'), limit(1)); - const pipeline1 = query1.pipeline(); - let result = await pipeline1.execute(); - verifyResults(result, { foo: 1 }); - - // Pass the document snapshot from the previous result - query1 = query(query1, startAfter(result[0].get('foo'))); - result = await query1.pipeline().execute(); - verifyResults(result, { foo: 2 }); - } + it('testLike', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(like('title', '%Guide%')) + .select('title') + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('testRegexContains', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(regexContains('title', '(?i)(the|of)')) + ); + expect(snapshot.results.length).to.equal(5); + }); + + it('testRegexMatches', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(regexMatch('title', '.*(?i)(the|of).*')) + ); + expect(snapshot.results.length).to.equal(5); + }); + + it('testArithmeticOperations', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', 'To Kill a Mockingbird')) + .select( + add(field('rating'), 1).as('ratingPlusOne'), + subtract(field('published'), 1900).as('yearsSince1900'), + field('rating').multiply(10).as('ratingTimesTen'), + divide('rating', 2).as('ratingDividedByTwo'), + multiply('rating', 10, 2).as('ratingTimes20'), + add('rating', 1, 2).as('ratingPlus3'), + mod('rating', 2).as('ratingMod2') + ) + .limit(1) ); + expectResults(snapshot, { + ratingPlusOne: 5.2, + yearsSince1900: 60, + ratingTimesTen: 42, + ratingDividedByTwo: 2.1, + ratingTimes20: 84, + ratingPlus3: 7.2, + ratingMod2: 0.20000000000000018 + }); }); - it('supports pagination on DocumentIds', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, + it('testComparisonOperators', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + gt('rating', 4.2), + lte(field('rating'), 4.5), + neq('genre', 'Science Fiction') + ) + ) + .select('rating', 'title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { rating: 4.3, title: 'Crime and Punishment' }, { - 1: { foo: 1 }, - 2: { foo: 2 } + rating: 4.3, + title: 'One Hundred Years of Solitude' }, - async collRef => { - let query1 = query( - collRef, - orderBy('foo'), - orderBy(documentId(), 'asc'), - limit(1) - ); - const pipeline1 = query1.pipeline(); - let result = await pipeline1.execute(); - verifyResults(result, { foo: 1 }); - - // Pass the document snapshot from the previous result - query1 = query( - query1, - startAfter(result[0].get('foo'), result[0].ref?.id) - ); - result = await query1.pipeline().execute(); - verifyResults(result, { foo: 2 }); - } + { rating: 4.5, title: 'Pride and Prejudice' } ); }); - it('supports collection groups', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - {}, - async collRef => { - const collectionGroupId = `${collRef.id}group`; + it('testLogicalOperators', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + and(gt('rating', 4.5), eq('genre', 'Science Fiction')), + lt('published', 1900) + ) + ) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: 'Crime and Punishment' }, + { title: 'Dune' }, + { title: 'Pride and Prejudice' } + ); + }); - const fooDoc = doc( - collRef.firestore, - `${collRef.id}/foo/${collectionGroupId}/doc1` - ); - const barDoc = doc( - collRef.firestore, - `${collRef.id}/bar/baz/boo/${collectionGroupId}/doc2` - ); - await setDoc(fooDoc, { foo: 1 }); - await setDoc(barDoc, { bar: 1 }); + it('testChecks', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + isNull('rating').as('ratingIsNull'), + isNan('rating').as('ratingIsNaN'), + isError(arrayOffset('title', 0)).as('isError'), + ifError(arrayOffset('title', 0), constant('was error')).as( + 'ifError' + ), + isAbsent('foo').as('isAbsent'), + isNotNull('title').as('titleIsNotNull'), + isNotNan('cost').as('costIsNotNan'), + exists('fooBarBaz').as('fooBarBazExists'), + field('title').exists().as('titleExists') + ) + ); + expectResults(snapshot, { + ratingIsNull: false, + ratingIsNaN: false, + isError: true, + ifError: 'was error', + isAbsent: true, + titleIsNotNull: true, + costIsNotNan: false, + fooBarBazExists: false, + titleExists: true + }); - const query1 = collectionGroup(collRef.firestore, collectionGroupId); - const result = await query1.pipeline().execute(); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + field('rating').isNull().as('ratingIsNull'), + field('rating').isNan().as('ratingIsNaN'), + arrayOffset('title', 0).isError().as('isError'), + arrayOffset('title', 0) + .ifError(constant('was error')) + .as('ifError'), + field('foo').isAbsent().as('isAbsent'), + field('title').isNotNull().as('titleIsNotNull'), + field('cost').isNotNan().as('costIsNotNan') + ) + ); + expectResults(snapshot, { + ratingIsNull: false, + ratingIsNaN: false, + isError: true, + ifError: 'was error', + isAbsent: true, + titleIsNotNull: true, + costIsNotNan: false + }); + }); - verifyResults(result, { bar: 1 }, { foo: 1 }); - } + it('testMapGet', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('published').descending()) + .select( + field('awards').mapGet('hugo').as('hugoAward'), + field('awards').mapGet('others').as('others'), + field('title') + ) + .where(eq('hugoAward', true)) + ); + expectResults( + snapshot, + { + hugoAward: true, + title: "The Hitchhiker's Guide to the Galaxy", + others: { unknown: { year: 1980 } } + }, + { hugoAward: true, title: 'Dune', others: null } ); }); - it('supports query over collection path with special characters', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, - {}, - async collRef => { - const docWithSpecials = doc(collRef, 'so!@#$%^&*()_+special'); + it('testDistanceFunctions', async () => { + const sourceVector = [0.1, 0.1]; + const targetVector = [0.5, 0.8]; + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + cosineDistance(constantVector(sourceVector), targetVector).as( + 'cosineDistance' + ), + dotProduct(constantVector(sourceVector), targetVector).as( + 'dotProductDistance' + ), + euclideanDistance(constantVector(sourceVector), targetVector).as( + 'euclideanDistance' + ) + ) + .limit(1) + ); - const collectionWithSpecials = collection( - docWithSpecials, - 'so!@#$%^&*()_+special' - ); - await addDoc(collectionWithSpecials, { foo: 1 }); - await addDoc(collectionWithSpecials, { foo: 2 }); + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855 + }); - const result = await query( - collectionWithSpecials, - orderBy('foo', 'asc') + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + constantVector(sourceVector) + .cosineDistance(targetVector) + .as('cosineDistance'), + constantVector(sourceVector) + .dotProduct(targetVector) + .as('dotProductDistance'), + constantVector(sourceVector) + .euclideanDistance(targetVector) + .as('euclideanDistance') ) - .pipeline() - .execute(); + .limit(1) + ); - verifyResults(result, { foo: 1 }, { foo: 2 }); - } + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855 + }); + }); + + it('testVectorLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(vectorLength(constantVector([1, 2, 3])).as('vectorLength')) + ); + expectResults(snapshot, { + vectorLength: 3 + }); + }); + + it('testNestedFields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('awards.hugo', true)) + .sort(descending('title')) + .select('title', 'awards.hugo') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + 'awards.hugo': true + }, + { title: 'Dune', 'awards.hugo': true } ); }); - it('supports multiple inequality on same field', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, + it('test mapGet with field name including . notation', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('awards.hugo', true)) + .select( + 'title', + field('nestedField.level.1'), + mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') + ) + .sort(descending('title')) + ); + expectResults( + snapshot, { - '01': { id: 1, foo: 1, bar: 1, baz: 1 }, - '02': { id: 2, foo: 1, bar: 1, baz: 2 }, - '03': { id: 3, foo: 1, bar: 1, baz: 2 }, - '04': { id: 4, foo: 1, bar: 2, baz: 1 }, - '05': { id: 5, foo: 1, bar: 2, baz: 2 }, - '06': { id: 6, foo: 1, bar: 2, baz: 2 }, - '07': { id: 7, foo: 2, bar: 1, baz: 1 }, - '08': { id: 8, foo: 2, bar: 1, baz: 2 }, - '09': { id: 9, foo: 2, bar: 1, baz: 2 }, - '10': { id: 10, foo: 2, bar: 2, baz: 1 }, - '11': { id: 11, foo: 2, bar: 2, baz: 2 }, - '12': { id: 12, foo: 2, bar: 2, baz: 2 } + title: "The Hitchhiker's Guide to the Galaxy", + 'nestedField.level.`1`': null, + nested: true }, - async collRef => { - const query1 = query( - collRef, - and(where('id', '>', 2), where('id', '<=', 10)) - ); - const result = await query1.pipeline().execute(); - verifyResults( - result, - { id: 3, foo: 1, bar: 1, baz: 2 }, - { id: 4, foo: 1, bar: 2, baz: 1 }, - { id: 5, foo: 1, bar: 2, baz: 2 }, - { id: 6, foo: 1, bar: 2, baz: 2 }, - { id: 7, foo: 2, bar: 1, baz: 1 }, - { id: 8, foo: 2, bar: 1, baz: 2 }, - { id: 9, foo: 2, bar: 1, baz: 2 }, - { id: 10, foo: 2, bar: 2, baz: 1 } - ); - } + { title: 'Dune', 'nestedField.level.`1`': null, nested: null } + ); + }); + + describe('genericFunction', () => { + it('add selectable', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(descending('rating')) + .limit(1) + .select( + new FunctionExpr('add', [field('rating'), constant(1)]).as( + 'rating' + ) + ) + ); + expectResults(snapshot, { + rating: 5.7 + }); + }); + + it('and (variadic) selectable', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + new BooleanExpr('and', [ + field('rating').gt(0), + field('title').charLength().lt(5), + field('tags').arrayContains('propaganda') + ]) + ) + .select('title') + ); + expectResults(snapshot, { + title: '1984' + }); + }); + + it('array contains any', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + new BooleanExpr('array_contains_any', [ + field('tags'), + array(['politics']) + ]) + ) + .select('title') + ); + expectResults(snapshot, { + title: 'Dune' + }); + }); + + it('countif aggregate', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + new AggregateFunction('count_if', [field('rating').gte(4.5)]).as( + 'countOfBest' + ) + ) + ); + expectResults(snapshot, { + countOfBest: 3 + }); + }); + + it('sort by char_len', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort( + new FunctionExpr('char_length', [field('title')]).ascending(), + descending('__name__') + ) + .limit(3) + .select('title') + ); + expectResults( + snapshot, + { + title: '1984' + }, + { + title: 'Dune' + }, + { + title: 'The Great Gatsby' + } + ); + }); + }); + + itIf(testUnsupportedFeatures)('testReplaceFirst', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', 'The Lord of the Rings')) + .limit(1) + .select(replaceFirst('title', 'o', '0').as('newName')) + ); + expectResults(snapshot, { newName: 'The L0rd of the Rings' }); + }); + + itIf(testUnsupportedFeatures)('testReplaceAll', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', 'The Lord of the Rings')) + .limit(1) + .select(replaceAll('title', 'o', '0').as('newName')) + ); + expectResults(snapshot, { newName: 'The L0rd 0f the Rings' }); + }); + + it('supports Rand', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(10) + .select(rand().as('result')) + ); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(d => { + expect(d.get('result')).to.be.lt(1); + expect(d.get('result')).to.be.gte(0); + }); + }); + + it('supports array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(array([1, 2, 3, 4]).as('metadata')) + ); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 3, 4] + }); + }); + + it('evaluates expression in array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + array([1, 2, field('genre'), multiply('rating', 10)]).as('metadata') + ) ); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 'Fantasy', 47] + }); }); - it('supports multiple inequality on different fields', () => { - return withTestCollection( - PERSISTENCE_MODE_UNSPECIFIED, + it('supports arrayOffset', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(arrayOffset('tags', 0).as('firstTag')) + ); + const expectedResults = [ { - '01': { id: 1, foo: 1, bar: 1, baz: 1 }, - '02': { id: 2, foo: 1, bar: 1, baz: 2 }, - '03': { id: 3, foo: 1, bar: 1, baz: 2 }, - '04': { id: 4, foo: 1, bar: 2, baz: 1 }, - '05': { id: 5, foo: 1, bar: 2, baz: 2 }, - '06': { id: 6, foo: 1, bar: 2, baz: 2 }, - '07': { id: 7, foo: 2, bar: 1, baz: 1 }, - '08': { id: 8, foo: 2, bar: 1, baz: 2 }, - '09': { id: 9, foo: 2, bar: 1, baz: 2 }, - '10': { id: 10, foo: 2, bar: 2, baz: 1 }, - '11': { id: 11, foo: 2, bar: 2, baz: 2 }, - '12': { id: 12, foo: 2, bar: 2, baz: 2 } + firstTag: 'adventure' }, - async collRef => { - const query1 = query( - collRef, - and(where('id', '>=', 2), where('baz', '<', 2)) - ); - const result = await query1.pipeline().execute(); - verifyResults( - result, - { id: 4, foo: 1, bar: 2, baz: 1 }, - { id: 7, foo: 2, bar: 1, baz: 1 }, - { id: 10, foo: 2, bar: 2, baz: 1 } - ); + { + firstTag: 'politics' + }, + { + firstTag: 'classic' } + ]; + expectResults(snapshot, ...expectedResults); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(field('tags').arrayOffset(0).as('firstTag')) + ); + expectResults(snapshot, ...expectedResults); + }); + + // TODO: current_context tests with are failing because of b/395937453 + itIf(testUnsupportedFeatures)('supports currentContext', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(currentContext().as('currentContext')) + ); + expectResults(snapshot, { + currentContext: 'TODO' + }); + }); + + it('supports map', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + foo: 'bar' + }).as('metadata') + ) ); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + foo: 'bar' + } + }); + }); + + it('evaluates expression in map', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + genre: field('genre'), + rating: field('rating').multiply(10) + }).as('metadata') + ) + ); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + genre: 'Fantasy', + rating: 47 + } + }); + }); + + it('supports mapRemove', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapRemove('awards', 'hugo').as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false } + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapRemove('hugo').as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false } + }); + }); + + it('supports mapMerge', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapMerge('awards', { fakeAward: true }).as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false, hugo: false, fakeAward: true } + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapMerge({ fakeAward: true }).as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false, hugo: false, fakeAward: true } + }); + }); + + it('supports timestamp conversions', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + unixSecondsToTimestamp(constant(1741380235)).as( + 'unixSecondsToTimestamp' + ), + unixMillisToTimestamp(constant(1741380235123)).as( + 'unixMillisToTimestamp' + ), + unixMicrosToTimestamp(constant(1741380235123456)).as( + 'unixMicrosToTimestamp' + ), + timestampToUnixSeconds( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixSeconds'), + timestampToUnixMicros( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixMicros'), + timestampToUnixMillis( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixMillis') + ) + ); + expectResults(snapshot, { + unixMicrosToTimestamp: new Timestamp(1741380235, 123456000), + unixMillisToTimestamp: new Timestamp(1741380235, 123000000), + unixSecondsToTimestamp: new Timestamp(1741380235, 0), + timestampToUnixSeconds: 1741380235, + timestampToUnixMicros: 1741380235123456, + timestampToUnixMillis: 1741380235123 + }); + }); + + it('supports timestamp math', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(new Timestamp(1741380235, 0)).as('timestamp')) + .select( + timestampAdd('timestamp', 'day', 10).as('plus10days'), + timestampAdd('timestamp', 'hour', 10).as('plus10hours'), + timestampAdd('timestamp', 'minute', 10).as('plus10minutes'), + timestampAdd('timestamp', 'second', 10).as('plus10seconds'), + timestampAdd('timestamp', 'microsecond', 10).as('plus10micros'), + timestampAdd('timestamp', 'millisecond', 10).as('plus10millis'), + timestampSub('timestamp', 'day', 10).as('minus10days'), + timestampSub('timestamp', 'hour', 10).as('minus10hours'), + timestampSub('timestamp', 'minute', 10).as('minus10minutes'), + timestampSub('timestamp', 'second', 10).as('minus10seconds'), + timestampSub('timestamp', 'microsecond', 10).as('minus10micros'), + timestampSub('timestamp', 'millisecond', 10).as('minus10millis') + ) + ); + expectResults(snapshot, { + plus10days: new Timestamp(1742244235, 0), + plus10hours: new Timestamp(1741416235, 0), + plus10minutes: new Timestamp(1741380835, 0), + plus10seconds: new Timestamp(1741380245, 0), + plus10micros: new Timestamp(1741380235, 10000), + plus10millis: new Timestamp(1741380235, 10000000), + minus10days: new Timestamp(1740516235, 0), + minus10hours: new Timestamp(1741344235, 0), + minus10minutes: new Timestamp(1741379635, 0), + minus10seconds: new Timestamp(1741380225, 0), + minus10micros: new Timestamp(1741380234, 999990000), + minus10millis: new Timestamp(1741380234, 990000000) + }); + }); + + it('supports byteLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .select( + constant( + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) + ).as('bytes') + ) + .select(byteLength('bytes').as('byteLength')) + ); + + expectResults(snapshot, { + byteLength: 8 + }); + }); + + it('supports not', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol) + .limit(1) + .select(constant(true).as('trueField')) + .select('trueField', not(eq('trueField', true)).as('falseField')) + ); + + expectResults(snapshot, { + trueField: true, + falseField: false + }); + }); + }); + + describe('not yet implemented in backend', () => { + itIf(testUnsupportedFeatures)('supports Bit_and', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(bitAnd(constant(5), 12).as('result')) + ); + expectResults(snapshot, { + result: 4 + }); + }); + + itIf(testUnsupportedFeatures)('supports Bit_and', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(5).bitAnd(12).as('result')) + ); + expectResults(snapshot, { + result: 4 + }); + }); + + itIf(testUnsupportedFeatures)('supports Bit_or', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(bitOr(constant(5), 12).as('result')) + ); + expectResults(snapshot, { + result: 13 + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(5).bitOr(12).as('result')) + ); + expectResults(snapshot, { + result: 13 + }); + }); + + itIf(testUnsupportedFeatures)('supports Bit_xor', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(bitXor(constant(5), 12).as('result')) + ); + expectResults(snapshot, { + result: 9 + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(5).bitXor(12).as('result')) + ); + expectResults(snapshot, { + result: 9 + }); + }); + + itIf(testUnsupportedFeatures)('supports Bit_not', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + bitNot(constant(Bytes.fromUint8Array(Uint8Array.of(0xfd)))).as( + 'result' + ) + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x02)) + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(Bytes.fromUint8Array(Uint8Array.of(0xfd))) + .bitNot() + .as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x02)) + }); + }); + + itIf(testUnsupportedFeatures)('supports Bit_left_shift', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + bitLeftShift( + constant(Bytes.fromUint8Array(Uint8Array.of(0x02))), + 2 + ).as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x04)) + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(Bytes.fromUint8Array(Uint8Array.of(0x02))) + .bitLeftShift(2) + .as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x04)) + }); + }); + + itIf(testUnsupportedFeatures)('supports Bit_right_shift', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + bitRightShift( + constant(Bytes.fromUint8Array(Uint8Array.of(0x02))), + 2 + ).as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x01)) + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(Bytes.fromUint8Array(Uint8Array.of(0x02))) + .bitRightShift(2) + .as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x01)) + }); + }); + + itIf(testUnsupportedFeatures)('supports Document_id', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(documentId(field('__path__')).as('docId')) + ); + expectResults(snapshot, { + docId: 'book4' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('__path__').documentId().as('docId')) + ); + expectResults(snapshot, { + docId: 'book4' + }); + }); + + itIf(testUnsupportedFeatures)('supports Substr', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substr('title', 9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substr(9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + }); + + itIf(testUnsupportedFeatures)( + 'supports Substr without length', + async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substr('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').substr(9).as('of')) + ); + expectResults(snapshot, { + of: 'of the Rings' + }); + } + ); + + itIf(testUnsupportedFeatures)('arrayConcat works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + arrayConcat('tags', ['newTag1', 'newTag2'], field('tags'), [ + null + ]).as('modifiedTags') + ) + .limit(1) + ); + expectResults(snapshot, { + modifiedTags: [ + 'comedy', + 'space', + 'adventure', + 'newTag1', + 'newTag2', + 'comedy', + 'space', + 'adventure', + null + ] + }); + }); + + itIf(testUnsupportedFeatures)('testToLowercase', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(toLower('title').as('lowercaseTitle')) + .limit(1) + ); + expectResults(snapshot, { + lowercaseTitle: "the hitchhiker's guide to the galaxy" + }); + }); + + itIf(testUnsupportedFeatures)('testToUppercase', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(toUpper('author').as('uppercaseAuthor')) + .limit(1) + ); + expectResults(snapshot, { uppercaseAuthor: 'DOUGLAS ADAMS' }); + }); + + itIf(testUnsupportedFeatures)('testTrim', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .addFields( + constant(" The Hitchhiker's Guide to the Galaxy ").as('spacedTitle') + ) + .select(trim('spacedTitle').as('trimmedTitle'), field('spacedTitle')) + .limit(1) + ); + expectResults(snapshot, { + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + trimmedTitle: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + itIf(testUnsupportedFeatures)('test reverse', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', '1984')) + .limit(1) + .select(reverse('title').as('reverseTitle')) + ); + expectResults(snapshot, { title: '4891' }); + }); + }); + + describe('pagination', () => { + /** + * Adds several books to the test collection. These + * additional books support pagination test scenarios + * that would otherwise not be possible with the original + * set of books. + * @param collectionReference + */ + async function addBooks( + collectionReference: CollectionReference + ): Promise { + await setDoc(doc(collectionReference, 'book11'), { + title: 'Jonathan Strange & Mr Norrell', + author: 'Susanna Clarke', + genre: 'Fantasy', + published: 2004, + rating: 4.6, + tags: ['historical fantasy', 'magic', 'alternate history', 'england'], + awards: { hugo: false, nebula: false } + }); + await setDoc(doc(collectionReference, 'book12'), { + title: 'The Master and Margarita', + author: 'Mikhail Bulgakov', + genre: 'Satire', + published: 1967, // Though written much earlier + rating: 4.6, + tags: [ + 'russian literature', + 'supernatural', + 'philosophy', + 'dark comedy' + ], + awards: {} + }); + await setDoc(doc(collectionReference, 'book13'), { + title: 'A Long Way to a Small, Angry Planet', + author: 'Becky Chambers', + genre: 'Science Fiction', + published: 2014, + rating: 4.6, + tags: ['space opera', 'found family', 'character-driven', 'optimistic'], + awards: { hugo: false, nebula: false, kitschies: true } + }); + } + + // 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()); + + 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 } + ); + + const lastDoc = snapshot.results[snapshot.results.length - 1]; + + snapshot = await execute( + pipeline + .where( + or( + and( + field('rating').eq(lastDoc.get('rating')), + field('__path__').gt(lastDoc.ref?.id) + ), + field('rating').lt(lastDoc.get('rating')) + ) + ) + .limit(pageSize) + ); + expectResults( + snapshot, + { title: 'Pride and Prejudice', rating: 4.5 }, + { title: 'Crime and Punishment', rating: 4.3 } + ); + } + ); + + // sort on __name__ is not working + itIf(testUnsupportedFeatures)( + 'supports pagination with offsets', + async () => { + await addBooks(randomCol); + + const secondFilterField = '__path__'; + + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'rating', secondFilterField) + .sort( + field('rating').descending(), + field(secondFilterField).ascending() + ); + + const pageSize = 2; + let currPage = 0; + + 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 } + ); + + 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: 'A Long Way to a Small, Angry Planet', + rating: 4.6 + }, + { + title: 'Pride and Prejudice', + rating: 4.5 + } + ); + } + ); + }); + + describe('console support', () => { + it('supports internal serialization to proto', async () => { + const pipeline = firestore + .pipeline() + .collection('books') + .where(eq('awards.hugo', true)) + .select( + 'title', + field('nestedField.level.1'), + mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') + ); + + const proto = _internalPipelineToExecutePipelineRequestProto(pipeline); + expect(proto).not.to.be.null; }); }); }); diff --git a/packages/firestore/test/integration/api/query_to_pipeline.test.ts b/packages/firestore/test/integration/api/query_to_pipeline.test.ts new file mode 100644 index 00000000000..8eac50a5afa --- /dev/null +++ b/packages/firestore/test/integration/api/query_to_pipeline.test.ts @@ -0,0 +1,827 @@ +/** + * @license + * Copyright 2024 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. + */ + +import { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { PipelineSnapshot } from '../../../src/lite-api/pipeline-result'; +import { addEqualityMatcher } from '../../util/equality_matcher'; +import { + doc, + DocumentData, + setDoc, + setLogLevel, + query, + where, + FieldPath, + orderBy, + limit, + limitToLast, + startAt, + startAfter, + endAt, + endBefore, + collectionGroup, + collection, + and, + documentId, + addDoc, + getDoc, + or +} from '../util/firebase_export'; +import { + apiDescribe, + PERSISTENCE_MODE_UNSPECIFIED, + withTestCollection, + itIf +} from '../util/helpers'; +import { execute } from '../util/pipeline_export'; + +use(chaiAsPromised); + +setLogLevel('debug'); + +const testUnsupportedFeatures = false; + +// This is the Query integration tests from the lite API (no cache support) +// with some additional test cases added for more complete coverage. +apiDescribe.only('Query to Pipeline', persistence => { + addEqualityMatcher(); + + function verifyResults( + actual: PipelineSnapshot, + ...expected: DocumentData[] + ): void { + const results = actual.results; + expect(results.length).to.equal(expected.length); + + for (let i = 0; i < expected.length; ++i) { + expect(results[i].data()).to.deep.equal(expected[i]); + } + } + + it('supports default query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { 1: { foo: 1 } }, + async (collRef, db) => { + const snapshot = await execute(db.pipeline().createFrom(collRef)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports filtered query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('foo', '==', 1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports filtered query (with FieldPath)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, where(new FieldPath('foo'), '==', 1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports ordered query (with default order)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo')); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }, { foo: 2 }); + } + ); + }); + + it('supports ordered query (with asc)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo', 'asc')); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }, { foo: 2 }); + } + ); + }); + + it('supports ordered query (with desc)', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo', 'desc')); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }, { foo: 1 }); + } + ); + }); + + it('supports limit query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), limit(1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports limitToLast query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 }, + 3: { foo: 3 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), limitToLast(2)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }, { foo: 3 }); + } + ); + }); + + it('supports startAt', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), startAt(2)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }); + } + ); + }); + + it('supports startAt with limitToLast', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 }, + 3: { foo: 3 }, + 4: { foo: 4 }, + 5: { foo: 5 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + orderBy('foo'), + startAt(3), + limitToLast(4) + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 3 }, { foo: 4 }, { foo: 5 }); + } + ); + }); + + it('supports endAt with limitToLast', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 }, + 3: { foo: 3 }, + 4: { foo: 4 }, + 5: { foo: 5 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), endAt(3), limitToLast(2)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }, { foo: 3 }); + } + ); + }); + + // sort on __name__ is not working + itIf(testUnsupportedFeatures)( + 'supports startAfter (with DocumentSnapshot)', + () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { id: 1, foo: 1, bar: 1, baz: 1 }, + 2: { id: 2, foo: 1, bar: 1, baz: 2 }, + 3: { id: 3, foo: 1, bar: 1, baz: 2 }, + 4: { id: 4, foo: 1, bar: 2, baz: 1 }, + 5: { id: 5, foo: 1, bar: 2, baz: 2 }, + 6: { id: 6, foo: 1, bar: 2, baz: 2 }, + 7: { id: 7, foo: 2, bar: 1, baz: 1 }, + 8: { id: 8, foo: 2, bar: 1, baz: 2 }, + 9: { id: 9, foo: 2, bar: 1, baz: 2 }, + 10: { id: 10, foo: 2, bar: 2, baz: 1 }, + 11: { id: 11, foo: 2, bar: 2, baz: 2 }, + 12: { id: 12, foo: 2, bar: 2, baz: 2 } + }, + async (collRef, db) => { + let docRef = await getDoc(doc(collRef, '2')); + let query1 = query( + collRef, + orderBy('foo'), + orderBy('bar'), + orderBy('baz'), + startAfter(docRef) + ); + let snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 3, foo: 1, bar: 1, baz: 2 }, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 }, + { id: 11, foo: 2, bar: 2, baz: 2 }, + { id: 12, foo: 2, bar: 2, baz: 2 } + ); + + docRef = await getDoc(doc(collRef, '3')); + query1 = query( + collRef, + orderBy('foo'), + orderBy('bar'), + orderBy('baz'), + startAfter(docRef) + ); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 }, + { id: 11, foo: 2, bar: 2, baz: 2 }, + { id: 12, foo: 2, bar: 2, baz: 2 } + ); + } + ); + } + ); + + // sort on __name__ is not working + itIf(testUnsupportedFeatures)( + 'supports startAt (with DocumentSnapshot)', + () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { id: 1, foo: 1, bar: 1, baz: 1 }, + 2: { id: 2, foo: 1, bar: 1, baz: 2 }, + 3: { id: 3, foo: 1, bar: 1, baz: 2 }, + 4: { id: 4, foo: 1, bar: 2, baz: 1 }, + 5: { id: 5, foo: 1, bar: 2, baz: 2 }, + 6: { id: 6, foo: 1, bar: 2, baz: 2 }, + 7: { id: 7, foo: 2, bar: 1, baz: 1 }, + 8: { id: 8, foo: 2, bar: 1, baz: 2 }, + 9: { id: 9, foo: 2, bar: 1, baz: 2 }, + 10: { id: 10, foo: 2, bar: 2, baz: 1 }, + 11: { id: 11, foo: 2, bar: 2, baz: 2 }, + 12: { id: 12, foo: 2, bar: 2, baz: 2 } + }, + async (collRef, db) => { + let docRef = await getDoc(doc(collRef, '2')); + let query1 = query( + collRef, + orderBy('foo'), + orderBy('bar'), + orderBy('baz'), + startAt(docRef) + ); + let snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 2, foo: 1, bar: 1, baz: 2 }, + { id: 3, foo: 1, bar: 1, baz: 2 }, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 }, + { id: 11, foo: 2, bar: 2, baz: 2 }, + { id: 12, foo: 2, bar: 2, baz: 2 } + ); + + docRef = await getDoc(doc(collRef, '3')); + query1 = query( + collRef, + orderBy('foo'), + orderBy('bar'), + orderBy('baz'), + startAt(docRef) + ); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 3, foo: 1, bar: 1, baz: 2 }, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 }, + { id: 11, foo: 2, bar: 2, baz: 2 }, + { id: 12, foo: 2, bar: 2, baz: 2 } + ); + } + ); + } + ); + + it('supports startAfter', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), startAfter(1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }); + } + ); + }); + + it('supports endAt', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), endAt(1)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports endBefore', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + const query1 = query(collRef, orderBy('foo'), endBefore(2)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports pagination', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + let query1 = query(collRef, orderBy('foo'), limit(1)); + const pipeline1 = db.pipeline().createFrom(query1); + let snapshot = await execute(pipeline1); + verifyResults(snapshot, { foo: 1 }); + + // Pass the document snapshot from the previous snapshot + query1 = query(query1, startAfter(snapshot.results[0].get('foo'))); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }); + } + ); + }); + + it('supports pagination on DocumentIds', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1 }, + 2: { foo: 2 } + }, + async (collRef, db) => { + let query1 = query( + collRef, + orderBy('foo'), + orderBy(documentId(), 'asc'), + limit(1) + ); + const pipeline1 = db.pipeline().createFrom(query1); + let snapshot = await execute(pipeline1); + verifyResults(snapshot, { foo: 1 }); + + // Pass the document snapshot from the previous snapshot + query1 = query( + query1, + startAfter( + snapshot.results[0].get('foo'), + snapshot.results[0].ref?.id + ) + ); + snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2 }); + } + ); + }); + + // needs subcollection support + itIf(testUnsupportedFeatures)('supports collection groups', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + {}, + async (collRef, db) => { + const collectionGroupId = `${collRef.id}group`; + + const fooDoc = doc( + collRef.firestore, + `${collRef.id}/foo/${collectionGroupId}/doc1` + ); + const barDoc = doc( + collRef.firestore, + `${collRef.id}/bar/baz/boo/${collectionGroupId}/doc2` + ); + await setDoc(fooDoc, { foo: 1 }); + await setDoc(barDoc, { bar: 1 }); + + const query1 = collectionGroup(collRef.firestore, collectionGroupId); + const snapshot = await execute(db.pipeline().createFrom(query1)); + + verifyResults(snapshot, { bar: 1 }, { foo: 1 }); + } + ); + }); + + // needs subcollection support + itIf(testUnsupportedFeatures)( + 'supports query over collection path with special characters', + () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + {}, + async (collRef, db) => { + const docWithSpecials = doc(collRef, 'so!@#$%^&*()_+special'); + + const collectionWithSpecials = collection( + docWithSpecials, + 'so!@#$%^&*()_+special' + ); + await addDoc(collectionWithSpecials, { foo: 1 }); + await addDoc(collectionWithSpecials, { foo: 2 }); + + const snapshot = await execute( + db + .pipeline() + .createFrom(query(collectionWithSpecials, orderBy('foo', 'asc'))) + ); + + verifyResults(snapshot, { foo: 1 }, { foo: 2 }); + } + ); + } + ); + + it('supports multiple inequality on same field', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + '01': { id: 1, foo: 1, bar: 1, baz: 1 }, + '02': { id: 2, foo: 1, bar: 1, baz: 2 }, + '03': { id: 3, foo: 1, bar: 1, baz: 2 }, + '04': { id: 4, foo: 1, bar: 2, baz: 1 }, + '05': { id: 5, foo: 1, bar: 2, baz: 2 }, + '06': { id: 6, foo: 1, bar: 2, baz: 2 }, + '07': { id: 7, foo: 2, bar: 1, baz: 1 }, + '08': { id: 8, foo: 2, bar: 1, baz: 2 }, + '09': { id: 9, foo: 2, bar: 1, baz: 2 }, + '10': { id: 10, foo: 2, bar: 2, baz: 1 }, + '11': { id: 11, foo: 2, bar: 2, baz: 2 }, + '12': { id: 12, foo: 2, bar: 2, baz: 2 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + and(where('id', '>', 2), where('id', '<=', 10)) + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 3, foo: 1, bar: 1, baz: 2 }, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 5, foo: 1, bar: 2, baz: 2 }, + { id: 6, foo: 1, bar: 2, baz: 2 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 8, foo: 2, bar: 1, baz: 2 }, + { id: 9, foo: 2, bar: 1, baz: 2 }, + { id: 10, foo: 2, bar: 2, baz: 1 } + ); + } + ); + }); + + it('supports multiple inequality on different fields', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + '01': { id: 1, foo: 1, bar: 1, baz: 1 }, + '02': { id: 2, foo: 1, bar: 1, baz: 2 }, + '03': { id: 3, foo: 1, bar: 1, baz: 2 }, + '04': { id: 4, foo: 1, bar: 2, baz: 1 }, + '05': { id: 5, foo: 1, bar: 2, baz: 2 }, + '06': { id: 6, foo: 1, bar: 2, baz: 2 }, + '07': { id: 7, foo: 2, bar: 1, baz: 1 }, + '08': { id: 8, foo: 2, bar: 1, baz: 2 }, + '09': { id: 9, foo: 2, bar: 1, baz: 2 }, + '10': { id: 10, foo: 2, bar: 2, baz: 1 }, + '11': { id: 11, foo: 2, bar: 2, baz: 2 }, + '12': { id: 12, foo: 2, bar: 2, baz: 2 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + and(where('id', '>=', 2), where('baz', '<', 2)) + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { id: 4, foo: 1, bar: 2, baz: 1 }, + { id: 7, foo: 2, bar: 1, baz: 1 }, + { id: 10, foo: 2, bar: 2, baz: 1 } + ); + } + ); + }); + + it('supports collectionGroup query', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { 1: { foo: 1 } }, + async (collRef, db) => { + const snapshot = await execute( + db.pipeline().createFrom(collectionGroup(db, collRef.id)) + ); + verifyResults(snapshot, { foo: 1 }); + } + ); + }); + + it('supports eq nan', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: NaN }, + 2: { foo: 2, bar: 1 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '==', NaN)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: NaN }); + } + ); + }); + + it('supports neq nan', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: NaN }, + 2: { foo: 2, bar: 1 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '!=', NaN)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2, bar: 1 }); + } + ); + }); + + it('supports eq null', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: null }, + 2: { foo: 2, bar: 1 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '==', null)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: null }); + } + ); + }); + + it('supports neq null', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: null }, + 2: { foo: 2, bar: 1 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '!=', null)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2, bar: 1 }); + } + ); + }); + + it('supports neq', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 0 }, + 2: { foo: 2, bar: 1 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', '!=', 0)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 2, bar: 1 }); + } + ); + }); + + it('supports array contains', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: [0, 2, 4, 6] }, + 2: { foo: 2, bar: [1, 3, 5, 7] } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'array-contains', 4)); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: [0, 2, 4, 6] }); + } + ); + }); + + // sorting on name required + itIf(testUnsupportedFeatures)('supports array contains any', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: [0, 2, 4, 6] }, + 2: { foo: 2, bar: [1, 3, 5, 7] }, + 3: { foo: 3, bar: [10, 20, 30, 40] } + }, + async (collRef, db) => { + const query1 = query( + collRef, + where('bar', 'array-contains-any', [4, 5]) + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults( + snapshot, + { foo: 1, bar: [0, 2, 4, 6] }, + { foo: 2, bar: [1, 3, 5, 7] } + ); + } + ); + }); + + it('supports in', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'in', [0, 10, 20])); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 3, bar: 10 }); + } + ); + }); + + it('supports in with 1', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'in', [2])); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: 2 }); + } + ); + }); + + itIf(testUnsupportedFeatures)('supports not in', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2, bar: 1 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'not-in', [0, 10, 20])); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: 2 }, { foo: 2, bar: 1 }); + } + ); + }); + + it('supports not in with 1', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query(collRef, where('bar', 'not-in', [2])); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 3, bar: 10 }); + } + ); + }); + + it('supports or operator', () => { + return withTestCollection( + PERSISTENCE_MODE_UNSPECIFIED, + { + 1: { foo: 1, bar: 2 }, + 2: { foo: 2, bar: 0 }, + 3: { foo: 3, bar: 10 } + }, + async (collRef, db) => { + const query1 = query( + collRef, + or(where('bar', '==', 2), where('foo', '==', 3)), + orderBy('foo') + ); + const snapshot = await execute(db.pipeline().createFrom(query1)); + verifyResults(snapshot, { foo: 1, bar: 2 }, { foo: 3, bar: 10 }); + } + ); + }); +}); diff --git a/packages/firestore/test/integration/util/firebase_export.ts b/packages/firestore/test/integration/util/firebase_export.ts index a6739dfe72f..f58b3ce045b 100644 --- a/packages/firestore/test/integration/util/firebase_export.ts +++ b/packages/firestore/test/integration/util/firebase_export.ts @@ -51,5 +51,4 @@ export function newTestFirestore( } export * from '../../../src'; -export * from '../../../pipelines/pipelines'; export { PrivateSettings }; diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 465bc8edd61..81d97867d09 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -557,3 +557,10 @@ export async function checkOnlineAndOfflineResultsMatch( const docsFromCache = await getDocsFromCache(query); expect(toIds(docsFromServer)).to.deep.equal(toIds(docsFromCache)); } + +export function itIf( + condition: boolean | 'only' +): Mocha.TestFunction | Mocha.PendingTestFunction { + // eslint-disable-next-line no-restricted-properties + return condition === 'only' ? it.only : condition ? it : it.skip; +} diff --git a/packages/firestore/src/all_packages.ts b/packages/firestore/test/integration/util/pipeline_export.ts similarity index 61% rename from packages/firestore/src/all_packages.ts rename to packages/firestore/test/integration/util/pipeline_export.ts index 82babeea706..d2495d772a5 100644 --- a/packages/firestore/src/all_packages.ts +++ b/packages/firestore/test/integration/util/pipeline_export.ts @@ -1,12 +1,6 @@ -/** - * Cloud Firestore - * - * @packageDocumentation - */ - /** * @license - * Copyright 2024 Google LLC + * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,5 +15,9 @@ * limitations under the License. */ -export * from './api'; -export * from './api_pipelines'; +// 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". + +export * from '../../../pipelines/pipelines'; diff --git a/packages/firestore/test/lite/pipeline.test.ts b/packages/firestore/test/lite/pipeline.test.ts new file mode 100644 index 00000000000..e3e5083a811 --- /dev/null +++ b/packages/firestore/test/lite/pipeline.test.ts @@ -0,0 +1,2919 @@ +/** + * @license + * Copyright 2024 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. + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { Bytes } from '../../src/lite-api/bytes'; +import { + Firestore, + getFirestore, + terminate +} from '../../src/lite-api/database'; +import { + field, + and, + array, + arrayOffset, + constant, + add, + subtract, + multiply, + avg, + bitAnd, + substr, + constantVector, + bitLeftShift, + bitNot, + count, + mapMerge, + mapRemove, + bitOr, + ifError, + isAbsent, + isError, + or, + rand, + bitRightShift, + bitXor, + isNotNan, + map, + isNotNull, + isNull, + mod, + documentId, + eq, + neq, + lt, + countIf, + currentContext, + lte, + gt, + arrayConcat, + arrayContains, + arrayContainsAny, + eqAny, + notEqAny, + xor, + cond, + logicalMaximum, + logicalMinimum, + exists, + isNan, + reverse, + like, + regexContains, + regexMatch, + strContains, + startsWith, + endsWith, + mapGet, + countAll, + minimum, + maximum, + cosineDistance, + dotProduct, + euclideanDistance, + vectorLength, + unixMicrosToTimestamp, + timestampToUnixMicros, + unixMillisToTimestamp, + timestampToUnixMillis, + unixSecondsToTimestamp, + timestampToUnixSeconds, + timestampAdd, + timestampSub, + ascending, + descending, + FunctionExpr, + BooleanExpr, + AggregateFunction +} 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 { 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 { FindNearestOptions } from '../../src/lite-api/stage'; +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'; + +use(chaiAsPromised); + +describe('Firestore Pipelines', () => { + addEqualityMatcher(); + + let firestore: Firestore; + let randomCol: CollectionReference; + let beginDocCreation: number = 0; + let endDocCreation: number = 0; + + async function testCollectionWithDocs(docs: { + [id: string]: DocumentData; + }): Promise> { + beginDocCreation = new Date().valueOf(); + for (const id in docs) { + if (docs.hasOwnProperty(id)) { + const ref = doc(randomCol, id); + await setDoc(ref, docs[id]); + } + } + endDocCreation = new Date().valueOf(); + return randomCol; + } + + function expectResults(snapshot: PipelineSnapshot, ...docs: string[]): void; + function expectResults( + snapshot: PipelineSnapshot, + ...data: DocumentData[] + ): void; + + function expectResults( + snapshot: PipelineSnapshot, + ...data: DocumentData[] | string[] + ): void { + const docs = snapshot.results; + + expect(docs.length).to.equal(data.length); + + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = docs.map(doc => doc.id); + expect(actualIds).to.deep.equal(data); + } else { + docs.forEach(r => { + expect(r.data()).to.deep.equal(data.shift()); + }); + } + } + } + + async function setupBookDocs(): Promise> { + const bookDocs: { [id: string]: DocumentData } = { + book1: { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } }, + embedding: vector([10, 1, 1, 1, 1, 1, 1, 1, 1, 1]) + }, + book2: { + title: 'Pride and Prejudice', + author: 'Jane Austen', + genre: 'Romance', + published: 1813, + rating: 4.5, + tags: ['classic', 'social commentary', 'love'], + awards: { none: true }, + embedding: vector([1, 10, 1, 1, 1, 1, 1, 1, 1, 1]) + }, + book3: { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + genre: 'Magical Realism', + published: 1967, + rating: 4.3, + tags: ['family', 'history', 'fantasy'], + awards: { nobel: true, nebula: false }, + embedding: vector([1, 1, 10, 1, 1, 1, 1, 1, 1, 1]) + }, + book4: { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: { hugo: false, nebula: false }, + remarks: null, + cost: NaN, + embedding: vector([1, 1, 1, 10, 1, 1, 1, 1, 1, 1]) + }, + book5: { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + genre: 'Dystopian', + published: 1985, + rating: 4.1, + tags: ['feminism', 'totalitarianism', 'resistance'], + awards: { 'arthur c. clarke': true, 'booker prize': false }, + embedding: vector([1, 1, 1, 1, 10, 1, 1, 1, 1, 1]) + }, + book6: { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + genre: 'Psychological Thriller', + published: 1866, + rating: 4.3, + tags: ['philosophy', 'crime', 'redemption'], + awards: { none: true }, + embedding: vector([1, 1, 1, 1, 1, 10, 1, 1, 1, 1]) + }, + book7: { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + genre: 'Southern Gothic', + published: 1960, + rating: 4.2, + tags: ['racism', 'injustice', 'coming-of-age'], + awards: { pulitzer: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 10, 1, 1, 1]) + }, + book8: { + title: '1984', + author: 'George Orwell', + genre: 'Dystopian', + published: 1949, + rating: 4.2, + tags: ['surveillance', 'totalitarianism', 'propaganda'], + awards: { prometheus: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 10, 1, 1]) + }, + book9: { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + genre: 'Modernist', + published: 1925, + rating: 4.0, + tags: ['wealth', 'american dream', 'love'], + awards: { none: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 1, 10, 1]) + }, + book10: { + title: 'Dune', + author: 'Frank Herbert', + genre: 'Science Fiction', + published: 1965, + rating: 4.6, + tags: ['politics', 'desert', 'ecology'], + awards: { hugo: true, nebula: true }, + embedding: vector([1, 1, 1, 1, 1, 1, 1, 1, 1, 10]) + } + }; + return testCollectionWithDocs(bookDocs); + } + + let testDeferred: Deferred | undefined; + let withTestCollectionPromise: Promise | undefined; + + beforeEach(async () => { + const setupDeferred = new Deferred(); + testDeferred = new Deferred(); + withTestCollectionPromise = withTestCollection(async collectionRef => { + randomCol = collectionRef; + firestore = collectionRef.firestore; + await setupBookDocs(); + setupDeferred.resolve(); + + return testDeferred?.promise; + }); + + await setupDeferred.promise; + }); + + afterEach(async () => { + testDeferred?.resolve(); + await withTestCollectionPromise; + }); + + describe('pipeline results', () => { + it('empty snapshot as expected', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol.path).limit(0) + ); + expect(snapshot.results.length).to.equal(0); + }); + + it('full snapshot as expected', async () => { + const ppl = firestore.pipeline().collection(randomCol.path); + const snapshot = await execute(ppl); + expect(snapshot.results.length).to.equal(10); + expect(snapshot.pipeline).to.equal(ppl); + expectResults( + snapshot, + 'book1', + 'book10', + 'book2', + 'book3', + 'book4', + 'book5', + 'book6', + 'book7', + 'book8', + 'book9' + ); + }); + + it('returns execution time', async () => { + const start = new Date().valueOf(); + const pipeline = firestore.pipeline().collection(randomCol.path); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + end - start + ); + }); + + it('returns execution time for an empty query', async () => { + const start = new Date().valueOf(); + const pipeline = firestore.pipeline().collection(randomCol.path).limit(0); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.results.length).to.equal(0); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + end - start + ); + }); + + it('returns create and update time for each document', async () => { + const pipeline = firestore.pipeline().collection(randomCol.path); + + let snapshot = await execute(pipeline); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(doc => { + expect(doc.createTime).to.not.be.null; + expect(doc.updateTime).to.not.be.null; + + expect(doc.createTime!.toDate().valueOf()).to.approximately( + (beginDocCreation + endDocCreation) / 2, + endDocCreation - beginDocCreation + ); + expect(doc.updateTime!.toDate().valueOf()).to.approximately( + (beginDocCreation + endDocCreation) / 2, + endDocCreation - beginDocCreation + ); + expect(doc.createTime?.valueOf()).to.equal(doc.updateTime?.valueOf()); + }); + + const wb = writeBatch(firestore); + snapshot.results.forEach(doc => { + wb.update(doc.ref!, { newField: 'value' }); + }); + await wb.commit(); + + snapshot = await execute(pipeline); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(doc => { + expect(doc.createTime).to.not.be.null; + expect(doc.updateTime).to.not.be.null; + expect(doc.createTime!.toDate().valueOf()).to.be.lessThan( + doc.updateTime!.toDate().valueOf() + ); + }); + }); + + it('returns execution time for an aggregate query', async () => { + const start = new Date().valueOf(); + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .aggregate(avg('rating').as('avgRating')); + + const snapshot = await execute(pipeline); + const end = new Date().valueOf(); + + expect(snapshot.results.length).to.equal(1); + + expect(snapshot.executionTime.toDate().valueOf()).to.approximately( + (start + end) / 2, + end - start + ); + }); + + it('returns undefined create and update time for each result in an aggregate query', async () => { + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .aggregate({ + accumulators: [avg('rating').as('avgRating')], + groups: ['genre'] + }); + + const snapshot = await execute(pipeline); + + expect(snapshot.results.length).to.equal(8); + + snapshot.results.forEach(doc => { + expect(doc.updateTime).to.be.undefined; + expect(doc.createTime).to.be.undefined; + }); + }); + }); + + describe('pipeline sources', () => { + it('supports CollectionReference as source', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol) + ); + expect(snapshot.results.length).to.equal(10); + }); + + it('supports list of documents as source', async () => { + const collName = randomCol.id; + + const snapshot = await execute( + firestore + .pipeline() + .documents([ + `${collName}/book1`, + doc(randomCol, 'book2'), + doc(randomCol, 'book3').path + ]) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('reject CollectionReference for another DB', async () => { + const db2 = getFirestore(firestore.app, 'notDefault'); + + expect(() => { + firestore.pipeline().collection(collection(db2, 'foo')); + }).to.throw(/Invalid CollectionReference/); + + await terminate(db2); + }); + + it('reject DocumentReference for another DB', async () => { + const db2 = getFirestore(firestore.app, 'notDefault'); + + expect(() => { + firestore.pipeline().documents([doc(db2, 'foo/bar')]); + }).to.throw(/Invalid DocumentReference/); + + await terminate(db2); + }); + + 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); + }); + + it('supports database as source', async () => { + const randomId = Math.random().toString(16).slice(2); + const doc1 = await addDoc(collection(randomCol, 'book1', 'sub'), { + order: 1, + randomId + }); + const doc2 = await addDoc(collection(randomCol, 'book2', 'sub'), { + order: 2, + randomId + }); + const snapshot = await execute( + firestore + .pipeline() + .database() + .where(eq('randomId', randomId)) + .sort(ascending('order')) + ); + expectResults(snapshot, doc1.id, doc2.id); + }); + }); + + describe('supported data types', () => { + it('accepts and returns all data types', async () => { + const refDate = new Date(); + const refTimestamp = Timestamp.now(); + const constants = [ + constant(1).as('number'), + constant('a string').as('string'), + constant(true).as('boolean'), + constant(null).as('null'), + constant(new GeoPoint(0.1, 0.2)).as('geoPoint'), + constant(refTimestamp).as('timestamp'), + constant(refDate).as('date'), + constant( + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])) + ).as('bytes'), + constant(doc(firestore, 'foo', 'bar')).as('documentReference'), + constantVector(vector([1, 2, 3])).as('vectorValue'), + constantVector([1, 2, 3]).as('vectorValue2'), + map({ + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': refDate, + 'uint8Array': Bytes.fromUint8Array( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) + ), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 2, + 'string': 'b string' + }, + 'array': [1, 'c string'] + }).as('map'), + array([ + 1, + 'a string', + true, + null, + new GeoPoint(0.1, 0.2), + refTimestamp, + refDate, + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + doc(firestore, 'foo', 'bar'), + vector([1, 2, 3]), + { + 'number': 2, + 'string': 'b string' + } + ]).as('array') + ]; + + const snapshots = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constants[0], ...constants.slice(1)) + ); + + expectResults(snapshots, { + 'number': 1, + 'string': 'a string', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': Timestamp.fromDate(refDate), + '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', + 'boolean': true, + 'null': null, + 'geoPoint': new GeoPoint(0.1, 0.2), + 'timestamp': refTimestamp, + 'date': Timestamp.fromDate(refDate), + 'uint8Array': Bytes.fromUint8Array( + new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0]) + ), + 'documentReference': doc(firestore, 'foo', 'bar'), + 'vectorValue': vector([1, 2, 3]), + 'map': { + 'number': 2, + 'string': 'b string' + }, + 'array': [1, 'c string'] + }, + 'array': [ + 1, + 'a string', + true, + null, + new GeoPoint(0.1, 0.2), + refTimestamp, + Timestamp.fromDate(refDate), + Bytes.fromUint8Array(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 0])), + doc(firestore, 'foo', 'bar'), + vector([1, 2, 3]), + { + 'number': 2, + 'string': 'b string' + } + ] + }); + }); + + it('throws on undefined in a map', async () => { + expect(() => { + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + map({ + 'number': 1, + 'undefined': array([undefined]) + }).as('foo') + ); + }).to.throw( + 'Function constant() called with invalid data. Unsupported field value: undefined' + ); + }); + + it('throws on undefined in an array', async () => { + expect(() => { + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(array([1, undefined]).as('foo')); + }).to.throw( + 'Function constant() called with invalid data. Unsupported field value: undefined' + ); + }); + + it('converts arrays and plain objects to functionValues if the customer intent is unspecified', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + 'title', + 'author', + 'genre', + 'rating', + 'published', + 'tags', + 'awards' + ) + .addFields( + array([ + 1, + 2, + field('genre'), + multiply('rating', 10), + [field('title')], + { + published: field('published') + } + ]).as('metadataArray'), + map({ + genre: field('genre'), + rating: multiply('rating', 10), + nestedArray: [field('title')], + nestedMap: { + published: field('published') + } + }).as('metadata') + ) + .where( + and( + eq('metadataArray', [ + 1, + 2, + field('genre'), + multiply('rating', 10), + [field('title')], + { + published: field('published') + } + ]), + eq('metadata', { + genre: field('genre'), + rating: multiply('rating', 10), + nestedArray: [field('title')], + nestedMap: { + published: field('published') + } + }) + ) + ) + ); + + expect(snapshot.results.length).to.equal(1); + + expectResults(snapshot, { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: { hugo: false, nebula: false }, + metadataArray: [ + 1, + 2, + 'Fantasy', + 47, + ['The Lord of the Rings'], + { + published: 1954 + } + ], + metadata: { + genre: 'Fantasy', + rating: 47, + nestedArray: ['The Lord of the Rings'], + nestedMap: { + published: 1954 + } + } + }); + }); + }); + + describe('stages', () => { + describe('aggregate stage', () => { + it('supports aggregate', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countAll().as('count')) + ); + expectResults(snapshot, { count: 10 }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('genre', 'Science Fiction')) + .aggregate( + countAll().as('count'), + avg('rating').as('avgRating'), + field('rating').maximum().as('maxRating') + ) + ); + expectResults(snapshot, { count: 2, avgRating: 4.4, maxRating: 4.6 }); + }); + + it('rejects groups without accumulators', async () => { + await expect( + execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(lt('published', 1900)) + .aggregate({ + accumulators: [], + groups: ['genre'] + }) + ) + ).to.be.rejected; + }); + + it('returns group and accumulate results', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(lt(field('published'), 1984)) + .aggregate({ + accumulators: [avg('rating').as('avgRating')], + groups: ['genre'] + }) + .where(gt('avgRating', 4.3)) + .sort(field('avgRating').descending()) + ); + expectResults( + snapshot, + { avgRating: 4.7, genre: 'Fantasy' }, + { avgRating: 4.5, genre: 'Romance' }, + { avgRating: 4.4, genre: 'Science Fiction' } + ); + }); + + it('returns min, max, count, and countAll accumulations', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + count('cost').as('booksWithCost'), + countAll().as('count'), + maximum('rating').as('maxRating'), + minimum('published').as('minPublished') + ) + ); + expectResults(snapshot, { + booksWithCost: 1, + count: 10, + maxRating: 4.7, + minPublished: 1813 + }); + }); + + it('returns countif accumulation', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countIf(field('rating').gt(4.3)).as('count')) + ); + const expectedResults = { + count: 3 + }; + expectResults(snapshot, expectedResults); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate(field('rating').gt(4.3).countIf().as('count')) + ); + expectResults(snapshot, expectedResults); + }); + }); + + describe('distinct stage', () => { + it('returns distinct values as expected', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .distinct('genre', 'author') + .sort(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', () => { + it('can select fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams' + }, + { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' }, + { title: 'Dune', author: 'Frank Herbert' }, + { title: 'Crime and Punishment', author: 'Fyodor Dostoevsky' }, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez' + }, + { title: '1984', author: 'George Orwell' }, + { title: 'To Kill a Mockingbird', author: 'Harper Lee' }, + { title: 'The Lord of the Rings', author: 'J.R.R. Tolkien' }, + { title: 'Pride and Prejudice', author: 'Jane Austen' }, + { title: "The Handmaid's Tale", author: 'Margaret Atwood' } + ); + }); + }); + + describe('addField stage', () => { + it('can add fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .addFields(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', () => { + it('can remove fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .sort(field('author').ascending()) + .removeFields(field('author')) + .sort(field('author').ascending()) + ); + 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( + gt('rating', 4.5), + eqAny('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( + gt('rating', 4.5), + eqAny('genre', ['Science Fiction', 'Romance', 'Fantasy']), + lt('published', 1965) + ) + ) + ); + expectResults(snapshot, 'book4'); + }); + it('where with or', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + eq('genre', 'Romance'), + eq('genre', 'Dystopian'), + eq('genre', 'Fantasy') + ) + ) + .select('title') + ); + expectResults( + snapshot, + { title: 'Pride and Prejudice' }, + { title: 'The Lord of the Rings' }, + { title: "The Handmaid's Tale" }, + { title: '1984' } + ); + }); + + it('where with xor', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + xor( + eq('genre', 'Romance'), + eq('genre', 'Dystopian'), + eq('genre', 'Fantasy'), + eq('published', 1949) + ) + ) + .select('title') + ); + expectResults( + snapshot, + { title: 'Pride and Prejudice' }, + { title: 'The Lord of the Rings' }, + { title: "The Handmaid's Tale" } + ); + }); + }); + + describe('sort, offset, and limit stages', () => { + it('supports sort, offset, and limits', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .offset(5) + .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('generic stage', () => { + it('can select fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .genericStage('select', [ + { + title: field('title'), + metadata: { + 'author': field('author') + } + } + ]) + .sort(field('author').ascending()) + .limit(1) + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + metadata: { + author: 'Douglas Adams' + } + }); + }); + + it('can add fields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('author').ascending()) + .limit(1) + .select('title', 'author') + .genericStage('add_fields', [ + { + display: field('title').strConcat(' - ', field('author')) + } + ]) + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + display: "The Hitchhiker's Guide to the Galaxy - Douglas Adams" + }); + }); + + it('can filter with where', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .genericStage('where', [field('author').eq('Douglas Adams')]) + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams' + }); + }); + + it('can limit, offset, and sort', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author') + .genericStage('sort', [ + { + direction: 'ascending', + expression: field('author') + } + ]) + .genericStage('offset', [3]) + .genericStage('limit', [1]) + ); + expectResults(snapshot, { + author: 'Fyodor Dostoevsky', + title: 'Crime and Punishment' + }); + }); + + it('can perform aggregate query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .genericStage('aggregate', [ + { averageRating: field('rating').avg() }, + {} + ]) + ); + expectResults(snapshot, { + averageRating: 4.3100000000000005 + }); + }); + + it('can perform distinct query', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'author', 'rating') + .genericStage('distinct', [{ rating: field('rating') }]) + .sort(field('rating').descending()) + ); + expectResults( + snapshot, + { + rating: 4.7 + }, + { + rating: 4.6 + }, + { + rating: 4.5 + }, + { + rating: 4.3 + }, + { + rating: 4.2 + }, + { + rating: 4.1 + }, + { + rating: 4.0 + } + ); + }); + }); + + describe('replace stage', () => { + it('run pipleine with replace', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', "The Hitchhiker's Guide to the Galaxy")) + .replaceWith('awards') + ); + expectResults(snapshot, { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }); + }); + }); + + describe('sample stage', () => { + it('run pipeline with sample limit of 3', async () => { + const snapshot = await execute( + firestore.pipeline().collection(randomCol.path).sample(3) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('run pipeline with sample limit of {documents: 3}', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sample({ documents: 3 }) + ); + expect(snapshot.results.length).to.equal(3); + }); + + it('run pipeline with sample limit of {percentage: 0.6}', async () => { + let avgSize = 0; + const numIterations = 20; + for (let i = 0; i < numIterations; i++) { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sample({ percentage: 0.6 }) + ); + + avgSize += snapshot.results.length; + } + avgSize /= numIterations; + expect(avgSize).to.be.closeTo(6, 1); + }); + }); + + describe('union stage', () => { + it('run pipeline with union', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .union(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', () => { + it('run pipeline with unnest', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', "The Hitchhiker's Guide to the Galaxy")) + .unnest(field('tags').as('tag')) + .select( + 'title', + 'author', + 'genre', + 'published', + 'rating', + 'tags', + 'tag', + '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'], + tag: 'comedy', + 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'], + tag: 'space', + 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'], + tag: 'adventure', + awards: { + hugo: true, + nebula: false, + others: { unknown: { year: 1980 } } + }, + nestedField: { 'level.1': { 'level.2': true } } + } + ); + }); + it('unnest an expr', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('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 } } + } + ); + }); + }); + + 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, + { + title: "The Hitchhiker's Guide to the Galaxy", + computedDistance: 1 + }, + { + title: 'One Hundred Years of Solitude', + computedDistance: 12.041594578792296 + } + ); + }); + }); + }); + + describe('function expressions', () => { + it('logical max works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMaximum(constant(1960), field('published'), 1961).as( + 'published-safe' + ) + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1961 }, + { title: 'Crime and Punishment', 'published-safe': 1961 }, + { title: 'Dune', 'published-safe': 1965 } + ); + }); + + it('logical min works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + logicalMinimum(constant(1960), field('published'), 1961).as( + 'published-safe' + ) + ) + .sort(field('title').ascending()) + .limit(3) + ); + expectResults( + snapshot, + { title: '1984', 'published-safe': 1949 }, + { title: 'Crime and Punishment', 'published-safe': 1866 }, + { title: 'Dune', 'published-safe': 1960 } + ); + }); + + it('cond works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + 'title', + cond( + lt(field('published'), 1960), + constant(1960), + field('published') + ).as('published-safe') + ) + .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 } + ); + }); + + it('eqAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eqAny('published', [1979, 1999, 1967])) + .select('title') + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'One Hundred Years of Solitude' } + ); + }); + + it('notEqAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + notEqAny( + 'published', + [1965, 1925, 1949, 1960, 1866, 1985, 1954, 1967, 1979] + ) + ) + .select('title') + ); + expectResults(snapshot, { title: 'Pride and Prejudice' }); + }); + + it('arrayContains works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContains('tags', 'comedy')) + .select('title') + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('arrayContainsAny works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(arrayContainsAny('tags', ['comedy', 'classic'])) + .select('title') + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'Pride and Prejudice' } + ); + }); + + it('arrayContainsAll works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(field('tags').arrayContainsAll(['adventure', 'magic'])) + .select('title') + ); + expectResults(snapshot, { title: 'The Lord of the Rings' }); + }); + + it('arrayLength works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(field('tags').arrayLength().as('tagsCount')) + .where(eq('tagsCount', 3)) + ); + expect(snapshot.results.length).to.equal(10); + }); + + it('testStrConcat', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + field('author').strConcat(' - ', field('title')).as('bookInfo') + ) + .limit(1) + ); + expectResults(snapshot, { + bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('testStartsWith', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(startsWith('title', 'The')) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: 'The Great Gatsby' }, + { title: "The Handmaid's Tale" }, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'The Lord of the Rings' } + ); + }); + + it('testEndsWith', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(endsWith('title', 'y')) + .select('title') + .sort(field('title').descending()) + ); + expectResults( + snapshot, + { title: "The Hitchhiker's Guide to the Galaxy" }, + { title: 'The Great Gatsby' } + ); + }); + + it('testStrContains', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(strContains('title', "'s")) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: "The Handmaid's Tale" }, + { title: "The Hitchhiker's Guide to the Galaxy" } + ); + }); + + it('testLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(field('title').charLength().as('titleLength'), field('title')) + .where(gt('titleLength', 20)) + .sort(field('title').ascending()) + ); + + expectResults( + snapshot, + + { + titleLength: 29, + title: 'One Hundred Years of Solitude' + }, + { + titleLength: 36, + title: "The Hitchhiker's Guide to the Galaxy" + }, + { + titleLength: 21, + title: 'The Lord of the Rings' + }, + { + titleLength: 21, + title: 'To Kill a Mockingbird' + } + ); + }); + + it('testLike', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(like('title', '%Guide%')) + .select('title') + ); + expectResults(snapshot, { + title: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('testRegexContains', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(regexContains('title', '(?i)(the|of)')) + ); + expect(snapshot.results.length).to.equal(5); + }); + + it('testRegexMatches', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(regexMatch('title', '.*(?i)(the|of).*')) + ); + expect(snapshot.results.length).to.equal(5); + }); + + it('testArithmeticOperations', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + add(field('rating'), 1).as('ratingPlusOne'), + subtract(field('published'), 1900).as('yearsSince1900'), + field('rating').multiply(10).as('ratingTimesTen'), + field('rating').divide(2).as('ratingDividedByTwo'), + multiply('rating', 10, 2).as('ratingTimes20'), + add('rating', 1, 2).as('ratingPlus3'), + mod('rating', 2).as('ratingMod2') + ) + .limit(1) + ); + expectResults(snapshot, { + ratingPlusOne: 5.2, + yearsSince1900: 79, + ratingTimesTen: 42, + ratingDividedByTwo: 2.1, + ratingTimes20: 84, + ratingPlus3: 7.2, + ratingMod2: 0.2 + }); + }); + + it('testComparisonOperators', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + and( + gt('rating', 4.2), + lte(field('rating'), 4.5), + neq('genre', 'Science Fiction') + ) + ) + .select('rating', 'title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { rating: 4.3, title: 'Crime and Punishment' }, + { + rating: 4.3, + title: 'One Hundred Years of Solitude' + }, + { rating: 4.5, title: 'Pride and Prejudice' } + ); + }); + + it('testLogicalOperators', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + or( + and(gt('rating', 4.5), eq('genre', 'Science Fiction')), + lt('published', 1900) + ) + ) + .select('title') + .sort(field('title').ascending()) + ); + expectResults( + snapshot, + { title: 'Crime and Punishment' }, + { title: 'Dune' }, + { title: 'Pride and Prejudice' } + ); + }); + + it('testChecks', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + isNull('rating').as('ratingIsNull'), + isNan('rating').as('ratingIsNaN'), + isError(arrayOffset('title', 0)).as('isError'), + ifError(arrayOffset('title', 0), constant('was error')).as( + 'ifError' + ), + isAbsent('foo').as('isAbsent'), + isNotNull('title').as('titleIsNotNull'), + isNotNan('cost').as('costIsNotNan'), + exists('fooBarBaz').as('fooBarBazExists'), + field('title').as('titleExists') + ) + ); + expectResults(snapshot, { + ratingIsNull: false, + ratingIsNaN: false, + isError: true, + ifError: 'was error', + isAbsent: true, + titleIsNotNull: true, + costIsNotNan: false, + fooBarBazExists: false, + titleExists: true + }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + field('rating').isNull().as('ratingIsNull'), + field('rating').isNan().as('ratingIsNaN'), + arrayOffset('title', 0).isError().as('isError'), + arrayOffset('title', 0) + .ifError(constant('was error')) + .as('ifError'), + field('foo').isAbsent().as('isAbsent'), + field('title').isNotNull().as('titleIsNotNull'), + field('cost').isNotNan().as('costIsNotNan') + ) + ); + expectResults(snapshot, { + ratingIsNull: false, + ratingIsNaN: false, + isError: true, + ifError: 'was error', + isAbsent: true, + titleIsNotNull: true, + costIsNotNan: false + }); + }); + + it('testMapGet', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('published').descending()) + .select( + field('awards').mapGet('hugo').as('hugoAward'), + field('awards').mapGet('others').as('others'), + field('title') + ) + .where(eq('hugoAward', true)) + ); + expectResults( + snapshot, + { + hugoAward: true, + title: "The Hitchhiker's Guide to the Galaxy", + others: { unknown: { year: 1980 } } + }, + { hugoAward: true, title: 'Dune', others: null } + ); + }); + + it('testDistanceFunctions', async () => { + const sourceVector = [0.1, 0.1]; + const targetVector = [0.5, 0.8]; + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + cosineDistance(constantVector(sourceVector), targetVector).as( + 'cosineDistance' + ), + dotProduct(constantVector(sourceVector), targetVector).as( + 'dotProductDistance' + ), + euclideanDistance(constantVector(sourceVector), targetVector).as( + 'euclideanDistance' + ) + ) + .limit(1) + ); + + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855 + }); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + constantVector(sourceVector) + .cosineDistance(targetVector) + .as('cosineDistance'), + constantVector(sourceVector) + .dotProduct(targetVector) + .as('dotProductDistance'), + constantVector(sourceVector) + .euclideanDistance(targetVector) + .as('euclideanDistance') + ) + .limit(1) + ); + + expectResults(snapshot, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855 + }); + }); + + it('testVectorLength', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(vectorLength(constantVector([1, 2, 3])).as('vectorLength')) + ); + expectResults(snapshot, { + vectorLength: 3 + }); + }); + + it('testNestedFields', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('awards.hugo', true)) + .select('title', 'awards.hugo') + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + 'awards.hugo': true + }, + { title: 'Dune', 'awards.hugo': true } + ); + }); + + it('test mapGet with field name including . notation', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('awards.hugo', true)) + .select( + 'title', + field('nestedField.level.1'), + mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') + ) + ); + expectResults( + snapshot, + { + title: "The Hitchhiker's Guide to the Galaxy", + 'nestedField.level.`1`': null, + nested: true + }, + { title: 'Dune', 'nestedField.level.`1`': null, nested: null } + ); + }); + + describe('genericFunction', () => { + it('add selectable', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(descending('rating')) + .limit(1) + .select( + new FunctionExpr('add', [field('rating'), constant(1)]).as( + 'rating' + ) + ) + ); + expectResults(snapshot, { + rating: 5.7 + }); + }); + + it('and (variadic) selectable', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + new BooleanExpr('and', [ + field('rating').gt(0), + field('title').charLength().lt(5), + field('tags').arrayContains('propaganda') + ]) + ) + .select('title') + ); + expectResults(snapshot, { + title: '1984' + }); + }); + + it('array contains any', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where( + new BooleanExpr('array_contains_any', [ + field('tags'), + array(['politics']) + ]) + ) + .select('title') + ); + expectResults(snapshot, { + title: 'Dune' + }); + }); + + it('countif aggregate', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .aggregate( + new AggregateFunction('count_if', [field('rating').gte(4.5)]).as( + 'countOfBest' + ) + ) + ); + expectResults(snapshot, { + countOfBest: 3 + }); + }); + + it('sort by char_len', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort( + new FunctionExpr('char_length', [field('title')]).ascending(), + descending('__name__') + ) + .limit(3) + .select('title') + ); + expectResults( + snapshot, + { + title: '1984' + }, + { + title: 'Dune' + }, + { + title: 'The Great Gatsby' + } + ); + }); + }); + + describe('not implemented in backend', () => { + it('supports Bit_and', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(bitAnd(constant(5), 12).as('result')) + ); + expectResults(snapshot, { + result: 4 + }); + }); + + it('supports Bit_and', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(5).bitAnd(12).as('result')) + ); + expectResults(snapshot, { + result: 4 + }); + }); + + it('supports Bit_or', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(bitOr(constant(5), 12).as('result')) + ); + expectResults(snapshot, { + result: 13 + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(5).bitOr(12).as('result')) + ); + expectResults(snapshot, { + result: 13 + }); + }); + + it('supports Bit_xor', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(bitXor(constant(5), 12).as('result')) + ); + expectResults(snapshot, { + result: 9 + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(5).bitXor(12).as('result')) + ); + expectResults(snapshot, { + result: 9 + }); + }); + + it('supports Bit_not', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + bitNot(constant(Bytes.fromUint8Array(Uint8Array.of(0xfd)))).as( + 'result' + ) + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x02)) + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(Bytes.fromUint8Array(Uint8Array.of(0xfd))) + .bitNot() + .as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x02)) + }); + }); + + it('supports Bit_left_shift', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + bitLeftShift( + constant(Bytes.fromUint8Array(Uint8Array.of(0x02))), + 2 + ).as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x04)) + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(Bytes.fromUint8Array(Uint8Array.of(0x02))) + .bitLeftShift(2) + .as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x04)) + }); + }); + + it('supports Bit_right_shift', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + bitRightShift( + constant(Bytes.fromUint8Array(Uint8Array.of(0x02))), + 2 + ).as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x01)) + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + constant(Bytes.fromUint8Array(Uint8Array.of(0x02))) + .bitRightShift(2) + .as('result') + ) + ); + expectResults(snapshot, { + result: Bytes.fromUint8Array(Uint8Array.of(0x01)) + }); + }); + + it('supports Document_id', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(documentId(field('__path__')).as('docId')) + ); + expectResults(snapshot, { + docId: 'book4' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('__path__').documentId().as('docId')) + ); + expectResults(snapshot, { + docId: 'book4' + }); + }); + + it('supports Substr', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substr('title', 9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('title').substr(9, 2).as('of')) + ); + expectResults(snapshot, { + of: 'of' + }); + }); + + it('supports Substr without length', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(substr('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').substr(9).as('of')) + ); + expectResults(snapshot, { + of: 'of the Rings' + }); + }); + + it('arrayConcat works', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select( + arrayConcat('tags', ['newTag1', 'newTag2'], field('tags'), [ + null + ]).as('modifiedTags') + ) + .limit(1) + ); + expectResults(snapshot, { + modifiedTags: [ + 'comedy', + 'space', + 'adventure', + 'newTag1', + 'newTag2', + 'comedy', + 'space', + 'adventure', + null + ] + }); + }); + + it('testToLowercase', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(field('title').toLower().as('lowercaseTitle')) + .limit(1) + ); + expectResults(snapshot, { + lowercaseTitle: "the hitchhiker's guide to the galaxy" + }); + }); + + it('testToUppercase', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .select(field('author').toUpper().as('uppercaseAuthor')) + .limit(1) + ); + expectResults(snapshot, { uppercaseAuthor: 'DOUGLAS ADAMS' }); + }); + + it('testTrim', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .addFields( + constant(" The Hitchhiker's Guide to the Galaxy ").as( + 'spacedTitle' + ) + ) + .select( + field('spacedTitle').trim().as('trimmedTitle'), + field('spacedTitle') + ) + .limit(1) + ); + expectResults(snapshot, { + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + trimmedTitle: "The Hitchhiker's Guide to the Galaxy" + }); + }); + + it('test reverse', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .where(eq('title', '1984')) + .limit(1) + .select(reverse('title').as('reverseTitle')) + ); + expectResults(snapshot, { title: '4891' }); + }); + }); + + it('supports Rand', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(10) + .select(rand().as('result')) + ); + expect(snapshot.results.length).to.equal(10); + snapshot.results.forEach(d => { + expect(d.get('result')).to.be.lt(1); + expect(d.get('result')).to.be.gte(0); + }); + }); + + it('supports array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(array([1, 2, 3, 4]).as('metadata')) + ); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 3, 4] + }); + }); + + it('evaluates expression in array', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + array([1, 2, field('genre'), multiply('rating', 10)]).as('metadata') + ) + ); + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: [1, 2, 'Fantasy', 47] + }); + }); + + it('supports arrayOffset', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(arrayOffset('tags', 0).as('firstTag')) + ); + const expectedResults = [ + { + firstTag: 'adventure' + }, + { + firstTag: 'politics' + }, + { + firstTag: 'classic' + } + ]; + expectResults(snapshot, ...expectedResults); + + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(3) + .select(field('tags').arrayOffset(0).as('firstTag')) + ); + expectResults(snapshot, ...expectedResults); + }); + + // TODO: current_context tests with are failing because of b/395937453 + it('supports currentContext', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(currentContext().as('currentContext')) + ); + expectResults(snapshot, { + currentContext: 'TODO' + }); + }); + + it('supports map', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + foo: 'bar' + }).as('metadata') + ) + ); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + foo: 'bar' + } + }); + }); + + it('evaluates expression in map', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select( + map({ + genre: field('genre'), + rating: field('rating').multiply(10) + }).as('metadata') + ) + ); + + expect(snapshot.results.length).to.equal(1); + expectResults(snapshot, { + metadata: { + genre: 'Fantasy', + rating: 47 + } + }); + }); + + it('supports mapRemove', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapRemove('awards', 'hugo').as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false } + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapRemove('hugo').as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false } + }); + }); + + it('supports mapMerge', async () => { + let snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(mapMerge('awards', { fakeAward: true }).as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false, hugo: false, fakeAward: true } + }); + snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .sort(field('rating').descending()) + .limit(1) + .select(field('awards').mapMerge({ fakeAward: true }).as('awards')) + ); + expectResults(snapshot, { + awards: { nebula: false, hugo: false, fakeAward: true } + }); + }); + + it('supports timestamp conversions', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select( + unixSecondsToTimestamp(constant(1741380235)).as( + 'unixSecondsToTimestamp' + ), + unixMillisToTimestamp(constant(1741380235123)).as( + 'unixMillisToTimestamp' + ), + unixMicrosToTimestamp(constant(1741380235123456)).as( + 'unixMicrosToTimestamp' + ), + timestampToUnixSeconds( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixSeconds'), + timestampToUnixMicros( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixMicros'), + timestampToUnixMillis( + constant(new Timestamp(1741380235, 123456789)) + ).as('timestampToUnixMillis') + ) + ); + expectResults(snapshot, { + unixMicrosToTimestamp: new Timestamp(1741380235, 123456000), + unixMillisToTimestamp: new Timestamp(1741380235, 123000000), + unixSecondsToTimestamp: new Timestamp(1741380235, 0), + timestampToUnixSeconds: 1741380235, + timestampToUnixMicros: 1741380235123456, + timestampToUnixMillis: 1741380235123 + }); + }); + + it('supports timestamp math', async () => { + const snapshot = await execute( + firestore + .pipeline() + .collection(randomCol.path) + .limit(1) + .select(constant(new Timestamp(1741380235, 0)).as('timestamp')) + .select( + timestampAdd('timestamp', 'day', 10).as('plus10days'), + timestampAdd('timestamp', 'hour', 10).as('plus10hours'), + timestampAdd('timestamp', 'minute', 10).as('plus10minutes'), + timestampAdd('timestamp', 'second', 10).as('plus10seconds'), + timestampAdd('timestamp', 'microsecond', 10).as('plus10micros'), + timestampAdd('timestamp', 'millisecond', 10).as('plus10millis'), + timestampSub('timestamp', 'day', 10).as('minus10days'), + timestampSub('timestamp', 'hour', 10).as('minus10hours'), + timestampSub('timestamp', 'minute', 10).as('minus10minutes'), + timestampSub('timestamp', 'second', 10).as('minus10seconds'), + timestampSub('timestamp', 'microsecond', 10).as('minus10micros'), + timestampSub('timestamp', 'millisecond', 10).as('minus10millis') + ) + ); + expectResults(snapshot, { + plus10days: new Timestamp(1742244235, 0), + plus10hours: new Timestamp(1741416235, 0), + plus10minutes: new Timestamp(1741380835, 0), + plus10seconds: new Timestamp(1741380245, 0), + plus10micros: new Timestamp(1741380235, 10000), + plus10millis: new Timestamp(1741380235, 10000000), + minus10days: new Timestamp(1740516235, 0), + minus10hours: new Timestamp(1741344235, 0), + minus10minutes: new Timestamp(1741379635, 0), + minus10seconds: new Timestamp(1741380225, 0), + minus10micros: new Timestamp(1741380234, 999990000), + minus10millis: new Timestamp(1741380234, 990000000) + }); + }); + }); + + describe('pagination', () => { + /** + * Adds several books to the test collection. These + * additional books support pagination test scenarios + * that would otherwise not be possible with the original + * set of books. + * @param collectionReference + */ + async function addBooks( + collectionReference: CollectionReference + ): Promise { + await setDoc(doc(collectionReference, 'book11'), { + title: 'Jonathan Strange & Mr Norrell', + author: 'Susanna Clarke', + genre: 'Fantasy', + published: 2004, + rating: 4.6, + tags: ['historical fantasy', 'magic', 'alternate history', 'england'], + awards: { hugo: false, nebula: false } + }); + await setDoc(doc(collectionReference, 'book12'), { + title: 'The Master and Margarita', + author: 'Mikhail Bulgakov', + genre: 'Satire', + published: 1967, // Though written much earlier + rating: 4.6, + tags: [ + 'russian literature', + 'supernatural', + 'philosophy', + 'dark comedy' + ], + awards: {} + }); + await setDoc(doc(collectionReference, 'book13'), { + title: 'A Long Way to a Small, Angry Planet', + author: 'Becky Chambers', + genre: 'Science Fiction', + published: 2014, + rating: 4.6, + tags: ['space opera', 'found family', 'character-driven', 'optimistic'], + awards: { hugo: false, nebula: false, kitschies: true } + }); + } + + 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: 'Jonathan Strange & Mr Norrell', rating: 4.6 } + ); + + const lastDoc = snapshot.results[snapshot.results.length - 1]; + + snapshot = await execute( + pipeline + .where( + or( + and( + field('rating').eq(lastDoc.get('rating')), + field('__path__').gt(lastDoc.ref?.id) + ), + field('rating').lt(lastDoc.get('rating')) + ) + ) + .limit(pageSize) + ); + expectResults( + snapshot, + { title: 'Pride and Prejudice', rating: 4.5 }, + { title: 'Crime and Punishment', rating: 4.3 } + ); + }); + + it('supports pagination with offsets', async () => { + await addBooks(randomCol); + + const secondFilterField = '__name__'; + + const pipeline = firestore + .pipeline() + .collection(randomCol.path) + .select('title', 'rating', secondFilterField) + .sort( + field('rating').descending(), + field(secondFilterField).ascending() + ); + + const pageSize = 2; + let currPage = 0; + + 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 } + ); + + 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: 'A Long Way to a Small, Angry Planet', + rating: 4.6 + }, + { + title: 'Pride and Prejudice', + rating: 4.5 + } + ); + }); + }); +});