diff --git a/packages/firestore/lite/pipelines/pipelines.ts b/packages/firestore/lite/pipelines/pipelines.ts
index d3e31f5e52b..b8fca507e59 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,6 +79,8 @@ export {
} from '../../src/lite-api/stage';
export {
+ field,
+ constant,
add,
subtract,
multiply,
@@ -132,79 +137,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/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..3fbfbf5f7a6 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,75 @@ 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 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 execute(firestore.pipeline().collection("books")
+ * .where(gt(field("rating"), 4.5))
+ * .select("title", "author", "rating"));
+ * ```
+ *
+ * @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..1d5f98c54ab 100644
--- a/packages/firestore/src/api_pipelines.ts
+++ b/packages/firestore/src/api_pipelines.ts
@@ -17,11 +17,11 @@
export { PipelineSource } from './lite-api/pipeline-source';
-export { PipelineResult } from './lite-api/pipeline-result';
+export { PipelineResult, PipelineSnapshot } 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 +43,8 @@ export {
} from './lite-api/stage';
export {
+ field,
+ constant,
add,
subtract,
multiply,
@@ -104,84 +106,43 @@ 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,
+ documentIdFunction,
+ substr,
+ manhattanDistance,
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';
diff --git a/packages/firestore/src/core/pipeline-util.ts b/packages/firestore/src/core/pipeline-util.ts
index 0800eba85ea..0a4f5d9e0fa 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,
+ BooleanExpr,
andFunction,
orFunction,
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,92 @@ 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 andFunction(fieldValue.exists(), fieldValue.isNan());
} else {
- return andFunction(field.exists(), not(field.isNaN()));
+ return andFunction(fieldValue.exists(), fieldValue.isNotNan());
}
} else if (isNullValue(f.value)) {
if (f.op === Operator.EQUAL) {
- return andFunction(field.exists(), field.eq(null));
+ return andFunction(fieldValue.exists(), fieldValue.eq(null));
} else {
- return andFunction(field.exists(), not(field.eq(null)));
+ return andFunction(fieldValue.exists(), fieldValue.neq(null));
}
} else {
// Comparison filters
const value = f.value;
switch (f.op) {
case Operator.LESS_THAN:
- return andFunction(field.exists(), field.lt(value));
+ return andFunction(
+ fieldValue.exists(),
+ fieldValue.lt(Constant._fromProto(value))
+ );
case Operator.LESS_THAN_OR_EQUAL:
- return andFunction(field.exists(), field.lte(value));
+ return andFunction(
+ fieldValue.exists(),
+ fieldValue.lte(Constant._fromProto(value))
+ );
case Operator.GREATER_THAN:
- return andFunction(field.exists(), field.gt(value));
+ return andFunction(
+ fieldValue.exists(),
+ fieldValue.gt(Constant._fromProto(value))
+ );
case Operator.GREATER_THAN_OR_EQUAL:
- return andFunction(field.exists(), field.gte(value));
+ return andFunction(
+ fieldValue.exists(),
+ fieldValue.gte(Constant._fromProto(value))
+ );
case Operator.EQUAL:
- return andFunction(field.exists(), field.eq(value));
+ return andFunction(
+ fieldValue.exists(),
+ fieldValue.eq(Constant._fromProto(value))
+ );
case Operator.NOT_EQUAL:
- return andFunction(field.exists(), field.neq(value));
+ return andFunction(
+ fieldValue.exists(),
+ fieldValue.neq(Constant._fromProto(value))
+ );
case Operator.ARRAY_CONTAINS:
- return andFunction(field.exists(), field.arrayContains(value));
+ return andFunction(
+ 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 fieldValue.exists();
+ } else if (values.length == 1) {
+ return andFunction(fieldValue.exists(), fieldValue.eq(values[0]));
+ } else {
+ return andFunction(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(
+ fieldValue.exists(),
+ fieldValue.arrayContainsAny(values!)
);
- return andFunction(field.exists(), field.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 fieldValue.exists();
+ } else if (values.length == 1) {
+ return andFunction(fieldValue.exists(), fieldValue.neq(values[0]));
+ } else {
+ return andFunction(fieldValue.exists(), fieldValue.notEqAny(values));
+ }
}
default:
fail('Unexpected operator');
@@ -114,16 +148,16 @@ 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 andFunction(
+ 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 orFunction(conditions[0], conditions[1], ...conditions.slice(2));
}
default:
fail('Unexpected operator');
@@ -155,17 +189,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))
+ andFunction(
+ existsConditions[0],
+ existsConditions[1],
+ ...existsConditions.slice(2)
+ )
);
} else {
pipeline = pipeline.where(existsConditions[0]);
@@ -173,42 +211,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, 'before')
+ );
+ }
- if (query.endAt !== null) {
- pipeline = pipeline.where(
- whereConditionsFromCursor(query.endAt, orderings, 'after')
- );
- }
+ if (query.endAt !== null) {
+ pipeline = pipeline.where(
+ whereConditionsFromCursor(query.endAt, orderings, 'after')
+ );
+ }
- 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 +260,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) {
return filterInclusiveFunc(orderings[index].expr as Field, cursor);
} else {
return filterFunc(orderings[index].expr as Field, cursor);
@@ -241,13 +282,19 @@ function whereConditionsFromCursor(
if (conditions.length === 1) {
orConditions.push(conditions[0]);
} else {
- orConditions.push(new And(conditions));
+ orConditions.push(
+ andFunction(conditions[0], conditions[1], ...conditions.slice(2))
+ );
}
}
if (orConditions.length === 1) {
return orConditions[0];
} else {
- return new Or(orConditions);
+ return orFunction(
+ 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..651a682eb96 100644
--- a/packages/firestore/src/lite-api/expressions.ts
+++ b/packages/firestore/src/lite-api/expressions.ts
@@ -25,16 +25,19 @@ 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 { GeoPoint } from './geo_point';
-import { Pipeline } from './pipeline';
import { DocumentReference } from './reference';
import { Timestamp } from './timestamp';
import {
@@ -54,9 +57,64 @@ 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: any): Expr {
+ if (value instanceof Expr) {
+ return value;
+ } else if (isPlainObject(value)) {
+ return map(value);
+ } else if (value instanceof Array) {
+ return array(value);
+ } else {
+ return constant(value);
+ }
+}
+
+/**
+ * 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 {
+ return constantVector(value);
+ }
+}
+
+/**
+ * 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 fieldOfOrExpr(value: any): Expr {
+ if (isString(value)) {
+ return field(value);
+ } else {
+ return valueToDefaultExpr(value);
+ }
+}
+
/**
* @beta
*
@@ -69,44 +127,44 @@ 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.
+ * @private
+ * @internal
+ */
+ abstract _toProto(serializer: JsonProtoSerializer): ProtoValue;
+ _protoValueType = 'ProtoValue' as const;
+
+ /**
+ * @private
+ * @internal
*/
- add(other: Expr): Add;
+ abstract _readUserData(dataReader: UserDataReader): 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 | any, ...others: Array): FunctionExpr {
+ const values = [second, ...others];
+ return new FunctionExpr('add', [
+ this,
+ ...values.map(value => valueToDefaultExpr(value))
+ ]);
}
/**
@@ -114,31 +172,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: any): FunctionExpr;
+ subtract(other: any): FunctionExpr {
+ return new FunctionExpr('subtract', [this, valueToDefaultExpr(other)]);
}
/**
@@ -146,31 +201,19 @@ 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 | any, ...others: Array): FunctionExpr {
+ return new FunctionExpr('multiply', [
+ this,
+ valueToDefaultExpr(second),
+ ...others.map(value => valueToDefaultExpr(value))
+ ]);
}
/**
@@ -178,31 +221,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: any): FunctionExpr;
+ divide(other: any): FunctionExpr {
+ return new FunctionExpr('divide', [this, valueToDefaultExpr(other)]);
}
/**
@@ -210,237 +250,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: any): FunctionExpr;
+ mod(other: any): 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: any): BooleanExpr;
+ eq(other: any): BooleanExpr {
+ return new BooleanExpr('eq', [this, valueToDefaultExpr(other)]);
}
/**
@@ -448,31 +308,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: any): BooleanExpr;
+ neq(other: any): BooleanExpr {
+ return new BooleanExpr('neq', [this, valueToDefaultExpr(other)]);
}
/**
@@ -480,31 +337,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: any): BooleanExpr;
+ lt(other: any): BooleanExpr {
+ return new BooleanExpr('lt', [this, valueToDefaultExpr(other)]);
}
/**
@@ -513,31 +367,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: any): BooleanExpr;
+ lte(other: any): BooleanExpr {
+ return new BooleanExpr('lte', [this, valueToDefaultExpr(other)]);
}
/**
@@ -545,31 +396,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: any): BooleanExpr;
+ gt(other: any): BooleanExpr {
+ return new BooleanExpr('gt', [this, valueToDefaultExpr(other)]);
}
/**
@@ -578,13 +426,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 +440,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: any): BooleanExpr;
+ gte(other: any): BooleanExpr {
+ return new BooleanExpr('gte', [this, valueToDefaultExpr(other)]);
}
/**
@@ -611,31 +456,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 | any[],
+ ...otherArrays: Array
+ ): FunctionExpr {
+ const elements = [secondArray, ...otherArrays];
+ const exprValues = elements.map(value => valueToDefaultExpr(value));
+ return new FunctionExpr('array_concat', [this, ...exprValues]);
}
/**
@@ -643,63 +476,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: any): BooleanExpr;
+ arrayContains(element: any): 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: any[] | Expr): BooleanExpr {
+ const normalizedExpr = Array.isArray(values)
+ ? new ListOfExprs(values.map(valueToDefaultExpr))
+ : values;
+ return new BooleanExpr('array_contains_all', [this, normalizedExpr]);
}
/**
@@ -707,13 +540,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 +554,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 +573,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 +588,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 +602,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: any[] | Expr): BooleanExpr {
+ const exprOthers = Array.isArray(others)
+ ? new ListOfExprs(others.map(valueToDefaultExpr))
+ : others;
+ return new BooleanExpr('eq_any', [this, exprOthers]);
}
/**
@@ -789,32 +622,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: any[] | Expr): BooleanExpr {
+ const exprOthers = Array.isArray(others)
+ ? new ListOfExprs(others.map(valueToDefaultExpr))
+ : others;
+ return new BooleanExpr('not_eq_any', [this, exprOthers]);
}
/**
@@ -822,13 +654,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 +682,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 +696,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 +710,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 +740,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 +754,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 +773,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 +805,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 +837,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 +851,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 +870,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 +884,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 +903,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 +917,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 +931,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 +945,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 +966,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 +980,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 +995,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 +1016,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 +1031,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 +1052,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 +1066,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 +1082,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 +1096,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 +1111,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 +1125,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 +1139,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 +1153,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 | any,
+ ...others: Array
+ ): FunctionExpr {
+ const values = [second, ...others];
+ return new FunctionExpr('logical_maximum', [
+ this,
+ ...values.map(valueToDefaultExpr)
+ ]);
}
/**
@@ -1345,31 +1176,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 | any,
+ ...others: Array
+ ): FunctionExpr {
+ const values = [second, ...others];
+ return new FunctionExpr('logical_min', [
+ this,
+ ...values.map(valueToDefaultExpr)
+ ]);
}
/**
@@ -1377,13 +1199,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 +1213,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,97 +1241,90 @@ 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;
+ dotProduct(vector: VectorValue | number[]): FunctionExpr;
+ dotProduct(other: Expr | VectorValue | number[]): FunctionExpr {
+ return new FunctionExpr('dot_product', [this, vectorToExpr(other)]);
+ }
/**
- * Calculates the dot product between two vectors.
+ * Calculates the Euclidean distance 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]);
+ * // Calculate the Euclidean distance between the 'location' field and a target location
+ * field("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 dot product between the two vectors.
+ * @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.
*/
- 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[])
- );
- }
- }
+ 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 calculate with.
+ * @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: Expr): EuclideanDistance;
+ euclideanDistance(vector: VectorValue | number[]): FunctionExpr;
+ euclideanDistance(other: Expr | VectorValue | number[]): FunctionExpr {
+ return new FunctionExpr('euclidean_distance', [this, vectorToExpr(other)]);
+ }
/**
- * Calculates the Euclidean distance between two vectors.
+ * @beta
+ *
+ * Calculates the Manhattan distance between the result of this expression and another VectorValue.
*
* ```typescript
- * // Calculate the Euclidean distance between the 'location' field and a target location
- * Field.of("location").euclideanDistance(new VectorValue([37.7749, -122.4194]));
+ * // Calculate the Manhattan distance between the 'location' field and a target location
+ * field("location").manhattanDistance(new VectorValue([37.7749, -122.4194]));
* ```
*
- * @param other The other vector (as a VectorValue) to compare against.
- * @return A new `Expr` representing the Euclidean distance between the two vectors.
+ * @param vector The other vector (as a VectorValue) to compare against.
+ * @return A new {@code Expr} representing the Manhattan distance between the two vectors.
*/
- euclideanDistance(other: VectorValue): EuclideanDistance;
+ manhattanDistance(vector: VectorValue | number[]): FunctionExpr;
/**
- * Calculates the Euclidean distance between two vectors.
+ * @beta
+ *
+ * Calculates the Manhattan distance between two vector expressions.
*
* ```typescript
- * // Calculate the Euclidean distance between the 'location' field and a target location
- * Field.of("location").euclideanDistance([37.7749, -122.4194]);
+ * // Calculate the Manhattan distance between two vector fields: 'pointA' and 'pointB'
+ * field("pointA").manhattanDistance(field("pointB"));
* ```
*
- * @param other The other vector (as an array of numbers) to compare against.
- * @return A new `Expr` representing the Euclidean distance between the two vectors.
+ * @param vectorExpression The other vector (represented as an Expr) to compare against.
+ * @return A new {@code Expr} representing the Manhattan 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[])
- );
- }
+ manhattanDistance(vectorExpression: Expr): FunctionExpr;
+ manhattanDistance(other: Expr | number[] | VectorValue): FunctionExpr {
+ return new FunctionExpr('manhattan_distance', [this, vectorToExpr(other)]);
}
/**
@@ -1537,13 +1333,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 +1347,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 +1362,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 +1376,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 +1391,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 +1405,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 +1419,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 +1443,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 +1454,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 +1467,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 +1491,7 @@ export abstract class Expr implements ProtoSerializable, UserData {
timestampSub(
unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day',
amount: number
- ): TimestampSub;
+ ): FunctionExpr;
timestampSub(
unit:
| Expr
@@ -1709,131 +1502,587 @@ 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;
+ /**
+ * @beta
+ *
+ * Creates an expression that applies a bitwise AND operation between two expressions.
+ *
+ * ```typescript
+ * // Calculate the bitwise AND of 'field1' and 'field2'.
+ * field("field1").bitAnd(field("field2"));
+ * ```
+ *
+ * @param bitsExpression An expression that returns bits when evaluated.
+ * @return A new {@code Expr} representing the bitwise AND operation.
+ */
+ bitAnd(bitsExpression: Expr): FunctionExpr;
+ bitAnd(bitsOrExpression: number | Expr | Bytes): FunctionExpr {
+ return new FunctionExpr('bit_and', [
+ this,
+ valueToDefaultExpr(bitsOrExpression)
+ ]);
}
/**
- * Creates an {@link Ordering} that sorts documents in descending order based on this expression.
+ * @beta
+ *
+ * Creates an expression that applies a bitwise OR operation between this expression and a constant.
*
* ```typescript
- * // Sort documents by the 'createdAt' field in descending order
- * firestore.pipeline().collection("users")
- * .sort(Field.of("createdAt").descending());
+ * // Calculate the bitwise OR of 'field1' and 0xFF.
+ * field("field1").bitOr(0xFF);
* ```
*
- * @return A new `Ordering` for descending sorting.
+ * @param otherBits A constant representing bits.
+ * @return A new {@code Expr} representing the bitwise OR operation.
*/
- descending(): Ordering {
- return descending(this);
+ 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)
+ ]);
}
/**
- * 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 XOR 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 XOR of 'field1' and 0xFF.
+ * field("field1").bitXor(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 XOR operation.
*/
- as(name: string): ExprWithAlias {
- return new ExprWithAlias(this, name);
+ bitXor(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'.
+ * field("field1").bitXor(field("field2"));
+ * ```
+ *
+ * @param bitsExpression An expression that returns bits when evaluated.
+ * @return A new {@code Expr} representing the bitwise XOR operation.
+ */
+ 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]);
+ }
/**
- * @private
- * @internal
+ * @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.
*/
- 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;
-
+ bitLeftShift(y: number): FunctionExpr;
/**
- * @private
- * @internal
+ * @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.
*/
- abstract _toProto(serializer: JsonProtoSerializer): ProtoValue;
+ 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: any): FunctionExpr;
+ ifError(catchValue: any): 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();
+ 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);
}
+ /**
+ * @private
+ * @internal
+ */
+ _toProto(serializer: JsonProtoSerializer): ProtoValue {
+ return {
+ functionValue: {
+ name: this.name,
+ args: this.params.map(p => p._toProto(serializer))
+ }
+ };
+ }
+
+ _protoValueType = 'ProtoValue' as const;
+
+ /**
+ * @private
+ * @internal
+ */
+ _readUserData(dataReader: UserDataReader): void {
+ this.params.forEach(expr => {
+ return expr._readUserData(dataReader);
+ });
+ }
+}
+
+/**
+ * @beta
+ *
+ * An AggregateFunction with alias.
+ */
+export class AggregateWithAlias
+ implements UserData, ProtoSerializable
+{
+ constructor(readonly aggregate: AggregateFunction, readonly alias: string) {}
+
/**
* @private
* @internal
@@ -1842,6 +2091,24 @@ export class ExprWithAlias extends Selectable {
throw new Error('ExprWithAlias should not be serialized directly.');
}
+ /**
+ * @private
+ * @internal
+ */
+ _readUserData(dataReader: UserDataReader): void {
+ this.aggregate._readUserData(dataReader);
+ }
+}
+
+/**
+ * @beta
+ */
+export class ExprWithAlias implements Selectable, UserData {
+ exprType: ExprType = 'ExprWithAlias';
+ selectable = true as const;
+
+ constructor(readonly expr: Expr, readonly alias: string) {}
+
/**
* @private
* @internal
@@ -1856,6 +2123,7 @@ export class ExprWithAlias extends Selectable {
*/
class ListOfExprs extends Expr {
exprType: ExprType = 'ListOfExprs';
+
constructor(private exprs: Expr[]) {
super();
}
@@ -1893,106 +2161,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 +2199,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 +2207,39 @@ 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(documentId()._internalPath);
+ }
+ return new Field(fieldPathFromArgument('of', nameOrPath));
+ } else {
+ if (documentId().isEqual(nameOrPath)) {
+ return new Field(documentId()._internalPath);
+ }
+ return new Field(nameOrPath._internalPath);
}
}
@@ -2025,158 +2252,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.
- */
- 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.
+ * @hideconstructor
+ * @param value The value of the constant.
*/
- 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: any) {
+ super();
}
/**
@@ -2211,10 +2305,8 @@ export class Constant extends Expr {
'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;
+ if (isFirestoreValue(this._protoValue)) {
+ return;
} else if (this.value === undefined) {
// TODO(pipeline) how should we treat the value of `undefined`?
this._protoValue = parseData(null, context)!;
@@ -2225,790 +2317,1213 @@ 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 an undefined value.
+ * @private
+ * @internal
+ *
+ * @param value The undefined 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: undefined): Constant;
/**
- * @beta
+ * Creates a `Constant` instance for a GeoPoint value.
+ *
+ * @param value The GeoPoint 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: GeoPoint): Constant;
/**
- * @beta
+ * Creates a `Constant` instance for a Timestamp value.
+ *
+ * @param value The Timestamp 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: Timestamp): Constant;
/**
- * @beta
+ * Creates a `Constant` instance for a Date value.
+ *
+ * @param value The Date value.
+ * @return A new `Constant` instance.
*/
-export class Divide extends FirestoreFunction {
- constructor(private left: Expr, private right: Expr) {
- super('divide', [left, right]);
- }
-}
+export function constant(value: Date): Constant;
/**
- * @beta
+ * Creates a `Constant` instance for a Bytes value.
+ *
+ * @param value The Bytes value.
+ * @return A new `Constant` instance.
*/
-export class Mod extends FirestoreFunction {
- constructor(private left: Expr, private right: Expr) {
- super('mod', [left, right]);
- }
-}
+export function constant(value: Bytes): Constant;
-// /**
-// * @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]);
- }
- filterable = true as const;
-}
+/**
+ * Creates a `Constant` instance for a DocumentReference value.
+ *
+ * @param value The DocumentReference value.
+ * @return A new `Constant` instance.
+ */
+export function constant(value: DocumentReference): Constant;
/**
- * @beta
+ * 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 class Neq extends FirestoreFunction implements FilterCondition {
- constructor(private left: Expr, private right: Expr) {
- super('neq', [left, right]);
- }
- filterable = true as const;
+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: any): 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 Lt extends FirestoreFunction implements FilterCondition {
- constructor(private left: Expr, private right: Expr) {
- super('lt', [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[]));
}
- filterable = true as const;
}
/**
- * @beta
+ * Internal only
+ * @internal
+ * @private
*/
-export class Lte extends FirestoreFunction implements FilterCondition {
- constructor(private left: Expr, private right: Expr) {
- super('lte', [left, right]);
+export class MapValue extends Expr {
+ constructor(private plainObject: Map) {
+ super();
+ }
+
+ exprType: ExprType = 'Constant';
+
+ _readUserData(dataReader: UserDataReader): void {
+ this.plainObject.forEach(expr => {
+ expr._readUserData(dataReader);
+ });
+ }
+
+ _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 Gt extends FirestoreFunction implements FilterCondition {
- constructor(private left: Expr, private right: Expr) {
- super('gt', [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 Gte extends FirestoreFunction implements FilterCondition {
- constructor(private left: Expr, private right: Expr) {
- super('gte', [left, right]);
+ /**
+ * @private
+ * @internal
+ */
+ _toProto(serializer: JsonProtoSerializer): ProtoValue {
+ return {
+ functionValue: {
+ name: this.name,
+ args: this.params.map(p => p._toProto(serializer))
+ }
+ };
+ }
+
+ /**
+ * @private
+ * @internal
+ */
+ _readUserData(dataReader: UserDataReader): void {
+ this.params.forEach(expr => {
+ return expr._readUserData(dataReader);
+ });
}
- filterable = true as const;
}
/**
* @beta
+ *
+ * An interface that represents a filter condition.
*/
-export class ArrayConcat extends FirestoreFunction {
- constructor(private array: Expr, private elements: Expr[]) {
- super('array_concat', [array, ...elements]);
+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]);
+ }
+
+ /**
+ * 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]);
}
}
/**
* @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 ArrayReverse extends FirestoreFunction {
- constructor(private array: Expr) {
- super('array_reverse', [array]);
- }
+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 ArrayContains
- extends FirestoreFunction
- implements FilterCondition
-{
- constructor(private array: Expr, private element: Expr) {
- super('array_contains', [array, element]);
- }
- filterable = true as const;
+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 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, 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 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(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 ArrayLength extends FirestoreFunction {
- constructor(private array: Expr) {
- super('array_length', [array]);
- }
-}
-
+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 ArrayElement extends FirestoreFunction {
- constructor() {
- super('array_element', []);
- }
+export function bitAnd(
+ bitsExpression: Expr,
+ otherBitsExpression: Expr
+): FunctionExpr;
+export function bitAnd(
+ fieldOrExpression: string | Expr,
+ bitsOrExpression: number | Expr | Bytes
+): FunctionExpr {
+ return fieldOfOrExpr(fieldOrExpression).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 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, 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 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(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 IsNan extends FirestoreFunction implements FilterCondition {
- constructor(private expr: Expr) {
- super('is_nan', [expr]);
- }
- 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 Exists extends FirestoreFunction implements FilterCondition {
- constructor(private expr: Expr) {
- super('exists', [expr]);
- }
- filterable = true as const;
+export function bitOr(
+ bitsExpression: Expr,
+ otherBitsExpression: Expr
+): FunctionExpr;
+export function bitOr(
+ fieldOrExpression: string | Expr,
+ bitsOrExpression: number | Expr | Bytes
+): FunctionExpr {
+ return fieldOfOrExpr(fieldOrExpression).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 Not extends FirestoreFunction implements FilterCondition {
- constructor(private expr: Expr) {
- super('not', [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 And extends FirestoreFunction implements FilterCondition {
- constructor(private conditions: FilterCondition[]) {
- super('and', conditions);
- }
-
- 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 Or extends FirestoreFunction implements FilterCondition {
- constructor(private conditions: FilterCondition[]) {
- super('or', 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 Xor extends FirestoreFunction implements FilterCondition {
- constructor(private conditions: FilterCondition[]) {
- super('xor', conditions);
- }
- filterable = true as const;
+export function bitXor(
+ bitsExpression: Expr,
+ otherBitsExpression: Expr
+): FunctionExpr;
+export function bitXor(
+ fieldOrExpression: string | Expr,
+ bitsOrExpression: number | Expr | Bytes
+): FunctionExpr {
+ return fieldOfOrExpr(fieldOrExpression).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 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(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 LogicalMaximum extends FirestoreFunction {
- constructor(private left: Expr, private right: Expr) {
- super('logical_maximum', [left, right]);
- }
+export function bitNot(bitsValueExpression: Expr): FunctionExpr;
+export function bitNot(bits: string | Expr): FunctionExpr {
+ return fieldOfOrExpr(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 LogicalMinimum extends FirestoreFunction {
- constructor(private left: Expr, private right: Expr) {
- super('logical_minimum', [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 Reverse extends FirestoreFunction {
- constructor(private value: Expr) {
- super('reverse', [value]);
- }
-}
-
+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 ReplaceFirst extends FirestoreFunction {
- constructor(private value: Expr, private find: Expr, private replace: Expr) {
- super('replace_first', [value, find, replace]);
- }
-}
-
+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 right The right operand expression representing the number of bits to shift.
+ * @return A new {@code Expr} representing the bitwise left 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 bitLeftShift(xValue: Expr, numberExpr: Expr): FunctionExpr;
+export function bitLeftShift(
+ xValue: string | Expr,
+ numberExpr: number | Expr
+): FunctionExpr {
+ return fieldOfOrExpr(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 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 class CharLength extends FirestoreFunction {
- constructor(private value: Expr) {
- super('char_length', [value]);
- }
-}
-
+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 ByteLength extends FirestoreFunction {
- constructor(private value: Expr) {
- super('byte_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 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, 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 RegexContains
- extends FirestoreFunction
- implements FilterCondition
-{
- constructor(private expr: Expr, private pattern: Expr) {
- super('regex_contains', [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 fieldOfOrExpr(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 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, 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 StrContains extends FirestoreFunction implements FilterCondition {
- constructor(private expr: Expr, private substring: Expr) {
- super('str_contains', [expr, substring]);
- }
- 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 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,
+ 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 EndsWith extends FirestoreFunction implements FilterCondition {
- constructor(private expr: Expr, private suffix: Expr) {
- super('ends_with', [expr, suffix]);
- }
- filterable = true as const;
+export function arrayOffset(
+ arrayExpression: Expr,
+ offsetExpr: Expr
+): FunctionExpr;
+export function arrayOffset(
+ array: Expr | string,
+ offset: Expr | number
+): FunctionExpr {
+ return fieldOfOrExpr(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 ToLower extends FirestoreFunction {
- constructor(private expr: Expr) {
- super('to_lower', [expr]);
- }
+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 ToUpper extends FirestoreFunction {
- constructor(private expr: Expr) {
- super('to_upper', [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 Trim extends FirestoreFunction {
- constructor(private expr: Expr) {
- super('trim', [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 StrConcat extends FirestoreFunction {
- constructor(private first: Expr, private rest: Expr[]) {
- super('str_concat', [first, ...rest]);
- }
+export function ifError(tryExpr: Expr, catchValue: any): FunctionExpr;
+export function ifError(tryExpr: Expr, catchValue: any): 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 MapGet extends FirestoreFunction {
- constructor(map: Expr, name: string) {
- super('map_get', [map, Constant.of(name)]);
- }
-}
+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 Count extends FirestoreFunction implements Accumulator {
- accumulator = true as const;
- constructor(private value: Expr | undefined, private distinct: boolean) {
- super('count', value === undefined ? [] : [value]);
- }
+export function isAbsent(field: string): BooleanExpr;
+export function isAbsent(value: Expr | string): BooleanExpr {
+ return fieldOfOrExpr(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 Sum extends FirestoreFunction implements Accumulator {
- accumulator = true as const;
- constructor(private value: Expr, private distinct: boolean) {
- super('sum', [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 Avg extends FirestoreFunction implements Accumulator {
- accumulator = true as const;
- constructor(private value: Expr, private distinct: boolean) {
- super('avg', [value]);
- }
+export function isNull(value: string): BooleanExpr;
+export function isNull(value: Expr | string): BooleanExpr {
+ return fieldOfOrExpr(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 Minimum extends FirestoreFunction implements Accumulator {
- accumulator = true as const;
- constructor(private value: Expr, private distinct: boolean) {
- super('minimum', [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 Maximum extends FirestoreFunction implements Accumulator {
- accumulator = true as const;
- constructor(private value: Expr, private distinct: boolean) {
- super('maximum', [value]);
- }
+export function isNotNull(value: string): BooleanExpr;
+export function isNotNull(value: Expr | string): BooleanExpr {
+ return fieldOfOrExpr(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 CosineDistance extends FirestoreFunction {
- constructor(private vector1: Expr, private vector2: Expr) {
- super('cosine_distance', [vector1, vector2]);
- }
-}
+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 DotProduct extends FirestoreFunction {
- constructor(private vector1: Expr, private vector2: Expr) {
- super('dot_product', [vector1, vector2]);
- }
+export function isNotNan(value: string): BooleanExpr;
+export function isNotNan(value: Expr | string): BooleanExpr {
+ return fieldOfOrExpr(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 EuclideanDistance extends FirestoreFunction {
- constructor(private vector1: Expr, private vector2: Expr) {
- super('euclidean_distance', [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 VectorLength extends FirestoreFunction {
- constructor(private value: Expr) {
- super('vector_length', [value]);
- }
-}
-
+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 UnixMicrosToTimestamp extends FirestoreFunction {
- constructor(private input: Expr) {
- super('unix_micros_to_timestamp', [input]);
- }
-}
-
+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 TimestampToUnixMicros extends FirestoreFunction {
- constructor(private input: Expr) {
- super('timestamp_to_unix_micros', [input]);
- }
+export function mapRemove(mapExpr: Expr, keyExpr: Expr): FunctionExpr;
+
+export function mapRemove(
+ mapExpr: Expr | string,
+ stringExpr: Expr | string
+): FunctionExpr {
+ return fieldOfOrExpr(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 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 fieldOfOrExpr(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 documentIdFunction(
+ 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 documentIdFunction(documentPathExpr: Expr): FunctionExpr;
+
+export function documentIdFunction(
+ 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.
- *
- * ```typescript
- * // Add 5 to the value of the 'age' field
- * add(Field.of("age"), 5);
- * ```
+ * Creates an expression that returns a substring of a string or byte array.
*
- * @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 = fieldOfOrExpr(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 | any,
+ ...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 | any,
+ ...others: Array
+): FunctionExpr;
+
+export function add(
+ first: Expr | string,
+ second: Expr | any,
+ ...others: Array
+): FunctionExpr {
+ return fieldOfOrExpr(first).add(
+ valueToDefaultExpr(second),
+ ...others.map(value => valueToDefaultExpr(value))
+ );
}
/**
@@ -3018,14 +3533,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 +3549,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: any): FunctionExpr;
/**
* @beta
@@ -3050,14 +3565,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 +3584,15 @@ 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: any): FunctionExpr;
+export function subtract(left: Expr | string, right: Expr | any): FunctionExpr {
+ const normalizedLeft = typeof left === 'string' ? field(left) : left;
+ const normalizedRight = valueToDefaultExpr(right);
+ return normalizedLeft.subtract(normalizedRight);
}
/**
@@ -3087,30 +3602,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 | any,
+ ...others: Array
+): FunctionExpr;
/**
* @beta
@@ -3119,34 +3623,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 | any,
+ ...others: Array
+): FunctionExpr;
+
+export function multiply(
+ first: Expr | string,
+ second: Expr | any,
+ ...others: Array
+): FunctionExpr {
+ return fieldOfOrExpr(first).multiply(
+ valueToDefaultExpr(second),
+ ...others.map(valueToDefaultExpr)
+ );
}
/**
@@ -3156,14 +3655,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 +3671,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: any): FunctionExpr;
/**
* @beta
@@ -3188,14 +3687,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 +3706,15 @@ 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: any): FunctionExpr;
+export function divide(left: Expr | string, right: Expr | any): FunctionExpr {
+ const normalizedLeft = typeof left === 'string' ? field(left) : left;
+ const normalizedRight = valueToDefaultExpr(right);
+ return normalizedLeft.divide(normalizedRight);
}
/**
@@ -3225,14 +3724,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 +3740,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: any): FunctionExpr;
/**
* @beta
@@ -3257,14 +3756,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 +3775,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: any): FunctionExpr;
+export function mod(left: Expr | string, right: Expr | any): 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: any[] = [];
+ 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 than 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: any[]): FunctionExpr {
+ return new FunctionExpr(
+ 'array',
+ elements.map(element => valueToDefaultExpr(element))
+ );
+}
/**
* @beta
@@ -3680,14 +3860,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 +3876,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: any): BooleanExpr;
/**
* @beta
@@ -3712,14 +3892,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 +3911,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: any): BooleanExpr;
+export function eq(left: Expr | string, right: any): BooleanExpr {
+ const leftExpr = left instanceof Expr ? left : field(left);
+ const rightExpr = valueToDefaultExpr(right);
+ return leftExpr.eq(rightExpr);
}
/**
@@ -3749,14 +3929,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 +3945,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: any): BooleanExpr;
/**
* @beta
@@ -3781,14 +3961,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 +3980,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: any): BooleanExpr;
+export function neq(left: Expr | string, right: any): BooleanExpr {
+ const leftExpr = left instanceof Expr ? left : field(left);
+ const rightExpr = valueToDefaultExpr(right);
+ return leftExpr.neq(rightExpr);
}
/**
@@ -3818,14 +3998,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 +4014,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: any): BooleanExpr;
/**
* @beta
@@ -3850,14 +4030,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 +4049,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: any): BooleanExpr;
+export function lt(left: Expr | string, right: any): BooleanExpr {
+ const leftExpr = left instanceof Expr ? left : field(left);
+ const rightExpr = valueToDefaultExpr(right);
+ return leftExpr.lt(rightExpr);
}
/**
@@ -3888,14 +4068,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 +4084,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: any): 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 +4117,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: any): BooleanExpr;
+export function lte(left: Expr | string, right: any): BooleanExpr {
+ const leftExpr = left instanceof Expr ? left : field(left);
+ const rightExpr = valueToDefaultExpr(right);
+ return leftExpr.lte(rightExpr);
}
/**
@@ -3956,14 +4136,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 +4152,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: any): BooleanExpr;
/**
* @beta
@@ -3988,14 +4168,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 +4187,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: any): BooleanExpr;
+export function gt(left: Expr | string, right: any): BooleanExpr {
+ const leftExpr = left instanceof Expr ? left : field(left);
+ const rightExpr = valueToDefaultExpr(right);
+ return leftExpr.gt(rightExpr);
}
/**
@@ -4026,14 +4206,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 +4223,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: any): BooleanExpr;
/**
* @beta
@@ -4059,14 +4239,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 +4259,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: any): BooleanExpr;
+export function gte(left: Expr | string, right: any): BooleanExpr {
+ const leftExpr = left instanceof Expr ? left : field(left);
+ const rightExpr = valueToDefaultExpr(right);
+ return leftExpr.gte(rightExpr);
}
/**
@@ -4097,71 +4277,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 | any,
+ ...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 | any[],
+ ...otherArrays: Array
+): FunctionExpr;
+
+export function arrayConcat(
+ firstArray: Expr | string,
+ secondArray: Expr | any[],
+ ...otherArrays: Array
+): FunctionExpr {
+ const exprValues = otherArrays.map(element => valueToDefaultExpr(element));
+ return fieldOfOrExpr(firstArray).arrayConcat(
+ fieldOfOrExpr(secondArray),
+ ...exprValues
);
- return new ArrayConcat(arrayExpr, exprValues);
}
/**
@@ -4171,14 +4331,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 +4347,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: any): FunctionExpr;
/**
* @beta
@@ -4203,14 +4363,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 +4382,15 @@ 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(
- 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);
+export function arrayContains(fieldName: string, element: any): BooleanExpr;
+export function arrayContains(array: Expr | string, element: any): BooleanExpr {
+ const arrayExpr = fieldOfOrExpr(array);
+ const elementExpr = valueToDefaultExpr(element);
+ return arrayExpr.arrayContains(elementExpr);
}
/**
@@ -4244,52 +4401,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 +4460,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: any[] | Expr
+): BooleanExpr {
+ // @ts-ignore implementation accepts both types
+ return fieldOfOrExpr(array).arrayContainsAny(values);
}
/**
@@ -4329,50 +4483,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,27 +4541,24 @@ 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: any[] | Expr
+): BooleanExpr {
+ // @ts-ignore implementation accepts both types
+ return fieldOfOrExpr(array).arrayContainsAll(values);
}
/**
@@ -4411,49 +4568,48 @@ 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 {
+ return 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 +4619,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 +4639,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: any[] | Expr
+): BooleanExpr {
+ // @ts-ignore implementation accepts both types
+ return fieldOfOrExpr(element).eqAny(values);
}
/**
@@ -4504,78 +4663,80 @@ 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: any[] | Expr
+): BooleanExpr {
+ // @ts-ignore implementation accepts both types
+ return fieldOfOrExpr(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 +4747,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 +4769,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 +4778,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 +4795,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 | any,
+ ...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 | any,
+ ...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 | any,
+ ...others: Array
+): FunctionExpr {
+ return fieldOfOrExpr(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 | any,
+ ...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 | any,
+ ...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 | any,
+ ...others: Array
+): FunctionExpr {
+ return fieldOfOrExpr(first).logicalMinimum(
+ valueToDefaultExpr(second),
+ ...others.map(value => valueToDefaultExpr(value))
+ );
}
/**
@@ -4787,13 +4924,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 +4942,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 fieldOfOrExpr(valueOrField).exists();
}
/**
@@ -4822,13 +4957,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 +4975,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 fieldOfOrExpr(value).isNan();
}
/**
@@ -4856,18 +4990,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 +5011,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 fieldOfOrExpr(expr).reverse();
}
/**
@@ -4890,7 +5023,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 +5035,7 @@ export function replaceFirst(
value: Expr,
find: string,
replace: string
-): ReplaceFirst;
+): FunctionExpr;
/**
* @beta
@@ -4912,7 +5045,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 +5057,7 @@ export function replaceFirst(
value: Expr,
find: Expr,
replace: Expr
-): ReplaceFirst;
+): FunctionExpr;
/**
* @beta
@@ -4936,26 +5069,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 = fieldOfOrExpr(value);
+ const normalizedFind = valueToDefaultExpr(find);
+ const normalizedReplace = valueToDefaultExpr(replace);
+ return normalizedValue.replaceFirst(normalizedFind, normalizedReplace);
}
/**
@@ -4965,7 +5097,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 +5109,7 @@ export function replaceAll(
value: Expr,
find: string,
replace: string
-): ReplaceAll;
+): FunctionExpr;
/**
* @beta
@@ -4987,7 +5119,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 +5127,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 +5143,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 = fieldOfOrExpr(value);
+ const normalizedFind = valueToDefaultExpr(find);
+ const normalizedReplace = valueToDefaultExpr(replace);
+ return normalizedValue.replaceAll(normalizedFind, normalizedReplace);
}
/**
@@ -5036,13 +5171,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 +5189,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 = fieldOfOrExpr(expr);
+ return normalizedExpr.byteLength();
}
/**
@@ -5073,10 +5208,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 +5220,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 = fieldOfOrExpr(value);
+ return valueExpr.charLength();
}
/**
@@ -5108,11 +5243,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 +5257,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 +5273,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 +5289,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 = fieldOfOrExpr(left);
+ const patternExpr = valueToDefaultExpr(pattern);
+ return leftExpr.like(patternExpr);
}
/**
@@ -5179,11 +5317,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 +5331,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 +5348,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 +5368,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 = fieldOfOrExpr(left);
+ const patternExpr = valueToDefaultExpr(pattern);
+ return leftExpr.regexContains(patternExpr);
}
/**
@@ -5254,11 +5398,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 +5411,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 +5428,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 +5448,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 = fieldOfOrExpr(left);
+ const patternExpr = valueToDefaultExpr(pattern);
+ return leftExpr.regexMatch(patternExpr);
}
/**
@@ -5328,11 +5475,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 +5488,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 +5504,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 +5523,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 = fieldOfOrExpr(left);
+ const substringExpr = valueToDefaultExpr(substring);
+ return leftExpr.strContains(substringExpr);
}
/**
@@ -5401,11 +5553,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 +5566,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 +5582,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 +5598,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 fieldOfOrExpr(expr).startsWith(valueToDefaultExpr(prefix));
}
/**
@@ -5473,11 +5623,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 +5636,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 +5652,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 +5668,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 fieldOfOrExpr(expr).endsWith(valueToDefaultExpr(suffix));
}
/**
@@ -5542,10 +5693,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 +5705,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 fieldOfOrExpr(expr).toLower();
}
/**
@@ -5575,10 +5726,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 +5738,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 fieldOfOrExpr(expr).toUpper();
}
/**
@@ -5608,10 +5759,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 +5771,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 fieldOfOrExpr(expr).trim();
}
/**
@@ -5638,17 +5789,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 +5809,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 valueToDefaultExpr(first).strConcat(
+ valueToDefaultExpr(second),
+ ...elements.map(valueToDefaultExpr)
+ );
}
/**
@@ -5685,11 +5843,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 +5856,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 fieldOfOrExpr(fieldOrExpr).mapGet(subField);
}
/**
@@ -5719,14 +5877,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,13 +5895,13 @@ 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 countFunction(expression: Expr): AggregateFunction;
/**
* Creates an aggregation that counts the number of stage inputs with valid evaluations of the
@@ -5754,13 +5912,12 @@ export function countFunction(value: Expr): Count;
* 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 countFunction(fieldName: string): AggregateFunction;
+export function countFunction(value: Expr | string): AggregateFunction {
+ return fieldOfOrExpr(value).count();
}
/**
@@ -5771,13 +5928,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 sumFunction(expression: Expr): AggregateFunction;
/**
* @beta
@@ -5790,13 +5947,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 sumFunction(fieldName: string): AggregateFunction;
+export function sumFunction(value: Expr | string): AggregateFunction {
+ return fieldOfOrExpr(value).sum();
}
/**
@@ -5807,13 +5963,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 avgFunction(expression: Expr): AggregateFunction;
/**
* @beta
@@ -5826,13 +5982,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 avgFunction(fieldName: string): AggregateFunction;
+export function avgFunction(value: Expr | string): AggregateFunction {
+ return fieldOfOrExpr(value).avg();
}
/**
@@ -5843,13 +5998,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 +6016,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 fieldOfOrExpr(value).minimum();
}
/**
@@ -5878,13 +6032,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 +6050,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 fieldOfOrExpr(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 +6084,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 +6122,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 = fieldOfOrExpr(expr);
+ const expr2 = vectorToExpr(other);
+ return expr1.cosineDistance(expr2);
}
/**
@@ -6022,215 +6152,248 @@ 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.
+ * @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: number[]): DotProduct;
+export function dotProduct(
+ fieldName: string,
+ vector: number[] | VectorValue
+): FunctionExpr;
/**
* @beta
*
- * Calculates the dot product between a field's vector value and a VectorValue.
+ * Calculates the dot product between a field's vector value and a vector expression.
*
* ```typescript
- * // Calculate the dot product distance between a feature vector and a target vector
- * dotProduct("features", new VectorValue([0.5, 0.8, 0.2]));
+ * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2'
+ * dotProduct("docVector1", field("docVector2"));
* ```
*
- * @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 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: VectorValue): DotProduct;
+export function dotProduct(
+ fieldName: string,
+ vectorExpression: Expr
+): FunctionExpr;
/**
* @beta
*
- * Calculates the dot product between a field's vector value and a vector expression.
+ * Calculates the dot product between a vector expression and a double array.
*
* ```typescript
- * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2'
- * dotProduct("docVector1", Field.of("docVector2"));
+ * // Calculate the dot product between a feature vector and a target vector
+ * dotProduct(field("features"), [0.5, 0.8, 0.2]);
* ```
*
- * @param expr The name of the field containing the first vector.
- * @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 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: Expr): DotProduct;
+export function dotProduct(
+ vectorExpression: Expr,
+ vector: number[] | VectorValue
+): FunctionExpr;
/**
* @beta
*
- * Calculates the dot product between a vector expression and a double array.
+ * Calculates the dot product between two vector expressions.
*
* ```typescript
- * // Calculate the dot product between a feature vector and a target vector
- * dotProduct(Field.of("features"), [0.5, 0.8, 0.2]);
+ * // Calculate the dot product between two document vectors: 'docVector1' and 'docVector2'
+ * dotProduct(field("docVector1"), field("docVector2"));
* ```
*
- * @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.
+ * @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: number[]): DotProduct;
+export function dotProduct(
+ vectorExpression: Expr,
+ otherVectorExpression: Expr
+): FunctionExpr;
+export function dotProduct(
+ expr: Expr | string,
+ other: Expr | number[] | VectorValue
+): FunctionExpr {
+ const expr1 = fieldOfOrExpr(expr);
+ const expr2 = vectorToExpr(other);
+ return expr1.dotProduct(expr2);
+}
/**
* @beta
*
- * Calculates the dot product between a vector expression and a VectorValue.
+ * Calculates the Euclidean distance between a field's vector value and a double array.
*
* ```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]));
+ * // Calculate the Euclidean distance between the 'location' field and a target location
+ * euclideanDistance("location", [37.7749, -122.4194]);
* ```
*
- * @param expr The first vector (represented as an Expr) to calculate with.
- * @param other The other vector (as a VectorValue) to calculate with.
- * @return A new {@code Expr} representing the dot product between the two vectors.
+ * @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 dotProduct(expr: Expr, other: VectorValue): DotProduct;
+export function euclideanDistance(
+ fieldName: string,
+ vector: number[] | VectorValue
+): FunctionExpr;
/**
* @beta
*
- * Calculates the dot product between two vector expressions.
+ * Calculates the Euclidean distance between a field's vector value and a vector expression.
*
* ```typescript
- * // Calculate the dot product between two document vectors: 'docVector1' and 'docVector2'
- * dotProduct(Field.of("docVector1"), Field.of("docVector2"));
+ * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB'
+ * euclideanDistance("pointA", field("pointB"));
* ```
*
- * @param expr The first vector (represented as an Expr) to calculate with.
- * @param other The other vector (represented as an Expr) to calculate with.
- * @return A new {@code Expr} representing the dot product between the two vectors.
+ * @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 dotProduct(expr: Expr, other: Expr): DotProduct;
-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);
-}
+export function euclideanDistance(
+ fieldName: string,
+ vectorExpression: Expr
+): FunctionExpr;
/**
* @beta
*
- * Calculates the Euclidean distance between a field's vector value and a double array.
+ * Calculates the Euclidean distance between a vector expression and a double array.
*
* ```typescript
* // Calculate the Euclidean distance between the 'location' field and a target location
- * euclideanDistance("location", [37.7749, -122.4194]);
+ *
+ * euclideanDistance(field("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.
+ * @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: string,
- other: number[]
-): EuclideanDistance;
+ vectorExpression: Expr,
+ vector: number[] | VectorValue
+): FunctionExpr;
/**
* @beta
*
- * Calculates the Euclidean distance between a field's vector value and a VectorValue.
+ * Calculates the Euclidean distance between two vector expressions.
*
* ```typescript
- * // Calculate the Euclidean distance between the 'location' field and a target location
- * euclideanDistance("location", new VectorValue([37.7749, -122.4194]));
+ * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB'
+ * euclideanDistance(field("pointA"), field("pointB"));
* ```
*
- * @param expr The name of the field containing the first vector.
- * @param other The other vector (as a VectorValue) 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: string,
- other: VectorValue
-): EuclideanDistance;
+ vectorExpression: Expr,
+ otherVectorExpression: Expr
+): FunctionExpr;
+export function euclideanDistance(
+ expr: Expr | string,
+ other: Expr | number[] | VectorValue
+): FunctionExpr {
+ const expr1 = fieldOfOrExpr(expr);
+ const expr2 = vectorToExpr(other);
+ return expr1.euclideanDistance(expr2);
+}
/**
* @beta
*
- * Calculates the Euclidean distance between a field's vector value and a vector expression.
+ * Calculates the Manhattan distance between a field's vector value and a double array.
*
* ```typescript
- * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB'
- * euclideanDistance("pointA", Field.of("pointB"));
+ * // Calculate the Manhattan distance between the 'location' field and a target location
+ * manhattanDistance("location", [37.7749, -122.4194]);
* ```
*
- * @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 Euclidean distance between the two vectors.
+ * @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 Manhattan distance between the two vectors.
*/
-export function euclideanDistance(expr: string, other: Expr): EuclideanDistance;
+export function manhattanDistance(
+ fieldName: string,
+ vector: number[] | VectorValue
+): FunctionExpr;
/**
* @beta
*
- * Calculates the Euclidean distance between a vector expression and a double array.
+ * Calculates the Manhattan distance between a field's vector value and a vector expression.
*
* ```typescript
- * // Calculate the Euclidean distance between the 'location' field and a target location
- *
- * euclideanDistance(Field.of("location"), [37.7749, -122.4194]);
+ * // Calculate the Manhattan distance between two vector fields: 'pointA' and 'pointB'
+ * manhattanDistance("pointA", field("pointB"));
* ```
*
- * @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.
+ * @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 Manhattan distance between the two vectors.
*/
-export function euclideanDistance(
- expr: Expr,
- other: number[]
-): EuclideanDistance;
+export function manhattanDistance(
+ fieldName: string,
+ vectorExpression: Expr
+): FunctionExpr;
/**
* @beta
*
- * Calculates the Euclidean distance between a vector expression and a VectorValue.
+ * Calculates the Manhattan distance between a vector expression and a double array.
*
* ```typescript
- * // Calculate the Euclidean distance between the 'location' field and a target location
- * euclideanDistance(Field.of("location"), new VectorValue([37.7749, -122.4194]));
+ * // Calculate the Manhattan distance between the 'location' field and a target location
+ *
+ * manhattanDistance(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.
- * @return A new {@code Expr} representing the Euclidean distance between the two vectors.
+ * @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 Manhattan distance between the two vectors.
*/
-export function euclideanDistance(
- expr: Expr,
- other: VectorValue
-): EuclideanDistance;
+export function manhattanDistance(
+ vectorExpression: Expr,
+ vector: number[] | VectorValue
+): FunctionExpr;
/**
* @beta
*
- * Calculates the Euclidean distance between two vector expressions.
+ * Calculates the Manhattan distance between two vector expressions.
*
* ```typescript
- * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB'
- * euclideanDistance(Field.of("pointA"), Field.of("pointB"));
+ * // Calculate the Manhattan distance between two vector fields: 'pointA' and 'pointB'
+ * manhattanDistance(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.
- * @return A new {@code Expr} representing the Euclidean distance between the two vectors.
+ * @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 Manhattan distance between the two vectors.
*/
-export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance;
-export function euclideanDistance(
- expr: Expr | string,
+export function manhattanDistance(
+ vectorExpression: Expr,
+ otherVectorExpression: Expr
+): FunctionExpr;
+export function manhattanDistance(
+ fieldOrExpr: 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 = fieldOfOrExpr(fieldOrExpr);
+ const expr2 = vectorToExpr(other);
+ return expr1.manhattanDistance(expr2);
}
/**
@@ -6240,13 +6403,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 +6421,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 fieldOfOrExpr(expr).vectorLength();
}
/**
@@ -6275,13 +6437,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 +6456,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 fieldOfOrExpr(expr).unixMicrosToTimestamp();
}
/**
@@ -6312,13 +6471,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 +6489,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 fieldOfOrExpr(expr).timestampToUnixMicros();
}
/**
@@ -6349,13 +6505,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 +6524,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 = fieldOfOrExpr(expr);
+ return normalizedExpr.unixMillisToTimestamp();
}
/**
@@ -6386,13 +6540,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 +6558,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 = fieldOfOrExpr(expr);
+ return normalizedExpr.timestampToUnixMillis();
}
/**
@@ -6423,13 +6575,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 +6594,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 = fieldOfOrExpr(expr);
+ return normalizedExpr.unixSecondsToTimestamp();
}
/**
@@ -6460,13 +6610,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 +6628,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 = fieldOfOrExpr(expr);
+ return normalizedExpr.timestampToUnixSeconds();
}
/**
@@ -6496,7 +6644,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 +6656,7 @@ export function timestampAdd(
timestamp: Expr,
unit: Expr,
amount: Expr
-): TimestampAdd;
+): FunctionExpr;
/**
* @beta
@@ -6517,7 +6665,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 +6677,7 @@ export function timestampAdd(
timestamp: Expr,
unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day',
amount: number
-): TimestampAdd;
+): FunctionExpr;
/**
* @beta
@@ -6541,16 +6689,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 +6710,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 = fieldOfOrExpr(timestamp);
+ const normalizedUnit = valueToDefaultExpr(unit);
+ const normalizedAmount = valueToDefaultExpr(amount);
+ return normalizedTimestamp.timestampAdd(normalizedUnit, normalizedAmount);
}
/**
@@ -6582,7 +6724,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 +6736,7 @@ export function timestampSub(
timestamp: Expr,
unit: Expr,
amount: Expr
-): TimestampSub;
+): FunctionExpr;
/**
* @beta
@@ -6603,7 +6745,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 +6757,7 @@ export function timestampSub(
timestamp: Expr,
unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day',
amount: number
-): TimestampSub;
+): FunctionExpr;
/**
* @beta
@@ -6627,16 +6769,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 +6790,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 = fieldOfOrExpr(timestamp);
+ const normalizedUnit = valueToDefaultExpr(unit);
+ const normalizedAmount = valueToDefaultExpr(amount);
+ return normalizedTimestamp.timestampSub(normalizedUnit, normalizedAmount);
}
/**
@@ -6694,15 +6808,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]);
+ first: BooleanExpr,
+ second: BooleanExpr,
+ ...more: BooleanExpr[]
+): BooleanExpr {
+ return new BooleanExpr('and', [first, second, ...more]);
}
/**
@@ -6716,51 +6832,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]);
+ 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(fieldOfOrExpr(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(fieldOfOrExpr(field), 'descending');
}
/**
@@ -6770,7 +6922,7 @@ 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'
@@ -6798,4 +6950,6 @@ export class Ordering {
_readUserData(dataReader: UserDataReader): void {
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..27c41de1908 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;
@@ -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..a655ab153b8 100644
--- a/packages/firestore/src/lite-api/pipeline.ts
+++ b/packages/firestore/src/lite-api/pipeline.ts
@@ -19,32 +19,26 @@
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 +48,13 @@ import {
GenericStage,
Limit,
Offset,
+ RemoveFields,
+ Replace,
Select,
Sort,
+ Sample,
+ Union,
+ Unnest,
Stage,
Where
} from './stage';
@@ -97,30 +96,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 +158,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 +220,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 +229,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 +274,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 +300,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 +309,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 +330,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,25 +338,21 @@ 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));
}
+ /**
+ * 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 {
- const copy = this.stages.map(s => s);
- copy.push(new Limit(limit, convertedFromLimitTolast));
- return this.newPipeline(
- this._db,
- this.userDataReader,
- this._userDataWriter,
- copy
- );
+ return this._addStage(new Limit(limit, convertedFromLimitTolast));
}
/**
@@ -436,35 +375,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 +412,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 +439,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,38 +450,38 @@ 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) => [
+ (target as unknown as AggregateWithAlias).alias,
this.readUserData(
'aggregate',
- (target as unknown as AccumulatorTarget).expr
+ (target as unknown as AggregateWithAlias).aggregate
)
])
),
@@ -547,14 +492,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,23 +507,16 @@ 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,
vectorObjectValue,
@@ -587,12 +525,6 @@ export class Pipeline implements ProtoSerializable {
options.distanceField
)
);
- return this.newPipeline(
- this._db,
- this.userDataReader,
- this._userDataWriter,
- copy
- );
}
/**
@@ -611,15 +543,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 +561,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 +573,168 @@ 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(field('parents'));
+ *
+ * // Output
+ * // {
+ * // 'father': 'John Doe Sr.',
+ * // 'mother': 'Jane Doe'
+ * // }
+ * ```
+ *
+ * @param field The {@link Field} field containing the nested map.
+ * @return A new {@code Pipeline} object with this stage appended to the stage list.
+ */
+ replaceWith(fieldValue: Field | string): Pipeline {
+ const fieldExpr =
+ typeof fieldValue === 'string' ? field(fieldValue) : fieldValue;
+ 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 unnested 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 unnested 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 +749,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");
* ```
*
@@ -675,13 +758,45 @@ export class Pipeline implements ProtoSerializable {
* @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 => {
+ // 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: any) => {
+ if (value instanceof Expr) {
+ return value;
+ }
+ if (value instanceof AggregateFunction) {
+ return value;
+ } else if (isPlainObject(value)) {
+ return _mapValue(value);
+ } else {
+ return 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 +805,63 @@ 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 as Field).fieldName(), selectable);
+ } else if (selectable instanceof ExprWithAlias) {
+ const expr = selectable as ExprWithAlias;
+ result.set(expr.alias, expr.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..6179a35b8b6 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,71 @@ 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 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 execute(firestore.pipeline().collection("books")
+ * .where(gt(field("rating"), 4.5))
+ * .select("title", "author", "rating"));
+ * ```
+ *
+ * @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/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..c5e1cbdbfa5 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
@@ -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';
+
+ constructor(
+ private field: Field,
+ 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..733fa9050cd 100644
--- a/packages/firestore/src/lite-api/user_data_reader.ts
+++ b/packages/firestore/src/lite-api/user_data_reader.ts
@@ -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';
@@ -909,6 +910,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)}`
@@ -955,7 +958,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,7 +969,8 @@ function looksLikeJsonObject(input: unknown): boolean {
!(input instanceof Bytes) &&
!(input instanceof DocumentReference) &&
!(input instanceof FieldValue) &&
- !(input instanceof VectorValue)
+ !(input instanceof VectorValue) &&
+ !isProtoValueSerializable(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..095e377eba6 100644
--- a/packages/firestore/src/remote/serializer.ts
+++ b/packages/firestore/src/remote/serializer.ts
@@ -94,7 +94,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,6 +1434,21 @@ 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 (
+ typeof value._toProto === 'function' &&
+ value._protoValueType === 'ProtoValue'
+ );
+}
+
export interface UserData {
_readUserData(dataReader: UserDataReader): void;
}
@@ -1466,6 +1482,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..2ed82824c2f 100644
--- a/packages/firestore/test/integration/api/pipeline.test.ts
+++ b/packages/firestore/test/integration/api/pipeline.test.ts
@@ -18,13 +18,26 @@
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,
+ BooleanExpr,
+ constantVector,
+ FunctionExpr
+} 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,
+ array,
+ descending,
+ isNan,
+ map,
+ Bytes,
+ getFirestore,
+ terminate,
+ vector,
execute,
_internalPipelineToExecutePipelineRequestProto,
add,
@@ -33,7 +46,6 @@ import {
arrayContainsAny,
avgFunction,
CollectionReference,
- Constant,
cosineDistance,
countAll,
doc,
@@ -42,7 +54,6 @@ import {
endsWith,
eq,
euclideanDistance,
- Field,
Firestore,
gt,
like,
@@ -50,9 +61,7 @@ import {
lte,
mapGet,
neq,
- not,
orFunction,
- PipelineResult,
regexContains,
regexMatch,
setDoc,
@@ -62,30 +71,38 @@ import {
cond,
eqAny,
logicalMaximum,
- logicalMinimum,
notEqAny,
- query,
- where,
- FieldPath,
- orderBy,
- limit,
- limitToLast,
- startAt,
- startAfter,
- endAt,
- endBefore,
- collectionGroup,
collection,
- and,
+ multiply,
+ countIf,
+ bitAnd,
+ bitOr,
+ bitXor,
+ bitNot,
+ bitLeftShift,
+ bitRightShift,
+ rand,
+ arrayOffset,
+ currentContext,
+ isError,
+ ifError,
+ isAbsent,
+ isNull,
+ isNotNull,
+ isNotNan,
+ mapRemove,
+ mapMerge,
+ documentIdFunction,
+ substr,
+ manhattanDistance,
documentId,
- addDoc,
- getDoc
+ logicalMinimum,
+ xor,
+ field,
+ constant,
+ writeBatch
} from '../util/firebase_export';
-import {
- apiDescribe,
- PERSISTENCE_MODE_UNSPECIFIED,
- withTestCollection
-} from '../util/helpers';
+import { apiDescribe, withTestCollection } from '../util/helpers';
use(chaiAsPromised);
@@ -94,236 +111,667 @@ setLogLevel('debug');
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;
+
+ 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.ref?.id);
+ expect(actualIds).to.deep.equal(data);
+ } else {
+ docs.forEach(r => {
+ expect(r.data()).to.deep.equal(data.shift());
+ });
}
- return randomCol;
}
+ }
+
+ 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 },
+ remarks: null,
+ cost: NaN
+ },
+ 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 }
+ },
+ 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);
+ }
+
+ 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;
+ }
+ );
- 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());
- });
+ await setupDeferred.promise;
+ });
+
+ afterEach(async () => {
+ testDeferred?.resolve();
+ await withTestCollectionPromise;
+ });
+
+ 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 snapshot = await execute(
+ firestore.pipeline().collection(randomCol.path)
+ );
+ expect(snapshot.results.length).to.equal(10);
+ });
+
+ 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('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(
+ andFunction(
+ 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
}
}
- }
+ });
+ });
- 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 }
+ 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'),
+ constant(vector([1, 2, 3])).as('vectorValue'),
+ 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'
},
- 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 }
+ '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]),
+ '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'
},
- book10: {
- title: 'Dune',
- author: 'Frank Herbert',
- genre: 'Science Fiction',
- published: 1965,
- rating: 4.6,
- tags: ['politics', 'desert', 'ecology'],
- awards: { hugo: true, nebula: true }
+ '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'
}
- };
- 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;
- }
+ 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')
);
- await setupDeferred.promise;
+ const proto = _internalPipelineToExecutePipelineRequestProto(pipeline);
+ expect(proto).not.to.be.null;
+ });
+
+ describe('timestamps', () => {
+ 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
+ );
});
- afterEach(async () => {
- testDeferred?.resolve();
- await withTestCollectionPromise;
+ 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
+ );
});
- describe('fluent API', () => {
- it('empty results as expected', async () => {
- const result = await firestore
- .pipeline()
- .collection(randomCol.path)
- .limit(0)
- .execute();
- expect(result.length).to.equal(0);
+ 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());
});
- it('full results as expected', async () => {
- const result = await firestore
- .pipeline()
- .collection(randomCol.path)
- .execute();
- expect(result.length).to.equal(10);
+ 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 aggregate results as expected', async () => {
- let result = await firestore
- .pipeline()
- .collection(randomCol.path)
- .aggregate(countAll().as('count'))
- .execute();
- expectResults(result, { count: 10 });
+ it('returns execution time for an aggregate query', async () => {
+ const start = new Date().valueOf();
+ const pipeline = firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .aggregate(avgFunction('rating').as('avgRating'));
- result = await randomCol
- .pipeline()
- .where(eq('genre', 'Science Fiction'))
- .aggregate(
- countAll().as('count'),
- avgFunction('rating').as('avgRating'),
- Field.of('rating').maximum().as('maxRating')
- )
- .execute();
- expectResults(result, { count: 2, avgRating: 4.4, maxRating: 4.6 });
+ 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: [avgFunction('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('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'),
+ avgFunction('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(
- 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: [avgFunction('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 and max accumulations', async () => {
+ const snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .aggregate(
+ countAll().as('count'),
+ field('rating').maximum().as('maxRating'),
+ field('published').minimum().as('minPublished')
+ )
+ );
+ expectResults(snapshot, {
+ 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 +784,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,234 +815,614 @@ 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');
+ 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'
+ }
+ );
});
+ });
- it('where with or', async () => {
- const results = await randomCol
- .pipeline()
- .where(orFunction(eq('genre', 'Romance'), eq('genre', 'Dystopian')))
- .select('title')
- .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,
+ 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" },
- { title: '1984' }
+ {
+ title: "The Handmaid's Tale"
+ }
);
});
+ });
- 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();
- 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' }
+ describe('where stage', () => {
+ it('where with and', async () => {
+ const snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .where(
+ andFunction(
+ gt('rating', 4.5),
+ eq('genre', 'Science Fiction'),
+ lte('published', 1965)
+ )
+ )
);
+ expectResults(snapshot, 'book10');
});
-
- it('logical min works', async () => {
- const results = await randomCol
- .pipeline()
- .select(
- 'title',
- logicalMinimum(Constant.of(1960), Field.of('published')).as(
- 'published-safe'
+ it('where with or', async () => {
+ const snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .where(
+ orFunction(
+ eq('genre', 'Romance'),
+ eq('genre', 'Dystopian'),
+ eq('genre', 'Fantasy')
+ )
)
- )
- .sort(Field.of('title').ascending())
- .limit(3)
- .execute();
+ .select('title')
+ );
expectResults(
- results,
- { title: '1984', 'published-safe': 1949 },
- { title: 'Crime and Punishment', 'published-safe': 1866 },
- { title: 'Dune', 'published-safe': 1960 }
+ snapshot,
+ { title: 'Pride and Prejudice' },
+ { title: 'The Lord of the Rings' },
+ { title: "The Handmaid's Tale" },
+ { title: '1984' }
);
});
- 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 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)
+ )
)
- )
- .sort(Field.of('title').ascending())
- .limit(3)
- .execute();
+ .select('title')
+ );
expectResults(
- results,
- { title: '1984', 'published-safe': 1960 },
- { title: 'Crime and Punishment', 'published-safe': 1960 },
- { title: 'Dune', 'published-safe': 1965 }
+ snapshot,
+ { title: 'Pride and Prejudice' },
+ { title: 'The Lord of the Rings' },
+ { title: "The Handmaid's Tale" }
);
});
+ });
- 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')
- ];
+ 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' }
+ );
+ });
+ });
- 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']
+ 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
},
- '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'
- }
- ]
+ {
+ 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(documentId()).ascending())
+ );
+ expectResults(
+ snapshot,
+ 'book1',
+ 'book1',
+ 'book10',
+ 'book10',
+ 'book2',
+ 'book2',
+ 'book3',
+ 'book3',
+ 'book4',
+ 'book4',
+ 'book5',
+ 'book5',
+ 'book6',
+ 'book6',
+ 'book7',
+ 'book7',
+ 'book8',
+ 'book8',
+ 'book9',
+ 'book9'
+ );
+ });
+ });
- it('cond works', async () => {
- const results = await randomCol
+ 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'))
+ );
+ 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'))
+ );
+ 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('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.of('published'), 1960),
- Constant.of(1960),
- Field.of('published')
+ lt(field('published'), 1960),
+ constant(1960),
+ field('published')
).as('published-safe')
)
- .sort(Field.of('title').ascending())
+ .sort(field('title').ascending())
.limit(3)
- .execute();
- expectResults(
- results,
- { title: '1984', 'published-safe': 1960 },
- { title: 'Crime and Punishment', 'published-safe': 1960 },
- { title: 'Dune', 'published-safe': 1965 }
- );
- });
+ );
+ expectResults(
+ snapshot,
+ { title: '1984', 'published-safe': 1960 },
+ { title: 'Crime and Punishment', 'published-safe': 1960 },
+ { title: 'Dune', 'published-safe': 1965 }
+ );
+ });
- it('eqAny works', async () => {
- const results = await randomCol
+ it('eqAny works', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(eqAny('published', [1979, 1999, 1967]))
.select('title')
- .execute();
- expectResults(
- results,
- { title: "The Hitchhiker's Guide to the Galaxy" },
- { title: 'One Hundred Years of Solitude' }
- );
- });
+ );
+ expectResults(
+ snapshot,
+ { title: "The Hitchhiker's Guide to the Galaxy" },
+ { title: 'One Hundred Years of Solitude' }
+ );
+ });
- it('notEqAny works', async () => {
- const results = await randomCol
+ it('notEqAny works', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(
notEqAny(
'published',
@@ -634,256 +1430,230 @@ apiDescribe.only('Pipelines', persistence => {
)
)
.select('title')
- .execute();
- expectResults(results, { title: 'Pride and Prejudice' });
- });
+ );
+ expectResults(snapshot, { title: 'Pride and Prejudice' });
+ });
- it('arrayContains works', async () => {
- const results = await randomCol
+ it('arrayContains works', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(arrayContains('tags', 'comedy'))
.select('title')
- .execute();
- expectResults(results, {
- title: "The Hitchhiker's Guide to the Galaxy"
- });
+ );
+ expectResults(snapshot, {
+ title: "The Hitchhiker's Guide to the Galaxy"
});
+ });
- it('arrayContainsAny works', async () => {
- const results = await randomCol
+ it('arrayContainsAny works', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(arrayContainsAny('tags', ['comedy', 'classic']))
.select('title')
- .execute();
- expectResults(
- results,
- { title: "The Hitchhiker's Guide to the Galaxy" },
- { title: 'Pride and Prejudice' }
- );
- });
+ );
+ expectResults(
+ snapshot,
+ { title: "The Hitchhiker's Guide to the Galaxy" },
+ { title: 'Pride and Prejudice' }
+ );
+ });
- it('arrayContainsAll works', async () => {
- const results = await randomCol
+ it('arrayContainsAll works', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
- .where(Field.of('tags').arrayContainsAll('adventure', 'magic'))
+ .collection(randomCol.path)
+ .where(field('tags').arrayContainsAll(['adventure', 'magic']))
.select('title')
- .execute();
- expectResults(results, { title: 'The Lord of the Rings' });
- });
+ );
+ expectResults(snapshot, { title: 'The Lord of the Rings' });
+ });
- it('arrayLength works', async () => {
- const results = await randomCol
+ it('arrayLength works', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
- .select(Field.of('tags').arrayLength().as('tagsCount'))
+ .collection(randomCol.path)
+ .select(field('tags').arrayLength().as('tagsCount'))
.where(eq('tagsCount', 3))
- .execute();
- expect(results.length).to.equal(10);
- });
+ );
+ expect(snapshot.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
+ it('testStrConcat', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.select(
- Field.of('author')
- .strConcat(' - ', Field.of('title'))
- .as('bookInfo')
+ field('author').strConcat(' - ', field('title')).as('bookInfo')
)
.limit(1)
- .execute();
- expectResults(results, {
- bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy"
- });
+ );
+ expectResults(snapshot, {
+ bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy"
});
+ });
- it('testStartsWith', async () => {
- const results = await randomCol
+ it('testStartsWith', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.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' }
- );
- });
+ .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 results = await randomCol
+ it('testEndsWith', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.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' }
- );
- });
+ .sort(field('title').descending())
+ );
+ expectResults(
+ snapshot,
+ { title: "The Hitchhiker's Guide to the Galaxy" },
+ { title: 'The Great Gatsby' }
+ );
+ });
- it('testLength', async () => {
- const results = await randomCol
+ it('testLength', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
- .select(
- Field.of('title').charLength().as('titleLength'),
- Field.of('title')
- )
+ .collection(randomCol.path)
+ .select(field('title').charLength().as('titleLength'), field('title'))
.where(gt('titleLength', 20))
- .sort(Field.of('title').ascending())
- .execute();
+ .sort(field('title').ascending())
+ );
- expectResults(
- results,
+ 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'
- }
- );
- });
+ {
+ 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'
+ }
+ );
+ });
- // 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
+ it('testLike', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(like('title', '%Guide%'))
.select('title')
- .execute();
- expectResults(results, {
- title: "The Hitchhiker's Guide to the Galaxy"
- });
+ );
+ expectResults(snapshot, {
+ title: "The Hitchhiker's Guide to the Galaxy"
});
+ });
- it('testRegexContains', async () => {
- const results = await randomCol
+ it('testRegexContains', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(regexContains('title', '(?i)(the|of)'))
- .execute();
- expect(results.length).to.equal(5);
- });
+ );
+ expect(snapshot.results.length).to.equal(5);
+ });
- it('testRegexMatches', async () => {
- const results = await randomCol
+ it('testRegexMatches', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(regexMatch('title', '.*(?i)(the|of).*'))
- .execute();
- expect(results.length).to.equal(5);
- });
+ );
+ expect(snapshot.results.length).to.equal(5);
+ });
- it('testArithmeticOperations', async () => {
- const results = await randomCol
+ it('testArithmeticOperations', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.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')
+ 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')
)
.limit(1)
- .execute();
- expectResults(results, {
- ratingPlusOne: 5.2,
- yearsSince1900: 79,
- ratingTimesTen: 42,
- ratingDividedByTwo: 2.1
- });
+ );
+ expectResults(snapshot, {
+ ratingPlusOne: 5.2,
+ yearsSince1900: 79,
+ ratingTimesTen: 42,
+ ratingDividedByTwo: 2.1,
+ ratingTimes20: 84,
+ ratingPlus3: 7.2
});
+ });
- it('testComparisonOperators', async () => {
- const results = await randomCol
+ it('testComparisonOperators', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(
andFunction(
gt('rating', 4.2),
- lte(Field.of('rating'), 4.5),
+ lte(field('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' }
- );
- });
+ .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 results = await randomCol
+ it('testLogicalOperators', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
.where(
orFunction(
andFunction(gt('rating', 4.5), eq('genre', 'Science Fiction')),
@@ -891,837 +1661,945 @@ apiDescribe.only('Pipelines', persistence => {
)
)
.select('title')
- .sort(Field.of('title').ascending())
- .execute();
- expectResults(
- results,
- { title: 'Crime and Punishment' },
- { title: 'Dune' },
- { title: 'Pride and Prejudice' }
- );
- });
+ .sort(field('title').ascending())
+ );
+ expectResults(
+ snapshot,
+ { title: 'Crime and Punishment' },
+ { title: 'Dune' },
+ { title: 'Pride and Prejudice' }
+ );
+ });
- it('testChecks', async () => {
- const results = await randomCol
+ it('testChecks', async () => {
+ let snapshot = await execute(
+ firestore
.pipeline()
- .where(not(Field.of('rating').isNaN()))
+ .collection(randomCol.path)
+ .sort(field('rating').descending())
+ .limit(1)
.select(
- Field.of('rating').eq(null).as('ratingIsNull'),
- not(Field.of('rating').isNaN()).as('ratingIsNotNaN')
+ 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')
)
+ );
+ expectResults(snapshot, {
+ ratingIsNull: false,
+ ratingIsNaN: false,
+ isError: true,
+ ifError: 'was error',
+ isAbsent: true,
+ titleIsNotNull: true,
+ costIsNotNan: false
+ });
+
+ snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .sort(field('rating').descending())
.limit(1)
- .execute();
- expectResults(results, { ratingIsNull: false, ratingIsNotNaN: true });
+ .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 results = await randomCol
+ it('testMapGet', async () => {
+ const snapshot = await execute(
+ firestore
.pipeline()
+ .collection(randomCol.path)
+ .sort(field('published').descending())
.select(
- Field.of('awards').mapGet('hugo').as('hugoAward'),
- Field.of('awards').mapGet('others').as('others'),
- Field.of('title')
+ field('awards').mapGet('hugo').as('hugoAward'),
+ field('awards').mapGet('others').as('others'),
+ field('title')
)
.where(eq('hugoAward', true))
- .execute();
- expectResults(
- results,
- {
- hugoAward: true,
- title: "The Hitchhiker's Guide to the Galaxy",
- others: { unknown: { year: 1980 } }
- },
- { hugoAward: true, title: 'Dune', others: null }
- );
- });
+ );
+ expectResults(
+ snapshot,
+ {
+ hugoAward: true,
+ title: "The Hitchhiker's Guide to the Galaxy",
+ others: { unknown: { year: 1980 } }
+ },
+ { 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
+ 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(Constant.vector(sourceVector), targetVector).as(
+ cosineDistance(constantVector(sourceVector), targetVector).as(
'cosineDistance'
),
- dotProduct(Constant.vector(sourceVector), targetVector).as(
+ dotProduct(constantVector(sourceVector), targetVector).as(
'dotProductDistance'
),
- euclideanDistance(Constant.vector(sourceVector), targetVector).as(
+ euclideanDistance(constantVector(sourceVector), targetVector).as(
'euclideanDistance'
+ ),
+ manhattanDistance(constantVector(sourceVector), targetVector).as(
+ 'manhattanDistance'
)
)
.limit(1)
- .execute();
+ );
- expectResults(results, {
- cosineDistance: 0.02560880430538015,
- dotProductDistance: 0.13,
- euclideanDistance: 0.806225774829855
- });
+ expectResults(snapshot, {
+ cosineDistance: 0.02560880430538015,
+ dotProductDistance: 0.13,
+ euclideanDistance: 0.806225774829855,
+ manhattanDistance: 1.1
});
- it('testNestedFields', async () => {
- const results = await randomCol
+ 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'),
+ constantVector(sourceVector)
+ .manhattanDistance(targetVector)
+ .as('manhattanDistance')
+ )
+ .limit(1)
+ );
+
+ expectResults(snapshot, {
+ cosineDistance: 0.02560880430538015,
+ dotProductDistance: 0.13,
+ euclideanDistance: 0.806225774829855,
+ manhattanDistance: 1.1
+ });
+ });
+
+ it('testNestedFields', async () => {
+ const snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
.where(eq('awards.hugo', true))
.select('title', 'awards.hugo')
- .execute();
- expectResults(
- results,
- {
- title: "The Hitchhiker's Guide to the Galaxy",
- 'awards.hugo': true
- },
- { title: 'Dune', 'awards.hugo': true }
- );
- });
+ );
+ 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 results = await randomCol
+ 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.of('nestedField.level.1'),
+ field('nestedField.level.1'),
mapGet('nestedField', 'level.1').mapGet('level.2').as('nested')
)
- .execute();
- expectResults(
- results,
- {
- title: "The Hitchhiker's Guide to the Galaxy",
- 'nestedField.level.`1`': null,
- nested: true
- },
- { title: 'Dune', 'nestedField.level.`1`': null, nested: null }
+ );
+ expectResults(
+ snapshot,
+ {
+ title: "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('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')
- );
+ 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
+ });
+ });
- const proto = _internalPipelineToExecutePipelineRequestProto(pipeline);
- expect(proto).not.to.be.null;
+ 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('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 }
+ describe.skip('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
});
- 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: {}
+ });
+
+ 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
});
- 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 }
+ snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .limit(1)
+ .select(constant(5).bitOr(12).as('result'))
+ );
+ expectResults(snapshot, {
+ result: 13
});
- }
+ });
- 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 }
+ 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
+ });
+ });
- const lastDoc = results[results.length - 1];
+ 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))
+ });
+ });
- 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'))
+ 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')
)
- )
- .limit(pageSize)
- .execute();
- expectResults(
- results,
- { title: 'Pride and Prejudice', rating: 4.5 },
- { title: 'Crime and Punishment', rating: 4.3 }
);
+ expectResults(snapshot, {
+ result: Bytes.fromUint8Array(Uint8Array.of(0x04))
+ });
});
- it('supports pagination with offsets', async () => {
- await addBooks(randomCol);
+ 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))
+ });
+ });
- const secondFilterField = '__path__';
+ it('supports Document_id', async () => {
+ let snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .sort(field('rating').descending())
+ .limit(1)
+ .select(documentIdFunction(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'
+ });
+ });
- const pipeline = randomCol
- .pipeline()
- .select('title', 'rating', secondFilterField)
- .sort(
- Field.of('rating').descending(),
- Field.of(secondFilterField).ascending()
- );
+ 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'
+ });
+ });
- const pageSize = 2;
- let currPage = 0;
+ 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'
+ });
+ });
- let results = await pipeline
- .offset(currPage++ * pageSize)
- .limit(pageSize)
- .execute();
+ it('arrayConcat works', async () => {
+ const snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .select(
+ field('tags')
+ .arrayConcat(['newTag1', 'newTag2'], field('tags'), [null])
+ .as('modifiedTags')
+ )
+ .limit(1)
+ );
+ expectResults(snapshot, {
+ modifiedTags: [
+ 'comedy',
+ 'space',
+ 'adventure',
+ 'newTag1',
+ 'newTag2',
+ 'comedy',
+ 'space',
+ 'adventure',
+ null
+ ]
+ });
+ });
- expectResults(
- results,
- {
- title: 'The Lord of the Rings',
- rating: 4.7
- },
- { title: 'Dune', rating: 4.6 }
+ 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"
+ });
+ });
- results = await pipeline
- .offset(currPage++ * pageSize)
- .limit(pageSize)
- .execute();
- expectResults(
- results,
- {
- title: 'Jonathan Strange & Mr Norrell',
- rating: 4.6
- },
- { title: 'The Master and Margarita', rating: 4.6 }
+ 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' });
+ });
- results = await pipeline
- .offset(currPage++ * pageSize)
- .limit(pageSize)
- .execute();
- expectResults(
- results,
- {
- title: 'A Long Way to a Small, Angry Planet',
- rating: 4.6
- },
- {
- title: 'Pride and Prejudice',
- rating: 4.5
- }
+ 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"
+ });
});
});
- });
-
- 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);
+ 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);
+ });
+ });
- expectResults(
- results,
- { avgRating: 4.7, genre: 'Fantasy' },
- { avgRating: 4.5, genre: 'Romance' },
- { avgRating: 4.4, genre: 'Science Fiction' }
+ 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('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());
+ 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]
+ });
+ });
- const results = await execute(myPipeline);
+ 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);
- expectResults(
- results,
- { avgRating: 4.7, genre: 'Fantasy' },
- { avgRating: 4.5, genre: 'Romance' },
- { avgRating: 4.4, genre: 'Science Fiction' }
+ snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .sort(field('rating').descending())
+ .limit(3)
+ .select(field('tags').arrayOffset(0).as('firstTag'))
);
+ expectResults(snapshot, ...expectedResults);
});
});
- });
-
- // 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 });
- }
+ // TODO: current_context tests with are failing because of b/395937453
+ it.skip('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 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('supports map', async () => {
+ const snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .sort(field('rating').descending())
+ .limit(1)
+ .select(
+ map({
+ foo: 'bar'
+ }).as('metadata')
+ )
);
- });
- 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 });
+ expect(snapshot.results.length).to.equal(1);
+ expectResults(snapshot, {
+ metadata: {
+ foo: 'bar'
}
- );
+ });
});
- 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('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')
+ )
);
- });
- 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 });
+ expect(snapshot.results.length).to.equal(1);
+ expectResults(snapshot, {
+ metadata: {
+ genre: 'Fantasy',
+ rating: 47
}
- );
+ });
});
- 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('supports mapRemove', async () => {
+ let snapshot = await execute(
+ firestore
+ .pipeline()
+ .collection(randomCol.path)
+ .sort(field('rating').descending())
+ .limit(1)
+ .select(mapRemove('awards', 'hugo').as('awards'))
);
- });
-
- 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 });
- }
+ 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 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('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'))
);
- });
-
- 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 });
- }
+ 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 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 }
- );
+ 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 }
+ });
+ }
- 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('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 }
);
- });
- 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 }
- );
+ const lastDoc = snapshot.results[snapshot.results.length - 1];
- 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 }
- );
- }
+ snapshot = await execute(
+ pipeline
+ .where(
+ orFunction(
+ andFunction(
+ field('rating').eq(lastDoc.get('rating')),
+ field('__path__').gt(lastDoc.ref?.id)
+ ),
+ field('rating').lt(lastDoc.get('rating'))
+ )
+ )
+ .limit(pageSize)
);
- });
-
- 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 });
- }
+ expectResults(
+ snapshot,
+ { title: 'Pride and Prejudice', rating: 4.5 },
+ { title: 'Crime and Punishment', rating: 4.3 }
);
});
- 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('supports pagination with offsets', async () => {
+ await addBooks(randomCol);
- it('supports endBefore', () => {
- return withTestCollection(
- PERSISTENCE_MODE_UNSPECIFIED,
- {
- 1: { foo: 1 },
- 2: { foo: 2 }
- },
- async collRef => {
- const query1 = query(collRef, orderBy('foo'), endBefore(2));
- const result = await query1.pipeline().execute();
- verifyResults(result, { foo: 1 });
- }
- );
- });
+ const secondFilterField = '__path__';
- 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 });
- }
+ 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)
);
- });
- it('supports pagination on DocumentIds', () => {
- return withTestCollection(
- PERSISTENCE_MODE_UNSPECIFIED,
+ expectResults(
+ snapshot,
{
- 1: { foo: 1 },
- 2: { foo: 2 }
+ title: 'The Lord of the Rings',
+ rating: 4.7
},
- 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 });
- }
- );
- });
-
- it('supports collection groups', () => {
- return withTestCollection(
- PERSISTENCE_MODE_UNSPECIFIED,
- {},
- async collRef => {
- 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 result = await query1.pipeline().execute();
-
- verifyResults(result, { bar: 1 }, { foo: 1 });
- }
+ { title: 'Dune', rating: 4.6 }
);
- });
-
- it('supports query over collection path with special characters', () => {
- return withTestCollection(
- PERSISTENCE_MODE_UNSPECIFIED,
- {},
- async collRef => {
- const docWithSpecials = doc(collRef, 'so!@#$%^&*()_+special');
-
- const collectionWithSpecials = collection(
- docWithSpecials,
- 'so!@#$%^&*()_+special'
- );
- await addDoc(collectionWithSpecials, { foo: 1 });
- await addDoc(collectionWithSpecials, { foo: 2 });
-
- const result = await query(
- collectionWithSpecials,
- orderBy('foo', 'asc')
- )
- .pipeline()
- .execute();
- verifyResults(result, { foo: 1 }, { foo: 2 });
- }
+ snapshot = await execute(
+ pipeline.offset(currPage++ * pageSize).limit(pageSize)
);
- });
-
- it('supports multiple inequality on same field', () => {
- return withTestCollection(
- PERSISTENCE_MODE_UNSPECIFIED,
+ 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: 'Jonathan Strange & Mr Norrell',
+ rating: 4.6
},
- 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: 'The Master and Margarita', rating: 4.6 }
);
- });
- it('supports multiple inequality on different fields', () => {
- return withTestCollection(
- PERSISTENCE_MODE_UNSPECIFIED,
+ snapshot = await execute(
+ pipeline.offset(currPage++ * pageSize).limit(pageSize)
+ );
+ 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: 'A Long Way to a Small, Angry Planet',
+ rating: 4.6
},
- 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 }
- );
+ {
+ title: 'Pride and Prejudice',
+ rating: 4.5
}
);
});
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..ab29f8634e9
--- /dev/null
+++ b/packages/firestore/test/integration/api/query_to_pipeline.test.ts
@@ -0,0 +1,558 @@
+/**
+ * @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,
+ execute
+} from '../util/firebase_export';
+import {
+ apiDescribe,
+ PERSISTENCE_MODE_UNSPECIFIED,
+ withTestCollection
+} from '../util/helpers';
+
+use(chaiAsPromised);
+
+setLogLevel('debug');
+
+// This is the Query integration tests from the lite API (no cache support)
+// with some additional test cases added for more complete coverage.
+apiDescribe('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 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, 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 }
+ );
+ }
+ );
+ });
+
+ 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, 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 });
+ }
+ );
+ });
+
+ it('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 });
+ }
+ );
+ });
+
+ it('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 }
+ );
+ }
+ );
+ });
+});
diff --git a/packages/firestore/test/integration/util/pipeline_export.ts b/packages/firestore/test/integration/util/pipeline_export.ts
new file mode 100644
index 00000000000..bb3edcda114
--- /dev/null
+++ b/packages/firestore/test/integration/util/pipeline_export.ts
@@ -0,0 +1,24 @@
+/**
+ * @license
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Imports firebase via the raw sources and re-exports it. The
+// "/integration/firestore" test suite replaces this file with a
+// reference to the minified sources. If you change any exports in this file,
+// you need to also adjust "integration/firestore/pipeline_export.ts".
+
+
+export * from '../../../pipelines/pipelines';