From 4080bfb45b86d5d03950809ac1759f90486e633e Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 6 Jun 2024 10:28:05 -0400 Subject: [PATCH 01/31] introduce pipeline proto changes --- dev/protos/firestore_v1_proto_api.d.ts | 404 ++++- dev/protos/firestore_v1_proto_api.js | 1343 ++++++++++++++--- dev/protos/google/firestore/v1/document.proto | 114 ++ .../google/firestore/v1/firestore.proto | 86 ++ dev/protos/google/firestore/v1/pipeline.proto | 41 + dev/protos/v1.json | 152 +- 6 files changed, 1958 insertions(+), 182 deletions(-) create mode 100644 dev/protos/google/firestore/v1/pipeline.proto diff --git a/dev/protos/firestore_v1_proto_api.d.ts b/dev/protos/firestore_v1_proto_api.d.ts index ac9a6bc1a..0d428b9ef 100644 --- a/dev/protos/firestore_v1_proto_api.d.ts +++ b/dev/protos/firestore_v1_proto_api.d.ts @@ -3921,6 +3921,15 @@ export namespace google { /** Value mapValue */ mapValue?: (google.firestore.v1.IMapValue|null); + + /** Value fieldReferenceValue */ + fieldReferenceValue?: (string|null); + + /** Value functionValue */ + functionValue?: (google.firestore.v1.IFunction|null); + + /** Value pipelineValue */ + pipelineValue?: (google.firestore.v1.IPipeline|null); } /** Represents a Value. */ @@ -3965,8 +3974,17 @@ export namespace google { /** Value mapValue. */ public mapValue?: (google.firestore.v1.IMapValue|null); + /** Value fieldReferenceValue. */ + public fieldReferenceValue?: (string|null); + + /** Value functionValue. */ + public functionValue?: (google.firestore.v1.IFunction|null); + + /** Value pipelineValue. */ + public pipelineValue?: (google.firestore.v1.IPipeline|null); + /** Value valueType. */ - public valueType?: ("nullValue"|"booleanValue"|"integerValue"|"doubleValue"|"timestampValue"|"stringValue"|"bytesValue"|"referenceValue"|"geoPointValue"|"arrayValue"|"mapValue"); + public valueType?: ("nullValue"|"booleanValue"|"integerValue"|"doubleValue"|"timestampValue"|"stringValue"|"bytesValue"|"referenceValue"|"geoPointValue"|"arrayValue"|"mapValue"|"fieldReferenceValue"|"functionValue"|"pipelineValue"); /** * Creates a Value message from a plain object. Also converts values to their respective internal types. @@ -4093,6 +4111,177 @@ export namespace google { public static getTypeUrl(typeUrlPrefix?: string): string; } + /** Properties of a Function. */ + interface IFunction { + + /** Function name */ + name?: (string|null); + + /** Function args */ + args?: (google.firestore.v1.IValue[]|null); + + /** Function options */ + options?: ({ [k: string]: google.firestore.v1.IValue }|null); + } + + /** Represents a Function. */ + class Function implements IFunction { + + /** + * Constructs a new Function. + * @param [properties] Properties to set + */ + constructor(properties?: google.firestore.v1.IFunction); + + /** Function name. */ + public name: string; + + /** Function args. */ + public args: google.firestore.v1.IValue[]; + + /** Function options. */ + public options: { [k: string]: google.firestore.v1.IValue }; + + /** + * Creates a Function message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns Function + */ + public static fromObject(object: { [k: string]: any }): google.firestore.v1.Function; + + /** + * Creates a plain object from a Function message. Also converts values to other types if specified. + * @param message Function + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.firestore.v1.Function, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Function to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for Function + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of a Pipeline. */ + interface IPipeline { + + /** Pipeline stages */ + stages?: (google.firestore.v1.Pipeline.IStage[]|null); + } + + /** Represents a Pipeline. */ + class Pipeline implements IPipeline { + + /** + * Constructs a new Pipeline. + * @param [properties] Properties to set + */ + constructor(properties?: google.firestore.v1.IPipeline); + + /** Pipeline stages. */ + public stages: google.firestore.v1.Pipeline.IStage[]; + + /** + * Creates a Pipeline message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns Pipeline + */ + public static fromObject(object: { [k: string]: any }): google.firestore.v1.Pipeline; + + /** + * Creates a plain object from a Pipeline message. Also converts values to other types if specified. + * @param message Pipeline + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.firestore.v1.Pipeline, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Pipeline to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for Pipeline + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + namespace Pipeline { + + /** Properties of a Stage. */ + interface IStage { + + /** Stage name */ + name?: (string|null); + + /** Stage args */ + args?: (google.firestore.v1.IValue[]|null); + + /** Stage options */ + options?: ({ [k: string]: google.firestore.v1.IValue }|null); + } + + /** Represents a Stage. */ + class Stage implements IStage { + + /** + * Constructs a new Stage. + * @param [properties] Properties to set + */ + constructor(properties?: google.firestore.v1.Pipeline.IStage); + + /** Stage name. */ + public name: string; + + /** Stage args. */ + public args: google.firestore.v1.IValue[]; + + /** Stage options. */ + public options: { [k: string]: google.firestore.v1.IValue }; + + /** + * Creates a Stage message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns Stage + */ + public static fromObject(object: { [k: string]: any }): google.firestore.v1.Pipeline.Stage; + + /** + * Creates a plain object from a Stage message. Also converts values to other types if specified. + * @param message Stage + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.firestore.v1.Pipeline.Stage, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this Stage to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for Stage + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + } + /** Properties of a BitSequence. */ interface IBitSequence { @@ -4602,6 +4791,20 @@ export namespace google { */ public runQuery(request: google.firestore.v1.IRunQueryRequest): Promise; + /** + * Calls ExecutePipeline. + * @param request ExecutePipelineRequest message or plain object + * @param callback Node-style callback called with the error, if any, and ExecutePipelineResponse + */ + public executePipeline(request: google.firestore.v1.IExecutePipelineRequest, callback: google.firestore.v1.Firestore.ExecutePipelineCallback): void; + + /** + * Calls ExecutePipeline. + * @param request ExecutePipelineRequest message or plain object + * @returns Promise + */ + public executePipeline(request: google.firestore.v1.IExecutePipelineRequest): Promise; + /** * Calls RunAggregationQuery. * @param request RunAggregationQueryRequest message or plain object @@ -4766,6 +4969,13 @@ export namespace google { */ type RunQueryCallback = (error: (Error|null), response?: google.firestore.v1.RunQueryResponse) => void; + /** + * Callback as used by {@link google.firestore.v1.Firestore#executePipeline}. + * @param error Error, if any + * @param [response] ExecutePipelineResponse + */ + type ExecutePipelineCallback = (error: (Error|null), response?: google.firestore.v1.ExecutePipelineResponse) => void; + /** * Callback as used by {@link google.firestore.v1.Firestore#runAggregationQuery}. * @param error Error, if any @@ -5815,6 +6025,144 @@ export namespace google { public static getTypeUrl(typeUrlPrefix?: string): string; } + /** Properties of an ExecutePipelineRequest. */ + interface IExecutePipelineRequest { + + /** ExecutePipelineRequest database */ + database?: (string|null); + + /** ExecutePipelineRequest structuredPipeline */ + structuredPipeline?: (google.firestore.v1.IStructuredPipeline|null); + + /** ExecutePipelineRequest transaction */ + transaction?: (Uint8Array|null); + + /** ExecutePipelineRequest newTransaction */ + newTransaction?: (google.firestore.v1.ITransactionOptions|null); + + /** ExecutePipelineRequest readTime */ + readTime?: (google.protobuf.ITimestamp|null); + } + + /** Represents an ExecutePipelineRequest. */ + class ExecutePipelineRequest implements IExecutePipelineRequest { + + /** + * Constructs a new ExecutePipelineRequest. + * @param [properties] Properties to set + */ + constructor(properties?: google.firestore.v1.IExecutePipelineRequest); + + /** ExecutePipelineRequest database. */ + public database: string; + + /** ExecutePipelineRequest structuredPipeline. */ + public structuredPipeline?: (google.firestore.v1.IStructuredPipeline|null); + + /** ExecutePipelineRequest transaction. */ + public transaction?: (Uint8Array|null); + + /** ExecutePipelineRequest newTransaction. */ + public newTransaction?: (google.firestore.v1.ITransactionOptions|null); + + /** ExecutePipelineRequest readTime. */ + public readTime?: (google.protobuf.ITimestamp|null); + + /** ExecutePipelineRequest pipelineType. */ + public pipelineType?: "structuredPipeline"; + + /** ExecutePipelineRequest consistencySelector. */ + public consistencySelector?: ("transaction"|"newTransaction"|"readTime"); + + /** + * Creates an ExecutePipelineRequest message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns ExecutePipelineRequest + */ + public static fromObject(object: { [k: string]: any }): google.firestore.v1.ExecutePipelineRequest; + + /** + * Creates a plain object from an ExecutePipelineRequest message. Also converts values to other types if specified. + * @param message ExecutePipelineRequest + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.firestore.v1.ExecutePipelineRequest, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this ExecutePipelineRequest to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for ExecutePipelineRequest + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + + /** Properties of an ExecutePipelineResponse. */ + interface IExecutePipelineResponse { + + /** ExecutePipelineResponse transaction */ + transaction?: (Uint8Array|null); + + /** ExecutePipelineResponse results */ + results?: (google.firestore.v1.IDocument[]|null); + + /** ExecutePipelineResponse executionTime */ + executionTime?: (google.protobuf.ITimestamp|null); + } + + /** Represents an ExecutePipelineResponse. */ + class ExecutePipelineResponse implements IExecutePipelineResponse { + + /** + * Constructs a new ExecutePipelineResponse. + * @param [properties] Properties to set + */ + constructor(properties?: google.firestore.v1.IExecutePipelineResponse); + + /** ExecutePipelineResponse transaction. */ + public transaction: Uint8Array; + + /** ExecutePipelineResponse results. */ + public results: google.firestore.v1.IDocument[]; + + /** ExecutePipelineResponse executionTime. */ + public executionTime?: (google.protobuf.ITimestamp|null); + + /** + * Creates an ExecutePipelineResponse message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns ExecutePipelineResponse + */ + public static fromObject(object: { [k: string]: any }): google.firestore.v1.ExecutePipelineResponse; + + /** + * Creates a plain object from an ExecutePipelineResponse message. Also converts values to other types if specified. + * @param message ExecutePipelineResponse + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.firestore.v1.ExecutePipelineResponse, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this ExecutePipelineResponse to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for ExecutePipelineResponse + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + /** Properties of a RunAggregationQueryRequest. */ interface IRunAggregationQueryRequest { @@ -6899,6 +7247,60 @@ export namespace google { public static getTypeUrl(typeUrlPrefix?: string): string; } + /** Properties of a StructuredPipeline. */ + interface IStructuredPipeline { + + /** StructuredPipeline pipeline */ + pipeline?: (google.firestore.v1.IPipeline|null); + + /** StructuredPipeline options */ + options?: ({ [k: string]: google.firestore.v1.IValue }|null); + } + + /** Represents a StructuredPipeline. */ + class StructuredPipeline implements IStructuredPipeline { + + /** + * Constructs a new StructuredPipeline. + * @param [properties] Properties to set + */ + constructor(properties?: google.firestore.v1.IStructuredPipeline); + + /** StructuredPipeline pipeline. */ + public pipeline?: (google.firestore.v1.IPipeline|null); + + /** StructuredPipeline options. */ + public options: { [k: string]: google.firestore.v1.IValue }; + + /** + * Creates a StructuredPipeline message from a plain object. Also converts values to their respective internal types. + * @param object Plain object + * @returns StructuredPipeline + */ + public static fromObject(object: { [k: string]: any }): google.firestore.v1.StructuredPipeline; + + /** + * Creates a plain object from a StructuredPipeline message. Also converts values to other types if specified. + * @param message StructuredPipeline + * @param [options] Conversion options + * @returns Plain object + */ + public static toObject(message: google.firestore.v1.StructuredPipeline, options?: $protobuf.IConversionOptions): { [k: string]: any }; + + /** + * Converts this StructuredPipeline to JSON. + * @returns JSON object + */ + public toJSON(): { [k: string]: any }; + + /** + * Gets the default type url for StructuredPipeline + * @param [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns The default type url + */ + public static getTypeUrl(typeUrlPrefix?: string): string; + } + /** Properties of a StructuredQuery. */ interface IStructuredQuery { diff --git a/dev/protos/firestore_v1_proto_api.js b/dev/protos/firestore_v1_proto_api.js index c4a08c1f1..a123ad6d9 100644 --- a/dev/protos/firestore_v1_proto_api.js +++ b/dev/protos/firestore_v1_proto_api.js @@ -10212,6 +10212,9 @@ * @property {google.type.ILatLng|null} [geoPointValue] Value geoPointValue * @property {google.firestore.v1.IArrayValue|null} [arrayValue] Value arrayValue * @property {google.firestore.v1.IMapValue|null} [mapValue] Value mapValue + * @property {string|null} [fieldReferenceValue] Value fieldReferenceValue + * @property {google.firestore.v1.IFunction|null} [functionValue] Value functionValue + * @property {google.firestore.v1.IPipeline|null} [pipelineValue] Value pipelineValue */ /** @@ -10317,17 +10320,41 @@ */ Value.prototype.mapValue = null; + /** + * Value fieldReferenceValue. + * @member {string|null|undefined} fieldReferenceValue + * @memberof google.firestore.v1.Value + * @instance + */ + Value.prototype.fieldReferenceValue = null; + + /** + * Value functionValue. + * @member {google.firestore.v1.IFunction|null|undefined} functionValue + * @memberof google.firestore.v1.Value + * @instance + */ + Value.prototype.functionValue = null; + + /** + * Value pipelineValue. + * @member {google.firestore.v1.IPipeline|null|undefined} pipelineValue + * @memberof google.firestore.v1.Value + * @instance + */ + Value.prototype.pipelineValue = null; + // OneOf field names bound to virtual getters and setters var $oneOfFields; /** * Value valueType. - * @member {"nullValue"|"booleanValue"|"integerValue"|"doubleValue"|"timestampValue"|"stringValue"|"bytesValue"|"referenceValue"|"geoPointValue"|"arrayValue"|"mapValue"|undefined} valueType + * @member {"nullValue"|"booleanValue"|"integerValue"|"doubleValue"|"timestampValue"|"stringValue"|"bytesValue"|"referenceValue"|"geoPointValue"|"arrayValue"|"mapValue"|"fieldReferenceValue"|"functionValue"|"pipelineValue"|undefined} valueType * @memberof google.firestore.v1.Value * @instance */ Object.defineProperty(Value.prototype, "valueType", { - get: $util.oneOfGetter($oneOfFields = ["nullValue", "booleanValue", "integerValue", "doubleValue", "timestampValue", "stringValue", "bytesValue", "referenceValue", "geoPointValue", "arrayValue", "mapValue"]), + get: $util.oneOfGetter($oneOfFields = ["nullValue", "booleanValue", "integerValue", "doubleValue", "timestampValue", "stringValue", "bytesValue", "referenceValue", "geoPointValue", "arrayValue", "mapValue", "fieldReferenceValue", "functionValue", "pipelineValue"]), set: $util.oneOfSetter($oneOfFields) }); @@ -10397,6 +10424,18 @@ throw TypeError(".google.firestore.v1.Value.mapValue: object expected"); message.mapValue = $root.google.firestore.v1.MapValue.fromObject(object.mapValue); } + if (object.fieldReferenceValue != null) + message.fieldReferenceValue = String(object.fieldReferenceValue); + if (object.functionValue != null) { + if (typeof object.functionValue !== "object") + throw TypeError(".google.firestore.v1.Value.functionValue: object expected"); + message.functionValue = $root.google.firestore.v1.Function.fromObject(object.functionValue); + } + if (object.pipelineValue != null) { + if (typeof object.pipelineValue !== "object") + throw TypeError(".google.firestore.v1.Value.pipelineValue: object expected"); + message.pipelineValue = $root.google.firestore.v1.Pipeline.fromObject(object.pipelineValue); + } return message; }; @@ -10471,6 +10510,21 @@ if (options.oneofs) object.valueType = "bytesValue"; } + if (message.fieldReferenceValue != null && message.hasOwnProperty("fieldReferenceValue")) { + object.fieldReferenceValue = message.fieldReferenceValue; + if (options.oneofs) + object.valueType = "fieldReferenceValue"; + } + if (message.functionValue != null && message.hasOwnProperty("functionValue")) { + object.functionValue = $root.google.firestore.v1.Function.toObject(message.functionValue, options); + if (options.oneofs) + object.valueType = "functionValue"; + } + if (message.pipelineValue != null && message.hasOwnProperty("pipelineValue")) { + object.pipelineValue = $root.google.firestore.v1.Pipeline.toObject(message.pipelineValue, options); + if (options.oneofs) + object.valueType = "pipelineValue"; + } return object; }; @@ -10724,6 +10778,422 @@ return MapValue; })(); + v1.Function = (function() { + + /** + * Properties of a Function. + * @memberof google.firestore.v1 + * @interface IFunction + * @property {string|null} [name] Function name + * @property {Array.|null} [args] Function args + * @property {Object.|null} [options] Function options + */ + + /** + * Constructs a new Function. + * @memberof google.firestore.v1 + * @classdesc Represents a Function. + * @implements IFunction + * @constructor + * @param {google.firestore.v1.IFunction=} [properties] Properties to set + */ + function Function(properties) { + this.args = []; + this.options = {}; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Function name. + * @member {string} name + * @memberof google.firestore.v1.Function + * @instance + */ + Function.prototype.name = ""; + + /** + * Function args. + * @member {Array.} args + * @memberof google.firestore.v1.Function + * @instance + */ + Function.prototype.args = $util.emptyArray; + + /** + * Function options. + * @member {Object.} options + * @memberof google.firestore.v1.Function + * @instance + */ + Function.prototype.options = $util.emptyObject; + + /** + * Creates a Function message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.firestore.v1.Function + * @static + * @param {Object.} object Plain object + * @returns {google.firestore.v1.Function} Function + */ + Function.fromObject = function fromObject(object) { + if (object instanceof $root.google.firestore.v1.Function) + return object; + var message = new $root.google.firestore.v1.Function(); + if (object.name != null) + message.name = String(object.name); + if (object.args) { + if (!Array.isArray(object.args)) + throw TypeError(".google.firestore.v1.Function.args: array expected"); + message.args = []; + for (var i = 0; i < object.args.length; ++i) { + if (typeof object.args[i] !== "object") + throw TypeError(".google.firestore.v1.Function.args: object expected"); + message.args[i] = $root.google.firestore.v1.Value.fromObject(object.args[i]); + } + } + if (object.options) { + if (typeof object.options !== "object") + throw TypeError(".google.firestore.v1.Function.options: object expected"); + message.options = {}; + for (var keys = Object.keys(object.options), i = 0; i < keys.length; ++i) { + if (typeof object.options[keys[i]] !== "object") + throw TypeError(".google.firestore.v1.Function.options: object expected"); + message.options[keys[i]] = $root.google.firestore.v1.Value.fromObject(object.options[keys[i]]); + } + } + return message; + }; + + /** + * Creates a plain object from a Function message. Also converts values to other types if specified. + * @function toObject + * @memberof google.firestore.v1.Function + * @static + * @param {google.firestore.v1.Function} message Function + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Function.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) + object.args = []; + if (options.objects || options.defaults) + object.options = {}; + if (options.defaults) + object.name = ""; + if (message.name != null && message.hasOwnProperty("name")) + object.name = message.name; + if (message.args && message.args.length) { + object.args = []; + for (var j = 0; j < message.args.length; ++j) + object.args[j] = $root.google.firestore.v1.Value.toObject(message.args[j], options); + } + var keys2; + if (message.options && (keys2 = Object.keys(message.options)).length) { + object.options = {}; + for (var j = 0; j < keys2.length; ++j) + object.options[keys2[j]] = $root.google.firestore.v1.Value.toObject(message.options[keys2[j]], options); + } + return object; + }; + + /** + * Converts this Function to JSON. + * @function toJSON + * @memberof google.firestore.v1.Function + * @instance + * @returns {Object.} JSON object + */ + Function.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Function + * @function getTypeUrl + * @memberof google.firestore.v1.Function + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Function.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.firestore.v1.Function"; + }; + + return Function; + })(); + + v1.Pipeline = (function() { + + /** + * Properties of a Pipeline. + * @memberof google.firestore.v1 + * @interface IPipeline + * @property {Array.|null} [stages] Pipeline stages + */ + + /** + * Constructs a new Pipeline. + * @memberof google.firestore.v1 + * @classdesc Represents a Pipeline. + * @implements IPipeline + * @constructor + * @param {google.firestore.v1.IPipeline=} [properties] Properties to set + */ + function Pipeline(properties) { + this.stages = []; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Pipeline stages. + * @member {Array.} stages + * @memberof google.firestore.v1.Pipeline + * @instance + */ + Pipeline.prototype.stages = $util.emptyArray; + + /** + * Creates a Pipeline message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.firestore.v1.Pipeline + * @static + * @param {Object.} object Plain object + * @returns {google.firestore.v1.Pipeline} Pipeline + */ + Pipeline.fromObject = function fromObject(object) { + if (object instanceof $root.google.firestore.v1.Pipeline) + return object; + var message = new $root.google.firestore.v1.Pipeline(); + if (object.stages) { + if (!Array.isArray(object.stages)) + throw TypeError(".google.firestore.v1.Pipeline.stages: array expected"); + message.stages = []; + for (var i = 0; i < object.stages.length; ++i) { + if (typeof object.stages[i] !== "object") + throw TypeError(".google.firestore.v1.Pipeline.stages: object expected"); + message.stages[i] = $root.google.firestore.v1.Pipeline.Stage.fromObject(object.stages[i]); + } + } + return message; + }; + + /** + * Creates a plain object from a Pipeline message. Also converts values to other types if specified. + * @function toObject + * @memberof google.firestore.v1.Pipeline + * @static + * @param {google.firestore.v1.Pipeline} message Pipeline + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Pipeline.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) + object.stages = []; + if (message.stages && message.stages.length) { + object.stages = []; + for (var j = 0; j < message.stages.length; ++j) + object.stages[j] = $root.google.firestore.v1.Pipeline.Stage.toObject(message.stages[j], options); + } + return object; + }; + + /** + * Converts this Pipeline to JSON. + * @function toJSON + * @memberof google.firestore.v1.Pipeline + * @instance + * @returns {Object.} JSON object + */ + Pipeline.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Pipeline + * @function getTypeUrl + * @memberof google.firestore.v1.Pipeline + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Pipeline.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.firestore.v1.Pipeline"; + }; + + Pipeline.Stage = (function() { + + /** + * Properties of a Stage. + * @memberof google.firestore.v1.Pipeline + * @interface IStage + * @property {string|null} [name] Stage name + * @property {Array.|null} [args] Stage args + * @property {Object.|null} [options] Stage options + */ + + /** + * Constructs a new Stage. + * @memberof google.firestore.v1.Pipeline + * @classdesc Represents a Stage. + * @implements IStage + * @constructor + * @param {google.firestore.v1.Pipeline.IStage=} [properties] Properties to set + */ + function Stage(properties) { + this.args = []; + this.options = {}; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Stage name. + * @member {string} name + * @memberof google.firestore.v1.Pipeline.Stage + * @instance + */ + Stage.prototype.name = ""; + + /** + * Stage args. + * @member {Array.} args + * @memberof google.firestore.v1.Pipeline.Stage + * @instance + */ + Stage.prototype.args = $util.emptyArray; + + /** + * Stage options. + * @member {Object.} options + * @memberof google.firestore.v1.Pipeline.Stage + * @instance + */ + Stage.prototype.options = $util.emptyObject; + + /** + * Creates a Stage message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.firestore.v1.Pipeline.Stage + * @static + * @param {Object.} object Plain object + * @returns {google.firestore.v1.Pipeline.Stage} Stage + */ + Stage.fromObject = function fromObject(object) { + if (object instanceof $root.google.firestore.v1.Pipeline.Stage) + return object; + var message = new $root.google.firestore.v1.Pipeline.Stage(); + if (object.name != null) + message.name = String(object.name); + if (object.args) { + if (!Array.isArray(object.args)) + throw TypeError(".google.firestore.v1.Pipeline.Stage.args: array expected"); + message.args = []; + for (var i = 0; i < object.args.length; ++i) { + if (typeof object.args[i] !== "object") + throw TypeError(".google.firestore.v1.Pipeline.Stage.args: object expected"); + message.args[i] = $root.google.firestore.v1.Value.fromObject(object.args[i]); + } + } + if (object.options) { + if (typeof object.options !== "object") + throw TypeError(".google.firestore.v1.Pipeline.Stage.options: object expected"); + message.options = {}; + for (var keys = Object.keys(object.options), i = 0; i < keys.length; ++i) { + if (typeof object.options[keys[i]] !== "object") + throw TypeError(".google.firestore.v1.Pipeline.Stage.options: object expected"); + message.options[keys[i]] = $root.google.firestore.v1.Value.fromObject(object.options[keys[i]]); + } + } + return message; + }; + + /** + * Creates a plain object from a Stage message. Also converts values to other types if specified. + * @function toObject + * @memberof google.firestore.v1.Pipeline.Stage + * @static + * @param {google.firestore.v1.Pipeline.Stage} message Stage + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Stage.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) + object.args = []; + if (options.objects || options.defaults) + object.options = {}; + if (options.defaults) + object.name = ""; + if (message.name != null && message.hasOwnProperty("name")) + object.name = message.name; + if (message.args && message.args.length) { + object.args = []; + for (var j = 0; j < message.args.length; ++j) + object.args[j] = $root.google.firestore.v1.Value.toObject(message.args[j], options); + } + var keys2; + if (message.options && (keys2 = Object.keys(message.options)).length) { + object.options = {}; + for (var j = 0; j < keys2.length; ++j) + object.options[keys2[j]] = $root.google.firestore.v1.Value.toObject(message.options[keys2[j]], options); + } + return object; + }; + + /** + * Converts this Stage to JSON. + * @function toJSON + * @memberof google.firestore.v1.Pipeline.Stage + * @instance + * @returns {Object.} JSON object + */ + Stage.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Stage + * @function getTypeUrl + * @memberof google.firestore.v1.Pipeline.Stage + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Stage.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.firestore.v1.Pipeline.Stage"; + }; + + return Stage; + })(); + + return Pipeline; + })(); + v1.BitSequence = (function() { /** @@ -11875,20 +12345,53 @@ */ /** - * Callback as used by {@link google.firestore.v1.Firestore#runAggregationQuery}. + * Callback as used by {@link google.firestore.v1.Firestore#executePipeline}. * @memberof google.firestore.v1.Firestore - * @typedef RunAggregationQueryCallback + * @typedef ExecutePipelineCallback * @type {function} * @param {Error|null} error Error, if any - * @param {google.firestore.v1.RunAggregationQueryResponse} [response] RunAggregationQueryResponse + * @param {google.firestore.v1.ExecutePipelineResponse} [response] ExecutePipelineResponse */ /** - * Calls RunAggregationQuery. - * @function runAggregationQuery + * Calls ExecutePipeline. + * @function executePipeline * @memberof google.firestore.v1.Firestore * @instance - * @param {google.firestore.v1.IRunAggregationQueryRequest} request RunAggregationQueryRequest message or plain object + * @param {google.firestore.v1.IExecutePipelineRequest} request ExecutePipelineRequest message or plain object + * @param {google.firestore.v1.Firestore.ExecutePipelineCallback} callback Node-style callback called with the error, if any, and ExecutePipelineResponse + * @returns {undefined} + * @variation 1 + */ + Object.defineProperty(Firestore.prototype.executePipeline = function executePipeline(request, callback) { + return this.rpcCall(executePipeline, $root.google.firestore.v1.ExecutePipelineRequest, $root.google.firestore.v1.ExecutePipelineResponse, request, callback); + }, "name", { value: "ExecutePipeline" }); + + /** + * Calls ExecutePipeline. + * @function executePipeline + * @memberof google.firestore.v1.Firestore + * @instance + * @param {google.firestore.v1.IExecutePipelineRequest} request ExecutePipelineRequest message or plain object + * @returns {Promise} Promise + * @variation 2 + */ + + /** + * Callback as used by {@link google.firestore.v1.Firestore#runAggregationQuery}. + * @memberof google.firestore.v1.Firestore + * @typedef RunAggregationQueryCallback + * @type {function} + * @param {Error|null} error Error, if any + * @param {google.firestore.v1.RunAggregationQueryResponse} [response] RunAggregationQueryResponse + */ + + /** + * Calls RunAggregationQuery. + * @function runAggregationQuery + * @memberof google.firestore.v1.Firestore + * @instance + * @param {google.firestore.v1.IRunAggregationQueryRequest} request RunAggregationQueryRequest message or plain object * @param {google.firestore.v1.Firestore.RunAggregationQueryCallback} callback Node-style callback called with the error, if any, and RunAggregationQueryResponse * @returns {undefined} * @variation 1 @@ -14138,64 +14641,475 @@ RunQueryRequest.prototype.newTransaction = null; /** - * RunQueryRequest readTime. - * @member {google.protobuf.ITimestamp|null|undefined} readTime - * @memberof google.firestore.v1.RunQueryRequest + * RunQueryRequest readTime. + * @member {google.protobuf.ITimestamp|null|undefined} readTime + * @memberof google.firestore.v1.RunQueryRequest + * @instance + */ + RunQueryRequest.prototype.readTime = null; + + /** + * RunQueryRequest explainOptions. + * @member {google.firestore.v1.IExplainOptions|null|undefined} explainOptions + * @memberof google.firestore.v1.RunQueryRequest + * @instance + */ + RunQueryRequest.prototype.explainOptions = null; + + // OneOf field names bound to virtual getters and setters + var $oneOfFields; + + /** + * RunQueryRequest queryType. + * @member {"structuredQuery"|undefined} queryType + * @memberof google.firestore.v1.RunQueryRequest + * @instance + */ + Object.defineProperty(RunQueryRequest.prototype, "queryType", { + get: $util.oneOfGetter($oneOfFields = ["structuredQuery"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * RunQueryRequest consistencySelector. + * @member {"transaction"|"newTransaction"|"readTime"|undefined} consistencySelector + * @memberof google.firestore.v1.RunQueryRequest + * @instance + */ + Object.defineProperty(RunQueryRequest.prototype, "consistencySelector", { + get: $util.oneOfGetter($oneOfFields = ["transaction", "newTransaction", "readTime"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Creates a RunQueryRequest message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.firestore.v1.RunQueryRequest + * @static + * @param {Object.} object Plain object + * @returns {google.firestore.v1.RunQueryRequest} RunQueryRequest + */ + RunQueryRequest.fromObject = function fromObject(object) { + if (object instanceof $root.google.firestore.v1.RunQueryRequest) + return object; + var message = new $root.google.firestore.v1.RunQueryRequest(); + if (object.parent != null) + message.parent = String(object.parent); + if (object.structuredQuery != null) { + if (typeof object.structuredQuery !== "object") + throw TypeError(".google.firestore.v1.RunQueryRequest.structuredQuery: object expected"); + message.structuredQuery = $root.google.firestore.v1.StructuredQuery.fromObject(object.structuredQuery); + } + if (object.transaction != null) + if (typeof object.transaction === "string") + $util.base64.decode(object.transaction, message.transaction = $util.newBuffer($util.base64.length(object.transaction)), 0); + else if (object.transaction.length >= 0) + message.transaction = object.transaction; + if (object.newTransaction != null) { + if (typeof object.newTransaction !== "object") + throw TypeError(".google.firestore.v1.RunQueryRequest.newTransaction: object expected"); + message.newTransaction = $root.google.firestore.v1.TransactionOptions.fromObject(object.newTransaction); + } + if (object.readTime != null) { + if (typeof object.readTime !== "object") + throw TypeError(".google.firestore.v1.RunQueryRequest.readTime: object expected"); + message.readTime = $root.google.protobuf.Timestamp.fromObject(object.readTime); + } + if (object.explainOptions != null) { + if (typeof object.explainOptions !== "object") + throw TypeError(".google.firestore.v1.RunQueryRequest.explainOptions: object expected"); + message.explainOptions = $root.google.firestore.v1.ExplainOptions.fromObject(object.explainOptions); + } + return message; + }; + + /** + * Creates a plain object from a RunQueryRequest message. Also converts values to other types if specified. + * @function toObject + * @memberof google.firestore.v1.RunQueryRequest + * @static + * @param {google.firestore.v1.RunQueryRequest} message RunQueryRequest + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + RunQueryRequest.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.parent = ""; + object.explainOptions = null; + } + if (message.parent != null && message.hasOwnProperty("parent")) + object.parent = message.parent; + if (message.structuredQuery != null && message.hasOwnProperty("structuredQuery")) { + object.structuredQuery = $root.google.firestore.v1.StructuredQuery.toObject(message.structuredQuery, options); + if (options.oneofs) + object.queryType = "structuredQuery"; + } + if (message.transaction != null && message.hasOwnProperty("transaction")) { + object.transaction = options.bytes === String ? $util.base64.encode(message.transaction, 0, message.transaction.length) : options.bytes === Array ? Array.prototype.slice.call(message.transaction) : message.transaction; + if (options.oneofs) + object.consistencySelector = "transaction"; + } + if (message.newTransaction != null && message.hasOwnProperty("newTransaction")) { + object.newTransaction = $root.google.firestore.v1.TransactionOptions.toObject(message.newTransaction, options); + if (options.oneofs) + object.consistencySelector = "newTransaction"; + } + if (message.readTime != null && message.hasOwnProperty("readTime")) { + object.readTime = $root.google.protobuf.Timestamp.toObject(message.readTime, options); + if (options.oneofs) + object.consistencySelector = "readTime"; + } + if (message.explainOptions != null && message.hasOwnProperty("explainOptions")) + object.explainOptions = $root.google.firestore.v1.ExplainOptions.toObject(message.explainOptions, options); + return object; + }; + + /** + * Converts this RunQueryRequest to JSON. + * @function toJSON + * @memberof google.firestore.v1.RunQueryRequest + * @instance + * @returns {Object.} JSON object + */ + RunQueryRequest.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for RunQueryRequest + * @function getTypeUrl + * @memberof google.firestore.v1.RunQueryRequest + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + RunQueryRequest.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.firestore.v1.RunQueryRequest"; + }; + + return RunQueryRequest; + })(); + + v1.RunQueryResponse = (function() { + + /** + * Properties of a RunQueryResponse. + * @memberof google.firestore.v1 + * @interface IRunQueryResponse + * @property {Uint8Array|null} [transaction] RunQueryResponse transaction + * @property {google.firestore.v1.IDocument|null} [document] RunQueryResponse document + * @property {google.protobuf.ITimestamp|null} [readTime] RunQueryResponse readTime + * @property {number|null} [skippedResults] RunQueryResponse skippedResults + * @property {boolean|null} [done] RunQueryResponse done + * @property {google.firestore.v1.IExplainMetrics|null} [explainMetrics] RunQueryResponse explainMetrics + */ + + /** + * Constructs a new RunQueryResponse. + * @memberof google.firestore.v1 + * @classdesc Represents a RunQueryResponse. + * @implements IRunQueryResponse + * @constructor + * @param {google.firestore.v1.IRunQueryResponse=} [properties] Properties to set + */ + function RunQueryResponse(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * RunQueryResponse transaction. + * @member {Uint8Array} transaction + * @memberof google.firestore.v1.RunQueryResponse + * @instance + */ + RunQueryResponse.prototype.transaction = $util.newBuffer([]); + + /** + * RunQueryResponse document. + * @member {google.firestore.v1.IDocument|null|undefined} document + * @memberof google.firestore.v1.RunQueryResponse + * @instance + */ + RunQueryResponse.prototype.document = null; + + /** + * RunQueryResponse readTime. + * @member {google.protobuf.ITimestamp|null|undefined} readTime + * @memberof google.firestore.v1.RunQueryResponse + * @instance + */ + RunQueryResponse.prototype.readTime = null; + + /** + * RunQueryResponse skippedResults. + * @member {number} skippedResults + * @memberof google.firestore.v1.RunQueryResponse + * @instance + */ + RunQueryResponse.prototype.skippedResults = 0; + + /** + * RunQueryResponse done. + * @member {boolean|null|undefined} done + * @memberof google.firestore.v1.RunQueryResponse + * @instance + */ + RunQueryResponse.prototype.done = null; + + /** + * RunQueryResponse explainMetrics. + * @member {google.firestore.v1.IExplainMetrics|null|undefined} explainMetrics + * @memberof google.firestore.v1.RunQueryResponse + * @instance + */ + RunQueryResponse.prototype.explainMetrics = null; + + // OneOf field names bound to virtual getters and setters + var $oneOfFields; + + /** + * RunQueryResponse continuationSelector. + * @member {"done"|undefined} continuationSelector + * @memberof google.firestore.v1.RunQueryResponse + * @instance + */ + Object.defineProperty(RunQueryResponse.prototype, "continuationSelector", { + get: $util.oneOfGetter($oneOfFields = ["done"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Creates a RunQueryResponse message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.firestore.v1.RunQueryResponse + * @static + * @param {Object.} object Plain object + * @returns {google.firestore.v1.RunQueryResponse} RunQueryResponse + */ + RunQueryResponse.fromObject = function fromObject(object) { + if (object instanceof $root.google.firestore.v1.RunQueryResponse) + return object; + var message = new $root.google.firestore.v1.RunQueryResponse(); + if (object.transaction != null) + if (typeof object.transaction === "string") + $util.base64.decode(object.transaction, message.transaction = $util.newBuffer($util.base64.length(object.transaction)), 0); + else if (object.transaction.length >= 0) + message.transaction = object.transaction; + if (object.document != null) { + if (typeof object.document !== "object") + throw TypeError(".google.firestore.v1.RunQueryResponse.document: object expected"); + message.document = $root.google.firestore.v1.Document.fromObject(object.document); + } + if (object.readTime != null) { + if (typeof object.readTime !== "object") + throw TypeError(".google.firestore.v1.RunQueryResponse.readTime: object expected"); + message.readTime = $root.google.protobuf.Timestamp.fromObject(object.readTime); + } + if (object.skippedResults != null) + message.skippedResults = object.skippedResults | 0; + if (object.done != null) + message.done = Boolean(object.done); + if (object.explainMetrics != null) { + if (typeof object.explainMetrics !== "object") + throw TypeError(".google.firestore.v1.RunQueryResponse.explainMetrics: object expected"); + message.explainMetrics = $root.google.firestore.v1.ExplainMetrics.fromObject(object.explainMetrics); + } + return message; + }; + + /** + * Creates a plain object from a RunQueryResponse message. Also converts values to other types if specified. + * @function toObject + * @memberof google.firestore.v1.RunQueryResponse + * @static + * @param {google.firestore.v1.RunQueryResponse} message RunQueryResponse + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + RunQueryResponse.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.document = null; + if (options.bytes === String) + object.transaction = ""; + else { + object.transaction = []; + if (options.bytes !== Array) + object.transaction = $util.newBuffer(object.transaction); + } + object.readTime = null; + object.skippedResults = 0; + object.explainMetrics = null; + } + if (message.document != null && message.hasOwnProperty("document")) + object.document = $root.google.firestore.v1.Document.toObject(message.document, options); + if (message.transaction != null && message.hasOwnProperty("transaction")) + object.transaction = options.bytes === String ? $util.base64.encode(message.transaction, 0, message.transaction.length) : options.bytes === Array ? Array.prototype.slice.call(message.transaction) : message.transaction; + if (message.readTime != null && message.hasOwnProperty("readTime")) + object.readTime = $root.google.protobuf.Timestamp.toObject(message.readTime, options); + if (message.skippedResults != null && message.hasOwnProperty("skippedResults")) + object.skippedResults = message.skippedResults; + if (message.done != null && message.hasOwnProperty("done")) { + object.done = message.done; + if (options.oneofs) + object.continuationSelector = "done"; + } + if (message.explainMetrics != null && message.hasOwnProperty("explainMetrics")) + object.explainMetrics = $root.google.firestore.v1.ExplainMetrics.toObject(message.explainMetrics, options); + return object; + }; + + /** + * Converts this RunQueryResponse to JSON. + * @function toJSON + * @memberof google.firestore.v1.RunQueryResponse + * @instance + * @returns {Object.} JSON object + */ + RunQueryResponse.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for RunQueryResponse + * @function getTypeUrl + * @memberof google.firestore.v1.RunQueryResponse + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + RunQueryResponse.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.firestore.v1.RunQueryResponse"; + }; + + return RunQueryResponse; + })(); + + v1.ExecutePipelineRequest = (function() { + + /** + * Properties of an ExecutePipelineRequest. + * @memberof google.firestore.v1 + * @interface IExecutePipelineRequest + * @property {string|null} [database] ExecutePipelineRequest database + * @property {google.firestore.v1.IStructuredPipeline|null} [structuredPipeline] ExecutePipelineRequest structuredPipeline + * @property {Uint8Array|null} [transaction] ExecutePipelineRequest transaction + * @property {google.firestore.v1.ITransactionOptions|null} [newTransaction] ExecutePipelineRequest newTransaction + * @property {google.protobuf.ITimestamp|null} [readTime] ExecutePipelineRequest readTime + */ + + /** + * Constructs a new ExecutePipelineRequest. + * @memberof google.firestore.v1 + * @classdesc Represents an ExecutePipelineRequest. + * @implements IExecutePipelineRequest + * @constructor + * @param {google.firestore.v1.IExecutePipelineRequest=} [properties] Properties to set + */ + function ExecutePipelineRequest(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * ExecutePipelineRequest database. + * @member {string} database + * @memberof google.firestore.v1.ExecutePipelineRequest + * @instance + */ + ExecutePipelineRequest.prototype.database = ""; + + /** + * ExecutePipelineRequest structuredPipeline. + * @member {google.firestore.v1.IStructuredPipeline|null|undefined} structuredPipeline + * @memberof google.firestore.v1.ExecutePipelineRequest + * @instance + */ + ExecutePipelineRequest.prototype.structuredPipeline = null; + + /** + * ExecutePipelineRequest transaction. + * @member {Uint8Array|null|undefined} transaction + * @memberof google.firestore.v1.ExecutePipelineRequest + * @instance + */ + ExecutePipelineRequest.prototype.transaction = null; + + /** + * ExecutePipelineRequest newTransaction. + * @member {google.firestore.v1.ITransactionOptions|null|undefined} newTransaction + * @memberof google.firestore.v1.ExecutePipelineRequest * @instance */ - RunQueryRequest.prototype.readTime = null; + ExecutePipelineRequest.prototype.newTransaction = null; /** - * RunQueryRequest explainOptions. - * @member {google.firestore.v1.IExplainOptions|null|undefined} explainOptions - * @memberof google.firestore.v1.RunQueryRequest + * ExecutePipelineRequest readTime. + * @member {google.protobuf.ITimestamp|null|undefined} readTime + * @memberof google.firestore.v1.ExecutePipelineRequest * @instance */ - RunQueryRequest.prototype.explainOptions = null; + ExecutePipelineRequest.prototype.readTime = null; // OneOf field names bound to virtual getters and setters var $oneOfFields; /** - * RunQueryRequest queryType. - * @member {"structuredQuery"|undefined} queryType - * @memberof google.firestore.v1.RunQueryRequest + * ExecutePipelineRequest pipelineType. + * @member {"structuredPipeline"|undefined} pipelineType + * @memberof google.firestore.v1.ExecutePipelineRequest * @instance */ - Object.defineProperty(RunQueryRequest.prototype, "queryType", { - get: $util.oneOfGetter($oneOfFields = ["structuredQuery"]), + Object.defineProperty(ExecutePipelineRequest.prototype, "pipelineType", { + get: $util.oneOfGetter($oneOfFields = ["structuredPipeline"]), set: $util.oneOfSetter($oneOfFields) }); /** - * RunQueryRequest consistencySelector. + * ExecutePipelineRequest consistencySelector. * @member {"transaction"|"newTransaction"|"readTime"|undefined} consistencySelector - * @memberof google.firestore.v1.RunQueryRequest + * @memberof google.firestore.v1.ExecutePipelineRequest * @instance */ - Object.defineProperty(RunQueryRequest.prototype, "consistencySelector", { + Object.defineProperty(ExecutePipelineRequest.prototype, "consistencySelector", { get: $util.oneOfGetter($oneOfFields = ["transaction", "newTransaction", "readTime"]), set: $util.oneOfSetter($oneOfFields) }); /** - * Creates a RunQueryRequest message from a plain object. Also converts values to their respective internal types. + * Creates an ExecutePipelineRequest message from a plain object. Also converts values to their respective internal types. * @function fromObject - * @memberof google.firestore.v1.RunQueryRequest + * @memberof google.firestore.v1.ExecutePipelineRequest * @static * @param {Object.} object Plain object - * @returns {google.firestore.v1.RunQueryRequest} RunQueryRequest + * @returns {google.firestore.v1.ExecutePipelineRequest} ExecutePipelineRequest */ - RunQueryRequest.fromObject = function fromObject(object) { - if (object instanceof $root.google.firestore.v1.RunQueryRequest) + ExecutePipelineRequest.fromObject = function fromObject(object) { + if (object instanceof $root.google.firestore.v1.ExecutePipelineRequest) return object; - var message = new $root.google.firestore.v1.RunQueryRequest(); - if (object.parent != null) - message.parent = String(object.parent); - if (object.structuredQuery != null) { - if (typeof object.structuredQuery !== "object") - throw TypeError(".google.firestore.v1.RunQueryRequest.structuredQuery: object expected"); - message.structuredQuery = $root.google.firestore.v1.StructuredQuery.fromObject(object.structuredQuery); + var message = new $root.google.firestore.v1.ExecutePipelineRequest(); + if (object.database != null) + message.database = String(object.database); + if (object.structuredPipeline != null) { + if (typeof object.structuredPipeline !== "object") + throw TypeError(".google.firestore.v1.ExecutePipelineRequest.structuredPipeline: object expected"); + message.structuredPipeline = $root.google.firestore.v1.StructuredPipeline.fromObject(object.structuredPipeline); } if (object.transaction != null) if (typeof object.transaction === "string") @@ -14204,45 +15118,38 @@ message.transaction = object.transaction; if (object.newTransaction != null) { if (typeof object.newTransaction !== "object") - throw TypeError(".google.firestore.v1.RunQueryRequest.newTransaction: object expected"); + throw TypeError(".google.firestore.v1.ExecutePipelineRequest.newTransaction: object expected"); message.newTransaction = $root.google.firestore.v1.TransactionOptions.fromObject(object.newTransaction); } if (object.readTime != null) { if (typeof object.readTime !== "object") - throw TypeError(".google.firestore.v1.RunQueryRequest.readTime: object expected"); + throw TypeError(".google.firestore.v1.ExecutePipelineRequest.readTime: object expected"); message.readTime = $root.google.protobuf.Timestamp.fromObject(object.readTime); } - if (object.explainOptions != null) { - if (typeof object.explainOptions !== "object") - throw TypeError(".google.firestore.v1.RunQueryRequest.explainOptions: object expected"); - message.explainOptions = $root.google.firestore.v1.ExplainOptions.fromObject(object.explainOptions); - } return message; }; /** - * Creates a plain object from a RunQueryRequest message. Also converts values to other types if specified. + * Creates a plain object from an ExecutePipelineRequest message. Also converts values to other types if specified. * @function toObject - * @memberof google.firestore.v1.RunQueryRequest + * @memberof google.firestore.v1.ExecutePipelineRequest * @static - * @param {google.firestore.v1.RunQueryRequest} message RunQueryRequest + * @param {google.firestore.v1.ExecutePipelineRequest} message ExecutePipelineRequest * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.} Plain object */ - RunQueryRequest.toObject = function toObject(message, options) { + ExecutePipelineRequest.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; - if (options.defaults) { - object.parent = ""; - object.explainOptions = null; - } - if (message.parent != null && message.hasOwnProperty("parent")) - object.parent = message.parent; - if (message.structuredQuery != null && message.hasOwnProperty("structuredQuery")) { - object.structuredQuery = $root.google.firestore.v1.StructuredQuery.toObject(message.structuredQuery, options); + if (options.defaults) + object.database = ""; + if (message.database != null && message.hasOwnProperty("database")) + object.database = message.database; + if (message.structuredPipeline != null && message.hasOwnProperty("structuredPipeline")) { + object.structuredPipeline = $root.google.firestore.v1.StructuredPipeline.toObject(message.structuredPipeline, options); if (options.oneofs) - object.queryType = "structuredQuery"; + object.pipelineType = "structuredPipeline"; } if (message.transaction != null && message.hasOwnProperty("transaction")) { object.transaction = options.bytes === String ? $util.base64.encode(message.transaction, 0, message.transaction.length) : options.bytes === Array ? Array.prototype.slice.call(message.transaction) : message.transaction; @@ -14259,63 +15166,59 @@ if (options.oneofs) object.consistencySelector = "readTime"; } - if (message.explainOptions != null && message.hasOwnProperty("explainOptions")) - object.explainOptions = $root.google.firestore.v1.ExplainOptions.toObject(message.explainOptions, options); return object; }; /** - * Converts this RunQueryRequest to JSON. + * Converts this ExecutePipelineRequest to JSON. * @function toJSON - * @memberof google.firestore.v1.RunQueryRequest + * @memberof google.firestore.v1.ExecutePipelineRequest * @instance * @returns {Object.} JSON object */ - RunQueryRequest.prototype.toJSON = function toJSON() { + ExecutePipelineRequest.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; /** - * Gets the default type url for RunQueryRequest + * Gets the default type url for ExecutePipelineRequest * @function getTypeUrl - * @memberof google.firestore.v1.RunQueryRequest + * @memberof google.firestore.v1.ExecutePipelineRequest * @static * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @returns {string} The default type url */ - RunQueryRequest.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + ExecutePipelineRequest.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { typeUrlPrefix = "type.googleapis.com"; } - return typeUrlPrefix + "/google.firestore.v1.RunQueryRequest"; + return typeUrlPrefix + "/google.firestore.v1.ExecutePipelineRequest"; }; - return RunQueryRequest; + return ExecutePipelineRequest; })(); - v1.RunQueryResponse = (function() { + v1.ExecutePipelineResponse = (function() { /** - * Properties of a RunQueryResponse. + * Properties of an ExecutePipelineResponse. * @memberof google.firestore.v1 - * @interface IRunQueryResponse - * @property {Uint8Array|null} [transaction] RunQueryResponse transaction - * @property {google.firestore.v1.IDocument|null} [document] RunQueryResponse document - * @property {google.protobuf.ITimestamp|null} [readTime] RunQueryResponse readTime - * @property {number|null} [skippedResults] RunQueryResponse skippedResults - * @property {boolean|null} [done] RunQueryResponse done - * @property {google.firestore.v1.IExplainMetrics|null} [explainMetrics] RunQueryResponse explainMetrics + * @interface IExecutePipelineResponse + * @property {Uint8Array|null} [transaction] ExecutePipelineResponse transaction + * @property {Array.|null} [results] ExecutePipelineResponse results + * @property {google.protobuf.ITimestamp|null} [executionTime] ExecutePipelineResponse executionTime */ /** - * Constructs a new RunQueryResponse. + * Constructs a new ExecutePipelineResponse. * @memberof google.firestore.v1 - * @classdesc Represents a RunQueryResponse. - * @implements IRunQueryResponse + * @classdesc Represents an ExecutePipelineResponse. + * @implements IExecutePipelineResponse * @constructor - * @param {google.firestore.v1.IRunQueryResponse=} [properties] Properties to set + * @param {google.firestore.v1.IExecutePipelineResponse=} [properties] Properties to set */ - function RunQueryResponse(properties) { + function ExecutePipelineResponse(properties) { + this.results = []; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -14323,121 +15226,80 @@ } /** - * RunQueryResponse transaction. + * ExecutePipelineResponse transaction. * @member {Uint8Array} transaction - * @memberof google.firestore.v1.RunQueryResponse - * @instance - */ - RunQueryResponse.prototype.transaction = $util.newBuffer([]); - - /** - * RunQueryResponse document. - * @member {google.firestore.v1.IDocument|null|undefined} document - * @memberof google.firestore.v1.RunQueryResponse - * @instance - */ - RunQueryResponse.prototype.document = null; - - /** - * RunQueryResponse readTime. - * @member {google.protobuf.ITimestamp|null|undefined} readTime - * @memberof google.firestore.v1.RunQueryResponse - * @instance - */ - RunQueryResponse.prototype.readTime = null; - - /** - * RunQueryResponse skippedResults. - * @member {number} skippedResults - * @memberof google.firestore.v1.RunQueryResponse - * @instance - */ - RunQueryResponse.prototype.skippedResults = 0; - - /** - * RunQueryResponse done. - * @member {boolean|null|undefined} done - * @memberof google.firestore.v1.RunQueryResponse + * @memberof google.firestore.v1.ExecutePipelineResponse * @instance */ - RunQueryResponse.prototype.done = null; + ExecutePipelineResponse.prototype.transaction = $util.newBuffer([]); /** - * RunQueryResponse explainMetrics. - * @member {google.firestore.v1.IExplainMetrics|null|undefined} explainMetrics - * @memberof google.firestore.v1.RunQueryResponse + * ExecutePipelineResponse results. + * @member {Array.} results + * @memberof google.firestore.v1.ExecutePipelineResponse * @instance */ - RunQueryResponse.prototype.explainMetrics = null; - - // OneOf field names bound to virtual getters and setters - var $oneOfFields; + ExecutePipelineResponse.prototype.results = $util.emptyArray; /** - * RunQueryResponse continuationSelector. - * @member {"done"|undefined} continuationSelector - * @memberof google.firestore.v1.RunQueryResponse + * ExecutePipelineResponse executionTime. + * @member {google.protobuf.ITimestamp|null|undefined} executionTime + * @memberof google.firestore.v1.ExecutePipelineResponse * @instance */ - Object.defineProperty(RunQueryResponse.prototype, "continuationSelector", { - get: $util.oneOfGetter($oneOfFields = ["done"]), - set: $util.oneOfSetter($oneOfFields) - }); + ExecutePipelineResponse.prototype.executionTime = null; /** - * Creates a RunQueryResponse message from a plain object. Also converts values to their respective internal types. + * Creates an ExecutePipelineResponse message from a plain object. Also converts values to their respective internal types. * @function fromObject - * @memberof google.firestore.v1.RunQueryResponse + * @memberof google.firestore.v1.ExecutePipelineResponse * @static * @param {Object.} object Plain object - * @returns {google.firestore.v1.RunQueryResponse} RunQueryResponse + * @returns {google.firestore.v1.ExecutePipelineResponse} ExecutePipelineResponse */ - RunQueryResponse.fromObject = function fromObject(object) { - if (object instanceof $root.google.firestore.v1.RunQueryResponse) + ExecutePipelineResponse.fromObject = function fromObject(object) { + if (object instanceof $root.google.firestore.v1.ExecutePipelineResponse) return object; - var message = new $root.google.firestore.v1.RunQueryResponse(); + var message = new $root.google.firestore.v1.ExecutePipelineResponse(); if (object.transaction != null) if (typeof object.transaction === "string") $util.base64.decode(object.transaction, message.transaction = $util.newBuffer($util.base64.length(object.transaction)), 0); else if (object.transaction.length >= 0) message.transaction = object.transaction; - if (object.document != null) { - if (typeof object.document !== "object") - throw TypeError(".google.firestore.v1.RunQueryResponse.document: object expected"); - message.document = $root.google.firestore.v1.Document.fromObject(object.document); - } - if (object.readTime != null) { - if (typeof object.readTime !== "object") - throw TypeError(".google.firestore.v1.RunQueryResponse.readTime: object expected"); - message.readTime = $root.google.protobuf.Timestamp.fromObject(object.readTime); + if (object.results) { + if (!Array.isArray(object.results)) + throw TypeError(".google.firestore.v1.ExecutePipelineResponse.results: array expected"); + message.results = []; + for (var i = 0; i < object.results.length; ++i) { + if (typeof object.results[i] !== "object") + throw TypeError(".google.firestore.v1.ExecutePipelineResponse.results: object expected"); + message.results[i] = $root.google.firestore.v1.Document.fromObject(object.results[i]); + } } - if (object.skippedResults != null) - message.skippedResults = object.skippedResults | 0; - if (object.done != null) - message.done = Boolean(object.done); - if (object.explainMetrics != null) { - if (typeof object.explainMetrics !== "object") - throw TypeError(".google.firestore.v1.RunQueryResponse.explainMetrics: object expected"); - message.explainMetrics = $root.google.firestore.v1.ExplainMetrics.fromObject(object.explainMetrics); + if (object.executionTime != null) { + if (typeof object.executionTime !== "object") + throw TypeError(".google.firestore.v1.ExecutePipelineResponse.executionTime: object expected"); + message.executionTime = $root.google.protobuf.Timestamp.fromObject(object.executionTime); } return message; }; /** - * Creates a plain object from a RunQueryResponse message. Also converts values to other types if specified. + * Creates a plain object from an ExecutePipelineResponse message. Also converts values to other types if specified. * @function toObject - * @memberof google.firestore.v1.RunQueryResponse + * @memberof google.firestore.v1.ExecutePipelineResponse * @static - * @param {google.firestore.v1.RunQueryResponse} message RunQueryResponse + * @param {google.firestore.v1.ExecutePipelineResponse} message ExecutePipelineResponse * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.} Plain object */ - RunQueryResponse.toObject = function toObject(message, options) { + ExecutePipelineResponse.toObject = function toObject(message, options) { if (!options) options = {}; var object = {}; + if (options.arrays || options.defaults) + object.results = []; if (options.defaults) { - object.document = null; if (options.bytes === String) object.transaction = ""; else { @@ -14445,55 +15307,47 @@ if (options.bytes !== Array) object.transaction = $util.newBuffer(object.transaction); } - object.readTime = null; - object.skippedResults = 0; - object.explainMetrics = null; + object.executionTime = null; } - if (message.document != null && message.hasOwnProperty("document")) - object.document = $root.google.firestore.v1.Document.toObject(message.document, options); if (message.transaction != null && message.hasOwnProperty("transaction")) object.transaction = options.bytes === String ? $util.base64.encode(message.transaction, 0, message.transaction.length) : options.bytes === Array ? Array.prototype.slice.call(message.transaction) : message.transaction; - if (message.readTime != null && message.hasOwnProperty("readTime")) - object.readTime = $root.google.protobuf.Timestamp.toObject(message.readTime, options); - if (message.skippedResults != null && message.hasOwnProperty("skippedResults")) - object.skippedResults = message.skippedResults; - if (message.done != null && message.hasOwnProperty("done")) { - object.done = message.done; - if (options.oneofs) - object.continuationSelector = "done"; + if (message.results && message.results.length) { + object.results = []; + for (var j = 0; j < message.results.length; ++j) + object.results[j] = $root.google.firestore.v1.Document.toObject(message.results[j], options); } - if (message.explainMetrics != null && message.hasOwnProperty("explainMetrics")) - object.explainMetrics = $root.google.firestore.v1.ExplainMetrics.toObject(message.explainMetrics, options); + if (message.executionTime != null && message.hasOwnProperty("executionTime")) + object.executionTime = $root.google.protobuf.Timestamp.toObject(message.executionTime, options); return object; }; /** - * Converts this RunQueryResponse to JSON. + * Converts this ExecutePipelineResponse to JSON. * @function toJSON - * @memberof google.firestore.v1.RunQueryResponse + * @memberof google.firestore.v1.ExecutePipelineResponse * @instance * @returns {Object.} JSON object */ - RunQueryResponse.prototype.toJSON = function toJSON() { + ExecutePipelineResponse.prototype.toJSON = function toJSON() { return this.constructor.toObject(this, $protobuf.util.toJSONOptions); }; /** - * Gets the default type url for RunQueryResponse + * Gets the default type url for ExecutePipelineResponse * @function getTypeUrl - * @memberof google.firestore.v1.RunQueryResponse + * @memberof google.firestore.v1.ExecutePipelineResponse * @static * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") * @returns {string} The default type url */ - RunQueryResponse.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + ExecutePipelineResponse.getTypeUrl = function getTypeUrl(typeUrlPrefix) { if (typeUrlPrefix === undefined) { typeUrlPrefix = "type.googleapis.com"; } - return typeUrlPrefix + "/google.firestore.v1.RunQueryResponse"; + return typeUrlPrefix + "/google.firestore.v1.ExecutePipelineResponse"; }; - return RunQueryResponse; + return ExecutePipelineResponse; })(); v1.RunAggregationQueryRequest = (function() { @@ -17197,6 +18051,135 @@ return BatchWriteResponse; })(); + v1.StructuredPipeline = (function() { + + /** + * Properties of a StructuredPipeline. + * @memberof google.firestore.v1 + * @interface IStructuredPipeline + * @property {google.firestore.v1.IPipeline|null} [pipeline] StructuredPipeline pipeline + * @property {Object.|null} [options] StructuredPipeline options + */ + + /** + * Constructs a new StructuredPipeline. + * @memberof google.firestore.v1 + * @classdesc Represents a StructuredPipeline. + * @implements IStructuredPipeline + * @constructor + * @param {google.firestore.v1.IStructuredPipeline=} [properties] Properties to set + */ + function StructuredPipeline(properties) { + this.options = {}; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * StructuredPipeline pipeline. + * @member {google.firestore.v1.IPipeline|null|undefined} pipeline + * @memberof google.firestore.v1.StructuredPipeline + * @instance + */ + StructuredPipeline.prototype.pipeline = null; + + /** + * StructuredPipeline options. + * @member {Object.} options + * @memberof google.firestore.v1.StructuredPipeline + * @instance + */ + StructuredPipeline.prototype.options = $util.emptyObject; + + /** + * Creates a StructuredPipeline message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof google.firestore.v1.StructuredPipeline + * @static + * @param {Object.} object Plain object + * @returns {google.firestore.v1.StructuredPipeline} StructuredPipeline + */ + StructuredPipeline.fromObject = function fromObject(object) { + if (object instanceof $root.google.firestore.v1.StructuredPipeline) + return object; + var message = new $root.google.firestore.v1.StructuredPipeline(); + if (object.pipeline != null) { + if (typeof object.pipeline !== "object") + throw TypeError(".google.firestore.v1.StructuredPipeline.pipeline: object expected"); + message.pipeline = $root.google.firestore.v1.Pipeline.fromObject(object.pipeline); + } + if (object.options) { + if (typeof object.options !== "object") + throw TypeError(".google.firestore.v1.StructuredPipeline.options: object expected"); + message.options = {}; + for (var keys = Object.keys(object.options), i = 0; i < keys.length; ++i) { + if (typeof object.options[keys[i]] !== "object") + throw TypeError(".google.firestore.v1.StructuredPipeline.options: object expected"); + message.options[keys[i]] = $root.google.firestore.v1.Value.fromObject(object.options[keys[i]]); + } + } + return message; + }; + + /** + * Creates a plain object from a StructuredPipeline message. Also converts values to other types if specified. + * @function toObject + * @memberof google.firestore.v1.StructuredPipeline + * @static + * @param {google.firestore.v1.StructuredPipeline} message StructuredPipeline + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + StructuredPipeline.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.objects || options.defaults) + object.options = {}; + if (options.defaults) + object.pipeline = null; + if (message.pipeline != null && message.hasOwnProperty("pipeline")) + object.pipeline = $root.google.firestore.v1.Pipeline.toObject(message.pipeline, options); + var keys2; + if (message.options && (keys2 = Object.keys(message.options)).length) { + object.options = {}; + for (var j = 0; j < keys2.length; ++j) + object.options[keys2[j]] = $root.google.firestore.v1.Value.toObject(message.options[keys2[j]], options); + } + return object; + }; + + /** + * Converts this StructuredPipeline to JSON. + * @function toJSON + * @memberof google.firestore.v1.StructuredPipeline + * @instance + * @returns {Object.} JSON object + */ + StructuredPipeline.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for StructuredPipeline + * @function getTypeUrl + * @memberof google.firestore.v1.StructuredPipeline + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + StructuredPipeline.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/google.firestore.v1.StructuredPipeline"; + }; + + return StructuredPipeline; + })(); + v1.StructuredQuery = (function() { /** diff --git a/dev/protos/google/firestore/v1/document.proto b/dev/protos/google/firestore/v1/document.proto index 5ad6752aa..5348fe643 100644 --- a/dev/protos/google/firestore/v1/document.proto +++ b/dev/protos/google/firestore/v1/document.proto @@ -128,6 +128,50 @@ message Value { // A map value. MapValue map_value = 6; + + + // Value which references a field. + // + // This is considered relative (vs absolute) since it only refers to a field + // and not a field within a particular document. + // + // **Requires:** + // + // * Must follow [field reference][FieldReference.field_path] limitations. + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): long term, there is no reason this type should not be + // allowed to be used on the write path. --) + string field_reference_value = 19; + + // A value that represents an unevaluated expression. + // + // **Requires:** + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): similar to above, there is no reason to not allow + // storing expressions into the database, just no plan to support in + // the near term. + // + // This would actually be an interesting way to represent user-defined + // functions or more expressive rules-based systems. --) + Function function_value = 20; + + // A value that represents an unevaluated pipeline. + // + // **Requires:** + // + // * Not allowed to be used when writing documents. + // + // (-- NOTE(batchik): similar to above, there is no reason to not allow + // storing expressions into the database, just no plan to support in + // the near term. + // + // This would actually be an interesting way to represent user-defined + // functions or more expressive rules-based systems. --) + Pipeline pipeline_value = 21; } } @@ -147,3 +191,73 @@ message MapValue { // not exceed 1,500 bytes and cannot be empty. map fields = 1; } + +// Represents an unevaluated scalar expression. +// +// For example, the expression `like(user_name, "%alice%")` is represented as: +// +// ``` +// name: "like" +// args { field_reference: "user_name" } +// args { string_value: "%alice%" } +// ``` +// +// (-- api-linter: core::0123::resource-annotation=disabled +// aip.dev/not-precedent: this is not a One Platform API resource. --) +message Function { + // The name of the function to evaluate. + // + // **Requires:** + // + // * must be in snake case (lower case with underscore separator). + // + string name = 1; + + // Ordered list of arguments the given function expects. + repeated Value args = 2; + + // Optional named arguments that certain functions may support. + map options = 3; +} + +// A Firestore query represented as an ordered list of operations / stages. +message Pipeline { + // A single operation within a pipeline. + // + // A stage is made up of a unique name, and a list of arguments. The exact + // number of arguments & types is dependent on the stage type. + // + // To give an example, the stage `filter(state = "MD")` would be encoded as: + // + // ``` + // name: "filter" + // args { + // function_value { + // name: "eq" + // args { field_reference_value: "state" } + // args { string_value: "MD" } + // } + // } + // ``` + // + // See public documentation for the full list. + message Stage { + // The name of the stage to evaluate. + // + // **Requires:** + // + // * must be in snake case (lower case with underscore separator). + // + string name = 1; + + // Ordered list of arguments the given stage expects. + repeated Value args = 2; + + // Optional named arguments that certain functions may support. + map options = 3; + } + + // Ordered list of stages to evaluate. + repeated Stage stages = 1; +} + diff --git a/dev/protos/google/firestore/v1/firestore.proto b/dev/protos/google/firestore/v1/firestore.proto index 2ed6bf070..f8718bb67 100644 --- a/dev/protos/google/firestore/v1/firestore.proto +++ b/dev/protos/google/firestore/v1/firestore.proto @@ -22,6 +22,7 @@ import "google/api/field_behavior.proto"; import "google/firestore/v1/aggregation_result.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; +import "google/firestore/v1/pipeline.proto"; import "google/firestore/v1/query.proto"; import "google/firestore/v1/query_profile.proto"; import "google/firestore/v1/write.proto"; @@ -140,6 +141,15 @@ service Firestore { }; } + // Executes a pipeline query. + rpc ExecutePipeline(ExecutePipelineRequest) + returns (stream ExecutePipelineResponse) { + option (google.api.http) = { + post: "/v1beta1/{database=projects/*/databases/*}:executePipeline" + body: "*" + }; + } + // Runs an aggregation query. // // Rather than producing [Document][google.firestore.v1.Document] results like @@ -624,6 +634,82 @@ message RunQueryResponse { ExplainMetrics explain_metrics = 11; } +// The request for [Firestore.ExecutePipeline][]. +message ExecutePipelineRequest { + // Database identifier, in the form `projects/{project}/databases/{database}`. + string database = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + + oneof pipeline_type { + // A pipelined operation. + StructuredPipeline structured_pipeline = 2; + } + + // Optional consistency arguments, defaults to strong consistency. + oneof consistency_selector { + // Run the query within an already active transaction. + // + // The value here is the opaque transaction ID to execute the query in. + bytes transaction = 5; + + // Execute the pipeline in a new transaction. + // + // The identifier of the newly created transaction will be returned in the + // first response on the stream. This defaults to a read-only transaction. + TransactionOptions new_transaction = 6; + + // Execute the pipeline in a snapshot transaction at the given time. + // + // This must be a microsecond precision timestamp within the past one hour, + // or if Point-in-Time Recovery is enabled, can additionally be a whole + // minute timestamp within the past 7 days. + google.protobuf.Timestamp read_time = 7; + } + + // Explain / analyze options for the pipeline. + // ExplainOptions explain_options = 8 [(google.api.field_behavior) = OPTIONAL]; +} + +// The response for [Firestore.Execute][]. +message ExecutePipelineResponse { + // Newly created transaction identifier. + // + // This field is only specified on the first response from the server when + // the request specified [ExecuteRequest.new_transaction][]. + bytes transaction = 1; + + // An ordered batch of results returned executing a pipeline. + // + // The batch size is variable, and can even be zero for when only a partial + // progress message is returned. + // + // The fields present in the returned documents are only those that were + // explicitly requested in the pipeline, this include those like + // [`__name__`][Document.name] & [`__update_time__`][Document.update_time]. + // This is explicitly a divergence from `Firestore.RunQuery` / + // `Firestore.GetDocument` RPCs which always return such fields even when they + // are not specified in the [`mask`][DocumentMask]. + repeated Document results = 2; + + // The time at which the document(s) were read. + // + // This may be monotonically increasing; in this case, the previous documents + // in the result stream are guaranteed not to have changed between their + // `execution_time` and this one. + // + // If the query returns no results, a response with `execution_time` and no + // `results` will be sent, and this represents the time at which the operation + // was run. + google.protobuf.Timestamp execution_time = 3; + + // Query explain metrics. + // + // Set on the last response when [ExecutePipelineRequest.explain_options][] + // was specified on the request. + // ExplainMetrics explain_metrics = 4; +} + // The request for // [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. message RunAggregationQueryRequest { diff --git a/dev/protos/google/firestore/v1/pipeline.proto b/dev/protos/google/firestore/v1/pipeline.proto new file mode 100644 index 000000000..ea5b23093 --- /dev/null +++ b/dev/protos/google/firestore/v1/pipeline.proto @@ -0,0 +1,41 @@ +/*! + * 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. + */ + +syntax = "proto3"; +package google.firestore.v1; +import "google/firestore/v1/document.proto"; +option csharp_namespace = "Google.Cloud.Firestore.V1"; +option php_namespace = "Google\\Cloud\\Firestore\\V1"; +option ruby_package = "Google::Cloud::Firestore::V1"; +option java_multiple_files = true; +option java_package = "com.google.firestore.v1"; +option java_outer_classname = "PipelineProto"; +option objc_class_prefix = "GCFS"; +// A Firestore query represented as an ordered list of operations / stages. +// +// This is considered the top-level function which plans & executes a query. +// It is logically equivalent to `query(stages, options)`, but prevents the +// client from having to build a function wrapper. +message StructuredPipeline { + // The pipeline query to execute. + Pipeline pipeline = 1; + // Optional query-level arguments. + // + // (-- Think query statement hints. --) + // + // (-- TODO(batchik): define the api contract of using an unsupported hint --) + map options = 2; +} diff --git a/dev/protos/v1.json b/dev/protos/v1.json index 19be0044b..7d3336323 100644 --- a/dev/protos/v1.json +++ b/dev/protos/v1.json @@ -1555,7 +1555,10 @@ "referenceValue", "geoPointValue", "arrayValue", - "mapValue" + "mapValue", + "fieldReferenceValue", + "functionValue", + "pipelineValue" ] } }, @@ -1603,6 +1606,18 @@ "mapValue": { "type": "MapValue", "id": 6 + }, + "fieldReferenceValue": { + "type": "string", + "id": 19 + }, + "functionValue": { + "type": "Function", + "id": 20 + }, + "pipelineValue": { + "type": "Pipeline", + "id": 21 } } }, @@ -1624,6 +1639,53 @@ } } }, + "Function": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "args": { + "rule": "repeated", + "type": "Value", + "id": 2 + }, + "options": { + "keyType": "string", + "type": "Value", + "id": 3 + } + } + }, + "Pipeline": { + "fields": { + "stages": { + "rule": "repeated", + "type": "Stage", + "id": 1 + } + }, + "nested": { + "Stage": { + "fields": { + "name": { + "type": "string", + "id": 1 + }, + "args": { + "rule": "repeated", + "type": "Value", + "id": 2 + }, + "options": { + "keyType": "string", + "type": "Value", + "id": 3 + } + } + } + } + }, "BitSequence": { "fields": { "bitmap": { @@ -1898,6 +1960,23 @@ } ] }, + "ExecutePipeline": { + "requestType": "ExecutePipelineRequest", + "responseType": "ExecutePipelineResponse", + "responseStream": true, + "options": { + "(google.api.http).post": "/v1beta1/{database=projects/*/databases/*}:executePipeline", + "(google.api.http).body": "*" + }, + "parsedOptions": [ + { + "(google.api.http)": { + "post": "/v1beta1/{database=projects/*/databases/*}:executePipeline", + "body": "*" + } + } + ] + }, "RunAggregationQuery": { "requestType": "RunAggregationQueryRequest", "responseType": "RunAggregationQueryResponse", @@ -2446,6 +2525,64 @@ } } }, + "ExecutePipelineRequest": { + "oneofs": { + "pipelineType": { + "oneof": [ + "structuredPipeline" + ] + }, + "consistencySelector": { + "oneof": [ + "transaction", + "newTransaction", + "readTime" + ] + } + }, + "fields": { + "database": { + "type": "string", + "id": 1, + "options": { + "(google.api.field_behavior)": "REQUIRED" + } + }, + "structuredPipeline": { + "type": "StructuredPipeline", + "id": 2 + }, + "transaction": { + "type": "bytes", + "id": 5 + }, + "newTransaction": { + "type": "TransactionOptions", + "id": 6 + }, + "readTime": { + "type": "google.protobuf.Timestamp", + "id": 7 + } + } + }, + "ExecutePipelineResponse": { + "fields": { + "transaction": { + "type": "bytes", + "id": 1 + }, + "results": { + "rule": "repeated", + "type": "Document", + "id": 2 + }, + "executionTime": { + "type": "google.protobuf.Timestamp", + "id": 3 + } + } + }, "RunAggregationQueryRequest": { "oneofs": { "queryType": { @@ -2877,6 +3014,19 @@ } } }, + "StructuredPipeline": { + "fields": { + "pipeline": { + "type": "Pipeline", + "id": 1 + }, + "options": { + "keyType": "string", + "type": "Value", + "id": 2 + } + } + }, "StructuredQuery": { "fields": { "select": { From 608a75e246a141a0a0b2e595cfd910f95b44b7bb Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 12 Jun 2024 14:52:13 -0400 Subject: [PATCH 02/31] Add expressions, stages, and pipeline --- dev/src/expression.ts | 803 ++++++++++++++++++ dev/src/pipeline-util.ts | 440 ++++++++++ dev/src/pipeline.ts | 318 +++++++ dev/src/reference/aggregate-query.ts | 21 + .../reference/composite-filter-internal.ts | 8 + dev/src/reference/field-filter-internal.ts | 44 +- dev/src/reference/query.ts | 142 +++- dev/src/reference/types.ts | 11 + dev/src/reference/vector-query.ts | 13 + dev/src/serializer.ts | 7 + dev/src/stage.ts | 228 +++++ dev/src/types.ts | 5 + types/firestore.d.ts | 12 +- 13 files changed, 2010 insertions(+), 42 deletions(-) create mode 100644 dev/src/expression.ts create mode 100644 dev/src/pipeline-util.ts create mode 100644 dev/src/pipeline.ts create mode 100644 dev/src/stage.ts diff --git a/dev/src/expression.ts b/dev/src/expression.ts new file mode 100644 index 000000000..0387699d3 --- /dev/null +++ b/dev/src/expression.ts @@ -0,0 +1,803 @@ +import * as protos from '../protos/firestore_v1_proto_api'; +import api = protos.google.firestore.v1; + +import {VectorValue} from './field-value'; +import {GeoPoint} from './geo-point'; +import {FieldPath} from './path'; +import {Pipeline} from './pipeline'; +import {isFirestoreValue} from './pipeline-util'; +import {DocumentReference} from './reference/document-reference'; +import {Serializer} from './serializer'; +import {Timestamp} from './timestamp'; + +export interface Selectable { + selectable: true; +} + +export interface FilterCondition { + filterable: true; +} + +export interface Accumulator { + toField(fieldName: string): AggregateTarget; +} + +export class AggregateTarget implements Selectable { + constructor( + public field: Field, + public accumulator: Accumulator & Expr + ) {} + + selectable = true as const; +} + +export type FilterExpr = Expr & FilterCondition; + +export type SelectableExpr = Expr & Selectable; + +export type ExprType = 'Field' | 'Constant' | 'Function' | 'ListOfExprs'; + +export abstract class Expr { + equal(other: Expr): Equal; + equal(other: any): Equal; + equal(other: any): Equal { + if (other instanceof Expr) { + return new Equal(this, other); + } + return new Equal(this, Constant.of(other)); + } + + notEqual(other: Expr): NotEqual; + notEqual(other: any): NotEqual; + notEqual(other: any): NotEqual { + if (other instanceof Expr) { + return new NotEqual(this, other); + } + return new NotEqual(this, Constant.of(other)); + } + + lessThan(other: Expr): LessThan; + lessThan(other: any): LessThan; + lessThan(other: any): LessThan { + if (other instanceof Expr) { + return new LessThan(this, other); + } + return new LessThan(this, Constant.of(other)); + } + + lessThanOrEqual(other: Expr): LessThanOrEqual; + lessThanOrEqual(other: any): LessThanOrEqual; + lessThanOrEqual(other: any): LessThanOrEqual { + if (other instanceof Expr) { + return new LessThanOrEqual(this, other); + } + return new LessThanOrEqual(this, Constant.of(other)); + } + + greaterThan(other: Expr): GreaterThan; + greaterThan(other: any): GreaterThan; + greaterThan(other: any): GreaterThan { + if (other instanceof Expr) { + return new GreaterThan(this, other); + } + return new GreaterThan(this, Constant.of(other)); + } + + greaterThanOrEqual(other: Expr): GreaterThanOrEqual; + greaterThanOrEqual(other: any): GreaterThanOrEqual; + greaterThanOrEqual(other: any): GreaterThanOrEqual { + if (other instanceof Expr) { + return new GreaterThanOrEqual(this, other); + } + return new GreaterThanOrEqual(this, Constant.of(other)); + } + + arrayContains(element: Expr): ArrayContains; + arrayContains(element: any): ArrayContains; + arrayContains(element: any): ArrayContains { + if (element instanceof Expr) { + return new ArrayContains(this, element); + } + return new ArrayContains(this, Constant.of(element)); + } + + arrayContainsAny(values: Expr[]): ArrayContainsAny; + 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); + } + + in(...others: Expr[]): In; + in(...others: any[]): In; + in(...others: any[]): In { + const exprOthers = others.map(other => + other instanceof Expr ? other : Constant.of(other) + ); + return new In(this, exprOthers); + } + + isNaN(): IsNan { + return new IsNan(this); + } + + isNull(): IsNull { + return new IsNull(this); + } + + count(): Count { + return new Count(this, false); + } + + sum(): Sum { + return new Sum(this, false); + } + + avg(): Avg { + return new Avg(this, false); + } + + min(): Min { + return new Min(this, false); + } + + max(): Max { + return new Max(this, false); + } + + cosineDistance(other: Expr): CosineDistance; + cosineDistance(other: VectorValue): CosineDistance; + cosineDistance(other: number[]): CosineDistance; + cosineDistance(other: Expr | VectorValue | number[]): CosineDistance { + if (other instanceof Expr) { + return new CosineDistance(this, other); + } else { + return new CosineDistance(this, Constant.ofVector(other)); + } + } + + dotProductDistance(other: Expr): DotProductDistance; + dotProductDistance(other: VectorValue): DotProductDistance; + dotProductDistance(other: number[]): DotProductDistance; + dotProductDistance(other: Expr | VectorValue | number[]): DotProductDistance { + if (other instanceof Expr) { + return new DotProductDistance(this, other); + } else { + return new DotProductDistance(this, Constant.ofVector(other)); + } + } + + euclideanDistance(other: Expr): EuclideanDistance; + euclideanDistance(other: VectorValue): EuclideanDistance; + euclideanDistance(other: number[]): EuclideanDistance; + euclideanDistance(other: Expr | VectorValue | number[]): EuclideanDistance { + if (other instanceof Expr) { + return new EuclideanDistance(this, other); + } else { + return new EuclideanDistance(this, Constant.ofVector(other)); + } + } + + abstract _toProto(serializer: Serializer): api.IValue; +} + +class ListOfExprs extends Expr { + exprType: ExprType = 'ListOfExprs'; + constructor(private exprs: Expr[]) { + super(); + } + + _toProto(serializer: Serializer): api.IValue { + return { + arrayValue: { + values: this.exprs.map(p => serializer.encodeValue(p)!), + }, + }; + } +} + +export class Field extends Expr implements Selectable { + exprType: ExprType = 'Field'; + selectable = true as const; + + private constructor( + private fieldPath: FirebaseFirestore.FieldPath, + private pipeline: Pipeline | null = null + ) { + super(); + } + + static of(name: string): Field; + static of(path: FirebaseFirestore.FieldPath): Field; + static of(nameOrPath: string | FirebaseFirestore.FieldPath): Field; + static of(pipeline: Pipeline, name: string): Field; + static of( + pipelineOrName: Pipeline | string | FirebaseFirestore.FieldPath, + name?: string + ): Field { + if (typeof pipelineOrName === 'string') { + return new Field(FieldPath.fromArgument(pipelineOrName)); + } else if (pipelineOrName instanceof FirebaseFirestore.FieldPath) { + return new Field(pipelineOrName); + } else { + return new Field(FieldPath.fromArgument(name!), pipelineOrName); + } + } + + fieldName(): string { + return (this.fieldPath as FieldPath).formattedName; + } + + _toProto(serializer: Serializer): api.IValue { + return { + fieldReferenceValue: (this.fieldPath as FieldPath).formattedName, + }; + } +} + +export class Fields extends Expr implements Selectable { + exprType: ExprType = 'Field'; + selectable = true as const; + + private constructor(private fields: Field[]) { + super(); + } + + static of(name: string, ...others: string[]): Fields { + return new Fields([Field.of(name), ...others.map(Field.of)]); + } + + static ofAll(): Fields { + return new Fields([]); + } + + fieldList(): Field[] { + return this.fields.map(f => f); + } + + _toProto(serializer: Serializer): api.IValue { + return { + arrayValue: { + values: this.fields.map(f => f._toProto(serializer)), + }, + }; + } +} + +export class Constant extends Expr { + exprType: ExprType = 'Constant'; + + private constructor(private value: any) { + super(); + } + + static of(value: number): Constant; + static of(value: string): Constant; + static of(value: boolean): Constant; + static of(value: null): Constant; + static of(value: undefined): Constant; + static of(value: GeoPoint): Constant; + static of(value: Timestamp): Constant; + static of(value: Date): Constant; + static of(value: Uint8Array): Constant; + static of(value: DocumentReference): Constant; + static of(value: api.IValue): Constant; + static of(value: Array): Constant; + static of(value: Map): Constant; + static of(value: VectorValue): Constant; + static of(value: api.IValue): Constant; + static of(value: any): Constant { + return new Constant(value); + } + + static ofVector(value: Array | VectorValue): Constant { + if (value instanceof VectorValue) { + return new Constant(value); + } else { + return new Constant(new VectorValue(value)); + } + } + + _toProto(serializer: Serializer): api.IValue { + if (isFirestoreValue(this.value)) { + return this.value; + } + + return serializer.encodeValue(this.value)!; + } +} + +export class Function extends Expr { + exprType: ExprType = 'Function'; + constructor( + private name: string, + private params: Expr[] + ) { + super(); + } + + _toProto(serializer: Serializer): api.IValue { + return { + functionValue: { + name: this.name, + args: this.params.map(p => serializer.encodeValue(p)!), + }, + }; + } +} + +export class Equal extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('eq', [left, right]); + } + filterable = true as const; +} + +class NotEqual extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('neq', [left, right]); + } + filterable = true as const; +} + +class LessThan extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('lt', [left, right]); + } + filterable = true as const; +} + +class LessThanOrEqual extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('lte', [left, right]); + } + filterable = true as const; +} + +class GreaterThan extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('gt', [left, right]); + } + filterable = true as const; +} + +class GreaterThanOrEqual extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('gte', [left, right]); + } + filterable = true as const; +} + +class ArrayContains extends Function implements FilterCondition { + constructor( + private array: Expr, + private element: Expr + ) { + super('array_contains', [array, element]); + } + filterable = true as const; +} + +class ArrayContainsAny extends Function implements FilterCondition { + constructor( + private array: Expr, + private values: Expr[] + ) { + super('array_contains_any', [array, new ListOfExprs(values)]); + } + filterable = true as const; +} + +class In extends Function implements FilterCondition { + constructor( + private left: Expr, + private others: Expr[] + ) { + super('in', [left, new ListOfExprs(others)]); + } + filterable = true as const; +} + +class IsNan extends Function implements FilterCondition { + constructor(private expr: Expr) { + super('is_nan', [expr]); + } + filterable = true as const; +} +class IsNull extends Function implements FilterCondition { + constructor(private expr: Expr) { + super('is_null', [expr]); + } + filterable = true as const; +} + +class Not extends Function implements FilterCondition { + constructor(private expr: Expr) { + super('not', [expr]); + } + filterable = true as const; +} + +class And extends Function implements FilterCondition { + constructor(private conditions: FilterExpr[]) { + super('and', conditions); + } + + filterable = true as const; +} + +class Or extends Function implements FilterCondition { + constructor(private conditions: FilterExpr[]) { + super('or', conditions); + } + filterable = true as const; +} + +class Count extends Function implements Accumulator { + constructor( + private value: Expr | undefined, + private distinct: boolean + ) { + super('count', value === undefined ? [] : [value]); + } + + toField(fieldName: string): AggregateTarget { + return new AggregateTarget(Field.of(fieldName), this); + } +} + +class Sum extends Function implements Accumulator { + constructor( + private value: Expr, + private distinct: boolean + ) { + super('sum', [value]); + } + toField(fieldName: string): AggregateTarget { + return new AggregateTarget(Field.of(fieldName), this); + } +} + +class Avg extends Function implements Accumulator { + constructor( + private value: Expr, + private distinct: boolean + ) { + super('avg', [value]); + } + toField(fieldName: string): AggregateTarget { + return new AggregateTarget(Field.of(fieldName), this); + } +} + +class Min extends Function implements Accumulator { + constructor( + private value: Expr, + private distinct: boolean + ) { + super('min', [value]); + } + toField(fieldName: string): AggregateTarget { + return new AggregateTarget(Field.of(fieldName), this); + } +} + +class Max extends Function implements Accumulator { + constructor( + private value: Expr, + private distinct: boolean + ) { + super('max', [value]); + } + toField(fieldName: string): AggregateTarget { + return new AggregateTarget(Field.of(fieldName), this); + } +} + +class CosineDistance extends Function { + constructor( + private vector1: Expr, + private vector2: Expr + ) { + super('cosine_distance', [vector1, vector2]); + } +} + +class DotProductDistance extends Function { + constructor( + private vector1: Expr, + private vector2: Expr + ) { + super('dot_product', [vector1, vector2]); + } +} + +class EuclideanDistance extends Function { + constructor( + private vector1: Expr, + private vector2: Expr + ) { + super('euclidean_distance', [vector1, vector2]); + } +} + +function equal(left: Expr, right: Expr): Equal; +function equal(left: Expr, right: any): Equal; +function equal(left: string, right: Expr): Equal; +function equal(left: string, right: any): Equal; +function equal(left: Expr | string, right: any): Equal { + const leftExpr = left instanceof Expr ? left : Field.of(left); + const rightExpr = right instanceof Expr ? right : Constant.of(right); + return new Equal(leftExpr, rightExpr); +} + +// notEqual +function notEqual(left: Expr, right: Expr): NotEqual; +function notEqual(left: Expr, right: any): NotEqual; +function notEqual(left: string, right: Expr): NotEqual; +function notEqual(left: string, right: any): NotEqual; +function notEqual(left: Expr | string, right: any): NotEqual { + const leftExpr = left instanceof Expr ? left : Field.of(left); + const rightExpr = right instanceof Expr ? right : Constant.of(right); + return new NotEqual(leftExpr, rightExpr); +} + +function lessThan(left: Expr, right: Expr): LessThan; +function lessThan(left: Expr, right: any): LessThan; +function lessThan(left: string, right: Expr): LessThan; +function lessThan(left: string, right: any): LessThan; +function lessThan(left: Expr | string, right: any): LessThan { + const leftExpr = left instanceof Expr ? left : Field.of(left); + const rightExpr = right instanceof Expr ? right : Constant.of(right); + return new LessThan(leftExpr, rightExpr); +} + +function lessThanOrEqual(left: Expr, right: Expr): LessThanOrEqual; +function lessThanOrEqual(left: Expr, right: any): LessThanOrEqual; +function lessThanOrEqual(left: string, right: Expr): LessThanOrEqual; +function lessThanOrEqual(left: string, right: any): LessThanOrEqual; +function lessThanOrEqual(left: Expr | string, right: any): LessThanOrEqual { + const leftExpr = left instanceof Expr ? left : Field.of(left); + const rightExpr = right instanceof Expr ? right : Constant.of(right); + return new LessThanOrEqual(leftExpr, rightExpr); +} + +function greaterThan(left: Expr, right: Expr): GreaterThan; +function greaterThan(left: Expr, right: any): GreaterThan; +function greaterThan(left: string, right: Expr): GreaterThan; +function greaterThan(left: string, right: any): GreaterThan; +function greaterThan(left: Expr | string, right: any): GreaterThan { + const leftExpr = left instanceof Expr ? left : Field.of(left); + const rightExpr = right instanceof Expr ? right : Constant.of(right); + return new GreaterThan(leftExpr, rightExpr); +} + +function greaterThanOrEqual(left: Expr, right: Expr): GreaterThanOrEqual; +function greaterThanOrEqual(left: Expr, right: any): GreaterThanOrEqual; +function greaterThanOrEqual(left: string, right: Expr): GreaterThanOrEqual; +function greaterThanOrEqual(left: string, right: any): GreaterThanOrEqual; +function greaterThanOrEqual( + left: Expr | string, + right: any +): GreaterThanOrEqual { + const leftExpr = left instanceof Expr ? left : Field.of(left); + const rightExpr = right instanceof Expr ? right : Constant.of(right); + return new GreaterThanOrEqual(leftExpr, rightExpr); +} + +function arrayContains(array: Expr, element: Expr): ArrayContains; +function arrayContains(array: Expr, element: any): ArrayContains; +function arrayContains(array: string, element: Expr): ArrayContains; +function arrayContains(array: string, element: any): ArrayContains; +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); +} + +function arrayContainsAny(array: Expr, values: Expr[]): ArrayContainsAny; +function arrayContainsAny(array: Expr, values: any[]): ArrayContainsAny; +function arrayContainsAny(array: string, values: Expr[]): ArrayContainsAny; +function arrayContainsAny(array: string, values: any[]): ArrayContainsAny; +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); +} + +function inAny(element: Expr, others: Expr[]): In; +function inAny(element: Expr, others: any[]): In; +function inAny(element: string, others: Expr[]): In; // Added overload +function inAny(element: string, others: any[]): In; // Added overload +function inAny(element: Expr | string, others: any[]): In { + const elementExpr = element instanceof Expr ? element : Field.of(element); + const exprOthers = others.map(other => + other instanceof Expr ? other : Constant.of(other) + ); + return new In(elementExpr, exprOthers); +} + +function notInAny(element: Expr, others: Expr[]): Not; +function notInAny(element: Expr, others: any[]): Not; +function notInAny(element: string, others: Expr[]): Not; // Added overload +function notInAny(element: string, others: any[]): Not; // Added overload +function notInAny(element: Expr | string, others: any[]): Not { + const elementExpr = element instanceof Expr ? element : Field.of(element); + const exprOthers = others.map(other => + other instanceof Expr ? other : Constant.of(other) + ); + return new Not(new In(elementExpr, exprOthers)); +} + +export function and(left: FilterExpr, ...right: FilterExpr[]): And { + return new And([left, ...right]); +} + +export function or(left: FilterExpr, ...right: FilterExpr[]): Or { + return new Or([left, ...right]); +} + +export function not(filter: FilterExpr): Not { + return new Not(filter); +} + +export function isNull(value: Expr): IsNull; +export function isNull(value: string): IsNull; +export function isNull(value: Expr | string): IsNull { + const valueExpr = value instanceof Expr ? value : Field.of(value); + return new IsNull(valueExpr); +} + +export function isNan(value: Expr): IsNan; +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 countAll(): Count { + return new Count(undefined, false); +} + +export function count(value: Expr): Count; +export function count(value: string): Count; +export function count(value: Expr | string): Count { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Count(exprValue, false); +} + +export function sum(value: Expr): Sum; +export function sum(value: string): Sum; +export function sum(value: Expr | string): Sum { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Sum(exprValue, false); +} + +export function avg(value: Expr): Avg; +export function avg(value: string): Avg; +export function avg(value: Expr | string): Avg { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Avg(exprValue, false); +} + +function min(value: Expr): Min; +function min(value: string): Min; +function min(value: Expr | string): Min { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Min(exprValue, false); +} + +function max(value: Expr): Max; +function max(value: string): Max; +function max(value: Expr | string): Max { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Max(exprValue, false); +} + +function cosineDistance(expr: Expr, other: Expr): CosineDistance; +function cosineDistance(expr: Expr, other: number[]): CosineDistance; +function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; +function cosineDistance(expr: string, other: Expr): CosineDistance; +function cosineDistance(expr: string, other: number[]): CosineDistance; +function cosineDistance(expr: string, other: VectorValue): CosineDistance; +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.ofVector(other); + return new CosineDistance(expr1, expr2); +} + +function dotProductDistance(expr: Expr, other: Expr): DotProductDistance; // Fixed return type +function dotProductDistance(expr: Expr, other: number[]): DotProductDistance; +function dotProductDistance(expr: Expr, other: VectorValue): DotProductDistance; +function dotProductDistance(expr: string, other: Expr): DotProductDistance; +function dotProductDistance(expr: string, other: number[]): DotProductDistance; +function dotProductDistance( + expr: string, + other: VectorValue +): DotProductDistance; +function dotProductDistance( + expr: Expr | string, + other: Expr | number[] | VectorValue +): DotProductDistance { + const expr1 = expr instanceof Expr ? expr : Field.of(expr); + const expr2 = other instanceof Expr ? other : Constant.ofVector(other); + return new DotProductDistance(expr1, expr2); +} + +function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; +function euclideanDistance(expr: Expr, other: number[]): EuclideanDistance; +function euclideanDistance(expr: Expr, other: VectorValue): EuclideanDistance; +function euclideanDistance(expr: string, other: Expr): EuclideanDistance; +function euclideanDistance(expr: string, other: number[]): EuclideanDistance; +function euclideanDistance(expr: string, other: VectorValue): EuclideanDistance; +function euclideanDistance( + expr: Expr | string, + other: Expr | number[] | VectorValue +): EuclideanDistance { + const expr1 = expr instanceof Expr ? expr : Field.of(expr); + const expr2 = other instanceof Expr ? other : Constant.ofVector(other); + return new EuclideanDistance(expr1, expr2); +} + +function genericFunction(name: string, params: Expr[]): Function { + return new Function(name, params); +} + +export class Ordering { + constructor( + private expr: Expr, + private direction: 'asc' | 'desc' + ) {} + + static of( + expr: Expr, + direction: 'asc' | 'desc' | undefined = undefined + ): Ordering { + return new Ordering(expr, direction || 'asc'); + } + static ascending(expr: Expr): Ordering { + return new Ordering(expr, 'asc'); + } + static descending(expr: Expr): Ordering { + return new Ordering(expr, 'desc'); + } + + _toProto(serializer: Serializer): api.IValue { + return { + mapValue: { + fields: { + direction: serializer.encodeValue(this.direction)!, + expression: serializer.encodeValue(this.expr)!, + }, + }, + }; + } +} diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts new file mode 100644 index 000000000..ef42b9138 --- /dev/null +++ b/dev/src/pipeline-util.ts @@ -0,0 +1,440 @@ +import * as firestore from '@google-cloud/firestore'; +import {GoogleError, serializer} from 'google-gax'; +import {Duplex, Transform} from 'stream'; +import {google} from '../protos/firestore_v1_proto_api'; + +import * as protos from '../protos/firestore_v1_proto_api'; +import { + Expr, + FilterCondition, + and, + or, + isNan, + Field, + not, + Constant, +} from './expression'; +import Firestore, {DocumentReference, Timestamp} from './index'; +import {logger} from './logger'; +import {QualifiedResourcePath} from './path'; +import {Pipeline, PipelineResult} from './pipeline'; +import {CompositeFilterInternal} from './reference/composite-filter-internal'; +import {NOOP_MESSAGE} from './reference/constants'; +import {FieldFilterInternal} from './reference/field-filter-internal'; +import {FilterInternal} from './reference/filter-internal'; +import {PipelineStreamElement, QueryResponse} from './reference/types'; +import {Serializer} from './serializer'; +import { + Deferred, + getTotalTimeout, + isObject, + isPermanentRpcError, + requestTag, + wrapError, +} from './util'; +import api = protos.google.firestore.v1; + +/** + * Returns a builder for DocumentSnapshot and QueryDocumentSnapshot instances. + * Invoke `.build()' to assemble the final snapshot. + * + * @private + * @internal + */ +export class ExecutionUtil< + AppModelType, + DbModelType extends firestore.DocumentData, +> { + constructor( + /** @private */ + readonly _firestore: Firestore, + /** @private */ + readonly _serializer: Serializer + ) {} + + _getResponse( + pipeline: Pipeline, + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + explainOptions?: firestore.ExplainOptions + ): Promise> | undefined> { + // Capture the error stack to preserve stack tracing across async calls. + const stack = Error().stack!; + + return new Promise((resolve, reject) => { + const results: Array> = []; + const output: Omit, 'result'> & { + executionTime?: Timestamp; + } = {}; + + this._stream(pipeline, transactionOrReadTime, explainOptions) + .on('error', err => { + reject(wrapError(err, stack)); + }) + .on( + 'data', + (data: PipelineStreamElement) => { + if (data.transaction) { + output.transaction = data.transaction; + } + if (data.executionTime) { + output.executionTime = data.executionTime; + } + if (data.explainMetrics) { + output.explainMetrics = data.explainMetrics; + } + if (data.result) { + results.push(data.result); + } + } + ) + .on('end', () => { + // Only return a snapshot when we have a readTime + // explain queries with analyze !== true will return no documents and no read time + const result = output.executionTime ? results : undefined; + + resolve(result); + }); + }); + } + + // This method exists solely to enable unit tests to mock it. + _isPermanentRpcError(err: GoogleError, methodName: string): boolean { + return isPermanentRpcError(err, methodName); + } + + _hasRetryTimedOut(methodName: string, startTime: number): boolean { + const totalTimeout = getTotalTimeout(methodName); + if (totalTimeout === 0) { + return false; + } + + return Date.now() - startTime >= totalTimeout; + } + + stream(pipeline: Pipeline): NodeJS.ReadableStream { + const responseStream = this._stream(pipeline); + const transform = new Transform({ + objectMode: true, + transform(chunk, encoding, callback) { + callback(undefined, chunk.result); + }, + }); + + responseStream.pipe(transform); + responseStream.on('error', e => transform.destroy(e)); + return transform; + } + + _stream( + pipeline: Pipeline, + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + explainOptions?: firestore.ExplainOptions + ): NodeJS.ReadableStream { + const tag = requestTag(); + const startTime = Date.now(); + const isExplain = explainOptions !== undefined; + + let backendStream: Duplex; + const stream = new Transform({ + objectMode: true, + transform: ( + proto: api.ExecutePipelineResponse | typeof NOOP_MESSAGE, + enc, + callback + ) => { + if (proto === NOOP_MESSAGE) { + callback(undefined); + return; + } + + if (proto.results) { + for (const r of proto.results) { + const output: PipelineStreamElement = {}; + + // Proto comes with zero-length buffer by default + if (proto.transaction?.length) { + output.transaction = proto.transaction; + } + + if (proto.executionTime) { + output.executionTime = Timestamp.fromProto(proto.executionTime); + } + + const ref = r.name + ? new DocumentReference( + this._firestore, + QualifiedResourcePath.fromSlashSeparatedString(r.name) + ) + : undefined; + output.result = new PipelineResult( + this._serializer, + ref, + r.fields, + Timestamp.fromProto(proto.executionTime!), + r.createTime ? Timestamp.fromProto(r.createTime!) : undefined, + r.updateTime ? Timestamp.fromProto(r.updateTime!) : undefined + ); + + callback(undefined, output); + } + } + }, + }); + + this._firestore + .initializeIfNeeded(tag) + .then(async () => { + // `toProto()` might throw an exception. We rely on the behavior of an + // async function to convert this exception into the rejected Promise we + // catch below. + const request = pipeline._toProto( + this._firestore, + transactionOrReadTime, + explainOptions + ); + + let streamActive: Deferred; + do { + streamActive = new Deferred(); + const methodName = 'executePipeline'; + backendStream = await this._firestore.requestStream( + methodName, + /* bidirectional= */ false, + request, + tag + ); + backendStream.on('error', err => { + backendStream.unpipe(stream); + + logger( + 'QueryUtil._stream', + tag, + 'Query failed with stream error:', + err + ); + stream.destroy(err); + streamActive.resolve(/* active= */ false); + }); + backendStream.on('end', () => { + streamActive.resolve(/* active= */ false); + }); + backendStream.resume(); + backendStream.pipe(stream); + } while (await streamActive.promise); + }) + .catch(e => stream.destroy(e)); + + return stream; + } +} + +function isITimestamp(obj: any): obj is google.protobuf.ITimestamp { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ( + ('seconds' in obj && + !( + obj.seconds === null || + typeof obj.seconds === 'number' || + typeof obj.seconds === 'string' + )) || + ('nanos' in obj && !(obj.nanos === null || typeof obj.nanos === 'number')) + ) { + return false; // Invalid property type + } + + return true; // All checks passed +} +function isILatLng(obj: any): obj is google.type.ILatLng { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ( + ('latitude' in obj && + !(obj.latitude === null || typeof obj.latitude === 'number')) || + ('longitude' in obj && + !(obj.longitude === null || typeof obj.longitude === 'number')) + ) { + return false; // Invalid property type + } + + return true; // All checks passed +} +function isIArrayValue(obj: any): obj is api.IArrayValue { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ('values' in obj && !(obj.values === null || Array.isArray(obj.values))) { + return false; // Invalid property type + } + + return true; // All checks passed +} +function isIMapValue(obj: any): obj is api.IMapValue { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ('fields' in obj && !(obj.fields === null || isObject(obj.fields))) { + return false; // Invalid property type + } + + return true; // All checks passed +} +function isIFunction(obj: any): obj is api.IFunction { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ( + ('name' in obj && !(obj.name === null || typeof obj.name === 'string')) || + ('args' in obj && !(obj.args === null || Array.isArray(obj.args))) + ) { + return false; // Invalid property type + } + + return true; // All checks passed +} + +function isIPipeline(obj: any): obj is api.IPipeline { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + if ('stages' in obj && !(obj.stages === null || Array.isArray(obj.stages))) { + return false; // Invalid property type + } + + return true; // All checks passed +} + +export function isFirestoreValue(obj: any): obj is api.IValue { + if (typeof obj !== 'object' || obj === null) { + return false; // Must be a non-null object + } + + // Check optional properties and their types + if ( + ('nullValue' in obj && + !( + obj.nullValue === null || + obj.nullValue instanceof google.protobuf.BoolValue + )) || + ('booleanValue' in obj && + !(obj.booleanValue === null || typeof obj.booleanValue === 'boolean')) || + ('integerValue' in obj && + !( + obj.integerValue === null || + typeof obj.integerValue === 'number' || + typeof obj.integerValue === 'string' + )) || + ('doubleValue' in obj && + !(obj.doubleValue === null || typeof obj.doubleValue === 'number')) || + ('timestampValue' in obj && + !(obj.timestampValue === null || isITimestamp(obj.timestampValue))) || + ('stringValue' in obj && + !(obj.stringValue === null || typeof obj.stringValue === 'string')) || + ('bytesValue' in obj && + !(obj.bytesValue === null || obj.bytesValue instanceof Uint8Array)) || + ('referenceValue' in obj && + !( + obj.referenceValue === null || typeof obj.referenceValue === 'string' + )) || + ('geoPointValue' in obj && + !(obj.geoPointValue === null || isILatLng(obj.geoPointValue))) || + ('arrayValue' in obj && + !(obj.arrayValue === null || isIArrayValue(obj.arrayValue))) || + ('mapValue' in obj && + !(obj.mapValue === null || isIMapValue(obj.mapValue))) || + ('fieldReferenceValue' in obj && + !( + obj.fieldReferenceValue === null || + typeof obj.fieldReferenceValue === 'string' + )) || + ('functionValue' in obj && + !(obj.functionValue === null || isIFunction(obj.functionValue))) || + ('pipelineValue' in obj && + !(obj.pipelineValue === null || isIPipeline(obj.pipelineValue))) + ) { + return false; // Invalid property type + } + + return true; // All checks passed +} + +export function toPipelineFilterCondition( + f: FilterInternal, + serializer: Serializer +): FilterCondition & Expr { + if (f instanceof FieldFilterInternal) { + if (f.isNanChecking()) { + if (f.nanOp() === 'IS_NAN') { + return Field.of(f.field).isNaN(); + } else { + return not(Field.of(f.field).isNaN()); + } + } else if (f.isNullChecking()) { + if (f.nullOp() === 'IS_NULL') { + return Field.of(f.field).isNull(); + } else { + return not(Field.of(f.field).isNull()); + } + } else { + // Comparison filters + const value = isFirestoreValue(f.value) + ? f.value + : serializer.encodeValue(f.value); + switch (f.op) { + case 'LESS_THAN': + return Field.of(f.field).lessThan(value); + case 'LESS_THAN_OR_EQUAL': + return Field.of(f.field).lessThanOrEqual(value); + case 'GREATER_THAN': + return Field.of(f.field).greaterThan(value); + case 'GREATER_THAN_OR_EQUAL': + return Field.of(f.field).greaterThanOrEqual(value); + case 'EQUAL': + return Field.of(f.field).equal(value); + case 'NOT_EQUAL': + return Field.of(f.field).notEqual(value); + case 'ARRAY_CONTAINS': + return Field.of(f.field).arrayContains(value); + case 'IN': { + const values = value?.arrayValue?.values?.map(val => + Constant.of(value) + ); + return Field.of(f.field).in(...values!); + } + case 'ARRAY_CONTAINS_ANY': { + const values = value?.arrayValue?.values?.map(val => + Constant.of(value) + ); + return Field.of(f.field).arrayContainsAny(values!); + } + case 'NOT_IN': { + const values = value?.arrayValue?.values?.map(val => + Constant.of(value) + ); + return not(Field.of(f.field).in(...values!)); + } + } + } + } else if (f instanceof CompositeFilterInternal) { + switch (f._getOperator()) { + case 'AND': { + const conditions = f + .getFilters() + .map(f => toPipelineFilterCondition(f, serializer)); + return and(conditions[0], ...conditions.slice(1)); + } + case 'OR': { + const conditions = f + .getFilters() + .map(f => toPipelineFilterCondition(f, serializer)); + return or(conditions[0], ...conditions.slice(1)); + } + } + } + + throw new Error( + `Failed to convert filter to pipeline conditions: ${f.toProto()}` + ); +} diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts new file mode 100644 index 000000000..eb0cf038e --- /dev/null +++ b/dev/src/pipeline.ts @@ -0,0 +1,318 @@ +import * as firestore from '@google-cloud/firestore'; +import {google} from '../protos/firestore_v1_proto_api'; +import { + Accumulator, + AggregateTarget, + Expr, + Field, + Fields, + FilterCondition, + Ordering, + Selectable, +} from './expression'; +import {VectorValue} from './field-value'; +import Firestore, {Timestamp} from './index'; +import {ExecutionUtil} from './pipeline-util'; +import {DocumentReference} from './reference/document-reference'; +import {Serializer} from './serializer'; +import { + AddField, + Aggregate, + Collection, + CollectionGroup, + Database, + Documents, + Filter, + FindNearest, + FindNearestOptions, + GenerateStage, + Limit, + Offset, + Select, + Sort, + Stage, +} from './stage'; +import {ApiMapValue} from './types'; +import * as protos from '../protos/firestore_v1_proto_api'; +import api = protos.google.firestore.v1; +import IStructuredPipeline = google.firestore.v1.IStructuredPipeline; +import IStage = google.firestore.v1.Pipeline.IStage; +import {QueryCursor} from './reference/types'; + +export class Pipeline< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData, +> { + private constructor(private stages: Stage[]) {} + + static fromCollection(collectionPath: string): Pipeline { + return new Pipeline([new Collection(collectionPath)]); + } + + static fromCollectionGroup(collectionId: string): Pipeline { + return new Pipeline([new CollectionGroup(collectionId)]); + } + + static fromDatabase(): Pipeline { + return new Pipeline([new Database()]); + } + + static fromDocuments(docs: DocumentReference[]): Pipeline { + return new Pipeline([Documents.of(docs)]); + } + + addFields(...fields: Selectable[]): Pipeline { + const copy = this.stages.map(s => s); + copy.push(new AddField(this.selectablesToMap(fields))); + return new Pipeline(copy); + } + + select(...fields: string[]): Pipeline; + select(...fields: Selectable[]): Pipeline; + select(...fields: (Selectable | string)[]): Pipeline { + const copy = this.stages.map(s => s); + copy.push(new Select(this.selectablesToMap(fields))); + return new Pipeline(copy); + } + + private selectablesToMap( + selectables: (Selectable | string)[] + ): 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 AggregateTarget) { + const target = selectable as AggregateTarget; + result.set(target.field.fieldName(), target.accumulator); + } else if (selectable instanceof Fields) { + const fields = selectable as Fields; + for (const field of fields.fieldList()) { + result.set(field.fieldName(), field); + } + } + } + return result; + } + + filter(condition: FilterCondition & Expr): Pipeline { + const copy = this.stages.map(s => s); + copy.push(new Filter(condition)); + return new Pipeline(copy); + } + + offset(offset: number): Pipeline { + const copy = this.stages.map(s => s); + copy.push(new Offset(offset)); + return new Pipeline(copy); + } + + limit(limit: number): Pipeline { + const copy = this.stages.map(s => s); + copy.push(new Limit(limit)); + return new Pipeline(copy); + } + + aggregate(...targets: AggregateTarget[]): Pipeline { + const copy = this.stages.map(s => s); + copy.push( + new Aggregate( + new Map(), + new Map( + targets.map(target => [target.field.fieldName(), target.accumulator]) + ) + ) + ); + return new Pipeline(copy); + } + + findNearest( + field: string, + vector: number[], + options: FindNearestOptions + ): Pipeline; + findNearest( + field: Field, + vector: FirebaseFirestore.VectorValue, + options: FindNearestOptions + ): Pipeline; + findNearest( + field: string | Field, + vector: FirebaseFirestore.VectorValue | number[], + options: FindNearestOptions + ): Pipeline; + findNearest( + field: string | Field, + vector: number[] | FirebaseFirestore.VectorValue, + options: FindNearestOptions + ): Pipeline { + const copy = this.stages.map(s => s); + const fieldExpr = typeof field === 'string' ? Field.of(field) : field; + copy.push(new FindNearest(fieldExpr, vector, options)); + return new Pipeline(copy); + } + + sort(orderings: Ordering[]): Pipeline; + sort( + orderings: Ordering[], + density?: 'unspecified' | 'required', + truncation?: 'unspecified' | 'disabled' + ): Pipeline; + sort( + orderings: Ordering[], + density?: 'unspecified' | 'required', + truncation?: 'unspecified' | 'disabled' + ): Pipeline { + const copy = this.stages.map(s => s); + copy.push( + new Sort(orderings, density ?? 'unspecified', truncation ?? 'unspecified') + ); + return new Pipeline(copy); + } + + paginate(pageSize: number, orderings?: Ordering[]): PaginatingPipeline { + const copy = this.stages.map(s => s); + return new PaginatingPipeline(new Pipeline(copy), pageSize, orderings); + } + + genericStage(name: string, params: any[]): Pipeline { + const copy = this.stages.map(s => s); + copy.push(new GenerateStage(name, params)); + return new Pipeline(copy); + } + + execute( + db: Firestore + ): Promise>> { + const util = new ExecutionUtil( + db, + db._serializer! + ); + return util._getResponse(this).then(result => result!); + } + + stream(db: Firestore): NodeJS.ReadableStream { + const util = new ExecutionUtil( + db, + db._serializer! + ); + return util.stream(this); + } + + _toProto( + db: Firestore, + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + explainOptions?: FirebaseFirestore.ExplainOptions + ): api.IExecutePipelineRequest { + const stages: IStage[] = this.stages.map(stage => + stage._toProto(db._serializer!) + ); + const structuredPipeline: IStructuredPipeline = {pipeline: {stages}}; + return { + database: db.formattedName, + structuredPipeline, + }; + } +} + +class PaginatingPipeline { + constructor( + private pipeline: Pipeline, + private pageSize: number, + private orderings?: Ordering[] + ) {} + + firstPage(): Pipeline { + return this.pipeline; + } + + lastPage(): Pipeline { + return this.pipeline; + } + + offset(): PaginatingPipeline { + return this; + } + + limit(): PaginatingPipeline { + return this; + } + + startAt(result: PipelineResult): PaginatingPipeline { + return this; + } + + startAfter(result: PipelineResult): PaginatingPipeline { + return this; + } + + endAt(result: PipelineResult): PaginatingPipeline { + return this; + } + + endBefore(result: PipelineResult): PaginatingPipeline { + return this; + } + + /** + * @internal + * @private + */ + withEndCursor(arg0: QueryCursor): PaginatingPipeline { + throw new Error('Method not implemented.'); + } + /** + * @internal + * @private + */ + withStartCursor(arg0: QueryCursor): PaginatingPipeline { + throw new Error('Method not implemented.'); + } +} + +export class PipelineResult< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData, +> { + private _ref: DocumentReference | undefined; + private _serializer: Serializer; + private _readTime: Timestamp | undefined; + private _createTime: Timestamp | undefined; + private _updateTime: Timestamp | undefined; + + /** + * @private + * @internal + * + * @param serializer The serializer used to encode/decode protobuf. + * @param ref The reference to the document. + * @param _fieldsProto The fields of the Firestore `Document` Protobuf backing + * this document (or undefined if the document does not exist). + * @param readTime The time when this snapshot was read (or undefined if + * the document exists only locally). + * @param createTime The time when the document was created (or undefined if + * the document does not exist). + * @param updateTime The time when the document was last updated (or undefined + * if the document does not exist). + */ + constructor( + serializer: Serializer, + ref?: DocumentReference, + /** + * @internal + * @private + **/ + readonly _fieldsProto?: ApiMapValue | null, + readTime?: Timestamp, + createTime?: Timestamp, + updateTime?: Timestamp + ) { + this._ref = ref; + this._serializer = serializer; + this._readTime = readTime; + this._createTime = createTime; + this._updateTime = updateTime; + } +} diff --git a/dev/src/reference/aggregate-query.ts b/dev/src/reference/aggregate-query.ts index 6171c34b4..a4c497571 100644 --- a/dev/src/reference/aggregate-query.ts +++ b/dev/src/reference/aggregate-query.ts @@ -22,6 +22,8 @@ import * as deepEqual from 'fast-deep-equal'; import * as firestore from '@google-cloud/firestore'; import {Aggregate, AggregateSpec} from '../aggregate'; +import {AggregateTarget, avg, count, countAll, Field, sum} from '../expression'; +import {Pipeline} from '../pipeline'; import {Timestamp} from '../timestamp'; import {mapToArray, requestTag, wrapError} from '../util'; import {ExplainMetrics, ExplainResults} from '../query-profile'; @@ -331,6 +333,25 @@ export class AggregateQuery< return runQueryRequest; } + toPipeline(): Pipeline { + const aggregates = mapToArray( + this._aggregates, + (aggregate, clientAlias) => { + if (aggregate.aggregateType === 'count') { + if (aggregate._field === undefined) { + return countAll().toField(clientAlias); + } + return count(Field.of(aggregate._field)).toField(clientAlias); + } else if (aggregate.aggregateType === 'avg') { + return avg(Field.of(aggregate._field!)).toField(clientAlias); + } else { + return sum(Field.of(aggregate._field!)).toField(clientAlias); + } + } + ); + return this._query.toPipeline().aggregate(...aggregates); + } + /** * Compares this object with the given object for equality. * diff --git a/dev/src/reference/composite-filter-internal.ts b/dev/src/reference/composite-filter-internal.ts index aa41d8c8d..204f4b87f 100644 --- a/dev/src/reference/composite-filter-internal.ts +++ b/dev/src/reference/composite-filter-internal.ts @@ -40,6 +40,14 @@ export class CompositeFilterInternal extends FilterInternal { return this.operator === 'AND'; } + /** + * @private + * @internal + */ + public _getOperator(): api.StructuredQuery.CompositeFilter.Operator { + return this.operator; + } + public getFlattenedFilters(): FieldFilterInternal[] { if (this.memoizedFlattenedFilters !== null) { return this.memoizedFlattenedFilters; diff --git a/dev/src/reference/field-filter-internal.ts b/dev/src/reference/field-filter-internal.ts index 70ef12725..4728511b5 100644 --- a/dev/src/reference/field-filter-internal.ts +++ b/dev/src/reference/field-filter-internal.ts @@ -48,8 +48,8 @@ export class FieldFilterInternal extends FilterInternal { constructor( private readonly serializer: Serializer, readonly field: FieldPath, - private readonly op: api.StructuredQuery.FieldFilter.Operator, - private readonly value: unknown + readonly op: api.StructuredQuery.FieldFilter.Operator, + readonly value: unknown ) { super(); } @@ -74,6 +74,38 @@ export class FieldFilterInternal extends FilterInternal { } } + /** + * @private + * @internal + */ + isNanChecking(): boolean { + return typeof this.value === 'number' && isNaN(this.value); + } + + /** + * @private + * @internal + */ + nanOp(): 'IS_NAN' | 'IS_NOT_NAN' { + return this.op === 'EQUAL' ? 'IS_NAN' : 'IS_NOT_NAN'; + } + + /** + * @private + * @internal + */ + isNullChecking(): boolean { + return this.value === null; + } + + /** + * @private + * @internal + */ + nullOp(): 'IS_NULL' | 'IS_NOT_NULL' { + return this.op === 'EQUAL' ? 'IS_NULL' : 'IS_NOT_NULL'; + } + /** * Generates the proto representation for this field filter. * @@ -81,24 +113,24 @@ export class FieldFilterInternal extends FilterInternal { * @internal */ toProto(): api.StructuredQuery.IFilter { - if (typeof this.value === 'number' && isNaN(this.value)) { + if (this.isNanChecking()) { return { unaryFilter: { field: { fieldPath: this.field.formattedName, }, - op: this.op === 'EQUAL' ? 'IS_NAN' : 'IS_NOT_NAN', + op: this.nanOp(), }, }; } - if (this.value === null) { + if (this.isNullChecking()) { return { unaryFilter: { field: { fieldPath: this.field.formattedName, }, - op: this.op === 'EQUAL' ? 'IS_NULL' : 'IS_NOT_NULL', + op: this.nullOp(), }, }; } diff --git a/dev/src/reference/query.ts b/dev/src/reference/query.ts index f7664b4d5..ab516e642 100644 --- a/dev/src/reference/query.ts +++ b/dev/src/reference/query.ts @@ -14,48 +14,54 @@ * limitations under the License. */ -import * as protos from '../../protos/firestore_v1_proto_api'; -import api = protos.google.firestore.v1; import * as firestore from '@google-cloud/firestore'; import {GoogleError} from 'google-gax'; import {Transform} from 'stream'; +import * as protos from '../../protos/firestore_v1_proto_api'; +import {Field, Ordering} from '../expression'; -import {QueryUtil} from './query-util'; +import {CompositeFilter, UnaryFilter} from '../filter'; import { - Firestore, AggregateField, DocumentChange, DocumentSnapshot, FieldPath, Filter, + Firestore, QueryDocumentSnapshot, Timestamp, } from '../index'; -import {QueryOptions} from './query-options'; -import {FieldOrder} from './field-order'; -import {FilterInternal} from './filter-internal'; -import {FieldFilterInternal} from './field-filter-internal'; +import {compare} from '../order'; +import {validateFieldPath} from '../path'; +import {Pipeline} from '../pipeline'; +import {toPipelineFilterCondition} from '../pipeline-util'; +import {ExplainResults} from '../query-profile'; +import {Serializer} from '../serializer'; +import {Limit} from '../stage'; +import {defaultConverter} from '../types'; +import { + invalidArgumentMessage, + validateFunction, + validateInteger, + validateMinNumberOfArguments, +} from '../validate'; +import {QueryWatch} from '../watch'; +import {AggregateQuery} from './aggregate-query'; import {CompositeFilterInternal} from './composite-filter-internal'; import {comparisonOperators, directionOperators} from './constants'; -import {VectorQueryOptions} from './vector-query-options'; import {DocumentReference} from './document-reference'; -import {QuerySnapshot} from './query-snapshot'; -import {Serializer} from '../serializer'; -import {ExplainResults} from '../query-profile'; - -import {CompositeFilter, UnaryFilter} from '../filter'; -import {validateFieldPath} from '../path'; +import {FieldFilterInternal} from './field-filter-internal'; +import {FieldOrder} from './field-order'; +import {FilterInternal} from './filter-internal'; import { validateQueryOperator, validateQueryOrder, validateQueryValue, } from './helpers'; -import { - invalidArgumentMessage, - validateFunction, - validateInteger, - validateMinNumberOfArguments, -} from '../validate'; +import {QueryOptions} from './query-options'; +import {QuerySnapshot} from './query-snapshot'; + +import {QueryUtil} from './query-util'; import { LimitType, QueryCursor, @@ -63,11 +69,9 @@ import { QuerySnapshotResponse, QueryStreamElement, } from './types'; -import {AggregateQuery} from './aggregate-query'; import {VectorQuery} from './vector-query'; -import {QueryWatch} from '../watch'; -import {compare} from '../order'; -import {defaultConverter} from '../types'; +import {VectorQueryOptions} from './vector-query-options'; +import api = protos.google.firestore.v1; /** * A Query refers to a query which you can read or stream from. You can also @@ -662,6 +666,80 @@ export class Query< ); } + toPipeline(): Pipeline { + let pipeline; + if (this._queryOptions.allDescendants) { + pipeline = Pipeline.fromCollectionGroup(this._queryOptions.collectionId); + } else { + pipeline = Pipeline.fromCollection( + this._queryOptions.parentPath.append(this._queryOptions.collectionId) + .relativeName + ); + } + + // filters + for (const f of this._queryOptions.filters) { + pipeline = pipeline.filter( + toPipelineFilterCondition(f, this._serializer) + ); + } + + // projections + const projections = this._queryOptions.projection?.fields || []; + if (projections.length > 0) { + pipeline = pipeline.select( + ...projections.map(p => Field.of(p.fieldPath!)) + ); + } + + // orderbys + const orderings = this.createImplicitOrderBy().map(fieldOrder => { + let dir: 'asc' | 'desc' | undefined = undefined; + switch (fieldOrder.direction) { + case 'ASCENDING': { + dir = 'asc'; + break; + } + case 'DESCENDING': { + dir = 'desc'; + break; + } + } + return Ordering.of(Field.of(fieldOrder.field), dir); + }); + if (orderings.length > 0) { + pipeline = pipeline.sort(orderings, 'required', 'unspecified'); + } + + // Cursors, Limit and Offset + if ( + !this._queryOptions.startAt || + !this._queryOptions.endAt || + this._queryOptions.limitType === LimitType.Last + ) { + let paginating = pipeline.paginate(this._queryOptions.limit || 10); + if (this._queryOptions.startAt) { + paginating = paginating.withStartCursor(this._queryOptions.startAt!); + } + if (this._queryOptions.endAt) { + paginating = paginating.withEndCursor(this._queryOptions.endAt!); + } + if (this._queryOptions.limit === LimitType.Last) { + return paginating.lastPage(); + } else { + return paginating.firstPage(); + } + } else { + if (this._queryOptions.offset) { + pipeline = pipeline.offset(this._queryOptions.offset); + } + if (this._queryOptions.limit) { + pipeline = pipeline.offset(this._queryOptions.limit); + } + } + return pipeline; + } + /** * Returns true if this `Query` is equal to the provided value. * @@ -706,7 +784,7 @@ export class Query< * set of field values to use as the boundary. * @returns The implicit ordering semantics. */ - private createImplicitOrderBy( + private createImplicitOrderByForCursor( cursorValuesOrDocumentSnapshot: Array< DocumentSnapshot | unknown > @@ -719,6 +797,10 @@ export class Query< return this._queryOptions.fieldOrders; } + return this.createImplicitOrderBy(); + } + + private createImplicitOrderBy(): FieldOrder[] { const fieldOrders = this._queryOptions.fieldOrders.slice(); const fieldsNormalized = new Set([ ...fieldOrders.map(item => item.field.toString()), @@ -914,7 +996,7 @@ export class Query< 1 ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot ); const startAt = this.createCursor( @@ -958,7 +1040,7 @@ export class Query< 1 ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot ); const startAt = this.createCursor( @@ -1001,7 +1083,7 @@ export class Query< 1 ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot ); const endAt = this.createCursor( @@ -1044,7 +1126,7 @@ export class Query< 1 ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot ); const endAt = this.createCursor( diff --git a/dev/src/reference/types.ts b/dev/src/reference/types.ts index 844233e24..96aa7098e 100644 --- a/dev/src/reference/types.ts +++ b/dev/src/reference/types.ts @@ -15,6 +15,7 @@ */ import * as protos from '../../protos/firestore_v1_proto_api'; +import {PipelineResult} from '../pipeline'; import api = protos.google.firestore.v1; import {Timestamp} from '../timestamp'; @@ -59,6 +60,16 @@ export enum LimitType { Last, } +export interface PipelineStreamElement< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData, +> { + transaction?: Uint8Array; + executionTime?: Timestamp; + explainMetrics?: ExplainMetrics; + result?: PipelineResult; +} + /** * onSnapshot() callback that receives a QuerySnapshot. * diff --git a/dev/src/reference/vector-query.ts b/dev/src/reference/vector-query.ts index 4df93e60f..4f0a8c349 100644 --- a/dev/src/reference/vector-query.ts +++ b/dev/src/reference/vector-query.ts @@ -17,6 +17,9 @@ import * as protos from '../../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; import * as firestore from '@google-cloud/firestore'; +import {Field} from '../expression'; +import {Pipeline} from '../pipeline'; +import {FindNearestOptions} from '../stage'; import {Timestamp} from '../timestamp'; import {VectorValue} from '../field-value'; @@ -128,6 +131,16 @@ export class VectorQuery< return result; } + toPipeline(): Pipeline { + const options = { + limit: this.options.limit, + distanceMeasure: this.options.distanceMeasure.toLowerCase(), + } as FindNearestOptions; + return this.query + .toPipeline() + .findNearest(Field.of(this.vectorField), this.queryVector, options); + } + _getResponse( explainOptions?: firestore.ExplainOptions ): Promise>> { diff --git a/dev/src/serializer.ts b/dev/src/serializer.ts index ace53ce4c..8ec31dd6a 100644 --- a/dev/src/serializer.ts +++ b/dev/src/serializer.ts @@ -184,6 +184,13 @@ export class Serializer { } } + if (isObject(val)) { + const _toProto = val['_toProto']; + if (typeof _toProto === 'function') { + return _toProto.bind(val)(this); + } + } + if (Array.isArray(val)) { const array: api.IValue = { arrayValue: {}, diff --git a/dev/src/stage.ts b/dev/src/stage.ts new file mode 100644 index 000000000..0f31aa311 --- /dev/null +++ b/dev/src/stage.ts @@ -0,0 +1,228 @@ +import * as protos from '../protos/firestore_v1_proto_api'; +import api = protos.google.firestore.v1; + +import { + Accumulator, + Expr, + Field, + FilterCondition, + Ordering, +} from './expression'; +import {VectorValue} from './field-value'; +import {DocumentReference} from './reference/document-reference'; +import {Serializer} from './serializer'; + +export interface Stage { + name: string; + _toProto(serializer: Serializer): api.Pipeline.IStage; +} + +export class AddField implements Stage { + name = 'add_field'; + + constructor(private fields: Map) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.fields)!], + }; + } +} + +export class Aggregate implements Stage { + name = 'aggregate'; + + constructor( + private groups: Map, + private accumulators: Map + ) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [ + serializer.encodeValue(this.groups)!, + serializer.encodeValue(this.accumulators)!, + ], + }; + } +} + +export class Collection implements Stage { + name = 'collection'; + + constructor(private collectionPath: string) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [{referenceValue: this.collectionPath}], + }; + } +} + +export class CollectionGroup implements Stage { + name = 'collection_group'; + + constructor(private collectionId: string) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [{referenceValue: ''}, serializer.encodeValue(this.collectionId)!], + }; + } +} + +export class Database implements Stage { + name = 'database'; + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + }; + } +} + +export class Documents implements Stage { + name = 'documents'; + + constructor(private docPaths: string[]) {} + + static of(refs: DocumentReference[]): Documents { + return new Documents(refs.map(ref => '/' + ref.path)); + } + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.docPaths.map(p => { + return {referenceValue: p}; + }), + }; + } +} + +export class Filter implements Stage { + name = 'filter'; + + constructor(private condition: FilterCondition & Expr) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [this.condition._toProto(serializer)], + }; + } +} + +export interface FindNearestOptions { + limit: number; + distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + distanceField?: string; +} + +export class FindNearest implements Stage { + name = 'find_nearest'; + + constructor( + private property: Field, + private vector: FirebaseFirestore.VectorValue | number[], + private options: FindNearestOptions + ) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + const options: {[k: string]: api.IValue} = { + limit: serializer.encodeValue(this.options.limit)!, + }; + if (this.options.distanceField) { + options.distance_field = Field.of(this.options.distanceField)._toProto( + serializer + ); + } + + return { + name: this.name, + args: [ + this.property._toProto(serializer), + this.vector instanceof FirebaseFirestore.VectorValue + ? serializer.encodeValue(this.vector)! + : serializer.encodeVector(this.vector), + serializer.encodeValue(this.options.distanceMeasure)!, + ], + options, + }; + } +} + +export class Limit implements Stage { + name = 'limit'; + + constructor(private limit: number) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.limit)!], + }; + } +} + +export class Offset implements Stage { + name = 'offset'; + + constructor(private offset: number) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.offset)!], + }; + } +} + +export class Select implements Stage { + name = 'select'; + + constructor(private projections: Map) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.projections)!], + }; + } +} + +export class Sort implements Stage { + name = 'filter'; + + constructor( + private orders: Ordering[], + private density: 'unspecified' | 'required', + private truncation: 'unspecified' | 'disabled' + ) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.orders.map(o => o._toProto(serializer)), + options: { + density: serializer.encodeValue(this.density)!, + truncation: serializer.encodeValue(this.truncation)!, + }, + }; + } +} + +export class GenerateStage implements Stage { + constructor( + public name: string, + params: any[] + ) {} + + _toProto(serializer: Serializer): api.Pipeline.Stage { + return new api.Pipeline.Stage(); + } +} diff --git a/dev/src/types.ts b/dev/src/types.ts index ac7a62d22..45d926818 100644 --- a/dev/src/types.ts +++ b/dev/src/types.ts @@ -64,6 +64,10 @@ export interface GapicClient { request?: api.IBatchGetDocumentsRequest, options?: CallOptions ): Duplex; + executePipeline( + request?: api.IExecutePipelineRequest, + options?: CallOptions + ): Duplex; runQuery(request?: api.IRunQueryRequest, options?: CallOptions): Duplex; runAggregationQuery( request?: api.IRunAggregationQueryRequest, @@ -96,6 +100,7 @@ export type FirestoreUnaryMethod = /** Streaming methods used in the Firestore SDK. */ export type FirestoreStreamingMethod = + | 'executePipeline' | 'listen' | 'partitionQueryStream' | 'runQuery' diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 702e5ee84..76cf5e10f 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -39,8 +39,8 @@ declare namespace FirebaseFirestore { | (T extends Primitive ? T : T extends {} - ? {[K in keyof T]?: PartialWithFieldValue | FieldValue} - : never); + ? {[K in keyof T]?: PartialWithFieldValue | FieldValue} + : never); /** * Allows FieldValues to be passed in as a property value while maintaining @@ -51,8 +51,8 @@ declare namespace FirebaseFirestore { | (T extends Primitive ? T : T extends {} - ? {[K in keyof T]: WithFieldValue | FieldValue} - : never); + ? {[K in keyof T]: WithFieldValue | FieldValue} + : never); /** * Update data (for use with [update]{@link DocumentReference#update}) @@ -71,8 +71,8 @@ declare namespace FirebaseFirestore { export type UpdateData = T extends Primitive ? T : T extends {} - ? {[K in keyof T]?: UpdateData | FieldValue} & NestedUpdateFields - : Partial; + ? {[K in keyof T]?: UpdateData | FieldValue} & NestedUpdateFields + : Partial; /** Primitive types. */ export type Primitive = string | number | boolean | undefined | null; From 25ee91a40da7cededc03302369704e12c340af66 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 25 Jul 2024 14:53:09 -0400 Subject: [PATCH 03/31] can run pipelines --- dev/src/expression.ts | 54 +- dev/src/index.ts | 5 + dev/src/pipeline-util.ts | 255 +-- dev/src/pipeline.ts | 89 +- dev/src/reference/query.ts | 42 +- dev/src/reference/vector-query.ts | 1 + dev/src/serializer.ts | 15 + dev/src/stage.ts | 16 +- dev/src/v1/firestore_client.ts | 22 + dev/src/v1/firestore_client_config.json | 5 + dev/system-test/firestore.ts | 2118 +-------------------- dev/system-test/pipeline.ts | 99 + dev/system-test/query.ts | 2234 +++++++++++++++++++++++ 13 files changed, 2668 insertions(+), 2287 deletions(-) create mode 100644 dev/system-test/pipeline.ts create mode 100644 dev/system-test/query.ts diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 0387699d3..e2642407b 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -1,6 +1,8 @@ import * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; +import * as firestore from '@google-cloud/firestore'; + import {VectorValue} from './field-value'; import {GeoPoint} from './geo-point'; import {FieldPath} from './path'; @@ -127,6 +129,10 @@ export abstract class Expr { return new IsNull(this); } + exists(): Exists { + return new Exists(this); + } + count(): Count { return new Count(this, false); } @@ -203,26 +209,36 @@ export class Field extends Expr implements Selectable { selectable = true as const; private constructor( - private fieldPath: FirebaseFirestore.FieldPath, + private fieldPath: firestore.FieldPath, private pipeline: Pipeline | null = null ) { super(); } static of(name: string): Field; - static of(path: FirebaseFirestore.FieldPath): Field; - static of(nameOrPath: string | FirebaseFirestore.FieldPath): Field; + static of(path: firestore.FieldPath): Field; + static of(nameOrPath: string | firestore.FieldPath): Field; static of(pipeline: Pipeline, name: string): Field; static of( - pipelineOrName: Pipeline | string | FirebaseFirestore.FieldPath, + pipelineOrName: Pipeline | string | firestore.FieldPath, name?: string ): Field { if (typeof pipelineOrName === 'string') { + if (FieldPath.documentId().formattedName === pipelineOrName) { + return new Field(new FieldPath('__path__')); + } + return new Field(FieldPath.fromArgument(pipelineOrName)); - } else if (pipelineOrName instanceof FirebaseFirestore.FieldPath) { + } else if (pipelineOrName instanceof FieldPath) { + if (FieldPath.documentId().isEqual(pipelineOrName)) { + return new Field(new FieldPath('__path__')); + } return new Field(pipelineOrName); } else { - return new Field(FieldPath.fromArgument(name!), pipelineOrName); + return new Field( + FieldPath.fromArgument(name!), + pipelineOrName as Pipeline + ); } } @@ -424,6 +440,14 @@ class IsNan extends Function implements FilterCondition { } filterable = true as const; } + +class Exists extends Function implements FilterCondition { + constructor(private expr: Expr) { + super('exists', [expr]); + } + filterable = true as const; +} + class IsNull extends Function implements FilterCondition { constructor(private expr: Expr) { super('is_null', [expr]); @@ -666,6 +690,14 @@ export function not(filter: FilterExpr): Not { return new Not(filter); } +export function exists(value: Expr): Exists; +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 isNull(value: Expr): IsNull; export function isNull(value: string): IsNull; export function isNull(value: Expr | string): IsNull { @@ -774,20 +806,20 @@ function genericFunction(name: string, params: Expr[]): Function { export class Ordering { constructor( private expr: Expr, - private direction: 'asc' | 'desc' + private direction: 'ascending' | 'descending' ) {} static of( expr: Expr, - direction: 'asc' | 'desc' | undefined = undefined + direction: 'ascending' | 'descending' | undefined = undefined ): Ordering { - return new Ordering(expr, direction || 'asc'); + return new Ordering(expr, direction || 'ascending'); } static ascending(expr: Expr): Ordering { - return new Ordering(expr, 'asc'); + return new Ordering(expr, 'ascending'); } static descending(expr: Expr): Ordering { - return new Ordering(expr, 'desc'); + return new Ordering(expr, 'descending'); } _toProto(serializer: Serializer): api.IValue { diff --git a/dev/src/index.ts b/dev/src/index.ts index 8d470c29e..61982a432 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -41,6 +41,7 @@ import { ResourcePath, validateResourcePath, } from './path'; +import {PipelineSource} from './pipeline'; import {ClientPool} from './pool'; import {CollectionReference} from './reference/collection-reference'; import {DocumentReference} from './reference/document-reference'; @@ -898,6 +899,10 @@ export class Firestore implements firestore.Firestore { return new CollectionGroup(this, collectionId, /* converter= */ undefined); } + pipeline(): PipelineSource { + return new PipelineSource(this); + } + /** * Creates a [WriteBatch]{@link WriteBatch}, used for performing * multiple writes as a single atomic operation. diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index ef42b9138..8b814e0a6 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -72,18 +72,20 @@ export class ExecutionUtil< }) .on( 'data', - (data: PipelineStreamElement) => { - if (data.transaction) { - output.transaction = data.transaction; - } - if (data.executionTime) { - output.executionTime = data.executionTime; - } - if (data.explainMetrics) { - output.explainMetrics = data.explainMetrics; - } - if (data.result) { - results.push(data.result); + (data: PipelineStreamElement[]) => { + for (const element of data) { + if (element.transaction) { + output.transaction = element.transaction; + } + if (element.executionTime) { + output.executionTime = element.executionTime; + } + if (element.explainMetrics) { + output.explainMetrics = element.explainMetrics; + } + if (element.result) { + results.push(element.result); + } } } ) @@ -142,41 +144,55 @@ export class ExecutionUtil< enc, callback ) => { + console.log(`Pipeline response: ${JSON.stringify(proto, null, 2)}`); if (proto === NOOP_MESSAGE) { callback(undefined); return; } - if (proto.results) { - for (const r of proto.results) { - const output: PipelineStreamElement = {}; - - // Proto comes with zero-length buffer by default - if (proto.transaction?.length) { - output.transaction = proto.transaction; - } - - if (proto.executionTime) { - output.executionTime = Timestamp.fromProto(proto.executionTime); - } - - const ref = r.name - ? new DocumentReference( - this._firestore, - QualifiedResourcePath.fromSlashSeparatedString(r.name) - ) - : undefined; - output.result = new PipelineResult( - this._serializer, - ref, - r.fields, - Timestamp.fromProto(proto.executionTime!), - r.createTime ? Timestamp.fromProto(r.createTime!) : undefined, - r.updateTime ? Timestamp.fromProto(r.updateTime!) : undefined - ); - - callback(undefined, output); + if (proto.results && proto.results.length === 0) { + const output: PipelineStreamElement = {}; + if (proto.transaction?.length) { + output.transaction = proto.transaction; } + if (proto.executionTime) { + output.executionTime = Timestamp.fromProto(proto.executionTime); + } + callback(undefined, [output]); + } else { + callback( + undefined, + proto.results.map(result => { + const output: PipelineStreamElement = + {}; + if (proto.transaction?.length) { + output.transaction = proto.transaction; + } + if (proto.executionTime) { + output.executionTime = Timestamp.fromProto(proto.executionTime); + } + + const ref = result.name + ? new DocumentReference( + this._firestore, + QualifiedResourcePath.fromSlashSeparatedString(result.name) + ) + : undefined; + output.result = new PipelineResult( + this._serializer, + ref, + result.fields, + Timestamp.fromProto(proto.executionTime!), + result.createTime + ? Timestamp.fromProto(result.createTime!) + : undefined, + result.updateTime + ? Timestamp.fromProto(result.updateTime!) + : undefined + ); + return output; + }) + ); } }, }); @@ -188,11 +204,14 @@ export class ExecutionUtil< // async function to convert this exception into the rejected Promise we // catch below. const request = pipeline._toProto( - this._firestore, transactionOrReadTime, explainOptions ); + console.log( + `Executing pipeline: \n ${JSON.stringify(request, null, 2)}` + ); + let streamActive: Deferred; do { streamActive = new Deferred(); @@ -207,9 +226,9 @@ export class ExecutionUtil< backendStream.unpipe(stream); logger( - 'QueryUtil._stream', + 'PipelineUtil._stream', tag, - 'Query failed with stream error:', + 'Pipeline failed with stream error:', err ); stream.destroy(err); @@ -222,7 +241,15 @@ export class ExecutionUtil< backendStream.pipe(stream); } while (await streamActive.promise); }) - .catch(e => stream.destroy(e)); + .catch(e => { + logger( + 'PipelineUtil._stream', + tag, + 'Pipeline failed with stream error:', + e + ); + stream.destroy(e); + }); return stream; } @@ -233,77 +260,78 @@ function isITimestamp(obj: any): obj is google.protobuf.ITimestamp { return false; // Must be a non-null object } if ( - ('seconds' in obj && - !( - obj.seconds === null || - typeof obj.seconds === 'number' || - typeof obj.seconds === 'string' - )) || - ('nanos' in obj && !(obj.nanos === null || typeof obj.nanos === 'number')) + 'seconds' in obj && + (obj.seconds === null || + typeof obj.seconds === 'number' || + typeof obj.seconds === 'string') && + 'nanos' in obj && + (obj.nanos === null || typeof obj.nanos === 'number') ) { - return false; // Invalid property type + return true; } - return true; // All checks passed + return false; } function isILatLng(obj: any): obj is google.type.ILatLng { if (typeof obj !== 'object' || obj === null) { return false; // Must be a non-null object } if ( - ('latitude' in obj && - !(obj.latitude === null || typeof obj.latitude === 'number')) || - ('longitude' in obj && - !(obj.longitude === null || typeof obj.longitude === 'number')) + 'latitude' in obj && + (obj.latitude === null || typeof obj.latitude === 'number') && + 'longitude' in obj && + (obj.longitude === null || typeof obj.longitude === 'number') ) { - return false; // Invalid property type + return true; } - return true; // All checks passed + return false; } function isIArrayValue(obj: any): obj is api.IArrayValue { if (typeof obj !== 'object' || obj === null) { return false; // Must be a non-null object } - if ('values' in obj && !(obj.values === null || Array.isArray(obj.values))) { - return false; // Invalid property type + if ('values' in obj && (obj.values === null || Array.isArray(obj.values))) { + return true; } - return true; // All checks passed + return false; } function isIMapValue(obj: any): obj is api.IMapValue { if (typeof obj !== 'object' || obj === null) { return false; // Must be a non-null object } - if ('fields' in obj && !(obj.fields === null || isObject(obj.fields))) { - return false; // Invalid property type + if ('fields' in obj && (obj.fields === null || isObject(obj.fields))) { + return true; } - return true; // All checks passed + return false; } function isIFunction(obj: any): obj is api.IFunction { if (typeof obj !== 'object' || obj === null) { return false; // Must be a non-null object } if ( - ('name' in obj && !(obj.name === null || typeof obj.name === 'string')) || - ('args' in obj && !(obj.args === null || Array.isArray(obj.args))) + 'name' in obj && + (obj.name === null || typeof obj.name === 'string') && + 'args' in obj && + (obj.args === null || Array.isArray(obj.args)) ) { - return false; // Invalid property type + return true; } - return true; // All checks passed + return false; } function isIPipeline(obj: any): obj is api.IPipeline { if (typeof obj !== 'object' || obj === null) { return false; // Must be a non-null object } - if ('stages' in obj && !(obj.stages === null || Array.isArray(obj.stages))) { - return false; // Invalid property type + if ('stages' in obj && (obj.stages === null || Array.isArray(obj.stages))) { + return true; } - return true; // All checks passed + return false; } export function isFirestoreValue(obj: any): obj is api.IValue { @@ -314,50 +342,42 @@ export function isFirestoreValue(obj: any): obj is api.IValue { // Check optional properties and their types if ( ('nullValue' in obj && - !( - obj.nullValue === null || - obj.nullValue instanceof google.protobuf.BoolValue - )) || + (obj.nullValue === null || obj.nullValue === 'NULL_VALUE')) || ('booleanValue' in obj && - !(obj.booleanValue === null || typeof obj.booleanValue === 'boolean')) || + (obj.booleanValue === null || typeof obj.booleanValue === 'boolean')) || ('integerValue' in obj && - !( - obj.integerValue === null || + (obj.integerValue === null || typeof obj.integerValue === 'number' || - typeof obj.integerValue === 'string' - )) || + typeof obj.integerValue === 'string')) || ('doubleValue' in obj && - !(obj.doubleValue === null || typeof obj.doubleValue === 'number')) || + (obj.doubleValue === null || typeof obj.doubleValue === 'number')) || ('timestampValue' in obj && - !(obj.timestampValue === null || isITimestamp(obj.timestampValue))) || + (obj.timestampValue === null || isITimestamp(obj.timestampValue))) || ('stringValue' in obj && - !(obj.stringValue === null || typeof obj.stringValue === 'string')) || + (obj.stringValue === null || typeof obj.stringValue === 'string')) || ('bytesValue' in obj && - !(obj.bytesValue === null || obj.bytesValue instanceof Uint8Array)) || + (obj.bytesValue === null || obj.bytesValue instanceof Uint8Array)) || ('referenceValue' in obj && - !( - obj.referenceValue === null || typeof obj.referenceValue === 'string' - )) || + (obj.referenceValue === null || + typeof obj.referenceValue === 'string')) || ('geoPointValue' in obj && - !(obj.geoPointValue === null || isILatLng(obj.geoPointValue))) || + (obj.geoPointValue === null || isILatLng(obj.geoPointValue))) || ('arrayValue' in obj && - !(obj.arrayValue === null || isIArrayValue(obj.arrayValue))) || + (obj.arrayValue === null || isIArrayValue(obj.arrayValue))) || ('mapValue' in obj && - !(obj.mapValue === null || isIMapValue(obj.mapValue))) || + (obj.mapValue === null || isIMapValue(obj.mapValue))) || ('fieldReferenceValue' in obj && - !( - obj.fieldReferenceValue === null || - typeof obj.fieldReferenceValue === 'string' - )) || + (obj.fieldReferenceValue === null || + typeof obj.fieldReferenceValue === 'string')) || ('functionValue' in obj && - !(obj.functionValue === null || isIFunction(obj.functionValue))) || + (obj.functionValue === null || isIFunction(obj.functionValue))) || ('pipelineValue' in obj && - !(obj.pipelineValue === null || isIPipeline(obj.pipelineValue))) + (obj.pipelineValue === null || isIPipeline(obj.pipelineValue))) ) { - return false; // Invalid property type + return true; } - return true; // All checks passed + return false; } export function toPipelineFilterCondition( @@ -365,17 +385,18 @@ export function toPipelineFilterCondition( serializer: Serializer ): FilterCondition & Expr { if (f instanceof FieldFilterInternal) { + const field = Field.of(f.field); if (f.isNanChecking()) { if (f.nanOp() === 'IS_NAN') { - return Field.of(f.field).isNaN(); + return and(field.exists(), field.isNaN()); } else { - return not(Field.of(f.field).isNaN()); + return and(field.exists(), not(field.isNaN())); } } else if (f.isNullChecking()) { if (f.nullOp() === 'IS_NULL') { - return Field.of(f.field).isNull(); + return and(field.exists(), field.isNull()); } else { - return not(Field.of(f.field).isNull()); + return and(field.exists(), not(field.isNull())); } } else { // Comparison filters @@ -384,36 +405,36 @@ export function toPipelineFilterCondition( : serializer.encodeValue(f.value); switch (f.op) { case 'LESS_THAN': - return Field.of(f.field).lessThan(value); + return and(field.exists(), field.lessThan(value)); case 'LESS_THAN_OR_EQUAL': - return Field.of(f.field).lessThanOrEqual(value); + return and(field.exists(), field.lessThanOrEqual(value)); case 'GREATER_THAN': - return Field.of(f.field).greaterThan(value); + return and(field.exists(), field.greaterThan(value)); case 'GREATER_THAN_OR_EQUAL': - return Field.of(f.field).greaterThanOrEqual(value); + return and(field.exists(), field.greaterThanOrEqual(value)); case 'EQUAL': - return Field.of(f.field).equal(value); + return and(field.exists(), field.equal(value)); case 'NOT_EQUAL': - return Field.of(f.field).notEqual(value); + return and(field.exists(), field.notEqual(value)); case 'ARRAY_CONTAINS': - return Field.of(f.field).arrayContains(value); + return and(field.exists(), field.arrayContains(value)); case 'IN': { const values = value?.arrayValue?.values?.map(val => - Constant.of(value) + Constant.of(val) ); - return Field.of(f.field).in(...values!); + return and(field.exists(), field.in(...values!)); } case 'ARRAY_CONTAINS_ANY': { const values = value?.arrayValue?.values?.map(val => - Constant.of(value) + Constant.of(val) ); - return Field.of(f.field).arrayContainsAny(values!); + return and(field.exists(), field.arrayContainsAny(values!)); } case 'NOT_IN': { const values = value?.arrayValue?.values?.map(val => - Constant.of(value) + Constant.of(val) ); - return not(Field.of(f.field).in(...values!)); + return and(field.exists(), not(field.in(...values!))); } } } diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index eb0cf038e..7ab677218 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -10,7 +10,6 @@ import { Ordering, Selectable, } from './expression'; -import {VectorValue} from './field-value'; import Firestore, {Timestamp} from './index'; import {ExecutionUtil} from './pipeline-util'; import {DocumentReference} from './reference/document-reference'; @@ -22,7 +21,7 @@ import { CollectionGroup, Database, Documents, - Filter, + Where, FindNearest, FindNearestOptions, GenerateStage, @@ -39,32 +38,39 @@ import IStructuredPipeline = google.firestore.v1.IStructuredPipeline; import IStage = google.firestore.v1.Pipeline.IStage; import {QueryCursor} from './reference/types'; -export class Pipeline< - AppModelType = firestore.DocumentData, - DbModelType extends firestore.DocumentData = firestore.DocumentData, -> { - private constructor(private stages: Stage[]) {} +export class PipelineSource { + constructor(private db: Firestore) {} - static fromCollection(collectionPath: string): Pipeline { - return new Pipeline([new Collection(collectionPath)]); + collection(collectionPath: string): Pipeline { + return new Pipeline(this.db, [new Collection(collectionPath)]); } - static fromCollectionGroup(collectionId: string): Pipeline { - return new Pipeline([new CollectionGroup(collectionId)]); + collectionGroup(collectionId: string): Pipeline { + return new Pipeline(this.db, [new CollectionGroup(collectionId)]); } - static fromDatabase(): Pipeline { - return new Pipeline([new Database()]); + database(): Pipeline { + return new Pipeline(this.db, [new Database()]); } - static fromDocuments(docs: DocumentReference[]): Pipeline { - return new Pipeline([Documents.of(docs)]); + documents(docs: DocumentReference[]): Pipeline { + return new Pipeline(this.db, [Documents.of(docs)]); } +} + +export class Pipeline< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData, +> { + constructor( + private db: Firestore, + private stages: Stage[] + ) {} addFields(...fields: Selectable[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new AddField(this.selectablesToMap(fields))); - return new Pipeline(copy); + return new Pipeline(this.db, copy); } select(...fields: string[]): Pipeline; @@ -72,7 +78,7 @@ export class Pipeline< select(...fields: (Selectable | string)[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new Select(this.selectablesToMap(fields))); - return new Pipeline(copy); + return new Pipeline(this.db, copy); } private selectablesToMap( @@ -97,22 +103,22 @@ export class Pipeline< return result; } - filter(condition: FilterCondition & Expr): Pipeline { + where(condition: FilterCondition & Expr): Pipeline { const copy = this.stages.map(s => s); - copy.push(new Filter(condition)); - return new Pipeline(copy); + copy.push(new Where(condition)); + return new Pipeline(this.db, copy); } offset(offset: number): Pipeline { const copy = this.stages.map(s => s); copy.push(new Offset(offset)); - return new Pipeline(copy); + return new Pipeline(this.db, copy); } limit(limit: number): Pipeline { const copy = this.stages.map(s => s); copy.push(new Limit(limit)); - return new Pipeline(copy); + return new Pipeline(this.db, copy); } aggregate(...targets: AggregateTarget[]): Pipeline { @@ -125,7 +131,7 @@ export class Pipeline< ) ) ); - return new Pipeline(copy); + return new Pipeline(this.db, copy); } findNearest( @@ -151,7 +157,7 @@ export class Pipeline< const copy = this.stages.map(s => s); const fieldExpr = typeof field === 'string' ? Field.of(field) : field; copy.push(new FindNearest(fieldExpr, vector, options)); - return new Pipeline(copy); + return new Pipeline(this.db, copy); } sort(orderings: Ordering[]): Pipeline; @@ -169,49 +175,50 @@ export class Pipeline< copy.push( new Sort(orderings, density ?? 'unspecified', truncation ?? 'unspecified') ); - return new Pipeline(copy); + return new Pipeline(this.db, copy); } paginate(pageSize: number, orderings?: Ordering[]): PaginatingPipeline { const copy = this.stages.map(s => s); - return new PaginatingPipeline(new Pipeline(copy), pageSize, orderings); + return new PaginatingPipeline( + new Pipeline(this.db, copy), + pageSize, + orderings + ); } genericStage(name: string, params: any[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new GenerateStage(name, params)); - return new Pipeline(copy); + return new Pipeline(this.db, copy); } - execute( - db: Firestore - ): Promise>> { + execute(): Promise>> { const util = new ExecutionUtil( - db, - db._serializer! + this.db, + this.db._serializer! ); return util._getResponse(this).then(result => result!); } - stream(db: Firestore): NodeJS.ReadableStream { + stream(): NodeJS.ReadableStream { const util = new ExecutionUtil( - db, - db._serializer! + this.db, + this.db._serializer! ); return util.stream(this); } _toProto( - db: Firestore, transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: FirebaseFirestore.ExplainOptions ): api.IExecutePipelineRequest { const stages: IStage[] = this.stages.map(stage => - stage._toProto(db._serializer!) + stage._toProto(this.db._serializer!) ); const structuredPipeline: IStructuredPipeline = {pipeline: {stages}}; return { - database: db.formattedName, + database: this.db.formattedName, structuredPipeline, }; } @@ -278,9 +285,9 @@ export class PipelineResult< > { private _ref: DocumentReference | undefined; private _serializer: Serializer; - private _readTime: Timestamp | undefined; - private _createTime: Timestamp | undefined; - private _updateTime: Timestamp | undefined; + public readonly _readTime: Timestamp | undefined; + public readonly _createTime: Timestamp | undefined; + public readonly _updateTime: Timestamp | undefined; /** * @private diff --git a/dev/src/reference/query.ts b/dev/src/reference/query.ts index ab516e642..75f6706b8 100644 --- a/dev/src/reference/query.ts +++ b/dev/src/reference/query.ts @@ -18,7 +18,7 @@ import * as firestore from '@google-cloud/firestore'; import {GoogleError} from 'google-gax'; import {Transform} from 'stream'; import * as protos from '../../protos/firestore_v1_proto_api'; -import {Field, Ordering} from '../expression'; +import {and, Field, Ordering} from '../expression'; import {CompositeFilter, UnaryFilter} from '../filter'; import { @@ -669,19 +669,21 @@ export class Query< toPipeline(): Pipeline { let pipeline; if (this._queryOptions.allDescendants) { - pipeline = Pipeline.fromCollectionGroup(this._queryOptions.collectionId); + pipeline = this.firestore + .pipeline() + .collectionGroup(this._queryOptions.collectionId); } else { - pipeline = Pipeline.fromCollection( - this._queryOptions.parentPath.append(this._queryOptions.collectionId) - .relativeName - ); + pipeline = this.firestore + .pipeline() + .collection( + this._queryOptions.parentPath.append(this._queryOptions.collectionId) + .relativeName + ); } // filters for (const f of this._queryOptions.filters) { - pipeline = pipeline.filter( - toPipelineFilterCondition(f, this._serializer) - ); + pipeline = pipeline.where(toPipelineFilterCondition(f, this._serializer)); } // projections @@ -693,15 +695,25 @@ export class Query< } // orderbys + const exists = this.createImplicitOrderBy().map(fieldOrder => { + return Field.of(fieldOrder.field).exists(); + }); + if (exists.length > 1) { + const [first, ...rest] = exists; + pipeline = pipeline.where(and(first, ...rest)); + } else if (exists.length == 1) { + pipeline = pipeline.where(exists[0]); + } + const orderings = this.createImplicitOrderBy().map(fieldOrder => { - let dir: 'asc' | 'desc' | undefined = undefined; + let dir: 'ascending' | 'descending' | undefined = undefined; switch (fieldOrder.direction) { case 'ASCENDING': { - dir = 'asc'; + dir = 'ascending'; break; } case 'DESCENDING': { - dir = 'desc'; + dir = 'descending'; break; } } @@ -713,8 +725,8 @@ export class Query< // Cursors, Limit and Offset if ( - !this._queryOptions.startAt || - !this._queryOptions.endAt || + !!this._queryOptions.startAt || + !!this._queryOptions.endAt || this._queryOptions.limitType === LimitType.Last ) { let paginating = pipeline.paginate(this._queryOptions.limit || 10); @@ -734,7 +746,7 @@ export class Query< pipeline = pipeline.offset(this._queryOptions.offset); } if (this._queryOptions.limit) { - pipeline = pipeline.offset(this._queryOptions.limit); + pipeline = pipeline.limit(this._queryOptions.limit); } } return pipeline; diff --git a/dev/src/reference/vector-query.ts b/dev/src/reference/vector-query.ts index 4f0a8c349..ad853c73b 100644 --- a/dev/src/reference/vector-query.ts +++ b/dev/src/reference/vector-query.ts @@ -138,6 +138,7 @@ export class VectorQuery< } as FindNearestOptions; return this.query .toPipeline() + .where(Field.of(this.vectorField).exists()) .findNearest(Field.of(this.vectorField), this.queryVector, options); } diff --git a/dev/src/serializer.ts b/dev/src/serializer.ts index 8ec31dd6a..a66bba1b4 100644 --- a/dev/src/serializer.ts +++ b/dev/src/serializer.ts @@ -209,6 +209,21 @@ export class Serializer { return array; } + if (val instanceof Map) { + const map: api.IMapValue = {fields: {}}; + for (const [key, value] of val.entries()) { + if (typeof key !== 'string') { + throw new Error(`Cannot encode map with non-string key: ${key}`); + } + + map.fields![key] = this.encodeValue(value)!; + } + + return { + mapValue: map, + }; + } + if (typeof val === 'object' && isPlainObject(val)) { const map: api.IValue = { mapValue: {}, diff --git a/dev/src/stage.ts b/dev/src/stage.ts index 0f31aa311..ca8d5f39b 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -52,7 +52,11 @@ export class Aggregate implements Stage { export class Collection implements Stage { name = 'collection'; - constructor(private collectionPath: string) {} + constructor(private collectionPath: string) { + if (!this.collectionPath.startsWith('/')) { + this.collectionPath = '/' + this.collectionPath; + } + } _toProto(serializer: Serializer): api.Pipeline.IStage { return { @@ -104,8 +108,8 @@ export class Documents implements Stage { } } -export class Filter implements Stage { - name = 'filter'; +export class Where implements Stage { + name = 'where'; constructor(private condition: FilterCondition & Expr) {} @@ -146,9 +150,9 @@ export class FindNearest implements Stage { name: this.name, args: [ this.property._toProto(serializer), - this.vector instanceof FirebaseFirestore.VectorValue + this.vector instanceof VectorValue ? serializer.encodeValue(this.vector)! - : serializer.encodeVector(this.vector), + : serializer.encodeVector(this.vector as number[]), serializer.encodeValue(this.options.distanceMeasure)!, ], options, @@ -196,7 +200,7 @@ export class Select implements Stage { } export class Sort implements Stage { - name = 'filter'; + name = 'sort'; constructor( private orders: Ordering[], diff --git a/dev/src/v1/firestore_client.ts b/dev/src/v1/firestore_client.ts index e1f4b242d..9affa770c 100644 --- a/dev/src/v1/firestore_client.ts +++ b/dev/src/v1/firestore_client.ts @@ -242,6 +242,11 @@ export class FirestoreClient { !!opts.fallback, !!opts.gaxServerStreamingRetries ), + executePipeline: new this._gaxModule.StreamDescriptor( + this._gaxModule.StreamType.SERVER_STREAMING, + !!opts.fallback, + !!opts.gaxServerStreamingRetries + ), runAggregationQuery: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.SERVER_STREAMING, !!opts.fallback, @@ -317,6 +322,7 @@ export class FirestoreClient { 'beginTransaction', 'commit', 'rollback', + 'executePipeline', 'runQuery', 'runAggregationQuery', 'partitionQuery', @@ -1316,6 +1322,22 @@ export class FirestoreClient { return this.innerApiCalls.runQuery(request, options); } + executePipeline( + request?: protos.google.firestore.v1.IExecutePipelineRequest, + options?: CallOptions + ): gax.CancellableStream { + request = request || {}; + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + options.otherArgs.headers['x-goog-request-params'] = + this._gaxModule.routingHeader.fromParams({ + parent: '', + }); + this.initialize(); + return this.innerApiCalls.executePipeline(request, options); + } + /** * Runs an aggregation query. * diff --git a/dev/src/v1/firestore_client_config.json b/dev/src/v1/firestore_client_config.json index 75487fc9b..47cbabb90 100644 --- a/dev/src/v1/firestore_client_config.json +++ b/dev/src/v1/firestore_client_config.json @@ -80,6 +80,11 @@ "retry_codes_name": "deadline_exceeded_resource_exhausted_internal_unavailable", "retry_params_name": "default" }, + "ExecutePipeline": { + "timeout_millis": 300000, + "retry_codes_name": "deadline_exceeded_resource_exhausted_internal_unavailable", + "retry_params_name": "default" + }, "RunAggregationQuery": { "timeout_millis": 300000, "retry_codes_name": "deadline_exceeded_resource_exhausted_internal_unavailable", diff --git a/dev/system-test/firestore.ts b/dev/system-test/firestore.ts index 5f7d683d7..cf040220b 100644 --- a/dev/system-test/firestore.ts +++ b/dev/system-test/firestore.ts @@ -65,7 +65,7 @@ use(chaiAsPromised); const version = require('../../package.json').version; -class DeferredPromise { +export class DeferredPromise { resolve: Function; reject: Function; promise: Promise | null; @@ -101,12 +101,31 @@ if (process.env.NODE_ENV === 'DEBUG') { setLogFunction(console.log); } -function getTestRoot(settings: Settings = {}): CollectionReference { +export function getTestRoot(settings: Settings = {}): CollectionReference { const internalSettings: Settings = {}; if (process.env.FIRESTORE_NAMED_DATABASE) { internalSettings.databaseId = process.env.FIRESTORE_NAMED_DATABASE; } + if (process.env.FIRESTORE_TARGET_BACKEND) { + switch (process.env.FIRESTORE_TARGET_BACKEND.toUpperCase()) { + case 'PROD': { + break; + } + case 'QA': { + internalSettings.host = 'staging-firestore.sandbox.googleapis.com'; + break; + } + case 'NIGHTLY': { + internalSettings.host = 'test-firestore.sandbox.googleapis.com'; + break; + } + default: { + break; + } + } + } + const firestore = new Firestore({ ...internalSettings, ...settings, // caller settings take precedent over internal settings @@ -1813,2101 +1832,6 @@ describe('runs query on a large collection', () => { }); }); -describe('Query class', () => { - interface PaginatedResults { - pages: number; - docs: QueryDocumentSnapshot[]; - } - - let firestore: Firestore; - let randomCol: CollectionReference; - - const paginateResults = ( - query: Query, - startAfter?: unknown - ): Promise => { - return (startAfter ? query.startAfter(startAfter) : query) - .get() - .then(snapshot => { - if (snapshot.empty) { - return {pages: 0, docs: []}; - } else { - const docs = snapshot.docs; - return paginateResults(query, docs[docs.length - 1]).then( - nextPage => { - return { - pages: nextPage.pages + 1, - docs: docs.concat(nextPage.docs), - }; - } - ); - } - }); - }; - - async function addDocs( - ...docs: DocumentData[] - ): Promise { - let id = 0; // Guarantees consistent ordering for the first documents - const refs: DocumentReference[] = []; - for (const doc of docs) { - const ref = randomCol.doc('doc' + id++); - await ref.set(doc); - refs.push(ref); - } - return refs; - } - - async function testCollectionWithDocs(docs: { - [id: string]: DocumentData; - }): Promise> { - for (const id in docs) { - const ref = randomCol.doc(id); - await ref.set(docs[id]); - } - return randomCol; - } - - function expectDocs(result: QuerySnapshot, ...docs: string[]): void; - function expectDocs(result: QuerySnapshot, ...data: DocumentData[]): void; - - function expectDocs( - result: QuerySnapshot, - ...data: DocumentData[] | string[] - ): void { - expect(result.size).to.equal(data.length); - - if (data.length > 0) { - if (typeof data[0] === 'string') { - const actualIds = result.docs.map(docSnapshot => docSnapshot.id); - expect(actualIds).to.deep.equal(data); - } else { - result.forEach(doc => { - expect(doc.data()).to.deep.equal(data.shift()); - }); - } - } - } - - beforeEach(() => { - randomCol = getTestRoot(); - firestore = randomCol.firestore; - }); - - afterEach(() => verifyInstance(firestore)); - - it('has firestore property', () => { - const ref = randomCol.limit(0); - expect(ref.firestore).to.be.an.instanceOf(Firestore); - }); - - it('has select() method', () => { - const ref = randomCol.doc('doc'); - return ref - .set({foo: 'bar', bar: 'foo'}) - .then(() => { - return randomCol.select('foo').get(); - }) - .then(res => { - expect(res.docs[0].data()).to.deep.equal({foo: 'bar'}); - }); - }); - - it('select() supports empty fields', () => { - const ref = randomCol.doc('doc'); - return ref - .set({foo: 'bar', bar: 'foo'}) - .then(() => { - return randomCol.select().get(); - }) - .then(res => { - expect(res.docs[0].ref.id).to.deep.equal('doc'); - expect(res.docs[0].data()).to.deep.equal({}); - }); - }); - - it('has where() method', () => { - const ref = randomCol.doc('doc'); - return ref - .set({foo: 'bar'}) - .then(() => { - return randomCol.where('foo', '==', 'bar').get(); - }) - .then(res => { - expect(res.docs[0].data()).to.deep.equal({foo: 'bar'}); - }); - }); - - it('supports NaN and Null', () => { - const ref = randomCol.doc('doc'); - return ref - .set({foo: NaN, bar: null}) - .then(() => { - return randomCol.where('foo', '==', NaN).where('bar', '==', null).get(); - }) - .then(res => { - expect( - typeof res.docs[0].get('foo') === 'number' && - isNaN(res.docs[0].get('foo')) - ); - expect(res.docs[0].get('bar')).to.equal(null); - }); - }); - - it('supports array-contains', () => { - return Promise.all([ - randomCol.add({foo: ['bar']}), - randomCol.add({foo: []}), - ]) - .then(() => randomCol.where('foo', 'array-contains', 'bar').get()) - .then(res => { - expect(res.size).to.equal(1); - expect(res.docs[0].get('foo')).to.deep.equal(['bar']); - }); - }); - - it('supports findNearest by EUCLIDEAN distance', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const collectionReference = await indexTestHelper.createTestDocs([ - {foo: 'bar'}, - {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, - {foo: 'bar', embedding: FieldValue.vector([1, 1])}, - {foo: 'bar', embedding: FieldValue.vector([10, 0])}, - {foo: 'bar', embedding: FieldValue.vector([20, 0])}, - {foo: 'bar', embedding: FieldValue.vector([100, 100])}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionReference) - .where('foo', '==', 'bar') - .findNearest('embedding', [10, 10], { - limit: 3, - distanceMeasure: 'EUCLIDEAN', - }); - - const res = await vectorQuery.get(); - expect(res.size).to.equal(3); - expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([10, 0]))).to - .be.true; - expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([1, 1]))).to - .be.true; - expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0]))).to - .be.true; - }); - - it('supports findNearest by COSINE distance', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const collectionReference = await indexTestHelper.setTestDocs({ - '1': {foo: 'bar'}, - '2': {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, - '3': {foo: 'bar', embedding: FieldValue.vector([1, 1])}, - '4': {foo: 'bar', embedding: FieldValue.vector([20, 0])}, - '5': {foo: 'bar', embedding: FieldValue.vector([10, 0])}, - '6': {foo: 'bar', embedding: FieldValue.vector([100, 100])}, - }); - - const vectorQuery = indexTestHelper - .query(collectionReference) - .where('foo', '==', 'bar') - .findNearest('embedding', [10, 10], { - limit: 3, - distanceMeasure: 'COSINE', - }); - - const res = await vectorQuery.get(); - - expect(res.size).to.equal(3); - - if (res.docs[0].get('embedding').isEqual(FieldValue.vector([1, 1]))) { - expect( - res.docs[1].get('embedding').isEqual(FieldValue.vector([100, 100])) - ).to.be.true; - } else { - expect( - res.docs[0].get('embedding').isEqual(FieldValue.vector([100, 100])) - ).to.be.true; - expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([1, 1]))).to - .be.true; - } - - expect( - res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0])) || - res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0])) - ).to.be.true; - }); - - it('supports findNearest by DOT_PRODUCT distance', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const collectionReference = await indexTestHelper.createTestDocs([ - {foo: 'bar'}, - {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, - {foo: 'bar', embedding: FieldValue.vector([1, 1])}, - {foo: 'bar', embedding: FieldValue.vector([10, 0])}, - {foo: 'bar', embedding: FieldValue.vector([20, 0])}, - {foo: 'bar', embedding: FieldValue.vector([100, 100])}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionReference) - .where('foo', '==', 'bar') - .findNearest('embedding', [10, 10], { - limit: 3, - distanceMeasure: 'DOT_PRODUCT', - }); - - const res = await vectorQuery.get(); - expect(res.size).to.equal(3); - expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([100, 100]))) - .to.be.true; - expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([20, 0]))).to - .be.true; - expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([10, 0]))).to - .be.true; - }); - - it('findNearest works with converters', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - class FooDistance { - constructor( - readonly foo: string, - readonly embedding: Array - ) {} - } - - const fooConverter = { - toFirestore(d: FooDistance): DocumentData { - return {title: d.foo, embedding: FieldValue.vector(d.embedding)}; - }, - fromFirestore(snapshot: QueryDocumentSnapshot): FooDistance { - const data = snapshot.data(); - return new FooDistance(data.foo, data.embedding.toArray()); - }, - }; - - const collectionRef = await indexTestHelper.createTestDocs([ - {foo: 'bar', embedding: FieldValue.vector([5, 5])}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionRef) - .withConverter(fooConverter) - .where('foo', '==', 'bar') - .findNearest('embedding', [10, 10], { - limit: 3, - distanceMeasure: 'EUCLIDEAN', - }); - - const res = await vectorQuery.get(); - - expect(res.size).to.equal(1); - expect(res.docs[0].data().foo).to.equal('bar'); - expect(res.docs[0].data().embedding).to.deep.equal([5, 5]); - }); - - it('supports findNearest skipping fields of wrong types', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const collectionRef = await indexTestHelper.createTestDocs([ - {foo: 'bar'}, - - // These documents are skipped because it is not really a vector value - {foo: 'bar', embedding: [10, 10]}, - {foo: 'bar', embedding: 'not actually a vector'}, - {foo: 'bar', embedding: null}, - - // Actual vector values - {foo: 'bar', embedding: FieldValue.vector([9, 9])}, - {foo: 'bar', embedding: FieldValue.vector([50, 50])}, - {foo: 'bar', embedding: FieldValue.vector([100, 100])}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionRef) - .where('foo', '==', 'bar') - .findNearest('embedding', [10, 10], { - limit: 100, // Intentionally large to get all matches. - distanceMeasure: 'EUCLIDEAN', - }); - - const res = await vectorQuery.get(); - expect(res.size).to.equal(3); - expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([9, 9]))).to - .be.true; - expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([50, 50]))).to - .be.true; - expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([100, 100]))) - .to.be.true; - }); - - it('findNearest ignores mismatching dimensions', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const collectionRef = await indexTestHelper.createTestDocs([ - {foo: 'bar'}, - - // Vectors with dimension mismatch - {foo: 'bar', embedding: FieldValue.vector([10])}, - - // Vectors with dimension match - {foo: 'bar', embedding: FieldValue.vector([9, 9])}, - {foo: 'bar', embedding: FieldValue.vector([50, 50])}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionRef) - .where('foo', '==', 'bar') - .findNearest('embedding', [10, 10], { - limit: 3, - distanceMeasure: 'EUCLIDEAN', - }); - - const res = await vectorQuery.get(); - expect(res.size).to.equal(2); - expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([9, 9]))).to - .be.true; - expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([50, 50]))).to - .be.true; - }); - - it('supports findNearest on non-existent field', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const collectionRef = await indexTestHelper.createTestDocs([ - {foo: 'bar'}, - {foo: 'bar', otherField: [10, 10]}, - {foo: 'bar', otherField: 'not actually a vector'}, - {foo: 'bar', otherField: null}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionRef) - .where('foo', '==', 'bar') - .findNearest('embedding', [10, 10], { - limit: 3, - distanceMeasure: 'EUCLIDEAN', - }); - - const res = await vectorQuery.get(); - - expect(res.size).to.equal(0); - }); - - it('supports findNearest on vector nested in a map', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const collectionReference = await indexTestHelper.createTestDocs([ - {nested: {foo: 'bar'}}, - {nested: {foo: 'xxx', embedding: FieldValue.vector([10, 10])}}, - {nested: {foo: 'bar', embedding: FieldValue.vector([1, 1])}}, - {nested: {foo: 'bar', embedding: FieldValue.vector([10, 0])}}, - {nested: {foo: 'bar', embedding: FieldValue.vector([20, 0])}}, - {nested: {foo: 'bar', embedding: FieldValue.vector([100, 100])}}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionReference) - .findNearest('nested.embedding', [10, 10], { - limit: 3, - distanceMeasure: 'EUCLIDEAN', - }); - - const res = await vectorQuery.get(); - expect(res.size).to.equal(3); - expect( - res.docs[0].get('nested.embedding').isEqual(FieldValue.vector([10, 10])) - ).to.be.true; - expect( - res.docs[1].get('nested.embedding').isEqual(FieldValue.vector([10, 0])) - ).to.be.true; - expect( - res.docs[2].get('nested.embedding').isEqual(FieldValue.vector([1, 1])) - ).to.be.true; - }); - - it('supports findNearest with select to exclude vector data in response', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const collectionReference = await indexTestHelper.createTestDocs([ - {foo: 1}, - {foo: 2, embedding: FieldValue.vector([10, 10])}, - {foo: 3, embedding: FieldValue.vector([1, 1])}, - {foo: 4, embedding: FieldValue.vector([10, 0])}, - {foo: 5, embedding: FieldValue.vector([20, 0])}, - {foo: 6, embedding: FieldValue.vector([100, 100])}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionReference) - .where('foo', 'in', [1, 2, 3, 4, 5, 6]) - .select('foo') - .findNearest('embedding', [10, 10], { - limit: 10, - distanceMeasure: 'EUCLIDEAN', - }); - - const res = await vectorQuery.get(); - expect(res.size).to.equal(5); - expect(res.docs[0].get('foo')).to.equal(2); - expect(res.docs[1].get('foo')).to.equal(4); - expect(res.docs[2].get('foo')).to.equal(3); - expect(res.docs[3].get('foo')).to.equal(5); - expect(res.docs[4].get('foo')).to.equal(6); - - res.docs.forEach(ds => expect(ds.get('embedding')).to.be.undefined); - }); - - it('supports findNearest limits', async () => { - const indexTestHelper = new IndexTestHelper(firestore); - - const embeddingVector = []; - const queryVector = []; - for (let i = 0; i < 2048; i++) { - embeddingVector.push(i + 1); - queryVector.push(i - 1); - } - - const collectionReference = await indexTestHelper.createTestDocs([ - {embedding: FieldValue.vector(embeddingVector)}, - ]); - - const vectorQuery = indexTestHelper - .query(collectionReference) - .findNearest('embedding', queryVector, { - limit: 1000, - distanceMeasure: 'EUCLIDEAN', - }); - - const res = await vectorQuery.get(); - expect(res.size).to.equal(1); - expect( - (res.docs[0].get('embedding') as VectorValue).toArray() - ).to.deep.equal(embeddingVector); - }); - - it('supports !=', async () => { - await addDocs( - {zip: NaN}, - {zip: 91102}, - {zip: 98101}, - {zip: 98103}, - {zip: [98101]}, - {zip: ['98101', {zip: 98101}]}, - {zip: {zip: 98101}}, - {zip: null} - ); - - let res = await randomCol.where('zip', '!=', 98101).get(); - expectDocs( - res, - {zip: NaN}, - {zip: 91102}, - {zip: 98103}, - {zip: [98101]}, - {zip: ['98101', {zip: 98101}]}, - {zip: {zip: 98101}} - ); - - res = await randomCol.where('zip', '!=', NaN).get(); - expectDocs( - res, - {zip: 91102}, - {zip: 98101}, - {zip: 98103}, - {zip: [98101]}, - {zip: ['98101', {zip: 98101}]}, - {zip: {zip: 98101}} - ); - - res = await randomCol.where('zip', '!=', null).get(); - expectDocs( - res, - {zip: NaN}, - {zip: 91102}, - {zip: 98101}, - {zip: 98103}, - {zip: [98101]}, - {zip: ['98101', {zip: 98101}]}, - {zip: {zip: 98101}} - ); - }); - - it('supports != with document ID', async () => { - const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); - const res = await randomCol - .where(FieldPath.documentId(), '!=', refs[0].id) - .get(); - expectDocs(res, {count: 2}, {count: 3}); - }); - - it('supports not-in', async () => { - await addDocs( - {zip: 98101}, - {zip: 91102}, - {zip: 98103}, - {zip: [98101]}, - {zip: ['98101', {zip: 98101}]}, - {zip: {zip: 98101}} - ); - let res = await randomCol.where('zip', 'not-in', [98101, 98103]).get(); - expectDocs( - res, - {zip: 91102}, - {zip: [98101]}, - {zip: ['98101', {zip: 98101}]}, - {zip: {zip: 98101}} - ); - - res = await randomCol.where('zip', 'not-in', [NaN]).get(); - expectDocs( - res, - {zip: 91102}, - {zip: 98101}, - {zip: 98103}, - {zip: [98101]}, - {zip: ['98101', {zip: 98101}]}, - {zip: {zip: 98101}} - ); - - res = await randomCol.where('zip', 'not-in', [null]).get(); - expect(res.size).to.equal(0); - }); - - it('supports not-in with document ID array', async () => { - const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); - const res = await randomCol - .where(FieldPath.documentId(), 'not-in', [refs[0].id, refs[1]]) - .get(); - expectDocs(res, {count: 3}); - }); - - it('supports "in"', async () => { - await addDocs( - {zip: 98101}, - {zip: 91102}, - {zip: 98103}, - {zip: [98101]}, - {zip: ['98101', {zip: 98101}]}, - {zip: {zip: 98101}} - ); - const res = await randomCol.where('zip', 'in', [98101, 98103]).get(); - expectDocs(res, {zip: 98101}, {zip: 98103}); - }); - - it('supports "in" with document ID array', async () => { - const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); - const res = await randomCol - .where(FieldPath.documentId(), 'in', [refs[0].id, refs[1]]) - .get(); - expectDocs(res, {count: 1}, {count: 2}); - }); - - it('supports array-contains-any', async () => { - await addDocs( - {array: [42]}, - {array: ['a', 42, 'c']}, - {array: [41.999, '42', {a: [42]}]}, - {array: [42], array2: ['sigh']}, - {array: [43]}, - {array: [{a: 42}]}, - {array: 42} - ); - - const res = await randomCol - .where('array', 'array-contains-any', [42, 43]) - .get(); - - expectDocs( - res, - {array: [42]}, - {array: ['a', 42, 'c']}, - { - array: [42], - array2: ['sigh'], - }, - {array: [43]} - ); - }); - - it('can query by FieldPath.documentId()', () => { - const ref = randomCol.doc('foo'); - - return ref - .set({}) - .then(() => { - return randomCol.where(FieldPath.documentId(), '>=', 'bar').get(); - }) - .then(res => { - expect(res.docs.length).to.equal(1); - }); - }); - - it('has orderBy() method', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - - let res = await randomCol.orderBy('foo').get(); - expectDocs(res, {foo: 'a'}, {foo: 'b'}); - - res = await randomCol.orderBy('foo', 'desc').get(); - expectDocs(res, {foo: 'b'}, {foo: 'a'}); - }); - - it('can order by FieldPath.documentId()', () => { - const ref1 = randomCol.doc('doc1'); - const ref2 = randomCol.doc('doc2'); - - return Promise.all([ref1.set({foo: 'a'}), ref2.set({foo: 'b'})]) - .then(() => { - return randomCol.orderBy(FieldPath.documentId()).get(); - }) - .then(res => { - expect(res.docs[0].data()).to.deep.equal({foo: 'a'}); - expect(res.docs[1].data()).to.deep.equal({foo: 'b'}); - }); - }); - - it('can run get() on empty collection', async () => { - return randomCol.get().then(res => { - return expect(res.empty); - }); - }); - - it('can run stream() on empty collection', async () => { - let received = 0; - const stream = randomCol.stream(); - - for await (const doc of stream) { - expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); - ++received; - } - - expect(received).to.equal(0); - }); - - it('has limit() method on get()', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - const res = await randomCol.orderBy('foo').limit(1).get(); - expectDocs(res, {foo: 'a'}); - }); - - it('has limit() method on stream()', async () => { - let received = 0; - await addDocs({foo: 'a'}, {foo: 'b'}); - - const stream = randomCol.orderBy('foo').limit(1).stream(); - for await (const doc of stream) { - expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); - ++received; - } - - expect(received).to.equal(1); - }); - - it('can run limit(num), where num is larger than the collection size on get()', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - const res = await randomCol.orderBy('foo').limit(3).get(); - expectDocs(res, {foo: 'a'}, {foo: 'b'}); - }); - - it('can run limit(num), where num is larger than the collection size on stream()', async () => { - let received = 0; - await addDocs({foo: 'a'}, {foo: 'b'}); - - const stream = randomCol.orderBy('foo').limit(3).stream(); - for await (const doc of stream) { - expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); - ++received; - } - - expect(received).to.equal(2); - }); - - it('has limitToLast() method', async () => { - await addDocs({doc: 1}, {doc: 2}, {doc: 3}); - const res = await randomCol.orderBy('doc').limitToLast(2).get(); - expectDocs(res, {doc: 2}, {doc: 3}); - }); - - it('limitToLast() supports Query cursors', async () => { - await addDocs({doc: 1}, {doc: 2}, {doc: 3}, {doc: 4}, {doc: 5}); - const res = await randomCol - .orderBy('doc') - .startAt(2) - .endAt(4) - .limitToLast(5) - .get(); - expectDocs(res, {doc: 2}, {doc: 3}, {doc: 4}); - }); - - it('can use offset() method with get()', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - const res = await randomCol.orderBy('foo').offset(1).get(); - expectDocs(res, {foo: 'b'}); - }); - - it('can use offset() method with stream()', async () => { - let received = 0; - await addDocs({foo: 'a'}, {foo: 'b'}); - - const stream = randomCol.orderBy('foo').offset(1).stream(); - for await (const doc of stream) { - expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); - ++received; - } - - expect(received).to.equal(1); - }); - - it('can run offset(num), where num is larger than the collection size on get()', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - const res = await randomCol.orderBy('foo').offset(3).get(); - expect(res.empty); - }); - - it('can run offset(num), where num is larger than the collection size on stream()', async () => { - let received = 0; - await addDocs({foo: 'a'}, {foo: 'b'}); - const stream = randomCol.orderBy('foo').offset(3).stream(); - for await (const doc of stream) { - expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); - ++received; - } - expect(received).to.equal(0); - }); - - it('supports Unicode in document names', async () => { - const collRef = randomCol.doc('доброеутро').collection('coll'); - await collRef.add({}); - const snapshot = await collRef.get(); - expect(snapshot.size).to.equal(1); - }); - - it('supports pagination', () => { - const batch = firestore.batch(); - - for (let i = 0; i < 10; ++i) { - batch.set(randomCol.doc('doc' + i), {val: i}); - } - - const query = randomCol.orderBy('val').limit(3); - - return batch - .commit() - .then(() => paginateResults(query)) - .then(results => { - expect(results.pages).to.equal(4); - expect(results.docs).to.have.length(10); - }); - }); - - it('supports pagination with where() clauses', () => { - const batch = firestore.batch(); - - for (let i = 0; i < 10; ++i) { - batch.set(randomCol.doc('doc' + i), {val: i}); - } - - const query = randomCol.where('val', '>=', 1).limit(3); - - return batch - .commit() - .then(() => paginateResults(query)) - .then(results => { - expect(results.pages).to.equal(3); - expect(results.docs).to.have.length(9); - }); - }); - - it('supports pagination with array-contains filter', () => { - const batch = firestore.batch(); - - for (let i = 0; i < 10; ++i) { - batch.set(randomCol.doc('doc' + i), {array: ['foo']}); - } - - const query = randomCol.where('array', 'array-contains', 'foo').limit(3); - - return batch - .commit() - .then(() => paginateResults(query)) - .then(results => { - expect(results.pages).to.equal(4); - expect(results.docs).to.have.length(10); - }); - }); - - it('has startAt() method', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - const res = await randomCol.orderBy('foo').startAt('b').get(); - expectDocs(res, {foo: 'b'}); - }); - - it('startAt() adds implicit order by for DocumentSnapshot', async () => { - const references = await addDocs({foo: 'a'}, {foo: 'b'}); - const docSnap = await references[1].get(); - const res = await randomCol.startAt(docSnap).get(); - expectDocs(res, {foo: 'b'}); - }); - - it('has startAfter() method', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - const res = await randomCol.orderBy('foo').startAfter('a').get(); - expectDocs(res, {foo: 'b'}); - }); - - it('has endAt() method', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - const res = await randomCol.orderBy('foo').endAt('b').get(); - expectDocs(res, {foo: 'a'}, {foo: 'b'}); - }); - - it('has endBefore() method', async () => { - await addDocs({foo: 'a'}, {foo: 'b'}); - const res = await randomCol.orderBy('foo').endBefore('b').get(); - expectDocs(res, {foo: 'a'}); - }); - - it('has stream() method', done => { - let received = 0; - const ref1 = randomCol.doc('doc1'); - const ref2 = randomCol.doc('doc2'); - - Promise.all([ref1.set({foo: 'a'}), ref2.set({foo: 'b'})]).then(() => { - return randomCol - .stream() - .on('data', d => { - expect(d).to.be.an.instanceOf(DocumentSnapshot); - ++received; - }) - .on('end', () => { - expect(received).to.equal(2); - done(); - }); - }); - }); - - it('stream() supports readable[Symbol.asyncIterator]()', async () => { - let received = 0; - await randomCol.doc().set({foo: 'bar'}); - await randomCol.doc().set({foo: 'bar'}); - - const stream = randomCol.stream(); - for await (const doc of stream) { - expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); - ++received; - } - - expect(received).to.equal(2); - }); - - it('can query collection groups', async () => { - // Use `randomCol` to get a random collection group name to use but ensure - // it starts with 'b' for predictable ordering. - const collectionGroup = 'b' + randomCol.id; - - const docPaths = [ - `abc/123/${collectionGroup}/cg-doc1`, - `abc/123/${collectionGroup}/cg-doc2`, - `${collectionGroup}/cg-doc3`, - `${collectionGroup}/cg-doc4`, - `def/456/${collectionGroup}/cg-doc5`, - `${collectionGroup}/virtual-doc/nested-coll/not-cg-doc`, - `x${collectionGroup}/not-cg-doc`, - `${collectionGroup}x/not-cg-doc`, - `abc/123/${collectionGroup}x/not-cg-doc`, - `abc/123/x${collectionGroup}/not-cg-doc`, - `abc/${collectionGroup}`, - ]; - const batch = firestore.batch(); - for (const docPath of docPaths) { - batch.set(firestore.doc(docPath), {x: 1}); - } - await batch.commit(); - - const querySnapshot = await firestore - .collectionGroup(collectionGroup) - .get(); - expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ - 'cg-doc1', - 'cg-doc2', - 'cg-doc3', - 'cg-doc4', - 'cg-doc5', - ]); - }); - - it('can query collection groups with startAt / endAt by arbitrary documentId', async () => { - // Use `randomCol` to get a random collection group name to use but - // ensure it starts with 'b' for predictable ordering. - const collectionGroup = 'b' + randomCol.id; - - const docPaths = [ - `a/a/${collectionGroup}/cg-doc1`, - `a/b/a/b/${collectionGroup}/cg-doc2`, - `a/b/${collectionGroup}/cg-doc3`, - `a/b/c/d/${collectionGroup}/cg-doc4`, - `a/c/${collectionGroup}/cg-doc5`, - `${collectionGroup}/cg-doc6`, - 'a/b/nope/nope', - ]; - const batch = firestore.batch(); - for (const docPath of docPaths) { - batch.set(firestore.doc(docPath), {x: 1}); - } - await batch.commit(); - - let querySnapshot = await firestore - .collectionGroup(collectionGroup) - .orderBy(FieldPath.documentId()) - .startAt('a/b') - .endAt('a/b0') - .get(); - expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ - 'cg-doc2', - 'cg-doc3', - 'cg-doc4', - ]); - - querySnapshot = await firestore - .collectionGroup(collectionGroup) - .orderBy(FieldPath.documentId()) - .startAfter('a/b') - .endBefore(`a/b/${collectionGroup}/cg-doc3`) - .get(); - expect(querySnapshot.docs.map(d => d.id)).to.deep.equal(['cg-doc2']); - }); - - it('can query collection groups with where filters on arbitrary documentId', async () => { - // Use `randomCol` to get a random collection group name to use but - // ensure it starts with 'b' for predictable ordering. - const collectionGroup = 'b' + randomCol.id; - - const docPaths = [ - `a/a/${collectionGroup}/cg-doc1`, - `a/b/a/b/${collectionGroup}/cg-doc2`, - `a/b/${collectionGroup}/cg-doc3`, - `a/b/c/d/${collectionGroup}/cg-doc4`, - `a/c/${collectionGroup}/cg-doc5`, - `${collectionGroup}/cg-doc6`, - 'a/b/nope/nope', - ]; - const batch = firestore.batch(); - for (const docPath of docPaths) { - batch.set(firestore.doc(docPath), {x: 1}); - } - await batch.commit(); - - let querySnapshot = await firestore - .collectionGroup(collectionGroup) - .where(FieldPath.documentId(), '>=', 'a/b') - .where(FieldPath.documentId(), '<=', 'a/b0') - .get(); - expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ - 'cg-doc2', - 'cg-doc3', - 'cg-doc4', - ]); - - querySnapshot = await firestore - .collectionGroup(collectionGroup) - .where(FieldPath.documentId(), '>', 'a/b') - .where(FieldPath.documentId(), '<', `a/b/${collectionGroup}/cg-doc3`) - .get(); - expect(querySnapshot.docs.map(d => d.id)).to.deep.equal(['cg-doc2']); - }); - - it('can query large collections', async () => { - // @grpc/grpc-js v0.4.1 failed to deliver the full set of query results for - // larger collections (https://github.com/grpc/grpc-node/issues/895); - const batch = firestore.batch(); - for (let i = 0; i < 100; ++i) { - batch.create(randomCol.doc(), {}); - } - await batch.commit(); - - const snapshot = await randomCol.get(); - expect(snapshot.size).to.equal(100); - }); - - it('supports OR queries', async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: 0}, - doc2: {a: 2, b: 1}, - doc3: {a: 3, b: 2}, - doc4: {a: 1, b: 3}, - doc5: {a: 1, b: 1}, - }); - - // Two equalities: a==1 || b==1. - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)) - ) - .get(), - 'doc1', - 'doc2', - 'doc4', - 'doc5' - ); - - // (a==1 && b==0) || (a==3 && b==2) - expectDocs( - await collection - .where( - Filter.or( - Filter.and(Filter.where('a', '==', 1), Filter.where('b', '==', 0)), - Filter.and(Filter.where('a', '==', 3), Filter.where('b', '==', 2)) - ) - ) - .get(), - 'doc1', - 'doc3' - ); - - // a==1 && (b==0 || b==3). - expectDocs( - await collection - .where( - Filter.and( - Filter.where('a', '==', 1), - Filter.or(Filter.where('b', '==', 0), Filter.where('b', '==', 3)) - ) - ) - .get(), - 'doc1', - 'doc4' - ); - - // (a==2 || b==2) && (a==3 || b==3) - expectDocs( - await collection - .where( - Filter.and( - Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 2)), - Filter.or(Filter.where('a', '==', 3), Filter.where('b', '==', 3)) - ) - ) - .get(), - 'doc3' - ); - - // Test with limits without orderBy (the __name__ ordering is the tie breaker). - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)) - ) - .limit(1) - .get(), - 'doc2' - ); - }); - - // Skip this test if running against production because it results in a 'missing index' error. - // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( - 'supports OR queries with composite indexes', - async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: 0}, - doc2: {a: 2, b: 1}, - doc3: {a: 3, b: 2}, - doc4: {a: 1, b: 3}, - doc5: {a: 1, b: 1}, - }); - - // with one inequality: a>2 || b==1. - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '>', 2), Filter.where('b', '==', 1)) - ) - .get(), - 'doc5', - 'doc2', - 'doc3' - ); - - // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0)) - ) - .limit(2) - .get(), - 'doc1', - 'doc2' - ); - - // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 - // Note: The public query API does not allow implicit ordering when limitToLast is used. - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0)) - ) - .limitToLast(2) - .orderBy('b') - .get(), - 'doc3', - 'doc4' - ); - - // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)) - ) - .limit(1) - .orderBy('a') - .get(), - 'doc5' - ); - - // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT 1 - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)) - ) - .limit(1) - .orderBy('a', 'desc') - .get(), - 'doc2' - ); - } - ); - - it('supports OR queries on documents with missing fields', async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: 0}, - doc2: {b: 1}, - doc3: {a: 3, b: 2}, - doc4: {a: 1, b: 3}, - doc5: {a: 1}, - doc6: {a: 2}, - }); - - // Query: a==1 || b==1 - // There's no explicit nor implicit orderBy. Documents with missing 'a' or missing 'b' should be - // allowed if the document matches at least one disjunction term. - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)) - ) - .get(), - 'doc1', - 'doc2', - 'doc4', - 'doc5' - ); - }); - - // Skip this test if running against production because it results in a 'missing index' error. - // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( - 'supports OR queries on documents with missing fields', - async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: 0}, - doc2: {b: 1}, - doc3: {a: 3, b: 2}, - doc4: {a: 1, b: 3}, - doc5: {a: 1}, - doc6: {a: 2}, - }); - - // Query: a==1 || b==1 order by a. - // doc2 should not be included because it's missing the field 'a', and we have "orderBy a". - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)) - ) - .orderBy('a') - .get(), - 'doc1', - 'doc4', - 'doc5' - ); - - // Query: a==1 || b==1 order by b. - // doc5 should not be included because it's missing the field 'b', and we have "orderBy b". - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)) - ) - .orderBy('b') - .get(), - 'doc1', - 'doc2', - 'doc4' - ); - - // Query: a>2 || b==1. - // This query has an implicit 'order by a'. - // doc2 should not be included because it's missing the field 'a'. - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '>', 2), Filter.where('b', '==', 1)) - ) - .get(), - 'doc3' - ); - - // Query: a>1 || b==1 order by a order by b. - // doc6 should not be included because it's missing the field 'b'. - // doc2 should not be included because it's missing the field 'a'. - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '>', 1), Filter.where('b', '==', 1)) - ) - .orderBy('a') - .orderBy('b') - .get(), - 'doc3' - ); - } - ); - - it('supports OR queries with in', async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: 0}, - doc2: {b: 1}, - doc3: {a: 3, b: 2}, - doc4: {a: 1, b: 3}, - doc5: {a: 1}, - doc6: {a: 2}, - }); - - // Query: a==2 || b in [2, 3] - expectDocs( - await collection - .where( - Filter.or(Filter.where('a', '==', 2), Filter.where('b', 'in', [2, 3])) - ) - .get(), - 'doc3', - 'doc4', - 'doc6' - ); - }); - - // Skip this test if running against production because it results in a 'missing index' error. - // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( - 'supports OR queries with not-in', - async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: 0}, - doc2: {b: 1}, - doc3: {a: 3, b: 2}, - doc4: {a: 1, b: 3}, - doc5: {a: 1}, - doc6: {a: 2}, - }); - - // a==2 || (b != 2 && b != 3) - // Has implicit "orderBy b" - expectDocs( - await collection - .where( - Filter.or( - Filter.where('a', '==', 2), - Filter.where('b', 'not-in', [2, 3]) - ) - ) - .get(), - 'doc1', - 'doc2' - ); - } - ); - - it('supports OR queries with array membership', async () => { - const collection = await testCollectionWithDocs({ - doc1: {a: 1, b: [0]}, - doc2: {b: [1]}, - doc3: {a: 3, b: [2, 7]}, - doc4: {a: 1, b: [3, 7]}, - doc5: {a: 1}, - doc6: {a: 2}, - }); - - // Query: a==2 || b array-contains 7 - expectDocs( - await collection - .where( - Filter.or( - Filter.where('a', '==', 2), - Filter.where('b', 'array-contains', 7) - ) - ) - .get(), - 'doc3', - 'doc4', - 'doc6' - ); - - // a==2 || b array-contains-any [0, 3] - // Has implicit "orderBy b" - expectDocs( - await collection - .where( - Filter.or( - Filter.where('a', '==', 2), - Filter.where('b', 'array-contains-any', [0, 3]) - ) - ) - .get(), - 'doc1', - 'doc4', - 'doc6' - ); - }); - - describe('watch', () => { - interface ExpectedChange { - type: string; - doc: DocumentSnapshot; - } - - const currentDeferred = new DeferredPromise(); - - const snapshot = (id: string, data: DocumentData) => { - const ref = randomCol.doc(id); - const fields = ref.firestore._serializer!.encodeFields(data); - return randomCol.firestore.snapshot_( - { - name: - 'projects/ignored/databases/(default)/documents/' + - ref._path.relativeName, - fields, - createTime: {seconds: 0, nanos: 0}, - updateTime: {seconds: 0, nanos: 0}, - }, - {seconds: 0, nanos: 0} - ); - }; - - const docChange = ( - type: string, - id: string, - data: DocumentData - ): ExpectedChange => { - return { - type, - doc: snapshot(id, data), - }; - }; - - const added = (id: string, data: DocumentData) => - docChange('added', id, data); - const modified = (id: string, data: DocumentData) => - docChange('modified', id, data); - const removed = (id: string, data: DocumentData) => - docChange('removed', id, data); - - function resetPromise() { - currentDeferred.promise = new Promise((resolve, reject) => { - currentDeferred.resolve = resolve; - currentDeferred.reject = reject; - }); - } - - function waitForSnapshot(): Promise { - return currentDeferred.promise!.then(snapshot => { - resetPromise(); - return snapshot; - }); - } - - function snapshotsEqual( - actual: QuerySnapshot, - expected: {docs: DocumentSnapshot[]; docChanges: ExpectedChange[]} - ) { - let i; - expect(actual.size).to.equal(expected.docs.length); - for (i = 0; i < expected.docs.length && i < actual.size; i++) { - expect(actual.docs[i].ref.id).to.equal(expected.docs[i].ref.id); - expect(actual.docs[i].data()).to.deep.equal(expected.docs[i].data()); - } - const actualDocChanges = actual.docChanges(); - expect(actualDocChanges.length).to.equal(expected.docChanges.length); - for (i = 0; i < expected.docChanges.length; i++) { - expect(actualDocChanges[i].type).to.equal(expected.docChanges[i].type); - expect(actualDocChanges[i].doc.ref.id).to.equal( - expected.docChanges[i].doc.ref.id - ); - expect(actualDocChanges[i].doc.data()).to.deep.equal( - expected.docChanges[i].doc.data() - ); - expect(actualDocChanges[i].doc.readTime).to.exist; - expect(actualDocChanges[i].doc.createTime).to.exist; - expect(actualDocChanges[i].doc.updateTime).to.exist; - } - expect(actual.readTime).to.exist; - } - - beforeEach(() => resetPromise()); - - it('handles changing a doc', () => { - const ref1 = randomCol.doc('doc1'); - const ref2 = randomCol.doc('doc2'); - - const unsubscribe = randomCol.onSnapshot( - snapshot => { - currentDeferred.resolve(snapshot); - }, - err => { - currentDeferred.reject!(err); - } - ); - - return waitForSnapshot() - .then(results => { - snapshotsEqual(results, {docs: [], docChanges: []}); - // Add a result. - return ref1.set({foo: 'a'}); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [snapshot('doc1', {foo: 'a'})], - docChanges: [added('doc1', {foo: 'a'})], - }); - // Add another result. - return ref2.set({foo: 'b'}); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [snapshot('doc1', {foo: 'a'}), snapshot('doc2', {foo: 'b'})], - docChanges: [added('doc2', {foo: 'b'})], - }); - // Change a result. - return ref2.set({bar: 'c'}); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [snapshot('doc1', {foo: 'a'}), snapshot('doc2', {bar: 'c'})], - docChanges: [modified('doc2', {bar: 'c'})], - }); - unsubscribe(); - }); - }); - - it("handles changing a doc so it doesn't match", () => { - const ref1 = randomCol.doc('doc1'); - const ref2 = randomCol.doc('doc2'); - - const query = randomCol.where('included', '==', 'yes'); - const unsubscribe = query.onSnapshot( - snapshot => { - currentDeferred.resolve(snapshot); - }, - err => { - currentDeferred.reject(err); - } - ); - - return waitForSnapshot() - .then(results => { - snapshotsEqual(results, {docs: [], docChanges: []}); - // Add a result. - return ref1.set({included: 'yes'}); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [snapshot('doc1', {included: 'yes'})], - docChanges: [added('doc1', {included: 'yes'})], - }); - // Add another result. - return ref2.set({included: 'yes'}); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [ - snapshot('doc1', {included: 'yes'}), - snapshot('doc2', {included: 'yes'}), - ], - docChanges: [added('doc2', {included: 'yes'})], - }); - // Change a result. - return ref2.set({included: 'no'}); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [snapshot('doc1', {included: 'yes'})], - docChanges: [removed('doc2', {included: 'yes'})], - }); - unsubscribe(); - }); - }); - - it('handles deleting a doc', () => { - const ref1 = randomCol.doc('doc1'); - const ref2 = randomCol.doc('doc2'); - - const unsubscribe = randomCol.onSnapshot( - snapshot => { - currentDeferred.resolve(snapshot); - }, - err => { - currentDeferred.reject(err); - } - ); - - return waitForSnapshot() - .then(results => { - snapshotsEqual(results, {docs: [], docChanges: []}); - // Add a result. - return ref1.set({included: 'yes'}); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [snapshot('doc1', {included: 'yes'})], - docChanges: [added('doc1', {included: 'yes'})], - }); - // Add another result. - return ref2.set({included: 'yes'}); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [ - snapshot('doc1', {included: 'yes'}), - snapshot('doc2', {included: 'yes'}), - ], - docChanges: [added('doc2', {included: 'yes'})], - }); - // Delete a result. - return ref2.delete(); - }) - .then(() => { - return waitForSnapshot(); - }) - .then(results => { - snapshotsEqual(results, { - docs: [snapshot('doc1', {included: 'yes'})], - docChanges: [removed('doc2', {included: 'yes'})], - }); - unsubscribe(); - }); - }); - - it('orders limitToLast() correctly', async () => { - const ref1 = randomCol.doc('doc1'); - const ref2 = randomCol.doc('doc2'); - const ref3 = randomCol.doc('doc3'); - - await ref1.set({doc: 1}); - await ref2.set({doc: 2}); - await ref3.set({doc: 3}); - - const unsubscribe = randomCol - .orderBy('doc') - .limitToLast(2) - .onSnapshot(snapshot => currentDeferred.resolve(snapshot)); - - const results = await waitForSnapshot(); - snapshotsEqual(results, { - docs: [snapshot('doc2', {doc: 2}), snapshot('doc3', {doc: 3})], - docChanges: [added('doc2', {doc: 2}), added('doc3', {doc: 3})], - }); - - unsubscribe(); - }); - - it('SDK orders vector field same way as backend', async () => { - // We validate that the SDK orders the vector field the same way as the backend - // by comparing the sort order of vector fields from a Query.get() and - // Query.onSnapshot(). Query.onSnapshot() will return sort order of the SDK, - // and Query.get() will return sort order of the backend. - - // Test data in the order that we expect the backend to sort it. - const docsInOrder = [ - {embedding: [1, 2, 3, 4, 5, 6]}, - {embedding: [100]}, - {embedding: FieldValue.vector([Number.NEGATIVE_INFINITY])}, - {embedding: FieldValue.vector([-100])}, - {embedding: FieldValue.vector([100])}, - {embedding: FieldValue.vector([Number.POSITIVE_INFINITY])}, - {embedding: FieldValue.vector([1, 2])}, - {embedding: FieldValue.vector([2, 2])}, - {embedding: FieldValue.vector([1, 2, 3])}, - {embedding: FieldValue.vector([1, 2, 3, 4])}, - {embedding: FieldValue.vector([1, 2, 3, 4, 5])}, - {embedding: FieldValue.vector([1, 2, 100, 4, 4])}, - {embedding: FieldValue.vector([100, 2, 3, 4, 5])}, - {embedding: {HELLO: 'WORLD'}}, - {embedding: {hello: 'world'}}, - ]; - - const expectedSnapshots = []; - const expectedChanges = []; - - for (let i = 0; i < docsInOrder.length; i++) { - const dr = await randomCol.add(docsInOrder[i]); - expectedSnapshots.push(snapshot(dr.id, docsInOrder[i])); - expectedChanges.push(added(dr.id, docsInOrder[i])); - } - - const orderedQuery = randomCol.orderBy('embedding'); - - const unsubscribe = orderedQuery.onSnapshot( - snapshot => { - currentDeferred.resolve(snapshot); - }, - err => { - currentDeferred.reject!(err); - } - ); - - const watchSnapshot = await waitForSnapshot(); - unsubscribe(); - - const getSnapshot = await orderedQuery.get(); - - // Compare the snapshot (including sort order) of a snapshot - // from Query.onSnapshot() to an actual snapshot from Query.get() - snapshotsEqual(watchSnapshot, { - docs: getSnapshot.docs, - docChanges: getSnapshot.docChanges(), - }); - - // Compare the snapshot (including sort order) of a snapshot - // from Query.onSnapshot() to the expected sort order from - // the backend. - snapshotsEqual(watchSnapshot, { - docs: expectedSnapshots, - docChanges: expectedChanges, - }); - }); - }); - - (process.env.FIRESTORE_EMULATOR_HOST === undefined - ? describe.skip - : describe)('multiple inequality', () => { - it('supports multiple inequality queries', async () => { - const collection = await testCollectionWithDocs({ - doc1: {key: 'a', sort: 0, v: 0}, - doc2: {key: 'b', sort: 3, v: 1}, - doc3: {key: 'c', sort: 1, v: 3}, - doc4: {key: 'd', sort: 2, v: 2}, - }); - - // Multiple inequality fields - let results = await collection - .where('key', '!=', 'a') - .where('sort', '<=', 2) - .where('v', '>', 2) - .get(); - expectDocs(results, 'doc3'); - - // Duplicate inequality fields - results = await collection - .where('key', '!=', 'a') - .where('sort', '<=', 2) - .where('sort', '>', 1) - .get(); - expectDocs(results, 'doc4'); - - // With multiple IN - results = await collection - .where('key', '>=', 'a') - .where('sort', '<=', 2) - .where('v', 'in', [2, 3, 4]) - .where('sort', 'in', [2, 3]) - .get(); - expectDocs(results, 'doc4'); - - // With NOT-IN - results = await collection - .where('key', '>=', 'a') - .where('sort', '<=', 2) - .where('v', 'not-in', [2, 4, 5]) - .get(); - expectDocs(results, 'doc1', 'doc3'); - - // With orderby - results = await collection - .where('key', '>=', 'a') - .where('sort', '<=', 2) - .orderBy('v', 'desc') - .get(); - expectDocs(results, 'doc3', 'doc4', 'doc1'); - - // With limit - results = await collection - .where('key', '>=', 'a') - .where('sort', '<=', 2) - .orderBy('v', 'desc') - .limit(2) - .get(); - expectDocs(results, 'doc3', 'doc4'); - - // With limitToLast - results = await collection - .where('key', '>=', 'a') - .where('sort', '<=', 2) - .orderBy('v', 'desc') - .limitToLast(2) - .get(); - expectDocs(results, 'doc4', 'doc1'); - }); - - it('can use on special values', async () => { - const collection = await testCollectionWithDocs({ - doc1: {key: 'a', sort: 0, v: 0}, - doc2: {key: 'b', sort: NaN, v: 1}, - doc3: {key: 'c', sort: null, v: 3}, - doc4: {key: 'd', v: 0}, - doc5: {key: 'e', sort: 1}, - doc6: {key: 'f', sort: 1, v: 1}, - }); - - let results = await collection - .where('key', '!=', 'a') - .where('sort', '<=', 2) - .get(); - expectDocs(results, 'doc5', 'doc6'); - - results = await collection - .where('key', '!=', 'a') - .where('sort', '<=', 2) - .where('v', '<=', 1) - .get(); - expectDocs(results, 'doc6'); - }); - - it('can use with array membership', async () => { - const collection = await testCollectionWithDocs({ - doc1: {key: 'a', sort: 0, v: [0]}, - doc2: {key: 'b', sort: 1, v: [0, 1, 3]}, - doc3: {key: 'c', sort: 1, v: []}, - doc4: {key: 'd', sort: 2, v: [1]}, - doc5: {key: 'e', sort: 3, v: [2, 4]}, - doc6: {key: 'f', sort: 4, v: [NaN]}, - doc7: {key: 'g', sort: 4, v: [null]}, - }); - - let results = await collection - .where('key', '!=', 'a') - .where('sort', '>=', 1) - .where('v', 'array-contains', 0) - .get(); - expectDocs(results, 'doc2'); - - results = await collection - .where('key', '!=', 'a') - .where('sort', '>=', 1) - .where('v', 'array-contains-any', [0, 1]) - .get(); - expectDocs(results, 'doc2', 'doc4'); - }); - - // Use cursor in following test cases to add implicit order by fields in the sdk and compare the - // result with the query fields normalized in the server. - it('can use with nested field', async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testData = (n?: number): any => { - n = n || 1; - return { - name: 'room ' + n, - metadata: { - createdAt: n, - }, - field: 'field ' + n, - 'field.dot': n, - 'field\\slash': n, - }; - }; - - const collection = await testCollectionWithDocs({ - doc1: testData(400), - doc2: testData(200), - doc3: testData(100), - doc4: testData(300), - }); - - // ordered by: name asc, metadata.createdAt asc, __name__ asc - let query = collection - .where('metadata.createdAt', '<=', 500) - .where('metadata.createdAt', '>', 100) - .where('name', '!=', 'room 200') - .orderBy('name'); - let docSnap = await collection.doc('doc4').get(); - let queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc4', 'doc1'); - expectDocs(await queryWithCursor.get(), 'doc4', 'doc1'); - - // ordered by: name desc, field desc, field.dot desc, field\\slash desc, __name__ desc - query = collection - .where('field', '>=', 'field 100') - .where(new FieldPath('field.dot'), '!=', 300) - .where('field\\slash', '<', 400) - .orderBy('name', 'desc'); - docSnap = await collection.doc('doc2').get(); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc2', 'doc3'); - expectDocs(await queryWithCursor.get(), 'doc2', 'doc3'); - }); - - it('can use with nested composite filters', async () => { - const collection = await testCollectionWithDocs({ - doc1: {key: 'a', sort: 0, v: 5}, - doc2: {key: 'aa', sort: 4, v: 4}, - doc3: {key: 'c', sort: 3, v: 3}, - doc4: {key: 'b', sort: 2, v: 2}, - doc5: {key: 'b', sort: 2, v: 1}, - doc6: {key: 'b', sort: 0, v: 0}, - }); - - // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc - let query = collection.where( - Filter.or( - Filter.and( - Filter.where('key', '==', 'b'), - Filter.where('sort', '<=', 2) - ), - Filter.and(Filter.where('key', '!=', 'b'), Filter.where('v', '>', 4)) - ) - ); - let docSnap = await collection.doc('doc1').get(); - let queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc1', 'doc6', 'doc5', 'doc4'); - expectDocs(await queryWithCursor.get(), 'doc1', 'doc6', 'doc5', 'doc4'); - - // Ordered by: 'sort' desc, 'key' asc, 'v' asc, __name__ asc - query = collection - .where( - Filter.or( - Filter.and( - Filter.where('key', '==', 'b'), - Filter.where('sort', '<=', 2) - ), - Filter.and( - Filter.where('key', '!=', 'b'), - Filter.where('v', '>', 4) - ) - ) - ) - .orderBy('sort', 'desc') - .orderBy('key'); - docSnap = await collection.doc('doc5').get(); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc5', 'doc4', 'doc1', 'doc6'); - expectDocs(await queryWithCursor.get(), 'doc5', 'doc4', 'doc1', 'doc6'); - - // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc - query = collection.where( - Filter.and( - Filter.or( - Filter.and( - Filter.where('key', '==', 'b'), - Filter.where('sort', '<=', 4) - ), - Filter.and( - Filter.where('key', '!=', 'b'), - Filter.where('v', '>=', 4) - ) - ), - Filter.or( - Filter.and( - Filter.where('key', '>', 'b'), - Filter.where('sort', '>=', 1) - ), - Filter.and(Filter.where('key', '<', 'b'), Filter.where('v', '>', 0)) - ) - ) - ); - docSnap = await collection.doc('doc1').get(); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc1', 'doc2'); - expectDocs(await queryWithCursor.get(), 'doc1', 'doc2'); - }); - - it('inequality fields will be implicitly ordered lexicographically by the server', async () => { - const collection = await testCollectionWithDocs({ - doc1: {key: 'a', sort: 0, v: 5}, - doc2: {key: 'aa', sort: 4, v: 4}, - doc3: {key: 'b', sort: 3, v: 3}, - doc4: {key: 'b', sort: 2, v: 2}, - doc5: {key: 'b', sort: 2, v: 1}, - doc6: {key: 'b', sort: 0, v: 0}, - }); - - const docSnap = await collection.doc('doc2').get(); - - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - let query = collection - .where('key', '!=', 'a') - .where('sort', '>', 1) - .where('v', 'in', [1, 2, 3, 4]); - let queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc2', 'doc4', 'doc5', 'doc3'); - expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc5', 'doc3'); - - // Changing filters order will not effect implicit order. - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - query = collection - .where('sort', '>', 1) - .where('key', '!=', 'a') - .where('v', 'in', [1, 2, 3, 4]); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc2', 'doc4', 'doc5', 'doc3'); - expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc5', 'doc3'); - }); - - it('can use multiple explicit order by field', async () => { - const collection = await testCollectionWithDocs({ - doc1: {key: 'a', sort: 5, v: 0}, - doc2: {key: 'aa', sort: 4, v: 0}, - doc3: {key: 'b', sort: 3, v: 1}, - doc4: {key: 'b', sort: 2, v: 1}, - doc5: {key: 'bb', sort: 1, v: 1}, - doc6: {key: 'c', sort: 0, v: 2}, - }); - - let docSnap = await collection.doc('doc2').get(); - - // Ordered by: 'v' asc, 'key' asc, 'sort' asc, __name__ asc - let query = collection - .where('key', '>', 'a') - .where('sort', '>=', 1) - .orderBy('v'); - let queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc2', 'doc4', 'doc3', 'doc5'); - expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3', 'doc5'); - - // Ordered by: 'v asc, 'sort' asc, 'key' asc, __name__ asc - query = collection - .where('key', '>', 'a') - .where('sort', '>=', 1) - .orderBy('v') - .orderBy('sort'); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc2', 'doc5', 'doc4', 'doc3'); - expectDocs(await queryWithCursor.get(), 'doc2', 'doc5', 'doc4', 'doc3'); - - docSnap = await collection.doc('doc5').get(); - - // Implicit order by matches the direction of last explicit order by. - // Ordered by: 'v' desc, 'key' desc, 'sort' desc, __name__ desc - query = collection - .where('key', '>', 'a') - .where('sort', '>=', 1) - .orderBy('v', 'desc'); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc5', 'doc3', 'doc4', 'doc2'); - expectDocs(await queryWithCursor.get(), 'doc5', 'doc3', 'doc4', 'doc2'); - - // Ordered by: 'v desc, 'sort' asc, 'key' asc, __name__ asc - query = collection - .where('key', '>', 'a') - .where('sort', '>=', 1) - .orderBy('v', 'desc') - .orderBy('sort'); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc5', 'doc4', 'doc3', 'doc2'); - expectDocs(await queryWithCursor.get(), 'doc5', 'doc4', 'doc3', 'doc2'); - }); - - it('can use in aggregate query', async () => { - const collection = await testCollectionWithDocs({ - doc1: {key: 'a', sort: 5, v: 0}, - doc2: {key: 'aa', sort: 4, v: 0}, - doc3: {key: 'b', sort: 3, v: 1}, - doc4: {key: 'b', sort: 2, v: 1}, - doc5: {key: 'bb', sort: 1, v: 1}, - }); - - const results = await collection - .where('key', '>', 'a') - .where('sort', '>=', 1) - .orderBy('v') - .count() - .get(); - expect(results.data().count).to.be.equal(4); - //TODO(MIEQ): Add sum and average when they are public. - }); - - it('can use document ID im multiple inequality query', async () => { - const collection = await testCollectionWithDocs({ - doc1: {key: 'a', sort: 5}, - doc2: {key: 'aa', sort: 4}, - doc3: {key: 'b', sort: 3}, - doc4: {key: 'b', sort: 2}, - doc5: {key: 'bb', sort: 1}, - }); - - const docSnap = await collection.doc('doc2').get(); - - // Document Key in inequality field will implicitly ordered to the last. - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - let query = collection - .where('sort', '>=', 1) - .where('key', '!=', 'a') - .where(FieldPath.documentId(), '<', 'doc5'); - let queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc2', 'doc4', 'doc3'); - expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3'); - - // Changing filters order will not effect implicit order. - // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc - query = collection - .where(FieldPath.documentId(), '<', 'doc5') - .where('sort', '>=', 1) - .where('key', '!=', 'a'); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc2', 'doc4', 'doc3'); - expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3'); - - // Ordered by: 'sort' desc,'key' desc, __name__ desc - query = collection - .where(FieldPath.documentId(), '<', 'doc5') - .where('sort', '>=', 1) - .where('key', '!=', 'a') - .orderBy('sort', 'desc'); - queryWithCursor = query.startAt(docSnap); - expectDocs(await query.get(), 'doc2', 'doc3', 'doc4'); - expectDocs(await queryWithCursor.get(), 'doc2', 'doc3', 'doc4'); - }); - }); -}); - describe('count queries', () => { let firestore: Firestore; let randomCol: CollectionReference; diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts new file mode 100644 index 000000000..8dcfe7006 --- /dev/null +++ b/dev/system-test/pipeline.ts @@ -0,0 +1,99 @@ +import { + AggregateQuery, + DocumentData, + QuerySnapshot, + VectorValue, +} from '@google-cloud/firestore'; + +import {expect} from 'chai'; +import {afterEach, beforeEach, describe, it} from 'mocha'; +import { + CollectionReference, + DocumentReference, + DocumentSnapshot, + FieldPath, + FieldValue, + Filter, + Firestore, + Query, + QueryDocumentSnapshot, +} from '../src'; +import {verifyInstance} from '../test/util/helpers'; +import {DeferredPromise, getTestRoot} from './firestore'; +import {IndexTestHelper} from './index_test_helper'; + +describe('Pipeline class', () => { + let firestore: Firestore; + let randomCol: CollectionReference; + + async function addDocs( + ...docs: DocumentData[] + ): Promise { + let id = 0; // Guarantees consistent ordering for the first documents + const refs: DocumentReference[] = []; + for (const doc of docs) { + const ref = randomCol.doc('doc' + id++); + await ref.set(doc); + refs.push(ref); + } + return refs; + } + + async function testCollectionWithDocs(docs: { + [id: string]: DocumentData; + }): Promise> { + for (const id in docs) { + const ref = randomCol.doc(id); + await ref.set(docs[id]); + } + return randomCol; + } + + function expectDocs(result: QuerySnapshot, ...docs: string[]): void; + function expectDocs(result: QuerySnapshot, ...data: DocumentData[]): void; + + function expectDocs( + result: QuerySnapshot, + ...data: DocumentData[] | string[] + ): void { + expect(result.size).to.equal(data.length); + + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = result.docs.map(docSnapshot => docSnapshot.id); + expect(actualIds).to.deep.equal(data); + } else { + result.forEach(doc => { + expect(doc.data()).to.deep.equal(data.shift()); + }); + } + } + } + + async function compareQueryAndPipeline(query: Query): Promise { + const queryResults = await query.get(); + const pipeline = query.toPipeline(); + const pipelineResults = await pipeline.execute(); + + expect(queryResults.docs.map(s => s._fieldsProto)).to.deep.equal( + pipelineResults.map(r => r._fieldsProto) + ); + return queryResults; + } + + beforeEach(() => { + randomCol = getTestRoot(); + firestore = randomCol.firestore; + }); + + afterEach(() => verifyInstance(firestore)); + + it('basic collection', async () => { + const result = await firestore + .pipeline() + .collection(randomCol.path) + .limit(0) + .execute(); + expect(result).to.be.empty; + }); +}); diff --git a/dev/system-test/query.ts b/dev/system-test/query.ts new file mode 100644 index 000000000..efc42be03 --- /dev/null +++ b/dev/system-test/query.ts @@ -0,0 +1,2234 @@ +import { + DocumentData, + QuerySnapshot, + VectorValue, +} from '@google-cloud/firestore'; + +import {expect} from 'chai'; +import {afterEach, beforeEach, describe, it} from 'mocha'; +import { + CollectionReference, + DocumentReference, + DocumentSnapshot, + FieldPath, + FieldValue, + Filter, + Firestore, + Query, + QueryDocumentSnapshot, + VectorQuery, + VectorQuerySnapshot, +} from '../src'; +import {verifyInstance} from '../test/util/helpers'; +import {DeferredPromise, getTestRoot} from './firestore'; +import {IndexTestHelper} from './index_test_helper'; + +describe.only('Query class', () => { + interface PaginatedResults { + pages: number; + docs: QueryDocumentSnapshot[]; + } + + let firestore: Firestore; + let randomCol: CollectionReference; + + const paginateResults = ( + query: Query, + startAfter?: unknown + ): Promise => { + return (startAfter ? query.startAfter(startAfter) : query) + .get() + .then(snapshot => { + if (snapshot.empty) { + return {pages: 0, docs: []}; + } else { + const docs = snapshot.docs; + return paginateResults(query, docs[docs.length - 1]).then( + nextPage => { + return { + pages: nextPage.pages + 1, + docs: docs.concat(nextPage.docs), + }; + } + ); + } + }); + }; + + async function addDocs( + ...docs: DocumentData[] + ): Promise { + let id = 0; // Guarantees consistent ordering for the first documents + const refs: DocumentReference[] = []; + for (const doc of docs) { + const ref = randomCol.doc('doc' + id++); + await ref.set(doc); + refs.push(ref); + } + return refs; + } + + async function testCollectionWithDocs(docs: { + [id: string]: DocumentData; + }): Promise> { + for (const id in docs) { + const ref = randomCol.doc(id); + await ref.set(docs[id]); + } + return randomCol; + } + + function expectDocs(result: QuerySnapshot, ...docs: string[]): void; + function expectDocs(result: QuerySnapshot, ...data: DocumentData[]): void; + + function expectDocs( + result: QuerySnapshot, + ...data: DocumentData[] | string[] + ): void { + expect(result.size).to.equal(data.length); + + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = result.docs.map(docSnapshot => docSnapshot.id); + expect(actualIds).to.deep.equal(data); + } else { + result.forEach(doc => { + expect(doc.data()).to.deep.equal(data.shift()); + }); + } + } + } + + async function compareQueryAndPipeline(query: Query): Promise { + const queryResults = await query.get(); + const pipeline = query.toPipeline(); + const pipelineResults = await pipeline.execute(); + + expect(pipelineResults.map(r => r._fieldsProto)).to.deep.equal( + queryResults.docs.map(s => s._fieldsProto) + ); + return queryResults; + } + + async function compareVectorQueryAndPipeline( + query: VectorQuery + ): Promise { + const queryResults = await query.get(); + const pipeline = query.toPipeline(); + const pipelineResults = await pipeline.execute(); + + expect(pipelineResults.map(r => r._fieldsProto)).to.deep.equal( + queryResults.docs.map(s => s._fieldsProto) + ); + return queryResults; + } + + beforeEach(() => { + randomCol = getTestRoot(); + firestore = randomCol.firestore; + }); + + afterEach(() => verifyInstance(firestore)); + + it('has firestore property', () => { + const ref = randomCol.limit(0); + expect(ref.firestore).to.be.an.instanceOf(Firestore); + }); + + it('has select() method', () => { + const ref = randomCol.doc('doc'); + return ref + .set({foo: 'bar', bar: 'foo'}) + .then(() => { + return randomCol.select('foo').get(); + }) + .then(res => { + expect(res.docs[0].data()).to.deep.equal({foo: 'bar'}); + }); + }); + + it('select() supports empty fields', () => { + const ref = randomCol.doc('doc'); + return ref + .set({foo: 'bar', bar: 'foo'}) + .then(() => { + return randomCol.select().get(); + }) + .then(res => { + expect(res.docs[0].ref.id).to.deep.equal('doc'); + expect(res.docs[0].data()).to.deep.equal({}); + }); + }); + + it('has where() method', () => { + const ref = randomCol.doc('doc'); + return ref + .set({foo: 'bar'}) + .then(() => { + return compareQueryAndPipeline(randomCol.where('foo', '==', 'bar')); + }) + .then(res => { + expect(res.docs[0].data()).to.deep.equal({foo: 'bar'}); + }); + }); + + it('supports NaN and Null', () => { + const ref = randomCol.doc('doc'); + return ref + .set({foo: NaN, bar: null}) + .then(() => { + return compareQueryAndPipeline( + randomCol.where('foo', '==', NaN).where('bar', '==', null) + ); + }) + .then(res => { + expect( + typeof res.docs[0].get('foo') === 'number' && + isNaN(res.docs[0].get('foo')) + ); + expect(res.docs[0].get('bar')).to.equal(null); + }); + }); + + it('supports array-contains', () => { + return Promise.all([ + randomCol.add({foo: ['bar']}), + randomCol.add({foo: []}), + ]) + .then(() => + compareQueryAndPipeline(randomCol.where('foo', 'array-contains', 'bar')) + ) + .then(res => { + expect(res.size).to.equal(1); + expect(res.docs[0].get('foo')).to.deep.equal(['bar']); + }); + }); + + it.skip('supports findNearest by EUCLIDEAN distance', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, + {foo: 'bar', embedding: FieldValue.vector([1, 1])}, + {foo: 'bar', embedding: FieldValue.vector([10, 0])}, + {foo: 'bar', embedding: FieldValue.vector([20, 0])}, + {foo: 'bar', embedding: FieldValue.vector([100, 100])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(3); + expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([10, 0]))).to + .be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([1, 1]))).to + .be.true; + expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0]))).to + .be.true; + }); + + it.skip('supports findNearest by COSINE distance', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.setTestDocs({ + '1': {foo: 'bar'}, + '2': {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, + '3': {foo: 'bar', embedding: FieldValue.vector([1, 1])}, + '4': {foo: 'bar', embedding: FieldValue.vector([20, 0])}, + '5': {foo: 'bar', embedding: FieldValue.vector([10, 0])}, + '6': {foo: 'bar', embedding: FieldValue.vector([100, 100])}, + }); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'COSINE', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + + expect(res.size).to.equal(3); + + if (res.docs[0].get('embedding').isEqual(FieldValue.vector([1, 1]))) { + expect( + res.docs[1].get('embedding').isEqual(FieldValue.vector([100, 100])) + ).to.be.true; + } else { + expect( + res.docs[0].get('embedding').isEqual(FieldValue.vector([100, 100])) + ).to.be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([1, 1]))).to + .be.true; + } + + expect( + res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0])) || + res.docs[2].get('embedding').isEqual(FieldValue.vector([20, 0])) + ).to.be.true; + }); + + it.skip('supports findNearest by DOT_PRODUCT distance', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + {foo: 'xxx', embedding: FieldValue.vector([10, 10])}, + {foo: 'bar', embedding: FieldValue.vector([1, 1])}, + {foo: 'bar', embedding: FieldValue.vector([10, 0])}, + {foo: 'bar', embedding: FieldValue.vector([20, 0])}, + {foo: 'bar', embedding: FieldValue.vector([100, 100])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'DOT_PRODUCT', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(3); + expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([100, 100]))) + .to.be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([20, 0]))).to + .be.true; + expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([10, 0]))).to + .be.true; + }); + + it.skip('findNearest works with converters', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + class FooDistance { + constructor( + readonly foo: string, + readonly embedding: Array + ) {} + } + + const fooConverter = { + toFirestore(d: FooDistance): DocumentData { + return {title: d.foo, embedding: FieldValue.vector(d.embedding)}; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): FooDistance { + const data = snapshot.data(); + return new FooDistance(data.foo, data.embedding.toArray()); + }, + }; + + const collectionRef = await indexTestHelper.createTestDocs([ + {foo: 'bar', embedding: FieldValue.vector([5, 5])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionRef) + .withConverter(fooConverter) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + + expect(res.size).to.equal(1); + expect(res.docs[0].data().foo).to.equal('bar'); + expect(res.docs[0].data().embedding).to.deep.equal([5, 5]); + }); + + it.skip('supports findNearest skipping fields of wrong types', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionRef = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + + // These documents are skipped because it is not really a vector value + {foo: 'bar', embedding: [10, 10]}, + {foo: 'bar', embedding: 'not actually a vector'}, + {foo: 'bar', embedding: null}, + + // Actual vector values + {foo: 'bar', embedding: FieldValue.vector([9, 9])}, + {foo: 'bar', embedding: FieldValue.vector([50, 50])}, + {foo: 'bar', embedding: FieldValue.vector([100, 100])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionRef) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 100, // Intentionally large to get all matches. + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(3); + expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([9, 9]))).to + .be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([50, 50]))).to + .be.true; + expect(res.docs[2].get('embedding').isEqual(FieldValue.vector([100, 100]))) + .to.be.true; + }); + + it.skip('findNearest ignores mismatching dimensions', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionRef = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + + // Vectors with dimension mismatch + {foo: 'bar', embedding: FieldValue.vector([10])}, + + // Vectors with dimension match + {foo: 'bar', embedding: FieldValue.vector([9, 9])}, + {foo: 'bar', embedding: FieldValue.vector([50, 50])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionRef) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(2); + expect(res.docs[0].get('embedding').isEqual(FieldValue.vector([9, 9]))).to + .be.true; + expect(res.docs[1].get('embedding').isEqual(FieldValue.vector([50, 50]))).to + .be.true; + }); + + it.skip('supports findNearest on non-existent field', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionRef = await indexTestHelper.createTestDocs([ + {foo: 'bar'}, + {foo: 'bar', otherField: [10, 10]}, + {foo: 'bar', otherField: 'not actually a vector'}, + {foo: 'bar', otherField: null}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionRef) + .where('foo', '==', 'bar') + .findNearest('embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + + expect(res.size).to.equal(0); + }); + + it.skip('supports findNearest on vector nested in a map', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.createTestDocs([ + {nested: {foo: 'bar'}}, + {nested: {foo: 'xxx', embedding: FieldValue.vector([10, 10])}}, + {nested: {foo: 'bar', embedding: FieldValue.vector([1, 1])}}, + {nested: {foo: 'bar', embedding: FieldValue.vector([10, 0])}}, + {nested: {foo: 'bar', embedding: FieldValue.vector([20, 0])}}, + {nested: {foo: 'bar', embedding: FieldValue.vector([100, 100])}}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .findNearest('nested.embedding', [10, 10], { + limit: 3, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(3); + expect( + res.docs[0].get('nested.embedding').isEqual(FieldValue.vector([10, 10])) + ).to.be.true; + expect( + res.docs[1].get('nested.embedding').isEqual(FieldValue.vector([10, 0])) + ).to.be.true; + expect( + res.docs[2].get('nested.embedding').isEqual(FieldValue.vector([1, 1])) + ).to.be.true; + }); + + it('supports findNearest with select to exclude vector data in response', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const collectionReference = await indexTestHelper.createTestDocs([ + {foo: 1}, + {foo: 2, embedding: FieldValue.vector([10, 10])}, + {foo: 3, embedding: FieldValue.vector([1, 1])}, + {foo: 4, embedding: FieldValue.vector([10, 0])}, + {foo: 5, embedding: FieldValue.vector([20, 0])}, + {foo: 6, embedding: FieldValue.vector([100, 100])}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .where('foo', 'in', [1, 2, 3, 4, 5, 6]) + .select('foo') + .findNearest('embedding', [10, 10], { + limit: 10, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await vectorQuery.get(); + expect(res.size).to.equal(5); + expect(res.docs[0].get('foo')).to.equal(2); + expect(res.docs[1].get('foo')).to.equal(4); + expect(res.docs[2].get('foo')).to.equal(3); + expect(res.docs[3].get('foo')).to.equal(5); + expect(res.docs[4].get('foo')).to.equal(6); + + res.docs.forEach(ds => expect(ds.get('embedding')).to.be.undefined); + }); + + it.skip('supports findNearest limits', async () => { + const indexTestHelper = new IndexTestHelper(firestore); + + const embeddingVector = []; + const queryVector = []; + for (let i = 0; i < 2048; i++) { + embeddingVector.push(i + 1); + queryVector.push(i - 1); + } + + const collectionReference = await indexTestHelper.createTestDocs([ + {embedding: FieldValue.vector(embeddingVector)}, + ]); + + const vectorQuery = indexTestHelper + .query(collectionReference) + .findNearest('embedding', queryVector, { + limit: 1000, + distanceMeasure: 'EUCLIDEAN', + }); + + const res = await compareVectorQueryAndPipeline(vectorQuery); + expect(res.size).to.equal(1); + expect( + (res.docs[0].get('embedding') as VectorValue).toArray() + ).to.deep.equal(embeddingVector); + }); + + it('supports !=', async () => { + await addDocs( + {zip: NaN}, + {zip: 91102}, + {zip: 98101}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}}, + {zip: null} + ); + + let res = await compareQueryAndPipeline( + randomCol.where('zip', '!=', 98101) + ); + expectDocs( + res, + {zip: NaN}, + {zip: 91102}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}} + ); + + res = await compareQueryAndPipeline(randomCol.where('zip', '!=', NaN)); + expectDocs( + res, + {zip: 91102}, + {zip: 98101}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}} + ); + + res = await compareQueryAndPipeline(randomCol.where('zip', '!=', null)); + expectDocs( + res, + {zip: NaN}, + {zip: 91102}, + {zip: 98101}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}} + ); + }); + + it('supports != with document ID', async () => { + const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); + const res = await compareQueryAndPipeline( + randomCol.where(FieldPath.documentId(), '!=', refs[0].id) + ); + expectDocs(res, {count: 2}, {count: 3}); + }); + + it.skip('supports not-in', async () => { + await addDocs( + {zip: 98101}, + {zip: 91102}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}} + ); + let res = await compareQueryAndPipeline( + randomCol.where('zip', 'not-in', [98101, 98103]) + ); + expectDocs( + res, + {zip: 91102}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}} + ); + + res = await compareQueryAndPipeline( + randomCol.where('zip', 'not-in', [NaN]) + ); + expectDocs( + res, + {zip: 91102}, + {zip: 98101}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}} + ); + + res = await compareQueryAndPipeline( + randomCol.where('zip', 'not-in', [null]) + ); + expect(res.size).to.equal(0); + }); + + it('supports not-in with document ID array', async () => { + const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); + const res = await compareQueryAndPipeline( + randomCol.where(FieldPath.documentId(), 'not-in', [refs[0].id, refs[1]]) + ); + expectDocs(res, {count: 3}); + }); + + it('supports "in"', async () => { + await addDocs( + {zip: 98101}, + {zip: 91102}, + {zip: 98103}, + {zip: [98101]}, + {zip: ['98101', {zip: 98101}]}, + {zip: {zip: 98101}} + ); + const res = await compareQueryAndPipeline( + randomCol.where('zip', 'in', [98101, 98103]) + ); + expectDocs(res, {zip: 98101}, {zip: 98103}); + }); + + it.only('supports "in" with document ID array', async () => { + const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); + const res = await compareQueryAndPipeline( + randomCol.where(FieldPath.documentId(), 'in', [refs[0].id, refs[1]]) + ); + expectDocs(res, {count: 1}, {count: 2}); + }); + + it('supports array-contains-any', async () => { + await addDocs( + {array: [42]}, + {array: ['a', 42, 'c']}, + {array: [41.999, '42', {a: [42]}]}, + {array: [42], array2: ['sigh']}, + {array: [43]}, + {array: [{a: 42}]}, + {array: 42} + ); + + const res = await compareQueryAndPipeline( + randomCol.where('array', 'array-contains-any', [42, 43]) + ); + + expectDocs( + res, + {array: [42]}, + {array: ['a', 42, 'c']}, + { + array: [42], + array2: ['sigh'], + }, + {array: [43]} + ); + }); + + it.skip('can query by FieldPath.documentId()', () => { + const ref = randomCol.doc('foo'); + + return ref + .set({}) + .then(() => { + return compareQueryAndPipeline( + randomCol.where(FieldPath.documentId(), '>=', 'bar') + ); + }) + .then(res => { + expect(res.docs.length).to.equal(1); + }); + }); + + it.skip('has orderBy() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + + let res = await compareQueryAndPipeline(randomCol.orderBy('foo')); + expectDocs(res, {foo: 'a'}, {foo: 'b'}); + + res = await compareQueryAndPipeline(randomCol.orderBy('foo', 'desc')); + expectDocs(res, {foo: 'b'}, {foo: 'a'}); + }); + + it('can order by FieldPath.documentId()', () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + return Promise.all([ref1.set({foo: 'a'}), ref2.set({foo: 'b'})]) + .then(() => { + return compareQueryAndPipeline( + randomCol.orderBy(FieldPath.documentId()) + ); + }) + .then(res => { + expect(res.docs[0].data()).to.deep.equal({foo: 'a'}); + expect(res.docs[1].data()).to.deep.equal({foo: 'b'}); + }); + }); + + it('can run get() on empty collection', async () => { + return compareQueryAndPipeline(randomCol).then(res => { + return expect(res.empty); + }); + }); + + it('can run stream() on empty collection', async () => { + let received = 0; + const stream = randomCol.stream(); + + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(0); + }); + + it.skip('has limit() method on get()', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('foo').limit(1) + ); + expectDocs(res, {foo: 'a'}); + }); + + it('has limit() method on stream()', async () => { + let received = 0; + await addDocs({foo: 'a'}, {foo: 'b'}); + + const stream = randomCol.orderBy('foo').limit(1).stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(1); + }); + + it.skip('can run limit(num), where num is larger than the collection size on get()', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('foo').limit(3) + ); + expectDocs(res, {foo: 'a'}, {foo: 'b'}); + }); + + it('can run limit(num), where num is larger than the collection size on stream()', async () => { + let received = 0; + await addDocs({foo: 'a'}, {foo: 'b'}); + + const stream = randomCol.orderBy('foo').limit(3).stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(2); + }); + + it.skip('has limitToLast() method', async () => { + await addDocs({doc: 1}, {doc: 2}, {doc: 3}); + // const res = await compareQueryAndPipeline(randomCol.orderBy('doc').limitToLast(2)); + const res = await randomCol.orderBy('doc').limitToLast(2).get(); + expectDocs(res, {doc: 2}, {doc: 3}); + }); + + it('limitToLast() supports Query cursors', async () => { + await addDocs({doc: 1}, {doc: 2}, {doc: 3}, {doc: 4}, {doc: 5}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('doc').startAt(2).endAt(4).limitToLast(5) + ); + expectDocs(res, {doc: 2}, {doc: 3}, {doc: 4}); + }); + + it.skip('can use offset() method with get()', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('foo').offset(1) + ); + expectDocs(res, {foo: 'b'}); + }); + + it('can use offset() method with stream()', async () => { + let received = 0; + await addDocs({foo: 'a'}, {foo: 'b'}); + + const stream = randomCol.orderBy('foo').offset(1).stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(1); + }); + + it.skip('can run offset(num), where num is larger than the collection size on get()', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await compareQueryAndPipeline( + randomCol.orderBy('foo').offset(3) + ); + expect(res.empty); + }); + + it('can run offset(num), where num is larger than the collection size on stream()', async () => { + let received = 0; + await addDocs({foo: 'a'}, {foo: 'b'}); + const stream = randomCol.orderBy('foo').offset(3).stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + expect(received).to.equal(0); + }); + + it('supports Unicode in document names', async () => { + const collRef = randomCol.doc('доброеутро').collection('coll'); + await collRef.add({}); + const snapshot = await compareQueryAndPipeline(collRef); + expect(snapshot.size).to.equal(1); + }); + + it('supports pagination', () => { + const batch = firestore.batch(); + + for (let i = 0; i < 10; ++i) { + batch.set(randomCol.doc('doc' + i), {val: i}); + } + + const query = randomCol.orderBy('val').limit(3); + + return batch + .commit() + .then(() => paginateResults(query)) + .then(results => { + expect(results.pages).to.equal(4); + expect(results.docs).to.have.length(10); + }); + }); + + it('supports pagination with where() clauses', () => { + const batch = firestore.batch(); + + for (let i = 0; i < 10; ++i) { + batch.set(randomCol.doc('doc' + i), {val: i}); + } + + const query = randomCol.where('val', '>=', 1).limit(3); + + return batch + .commit() + .then(() => paginateResults(query)) + .then(results => { + expect(results.pages).to.equal(3); + expect(results.docs).to.have.length(9); + }); + }); + + it('supports pagination with array-contains filter', () => { + const batch = firestore.batch(); + + for (let i = 0; i < 10; ++i) { + batch.set(randomCol.doc('doc' + i), {array: ['foo']}); + } + + const query = randomCol.where('array', 'array-contains', 'foo').limit(3); + + return batch + .commit() + .then(() => paginateResults(query)) + .then(results => { + expect(results.pages).to.equal(4); + expect(results.docs).to.have.length(10); + }); + }); + + it('has startAt() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await randomCol.orderBy('foo').startAt('b').get(); + expectDocs(res, {foo: 'b'}); + }); + + it('startAt() adds implicit order by for DocumentSnapshot', async () => { + const references = await addDocs({foo: 'a'}, {foo: 'b'}); + const docSnap = await references[1].get(); + const res = await randomCol.startAt(docSnap).get(); + expectDocs(res, {foo: 'b'}); + }); + + it('has startAfter() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await randomCol.orderBy('foo').startAfter('a').get(); + expectDocs(res, {foo: 'b'}); + }); + + it('has endAt() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await randomCol.orderBy('foo').endAt('b').get(); + expectDocs(res, {foo: 'a'}, {foo: 'b'}); + }); + + it('has endBefore() method', async () => { + await addDocs({foo: 'a'}, {foo: 'b'}); + const res = await randomCol.orderBy('foo').endBefore('b').get(); + expectDocs(res, {foo: 'a'}); + }); + + it('has stream() method', done => { + let received = 0; + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + Promise.all([ref1.set({foo: 'a'}), ref2.set({foo: 'b'})]).then(() => { + return randomCol + .stream() + .on('data', d => { + expect(d).to.be.an.instanceOf(DocumentSnapshot); + ++received; + }) + .on('end', () => { + expect(received).to.equal(2); + done(); + }); + }); + }); + + it('stream() supports readable[Symbol.asyncIterator]()', async () => { + let received = 0; + await randomCol.doc().set({foo: 'bar'}); + await randomCol.doc().set({foo: 'bar'}); + + const stream = randomCol.stream(); + for await (const doc of stream) { + expect(doc).to.be.an.instanceOf(QueryDocumentSnapshot); + ++received; + } + + expect(received).to.equal(2); + }); + + it.skip('can query collection groups', async () => { + // Use `randomCol` to get a random collection group name to use but ensure + // it starts with 'b' for predictable ordering. + const collectionGroup = 'b' + randomCol.id; + + const docPaths = [ + `abc/123/${collectionGroup}/cg-doc1`, + `abc/123/${collectionGroup}/cg-doc2`, + `${collectionGroup}/cg-doc3`, + `${collectionGroup}/cg-doc4`, + `def/456/${collectionGroup}/cg-doc5`, + `${collectionGroup}/virtual-doc/nested-coll/not-cg-doc`, + `x${collectionGroup}/not-cg-doc`, + `${collectionGroup}x/not-cg-doc`, + `abc/123/${collectionGroup}x/not-cg-doc`, + `abc/123/x${collectionGroup}/not-cg-doc`, + `abc/${collectionGroup}`, + ]; + const batch = firestore.batch(); + for (const docPath of docPaths) { + batch.set(firestore.doc(docPath), {x: 1}); + } + await batch.commit(); + + const querySnapshot = await compareQueryAndPipeline( + firestore.collectionGroup(collectionGroup) + ); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ + 'cg-doc1', + 'cg-doc2', + 'cg-doc3', + 'cg-doc4', + 'cg-doc5', + ]); + }); + + it('can query collection groups with startAt / endAt by arbitrary documentId', async () => { + // Use `randomCol` to get a random collection group name to use but + // ensure it starts with 'b' for predictable ordering. + const collectionGroup = 'b' + randomCol.id; + + const docPaths = [ + `a/a/${collectionGroup}/cg-doc1`, + `a/b/a/b/${collectionGroup}/cg-doc2`, + `a/b/${collectionGroup}/cg-doc3`, + `a/b/c/d/${collectionGroup}/cg-doc4`, + `a/c/${collectionGroup}/cg-doc5`, + `${collectionGroup}/cg-doc6`, + 'a/b/nope/nope', + ]; + const batch = firestore.batch(); + for (const docPath of docPaths) { + batch.set(firestore.doc(docPath), {x: 1}); + } + await batch.commit(); + + let querySnapshot = await firestore + .collectionGroup(collectionGroup) + .orderBy(FieldPath.documentId()) + .startAt('a/b') + .endAt('a/b0') + .get(); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ + 'cg-doc2', + 'cg-doc3', + 'cg-doc4', + ]); + + querySnapshot = await firestore + .collectionGroup(collectionGroup) + .orderBy(FieldPath.documentId()) + .startAfter('a/b') + .endBefore(`a/b/${collectionGroup}/cg-doc3`) + .get(); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal(['cg-doc2']); + }); + + it('can query collection groups with where filters on arbitrary documentId', async () => { + // Use `randomCol` to get a random collection group name to use but + // ensure it starts with 'b' for predictable ordering. + const collectionGroup = 'b' + randomCol.id; + + const docPaths = [ + `a/a/${collectionGroup}/cg-doc1`, + `a/b/a/b/${collectionGroup}/cg-doc2`, + `a/b/${collectionGroup}/cg-doc3`, + `a/b/c/d/${collectionGroup}/cg-doc4`, + `a/c/${collectionGroup}/cg-doc5`, + `${collectionGroup}/cg-doc6`, + 'a/b/nope/nope', + ]; + const batch = firestore.batch(); + for (const docPath of docPaths) { + batch.set(firestore.doc(docPath), {x: 1}); + } + await batch.commit(); + + let querySnapshot = await compareQueryAndPipeline( + firestore + .collectionGroup(collectionGroup) + .where(FieldPath.documentId(), '>=', 'a/b') + .where(FieldPath.documentId(), '<=', 'a/b0') + ); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([ + 'cg-doc2', + 'cg-doc3', + 'cg-doc4', + ]); + + querySnapshot = await compareQueryAndPipeline( + firestore + .collectionGroup(collectionGroup) + .where(FieldPath.documentId(), '>', 'a/b') + .where(FieldPath.documentId(), '<', `a/b/${collectionGroup}/cg-doc3`) + ); + expect(querySnapshot.docs.map(d => d.id)).to.deep.equal(['cg-doc2']); + }); + + it('can query large collections', async () => { + // @grpc/grpc-js v0.4.1 failed to deliver the full set of query results for + // larger collections (https://github.com/grpc/grpc-node/issues/895); + const batch = firestore.batch(); + for (let i = 0; i < 100; ++i) { + batch.create(randomCol.doc(), {}); + } + await batch.commit(); + + const snapshot = await compareQueryAndPipeline(randomCol); + expect(snapshot.size).to.equal(100); + }); + + it('supports OR queries', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {a: 2, b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1, b: 1}, + }); + + // Two equalities: a==1 || b==1. + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)) + ) + ), + 'doc1', + 'doc2', + 'doc4', + 'doc5' + ); + + // (a==1 && b==0) || (a==3 && b==2) + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or( + Filter.and(Filter.where('a', '==', 1), Filter.where('b', '==', 0)), + Filter.and(Filter.where('a', '==', 3), Filter.where('b', '==', 2)) + ) + ) + ), + 'doc1', + 'doc3' + ); + + // a==1 && (b==0 || b==3). + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.and( + Filter.where('a', '==', 1), + Filter.or(Filter.where('b', '==', 0), Filter.where('b', '==', 3)) + ) + ) + ), + 'doc1', + 'doc4' + ); + + // (a==2 || b==2) && (a==3 || b==3) + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.and( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 2)), + Filter.or(Filter.where('a', '==', 3), Filter.where('b', '==', 3)) + ) + ) + ), + 'doc3' + ); + + // Test with limits without orderBy (the __name__ ordering is the tie breaker). + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)) + ) + .limit(1) + ), + 'doc2' + ); + }); + + // Skip this test if running against production because it results in a 'missing index' error. + // The Firestore Emulator, however, does serve these queries. + (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( + 'supports OR queries with composite indexes', + async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {a: 2, b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1, b: 1}, + }); + + // with one inequality: a>2 || b==1. + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or(Filter.where('a', '>', 2), Filter.where('b', '==', 1)) + ) + ), + 'doc5', + 'doc2', + 'doc3' + ); + + // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0)) + ) + .limit(2) + ), + 'doc1', + 'doc2' + ); + + // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 + // Note: The public query API does not allow implicit ordering when limitToLast is used. + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '>', 0)) + ) + .limitToLast(2) + .orderBy('b') + ), + 'doc3', + 'doc4' + ); + + // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)) + ) + .limit(1) + .orderBy('a') + ), + 'doc5' + ); + + // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', '==', 1)) + ) + .limit(1) + .orderBy('a', 'desc') + ), + 'doc2' + ); + } + ); + + it('supports OR queries on documents with missing fields', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // Query: a==1 || b==1 + // There's no explicit nor implicit orderBy. Documents with missing 'a' or missing 'b' should be + // allowed if the document matches at least one disjunction term. + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)) + ) + ), + 'doc1', + 'doc2', + 'doc4', + 'doc5' + ); + }); + + // Skip this test if running against production because it results in a 'missing index' error. + // The Firestore Emulator, however, does serve these queries. + (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( + 'supports OR queries on documents with missing fields', + async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // Query: a==1 || b==1 order by a. + // doc2 should not be included because it's missing the field 'a', and we have "orderBy a". + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)) + ) + .orderBy('a') + ), + 'doc1', + 'doc4', + 'doc5' + ); + + // Query: a==1 || b==1 order by b. + // doc5 should not be included because it's missing the field 'b', and we have "orderBy b". + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '==', 1), Filter.where('b', '==', 1)) + ) + .orderBy('b') + ), + 'doc1', + 'doc2', + 'doc4' + ); + + // Query: a>2 || b==1. + // This query has an implicit 'order by a'. + // doc2 should not be included because it's missing the field 'a'. + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or(Filter.where('a', '>', 2), Filter.where('b', '==', 1)) + ) + ), + 'doc3' + ); + + // Query: a>1 || b==1 order by a order by b. + // doc6 should not be included because it's missing the field 'b'. + // doc2 should not be included because it's missing the field 'a'. + expectDocs( + await compareQueryAndPipeline( + collection + .where( + Filter.or(Filter.where('a', '>', 1), Filter.where('b', '==', 1)) + ) + .orderBy('a') + .orderBy('b') + ), + 'doc3' + ); + } + ); + + it('supports OR queries with in', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // Query: a==2 || b in [2, 3] + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or(Filter.where('a', '==', 2), Filter.where('b', 'in', [2, 3])) + ) + ), + 'doc3', + 'doc4', + 'doc6' + ); + }); + + // Skip this test if running against production because it results in a 'missing index' error. + // The Firestore Emulator, however, does serve these queries. + (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( + 'supports OR queries with not-in', + async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: 0}, + doc2: {b: 1}, + doc3: {a: 3, b: 2}, + doc4: {a: 1, b: 3}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // a==2 || (b != 2 && b != 3) + // Has implicit "orderBy b" + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or( + Filter.where('a', '==', 2), + Filter.where('b', 'not-in', [2, 3]) + ) + ) + ), + 'doc1', + 'doc2' + ); + } + ); + + it('supports OR queries with array membership', async () => { + const collection = await testCollectionWithDocs({ + doc1: {a: 1, b: [0]}, + doc2: {b: [1]}, + doc3: {a: 3, b: [2, 7]}, + doc4: {a: 1, b: [3, 7]}, + doc5: {a: 1}, + doc6: {a: 2}, + }); + + // Query: a==2 || b array-contains 7 + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or( + Filter.where('a', '==', 2), + Filter.where('b', 'array-contains', 7) + ) + ) + ), + 'doc3', + 'doc4', + 'doc6' + ); + + // a==2 || b array-contains-any [0, 3] + // Has implicit "orderBy b" + expectDocs( + await compareQueryAndPipeline( + collection.where( + Filter.or( + Filter.where('a', '==', 2), + Filter.where('b', 'array-contains-any', [0, 3]) + ) + ) + ), + 'doc1', + 'doc4', + 'doc6' + ); + }); + + describe('watch', () => { + interface ExpectedChange { + type: string; + doc: DocumentSnapshot; + } + + const currentDeferred = new DeferredPromise(); + + const snapshot = (id: string, data: DocumentData) => { + const ref = randomCol.doc(id); + const fields = ref.firestore._serializer!.encodeFields(data); + return randomCol.firestore.snapshot_( + { + name: + 'projects/ignored/databases/(default)/documents/' + + ref._path.relativeName, + fields, + createTime: {seconds: 0, nanos: 0}, + updateTime: {seconds: 0, nanos: 0}, + }, + {seconds: 0, nanos: 0} + ); + }; + + const docChange = ( + type: string, + id: string, + data: DocumentData + ): ExpectedChange => { + return { + type, + doc: snapshot(id, data), + }; + }; + + const added = (id: string, data: DocumentData) => + docChange('added', id, data); + const modified = (id: string, data: DocumentData) => + docChange('modified', id, data); + const removed = (id: string, data: DocumentData) => + docChange('removed', id, data); + + function resetPromise() { + currentDeferred.promise = new Promise((resolve, reject) => { + currentDeferred.resolve = resolve; + currentDeferred.reject = reject; + }); + } + + function waitForSnapshot(): Promise { + return currentDeferred.promise!.then(snapshot => { + resetPromise(); + return snapshot; + }); + } + + function snapshotsEqual( + actual: QuerySnapshot, + expected: {docs: DocumentSnapshot[]; docChanges: ExpectedChange[]} + ) { + let i; + expect(actual.size).to.equal(expected.docs.length); + for (i = 0; i < expected.docs.length && i < actual.size; i++) { + expect(actual.docs[i].ref.id).to.equal(expected.docs[i].ref.id); + expect(actual.docs[i].data()).to.deep.equal(expected.docs[i].data()); + } + const actualDocChanges = actual.docChanges(); + expect(actualDocChanges.length).to.equal(expected.docChanges.length); + for (i = 0; i < expected.docChanges.length; i++) { + expect(actualDocChanges[i].type).to.equal(expected.docChanges[i].type); + expect(actualDocChanges[i].doc.ref.id).to.equal( + expected.docChanges[i].doc.ref.id + ); + expect(actualDocChanges[i].doc.data()).to.deep.equal( + expected.docChanges[i].doc.data() + ); + expect(actualDocChanges[i].doc.readTime).to.exist; + expect(actualDocChanges[i].doc.createTime).to.exist; + expect(actualDocChanges[i].doc.updateTime).to.exist; + } + expect(actual.readTime).to.exist; + } + + beforeEach(() => resetPromise()); + + it('handles changing a doc', () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + const unsubscribe = randomCol.onSnapshot( + snapshot => { + currentDeferred.resolve(snapshot); + }, + err => { + currentDeferred.reject!(err); + } + ); + + return waitForSnapshot() + .then(results => { + snapshotsEqual(results, {docs: [], docChanges: []}); + // Add a result. + return ref1.set({foo: 'a'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {foo: 'a'})], + docChanges: [added('doc1', {foo: 'a'})], + }); + // Add another result. + return ref2.set({foo: 'b'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {foo: 'a'}), snapshot('doc2', {foo: 'b'})], + docChanges: [added('doc2', {foo: 'b'})], + }); + // Change a result. + return ref2.set({bar: 'c'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {foo: 'a'}), snapshot('doc2', {bar: 'c'})], + docChanges: [modified('doc2', {bar: 'c'})], + }); + unsubscribe(); + }); + }); + + it("handles changing a doc so it doesn't match", () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + const query = randomCol.where('included', '==', 'yes'); + const unsubscribe = query.onSnapshot( + snapshot => { + currentDeferred.resolve(snapshot); + }, + err => { + currentDeferred.reject(err); + } + ); + + return waitForSnapshot() + .then(results => { + snapshotsEqual(results, {docs: [], docChanges: []}); + // Add a result. + return ref1.set({included: 'yes'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {included: 'yes'})], + docChanges: [added('doc1', {included: 'yes'})], + }); + // Add another result. + return ref2.set({included: 'yes'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [ + snapshot('doc1', {included: 'yes'}), + snapshot('doc2', {included: 'yes'}), + ], + docChanges: [added('doc2', {included: 'yes'})], + }); + // Change a result. + return ref2.set({included: 'no'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {included: 'yes'})], + docChanges: [removed('doc2', {included: 'yes'})], + }); + unsubscribe(); + }); + }); + + it('handles deleting a doc', () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + + const unsubscribe = randomCol.onSnapshot( + snapshot => { + currentDeferred.resolve(snapshot); + }, + err => { + currentDeferred.reject(err); + } + ); + + return waitForSnapshot() + .then(results => { + snapshotsEqual(results, {docs: [], docChanges: []}); + // Add a result. + return ref1.set({included: 'yes'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {included: 'yes'})], + docChanges: [added('doc1', {included: 'yes'})], + }); + // Add another result. + return ref2.set({included: 'yes'}); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [ + snapshot('doc1', {included: 'yes'}), + snapshot('doc2', {included: 'yes'}), + ], + docChanges: [added('doc2', {included: 'yes'})], + }); + // Delete a result. + return ref2.delete(); + }) + .then(() => { + return waitForSnapshot(); + }) + .then(results => { + snapshotsEqual(results, { + docs: [snapshot('doc1', {included: 'yes'})], + docChanges: [removed('doc2', {included: 'yes'})], + }); + unsubscribe(); + }); + }); + + it('orders limitToLast() correctly', async () => { + const ref1 = randomCol.doc('doc1'); + const ref2 = randomCol.doc('doc2'); + const ref3 = randomCol.doc('doc3'); + + await ref1.set({doc: 1}); + await ref2.set({doc: 2}); + await ref3.set({doc: 3}); + + const unsubscribe = randomCol + .orderBy('doc') + .limitToLast(2) + .onSnapshot(snapshot => currentDeferred.resolve(snapshot)); + + const results = await waitForSnapshot(); + snapshotsEqual(results, { + docs: [snapshot('doc2', {doc: 2}), snapshot('doc3', {doc: 3})], + docChanges: [added('doc2', {doc: 2}), added('doc3', {doc: 3})], + }); + + unsubscribe(); + }); + + it('SDK orders vector field same way as backend', async () => { + // We validate that the SDK orders the vector field the same way as the backend + // by comparing the sort order of vector fields from a Query.get() and + // Query.onSnapshot(). Query.onSnapshot() will return sort order of the SDK, + // and Query.get() will return sort order of the backend. + + // Test data in the order that we expect the backend to sort it. + const docsInOrder = [ + {embedding: [1, 2, 3, 4, 5, 6]}, + {embedding: [100]}, + {embedding: FieldValue.vector([Number.NEGATIVE_INFINITY])}, + {embedding: FieldValue.vector([-100])}, + {embedding: FieldValue.vector([100])}, + {embedding: FieldValue.vector([Number.POSITIVE_INFINITY])}, + {embedding: FieldValue.vector([1, 2])}, + {embedding: FieldValue.vector([2, 2])}, + {embedding: FieldValue.vector([1, 2, 3])}, + {embedding: FieldValue.vector([1, 2, 3, 4])}, + {embedding: FieldValue.vector([1, 2, 3, 4, 5])}, + {embedding: FieldValue.vector([1, 2, 100, 4, 4])}, + {embedding: FieldValue.vector([100, 2, 3, 4, 5])}, + {embedding: {HELLO: 'WORLD'}}, + {embedding: {hello: 'world'}}, + ]; + + const expectedSnapshots = []; + const expectedChanges = []; + + for (let i = 0; i < docsInOrder.length; i++) { + const dr = await randomCol.add(docsInOrder[i]); + expectedSnapshots.push(snapshot(dr.id, docsInOrder[i])); + expectedChanges.push(added(dr.id, docsInOrder[i])); + } + + const orderedQuery = randomCol.orderBy('embedding'); + + const unsubscribe = orderedQuery.onSnapshot( + snapshot => { + currentDeferred.resolve(snapshot); + }, + err => { + currentDeferred.reject!(err); + } + ); + + const watchSnapshot = await waitForSnapshot(); + unsubscribe(); + + const getSnapshot = await orderedQuery.get(); + + // Compare the snapshot (including sort order) of a snapshot + // from Query.onSnapshot() to an actual snapshot from Query.get() + snapshotsEqual(watchSnapshot, { + docs: getSnapshot.docs, + docChanges: getSnapshot.docChanges(), + }); + + // Compare the snapshot (including sort order) of a snapshot + // from Query.onSnapshot() to the expected sort order from + // the backend. + snapshotsEqual(watchSnapshot, { + docs: expectedSnapshots, + docChanges: expectedChanges, + }); + }); + }); + + (process.env.FIRESTORE_EMULATOR_HOST === undefined + ? describe.skip + : describe.only)('multiple inequality', () => { + it('supports multiple inequality queries', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: 0}, + doc2: {key: 'b', sort: 3, v: 1}, + doc3: {key: 'c', sort: 1, v: 3}, + doc4: {key: 'd', sort: 2, v: 2}, + }); + + // Multiple inequality fields + let results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '<=', 2) + .where('v', '>', 2) + ); + expectDocs(results, 'doc3'); + + // Duplicate inequality fields + results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '<=', 2) + .where('sort', '>', 1) + ); + expectDocs(results, 'doc4'); + + // With multiple IN + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .where('v', 'in', [2, 3, 4]) + .where('sort', 'in', [2, 3]) + ); + expectDocs(results, 'doc4'); + + // With NOT-IN + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .where('v', 'not-in', [2, 4, 5]) + ); + expectDocs(results, 'doc1', 'doc3'); + + // With orderby + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .orderBy('v', 'desc') + ); + expectDocs(results, 'doc3', 'doc4', 'doc1'); + + // With limit + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .orderBy('v', 'desc') + .limit(2) + ); + expectDocs(results, 'doc3', 'doc4'); + + // With limitToLast + results = await compareQueryAndPipeline( + collection + .where('key', '>=', 'a') + .where('sort', '<=', 2) + .orderBy('v', 'desc') + .limitToLast(2) + ); + expectDocs(results, 'doc4', 'doc1'); + }); + + it('can use on special values', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: 0}, + doc2: {key: 'b', sort: NaN, v: 1}, + doc3: {key: 'c', sort: null, v: 3}, + doc4: {key: 'd', v: 0}, + doc5: {key: 'e', sort: 1}, + doc6: {key: 'f', sort: 1, v: 1}, + }); + + let results = await compareQueryAndPipeline( + collection.where('key', '!=', 'a').where('sort', '<=', 2) + ); + expectDocs(results, 'doc5', 'doc6'); + + results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '<=', 2) + .where('v', '<=', 1) + ); + expectDocs(results, 'doc6'); + }); + + it('can use with array membership', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: [0]}, + doc2: {key: 'b', sort: 1, v: [0, 1, 3]}, + doc3: {key: 'c', sort: 1, v: []}, + doc4: {key: 'd', sort: 2, v: [1]}, + doc5: {key: 'e', sort: 3, v: [2, 4]}, + doc6: {key: 'f', sort: 4, v: [NaN]}, + doc7: {key: 'g', sort: 4, v: [null]}, + }); + + let results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '>=', 1) + .where('v', 'array-contains', 0) + ); + expectDocs(results, 'doc2'); + + results = await compareQueryAndPipeline( + collection + .where('key', '!=', 'a') + .where('sort', '>=', 1) + .where('v', 'array-contains-any', [0, 1]) + ); + expectDocs(results, 'doc2', 'doc4'); + }); + + // Use cursor in following test cases to add implicit order by fields in the sdk and compare the + // result with the query fields normalized in the server. + it('can use with nested field', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const testData = (n?: number): any => { + n = n || 1; + return { + name: 'room ' + n, + metadata: { + createdAt: n, + }, + field: 'field ' + n, + 'field.dot': n, + 'field\\slash': n, + }; + }; + + const collection = await testCollectionWithDocs({ + doc1: testData(400), + doc2: testData(200), + doc3: testData(100), + doc4: testData(300), + }); + + // ordered by: name asc, metadata.createdAt asc, __name__ asc + let query = collection + .where('metadata.createdAt', '<=', 500) + .where('metadata.createdAt', '>', 100) + .where('name', '!=', 'room 200') + .orderBy('name'); + let docSnap = await collection.doc('doc4').get(); + let queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc4', 'doc1'); + expectDocs(await queryWithCursor.get(), 'doc4', 'doc1'); + + // ordered by: name desc, field desc, field.dot desc, field\\slash desc, __name__ desc + query = collection + .where('field', '>=', 'field 100') + .where(new FieldPath('field.dot'), '!=', 300) + .where('field\\slash', '<', 400) + .orderBy('name', 'desc'); + docSnap = await collection.doc('doc2').get(); + queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc2', 'doc3'); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc3'); + }); + + it('can use with nested composite filters', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: 5}, + doc2: {key: 'aa', sort: 4, v: 4}, + doc3: {key: 'c', sort: 3, v: 3}, + doc4: {key: 'b', sort: 2, v: 2}, + doc5: {key: 'b', sort: 2, v: 1}, + doc6: {key: 'b', sort: 0, v: 0}, + }); + + // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc + let query = collection.where( + Filter.or( + Filter.and( + Filter.where('key', '==', 'b'), + Filter.where('sort', '<=', 2) + ), + Filter.and(Filter.where('key', '!=', 'b'), Filter.where('v', '>', 4)) + ) + ); + let docSnap = await collection.doc('doc1').get(); + let queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc1', + 'doc6', + 'doc5', + 'doc4' + ); + expectDocs(await queryWithCursor.get(), 'doc1', 'doc6', 'doc5', 'doc4'); + + // Ordered by: 'sort' desc, 'key' asc, 'v' asc, __name__ asc + query = collection + .where( + Filter.or( + Filter.and( + Filter.where('key', '==', 'b'), + Filter.where('sort', '<=', 2) + ), + Filter.and( + Filter.where('key', '!=', 'b'), + Filter.where('v', '>', 4) + ) + ) + ) + .orderBy('sort', 'desc') + .orderBy('key'); + docSnap = await collection.doc('doc5').get(); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc5', + 'doc4', + 'doc1', + 'doc6' + ); + expectDocs(await queryWithCursor.get(), 'doc5', 'doc4', 'doc1', 'doc6'); + + // Implicitly ordered by: 'key' asc, 'sort' asc, 'v' asc, __name__ asc + query = collection.where( + Filter.and( + Filter.or( + Filter.and( + Filter.where('key', '==', 'b'), + Filter.where('sort', '<=', 4) + ), + Filter.and( + Filter.where('key', '!=', 'b'), + Filter.where('v', '>=', 4) + ) + ), + Filter.or( + Filter.and( + Filter.where('key', '>', 'b'), + Filter.where('sort', '>=', 1) + ), + Filter.and(Filter.where('key', '<', 'b'), Filter.where('v', '>', 0)) + ) + ) + ); + docSnap = await collection.doc('doc1').get(); + queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc1', 'doc2'); + expectDocs(await queryWithCursor.get(), 'doc1', 'doc2'); + }); + + it('inequality fields will be implicitly ordered lexicographically by the server', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 0, v: 5}, + doc2: {key: 'aa', sort: 4, v: 4}, + doc3: {key: 'b', sort: 3, v: 3}, + doc4: {key: 'b', sort: 2, v: 2}, + doc5: {key: 'b', sort: 2, v: 1}, + doc6: {key: 'b', sort: 0, v: 0}, + }); + + const docSnap = await collection.doc('doc2').get(); + + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + let query = collection + .where('key', '!=', 'a') + .where('sort', '>', 1) + .where('v', 'in', [1, 2, 3, 4]); + let queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc2', + 'doc4', + 'doc5', + 'doc3' + ); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc5', 'doc3'); + + // Changing filters order will not effect implicit order. + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + query = collection + .where('sort', '>', 1) + .where('key', '!=', 'a') + .where('v', 'in', [1, 2, 3, 4]); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc2', + 'doc4', + 'doc5', + 'doc3' + ); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc5', 'doc3'); + }); + + it('can use multiple explicit order by field', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 5, v: 0}, + doc2: {key: 'aa', sort: 4, v: 0}, + doc3: {key: 'b', sort: 3, v: 1}, + doc4: {key: 'b', sort: 2, v: 1}, + doc5: {key: 'bb', sort: 1, v: 1}, + doc6: {key: 'c', sort: 0, v: 2}, + }); + + let docSnap = await collection.doc('doc2').get(); + + // Ordered by: 'v' asc, 'key' asc, 'sort' asc, __name__ asc + let query = collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v'); + let queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc2', + 'doc4', + 'doc3', + 'doc5' + ); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3', 'doc5'); + + // Ordered by: 'v asc, 'sort' asc, 'key' asc, __name__ asc + query = collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v') + .orderBy('sort'); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc2', + 'doc5', + 'doc4', + 'doc3' + ); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc5', 'doc4', 'doc3'); + + docSnap = await collection.doc('doc5').get(); + + // Implicit order by matches the direction of last explicit order by. + // Ordered by: 'v' desc, 'key' desc, 'sort' desc, __name__ desc + query = collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v', 'desc'); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc5', + 'doc3', + 'doc4', + 'doc2' + ); + expectDocs(await queryWithCursor.get(), 'doc5', 'doc3', 'doc4', 'doc2'); + + // Ordered by: 'v desc, 'sort' asc, 'key' asc, __name__ asc + query = collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v', 'desc') + .orderBy('sort'); + queryWithCursor = query.startAt(docSnap); + expectDocs( + await compareQueryAndPipeline(query), + 'doc5', + 'doc4', + 'doc3', + 'doc2' + ); + expectDocs(await queryWithCursor.get(), 'doc5', 'doc4', 'doc3', 'doc2'); + }); + + it('can use in aggregate query', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 5, v: 0}, + doc2: {key: 'aa', sort: 4, v: 0}, + doc3: {key: 'b', sort: 3, v: 1}, + doc4: {key: 'b', sort: 2, v: 1}, + doc5: {key: 'bb', sort: 1, v: 1}, + }); + + const results = await collection + .where('key', '>', 'a') + .where('sort', '>=', 1) + .orderBy('v') + .count() + .get(); + expect(results.data().count).to.be.equal(4); + //TODO(MIEQ): Add sum and average when they are public. + }); + + it('can use document ID im multiple inequality query', async () => { + const collection = await testCollectionWithDocs({ + doc1: {key: 'a', sort: 5}, + doc2: {key: 'aa', sort: 4}, + doc3: {key: 'b', sort: 3}, + doc4: {key: 'b', sort: 2}, + doc5: {key: 'bb', sort: 1}, + }); + + const docSnap = await collection.doc('doc2').get(); + + // Document Key in inequality field will implicitly ordered to the last. + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + let query = collection + .where('sort', '>=', 1) + .where('key', '!=', 'a') + .where(FieldPath.documentId(), '<', 'doc5'); + let queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc2', 'doc4', 'doc3'); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3'); + + // Changing filters order will not effect implicit order. + // Implicitly ordered by: 'key' asc, 'sort' asc, __name__ asc + query = collection + .where(FieldPath.documentId(), '<', 'doc5') + .where('sort', '>=', 1) + .where('key', '!=', 'a'); + queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc2', 'doc4', 'doc3'); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc4', 'doc3'); + + // Ordered by: 'sort' desc,'key' desc, __name__ desc + query = collection + .where(FieldPath.documentId(), '<', 'doc5') + .where('sort', '>=', 1) + .where('key', '!=', 'a') + .orderBy('sort', 'desc'); + queryWithCursor = query.startAt(docSnap); + expectDocs(await compareQueryAndPipeline(query), 'doc2', 'doc3', 'doc4'); + expectDocs(await queryWithCursor.get(), 'doc2', 'doc3', 'doc4'); + }); + }); +}); From 9ad261455a4b53d7dd5ab997ed8ce9baa8776afd Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 30 Jul 2024 11:47:22 -0400 Subject: [PATCH 04/31] pipeline impl --- dev/src/expression.ts | 898 ++++++++++++++++++++++----- dev/src/pipeline-util.ts | 20 +- dev/src/pipeline.ts | 343 +++++++++- dev/src/reference/aggregate-query.ts | 19 +- dev/src/reference/query.ts | 25 +- dev/src/reference/vector-query.ts | 2 +- dev/src/stage.ts | 19 +- dev/src/util.ts | 24 + dev/system-test/pipeline.ts | 660 +++++++++++++++++++- dev/system-test/query.ts | 59 +- 10 files changed, 1809 insertions(+), 260 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index e2642407b..32ab6f6f1 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -21,77 +21,119 @@ export interface FilterCondition { } export interface Accumulator { - toField(fieldName: string): AggregateTarget; -} - -export class AggregateTarget implements Selectable { - constructor( - public field: Field, - public accumulator: Accumulator & Expr - ) {} - - selectable = true as const; + accumulator: true; } +export type AccumulatorTarget = ExprWithAlias; export type FilterExpr = Expr & FilterCondition; export type SelectableExpr = Expr & Selectable; -export type ExprType = 'Field' | 'Constant' | 'Function' | 'ListOfExprs'; +export type ExprType = + | 'Field' + | 'Constant' + | 'Function' + | 'ListOfExprs' + | 'ExprWithAlias'; export abstract class Expr { - equal(other: Expr): Equal; - equal(other: any): Equal; - equal(other: any): Equal { + add(other: Expr): Add; + add(other: any): Add; + add(other: any): Add { + if (other instanceof Expr) { + return new Add(this, other); + } + return new Add(this, Constant.of(other)); + } + + subtract(other: Expr): Subtract; + subtract(other: any): Subtract; + subtract(other: any): Subtract { + if (other instanceof Expr) { + return new Subtract(this, other); + } + return new Subtract(this, Constant.of(other)); + } + + multiply(other: Expr): Multiply; + multiply(other: any): Multiply; + multiply(other: any): Multiply { + if (other instanceof Expr) { + return new Multiply(this, other); + } + return new Multiply(this, Constant.of(other)); + } + + divide(other: Expr): Divide; + divide(other: any): Divide; + divide(other: any): Divide { + if (other instanceof Expr) { + return new Divide(this, other); + } + return new Divide(this, Constant.of(other)); + } + + eq(other: Expr): Eq; + eq(other: any): Eq; + eq(other: any): Eq { if (other instanceof Expr) { - return new Equal(this, other); + return new Eq(this, other); } - return new Equal(this, Constant.of(other)); + return new Eq(this, Constant.of(other)); } - notEqual(other: Expr): NotEqual; - notEqual(other: any): NotEqual; - notEqual(other: any): NotEqual { + neq(other: Expr): Neq; + neq(other: any): Neq; + neq(other: any): Neq { if (other instanceof Expr) { - return new NotEqual(this, other); + return new Neq(this, other); } - return new NotEqual(this, Constant.of(other)); + return new Neq(this, Constant.of(other)); } - lessThan(other: Expr): LessThan; - lessThan(other: any): LessThan; - lessThan(other: any): LessThan { + lt(other: Expr): Lt; + lt(other: any): Lt; + lt(other: any): Lt { if (other instanceof Expr) { - return new LessThan(this, other); + return new Lt(this, other); } - return new LessThan(this, Constant.of(other)); + return new Lt(this, Constant.of(other)); } - lessThanOrEqual(other: Expr): LessThanOrEqual; - lessThanOrEqual(other: any): LessThanOrEqual; - lessThanOrEqual(other: any): LessThanOrEqual { + lte(other: Expr): Lte; + lte(other: any): Lte; + lte(other: any): Lte { if (other instanceof Expr) { - return new LessThanOrEqual(this, other); + return new Lte(this, other); } - return new LessThanOrEqual(this, Constant.of(other)); + return new Lte(this, Constant.of(other)); } - greaterThan(other: Expr): GreaterThan; - greaterThan(other: any): GreaterThan; - greaterThan(other: any): GreaterThan { + gt(other: Expr): Gt; + gt(other: any): Gt; + gt(other: any): Gt { if (other instanceof Expr) { - return new GreaterThan(this, other); + return new Gt(this, other); } - return new GreaterThan(this, Constant.of(other)); + return new Gt(this, Constant.of(other)); } - greaterThanOrEqual(other: Expr): GreaterThanOrEqual; - greaterThanOrEqual(other: any): GreaterThanOrEqual; - greaterThanOrEqual(other: any): GreaterThanOrEqual { + gte(other: Expr): Gte; + gte(other: any): Gte; + gte(other: any): Gte { if (other instanceof Expr) { - return new GreaterThanOrEqual(this, other); + return new Gte(this, other); } - return new GreaterThanOrEqual(this, Constant.of(other)); + return new Gte(this, Constant.of(other)); + } + + arrayConcat(...values: Expr[]): ArrayConcat; + arrayConcat(...values: any[]): ArrayConcat; + arrayConcat(...values: any[]): ArrayConcat { + const exprValues = values.map(value => + value instanceof Expr ? value : Constant.of(value) + ); + return new ArrayConcat(this, exprValues); } arrayContains(element: Expr): ArrayContains; @@ -103,15 +145,36 @@ export abstract class Expr { return new ArrayContains(this, Constant.of(element)); } - arrayContainsAny(values: Expr[]): ArrayContainsAny; - arrayContainsAny(values: any[]): ArrayContainsAny; - arrayContainsAny(values: any[]): ArrayContainsAny { + arrayContainsAll(...values: Expr[]): ArrayContainsAll; + 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); + } + + arrayContainsAny(...values: Expr[]): ArrayContainsAny; + 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); } + arrayFilter(filter: FilterExpr): ArrayFilter { + return new ArrayFilter(this, filter); + } + + arrayLength(): ArrayLength { + return new ArrayLength(this); + } + + arrayTransform(transform: Function): ArrayTransform { + return new ArrayTransform(this, transform); + } + in(...others: Expr[]): In; in(...others: any[]): In; in(...others: any[]): In { @@ -133,6 +196,76 @@ export abstract class Expr { return new Exists(this); } + length(): Length { + return new Length(this); + } + + like(pattern: string): Like; + like(pattern: Expr): Like; + like(stringOrExpr: string | Expr): Like { + if (stringOrExpr instanceof Expr) { + return new Like(this, stringOrExpr); + } + return new Like(this, Constant.of(stringOrExpr)); + } + + regexContains(pattern: string): RegexContains; + regexContains(pattern: Expr): RegexContains; + regexContains(stringOrExpr: string | Expr): RegexContains { + if (stringOrExpr instanceof Expr) { + return new RegexContains(this, stringOrExpr); + } + return new RegexContains(this, Constant.of(stringOrExpr)); + } + + regexMatch(pattern: string): RegexMatch; + regexMatch(pattern: Expr): RegexMatch; + regexMatch(stringOrExpr: string | Expr): RegexMatch { + if (stringOrExpr instanceof Expr) { + return new RegexMatch(this, stringOrExpr); + } + return new RegexMatch(this, Constant.of(stringOrExpr)); + } + + startsWith(prefix: string): StartsWith; + startsWith(prefix: Expr): StartsWith; + startsWith(stringOrExpr: string | Expr): StartsWith { + if (stringOrExpr instanceof Expr) { + return new StartsWith(this, stringOrExpr); + } + return new StartsWith(this, Constant.of(stringOrExpr)); + } + + endsWith(suffix: string): EndsWith; + endsWith(suffix: Expr): EndsWith; + endsWith(stringOrExpr: string | Expr): EndsWith { + if (stringOrExpr instanceof Expr) { + return new EndsWith(this, stringOrExpr); + } + return new EndsWith(this, Constant.of(stringOrExpr)); + } + + toLowercase(): ToLowercase { + return new ToLowercase(this); + } + + toUppercase(): ToUppercase { + return new ToUppercase(this); + } + + trim(): Trim { + return new Trim(this); + } + + strConcat(...elements: (string | Expr)[]): StrConcat { + const exprs = elements.map(e => (e instanceof Expr ? e : Constant.of(e))); + return new StrConcat(this, exprs); + } + + mapGet(subfield: string): MapGet { + return new MapGet(this, subfield); + } + count(): Count { return new Count(this, false); } @@ -186,9 +319,37 @@ export abstract class Expr { } } + ascending(): Ordering { + return Ordering.ascending(this); + } + + descending(): Ordering { + return Ordering.descending(this); + } + + as(name: string): ExprWithAlias { + return new ExprWithAlias(this, name); + } + abstract _toProto(serializer: Serializer): api.IValue; } +export class ExprWithAlias extends Expr implements Selectable { + exprType: ExprType = 'ExprWithAlias'; + selectable = true as const; + + constructor( + public expr: T, + public alias: string + ) { + super(); + } + + _toProto(serializer: Serializer): api.IValue { + throw new Error('ExprWithAlias should not be serialized directly.'); + } +} + class ListOfExprs extends Expr { exprType: ExprType = 'ListOfExprs'; constructor(private exprs: Expr[]) { @@ -344,7 +505,43 @@ export class Function extends Expr { } } -export class Equal extends Function implements FilterCondition { +export class Add extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('add', [left, right]); + } +} + +export class Subtract extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('subtract', [left, right]); + } +} + +export class Multiply extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('multiply', [left, right]); + } +} + +export class Divide extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('divide', [left, right]); + } +} + +export class Eq extends Function implements FilterCondition { constructor( private left: Expr, private right: Expr @@ -354,7 +551,7 @@ export class Equal extends Function implements FilterCondition { filterable = true as const; } -class NotEqual extends Function implements FilterCondition { +class Neq extends Function implements FilterCondition { constructor( private left: Expr, private right: Expr @@ -364,7 +561,7 @@ class NotEqual extends Function implements FilterCondition { filterable = true as const; } -class LessThan extends Function implements FilterCondition { +class Lt extends Function implements FilterCondition { constructor( private left: Expr, private right: Expr @@ -374,7 +571,7 @@ class LessThan extends Function implements FilterCondition { filterable = true as const; } -class LessThanOrEqual extends Function implements FilterCondition { +class Lte extends Function implements FilterCondition { constructor( private left: Expr, private right: Expr @@ -384,7 +581,7 @@ class LessThanOrEqual extends Function implements FilterCondition { filterable = true as const; } -class GreaterThan extends Function implements FilterCondition { +class Gt extends Function implements FilterCondition { constructor( private left: Expr, private right: Expr @@ -394,7 +591,7 @@ class GreaterThan extends Function implements FilterCondition { filterable = true as const; } -class GreaterThanOrEqual extends Function implements FilterCondition { +class Gte extends Function implements FilterCondition { constructor( private left: Expr, private right: Expr @@ -404,6 +601,15 @@ class GreaterThanOrEqual extends Function implements FilterCondition { filterable = true as const; } +class ArrayConcat extends Function { + constructor( + private array: Expr, + private elements: Expr[] + ) { + super('array_concat', [array, ...elements]); + } +} + class ArrayContains extends Function implements FilterCondition { constructor( private array: Expr, @@ -414,6 +620,16 @@ class ArrayContains extends Function implements FilterCondition { filterable = true as const; } +class ArrayContainsAll extends Function implements FilterCondition { + constructor( + private array: Expr, + private values: Expr[] + ) { + super('array_contains_all', [array, new ListOfExprs(values)]); + } + filterable = true as const; +} + class ArrayContainsAny extends Function implements FilterCondition { constructor( private array: Expr, @@ -424,6 +640,36 @@ class ArrayContainsAny extends Function implements FilterCondition { filterable = true as const; } +class ArrayFilter extends Function { + constructor( + private array: Expr, + private filter: FilterExpr + ) { + super('array_filter', [array, filter]); + } +} + +class ArrayLength extends Function { + constructor(private array: Expr) { + super('array_length', [array]); + } +} + +class ArrayTransform extends Function { + constructor( + private array: Expr, + private transform: Function + ) { + super('array_transform', [array, transform]); + } +} + +class ArrayElement extends Function { + constructor() { + super('array_element', []); + } +} + class In extends Function implements FilterCondition { constructor( private left: Expr, @@ -477,65 +723,161 @@ class Or extends Function implements FilterCondition { filterable = true as const; } +class Xor extends Function implements FilterCondition { + constructor(private conditions: FilterExpr[]) { + super('xor', conditions); + } + filterable = true as const; +} + +class If extends Function implements FilterCondition { + constructor( + private condition: FilterExpr, + private thenExpr: Expr, + private elseExpr: Expr + ) { + super('if', [condition, thenExpr, elseExpr]); + } + filterable = true as const; +} + +class Length extends Function { + constructor(private expr: Expr) { + super('length', [expr]); + } +} + +class Like extends Function implements FilterCondition { + constructor( + private expr: Expr, + private pattern: Expr + ) { + super('like', [expr, pattern]); + } + filterable = true as const; +} + +class RegexContains extends Function implements FilterCondition { + constructor( + private expr: Expr, + private pattern: Expr + ) { + super('regex_contains', [expr, pattern]); + } + filterable = true as const; +} + +class RegexMatch extends Function implements FilterCondition { + constructor( + private expr: Expr, + private pattern: Expr + ) { + super('regex_match', [expr, pattern]); + } + filterable = true as const; +} + +class StartsWith extends Function implements FilterCondition { + constructor( + private expr: Expr, + private prefix: Expr + ) { + super('starts_with', [expr, prefix]); + } + filterable = true as const; +} + +class EndsWith extends Function implements FilterCondition { + constructor( + private expr: Expr, + private suffix: Expr + ) { + super('ends_with', [expr, suffix]); + } + filterable = true as const; +} + +class ToLowercase extends Function { + constructor(private expr: Expr) { + super('to_lowercase', [expr]); + } +} + +class ToUppercase extends Function { + constructor(private expr: Expr) { + super('to_uppercase', [expr]); + } +} + +class Trim extends Function { + constructor(private expr: Expr) { + super('trim', [expr]); + } +} + +class StrConcat extends Function { + constructor( + private first: Expr, + private rest: Expr[] + ) { + super('str_concat', [first, ...rest]); + } +} + +class MapGet extends Function { + constructor(map: Expr, name: string) { + super('map_get', [map, Constant.of(name)]); + } +} + class Count extends Function implements Accumulator { + accumulator = true as const; constructor( private value: Expr | undefined, private distinct: boolean ) { super('count', value === undefined ? [] : [value]); } - - toField(fieldName: string): AggregateTarget { - return new AggregateTarget(Field.of(fieldName), this); - } } class Sum extends Function implements Accumulator { + accumulator = true as const; constructor( private value: Expr, private distinct: boolean ) { super('sum', [value]); } - toField(fieldName: string): AggregateTarget { - return new AggregateTarget(Field.of(fieldName), this); - } } class Avg extends Function implements Accumulator { + accumulator = true as const; constructor( private value: Expr, private distinct: boolean ) { super('avg', [value]); } - toField(fieldName: string): AggregateTarget { - return new AggregateTarget(Field.of(fieldName), this); - } } class Min extends Function implements Accumulator { + accumulator = true as const; constructor( private value: Expr, private distinct: boolean ) { super('min', [value]); } - toField(fieldName: string): AggregateTarget { - return new AggregateTarget(Field.of(fieldName), this); - } } class Max extends Function implements Accumulator { + accumulator = true as const; constructor( private value: Expr, private distinct: boolean ) { super('max', [value]); } - toField(fieldName: string): AggregateTarget { - return new AggregateTarget(Field.of(fieldName), this); - } } class CosineDistance extends Function { @@ -565,85 +907,145 @@ class EuclideanDistance extends Function { } } -function equal(left: Expr, right: Expr): Equal; -function equal(left: Expr, right: any): Equal; -function equal(left: string, right: Expr): Equal; -function equal(left: string, right: any): Equal; -function equal(left: Expr | string, right: any): Equal { +export function add(left: Expr, right: Expr): Add; +export function add(left: Expr, right: any): Add; +export function add(left: string, right: Expr): Add; +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 subtract(left: Expr, right: Expr): Subtract; +export function subtract(left: Expr, right: any): Subtract; +export function subtract(left: string, right: Expr): Subtract; +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 multiply(left: Expr, right: Expr): Multiply; +export function multiply(left: Expr, right: any): Multiply; +export function multiply(left: string, right: Expr): Multiply; +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 divide(left: Expr, right: Expr): Divide; +export function divide(left: Expr, right: any): Divide; +export function divide(left: string, right: Expr): Divide; +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 eq(left: Expr, right: Expr): Eq; +export function eq(left: Expr, right: any): Eq; +export function eq(left: string, right: Expr): Eq; +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 Equal(leftExpr, rightExpr); + return new Eq(leftExpr, rightExpr); } -// notEqual -function notEqual(left: Expr, right: Expr): NotEqual; -function notEqual(left: Expr, right: any): NotEqual; -function notEqual(left: string, right: Expr): NotEqual; -function notEqual(left: string, right: any): NotEqual; -function notEqual(left: Expr | string, right: any): NotEqual { +export function neq(left: Expr, right: Expr): Neq; +export function neq(left: Expr, right: any): Neq; +export function neq(left: string, right: Expr): Neq; +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 NotEqual(leftExpr, rightExpr); + return new Neq(leftExpr, rightExpr); } -function lessThan(left: Expr, right: Expr): LessThan; -function lessThan(left: Expr, right: any): LessThan; -function lessThan(left: string, right: Expr): LessThan; -function lessThan(left: string, right: any): LessThan; -function lessThan(left: Expr | string, right: any): LessThan { +export function lt(left: Expr, right: Expr): Lt; +export function lt(left: Expr, right: any): Lt; +export function lt(left: string, right: Expr): Lt; +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 LessThan(leftExpr, rightExpr); + return new Lt(leftExpr, rightExpr); } -function lessThanOrEqual(left: Expr, right: Expr): LessThanOrEqual; -function lessThanOrEqual(left: Expr, right: any): LessThanOrEqual; -function lessThanOrEqual(left: string, right: Expr): LessThanOrEqual; -function lessThanOrEqual(left: string, right: any): LessThanOrEqual; -function lessThanOrEqual(left: Expr | string, right: any): LessThanOrEqual { +export function lte(left: Expr, right: Expr): Lte; +export function lte(left: Expr, right: any): Lte; +export function lte(left: string, right: Expr): Lte; +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 LessThanOrEqual(leftExpr, rightExpr); + return new Lte(leftExpr, rightExpr); } -function greaterThan(left: Expr, right: Expr): GreaterThan; -function greaterThan(left: Expr, right: any): GreaterThan; -function greaterThan(left: string, right: Expr): GreaterThan; -function greaterThan(left: string, right: any): GreaterThan; -function greaterThan(left: Expr | string, right: any): GreaterThan { +export function gt(left: Expr, right: Expr): Gt; +export function gt(left: Expr, right: any): Gt; +export function gt(left: string, right: Expr): Gt; +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 GreaterThan(leftExpr, rightExpr); + return new Gt(leftExpr, rightExpr); } -function greaterThanOrEqual(left: Expr, right: Expr): GreaterThanOrEqual; -function greaterThanOrEqual(left: Expr, right: any): GreaterThanOrEqual; -function greaterThanOrEqual(left: string, right: Expr): GreaterThanOrEqual; -function greaterThanOrEqual(left: string, right: any): GreaterThanOrEqual; -function greaterThanOrEqual( - left: Expr | string, - right: any -): GreaterThanOrEqual { +export function gte(left: Expr, right: Expr): Gte; +export function gte(left: Expr, right: any): Gte; +export function gte(left: string, right: Expr): Gte; +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 GreaterThanOrEqual(leftExpr, rightExpr); + return new Gte(leftExpr, rightExpr); +} + +export function arrayConcat(array: Expr, elements: Expr[]): ArrayConcat; +export function arrayConcat(array: Expr, elements: any[]): ArrayConcat; +export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; +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) + ); + return new ArrayConcat(arrayExpr, exprValues); } -function arrayContains(array: Expr, element: Expr): ArrayContains; -function arrayContains(array: Expr, element: any): ArrayContains; -function arrayContains(array: string, element: Expr): ArrayContains; -function arrayContains(array: string, element: any): ArrayContains; -function arrayContains(array: Expr | string, element: any): ArrayContains { +export function arrayContains(array: Expr, element: Expr): ArrayContains; +export function arrayContains(array: Expr, element: any): ArrayContains; +export function arrayContains(array: string, element: Expr): ArrayContains; +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); } -function arrayContainsAny(array: Expr, values: Expr[]): ArrayContainsAny; -function arrayContainsAny(array: Expr, values: any[]): ArrayContainsAny; -function arrayContainsAny(array: string, values: Expr[]): ArrayContainsAny; -function arrayContainsAny(array: string, values: any[]): ArrayContainsAny; -function arrayContainsAny( +export function arrayContainsAny(array: Expr, values: Expr[]): ArrayContainsAny; +export function arrayContainsAny(array: Expr, values: any[]): ArrayContainsAny; +export function arrayContainsAny( + array: string, + values: Expr[] +): ArrayContainsAny; +export function arrayContainsAny( + array: string, + values: any[] +): ArrayContainsAny; +export function arrayContainsAny( array: Expr | string, values: any[] ): ArrayContainsAny { @@ -654,11 +1056,51 @@ function arrayContainsAny( return new ArrayContainsAny(arrayExpr, exprValues); } -function inAny(element: Expr, others: Expr[]): In; -function inAny(element: Expr, others: any[]): In; -function inAny(element: string, others: Expr[]): In; // Added overload -function inAny(element: string, others: any[]): In; // Added overload -function inAny(element: Expr | string, others: any[]): In { +export function arrayContainsAll(array: Expr, values: Expr[]): ArrayContainsAll; +export function arrayContainsAll(array: Expr, values: any[]): ArrayContainsAll; +export function arrayContainsAll( + array: string, + values: Expr[] +): ArrayContainsAll; +export function arrayContainsAll( + array: string, + values: any[] +): ArrayContainsAll; +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); +} + +export function arrayFilter(array: Expr, filter: FilterExpr): ArrayFilter { + return new ArrayFilter(array, filter); +} + +export function arrayLength(array: Expr): ArrayLength { + return new ArrayLength(array); +} + +export function arrayTransform( + array: Expr, + transform: Function +): ArrayTransform { + return new ArrayTransform(array, transform); +} + +export function arrayElement(): ArrayElement { + return new ArrayElement(); +} + +export function inAny(element: Expr, others: Expr[]): In; +export function inAny(element: Expr, others: any[]): In; +export function inAny(element: string, others: Expr[]): In; // Added overload +export function inAny(element: string, others: any[]): In; // Added overload +export function inAny(element: Expr | string, others: any[]): In { const elementExpr = element instanceof Expr ? element : Field.of(element); const exprOthers = others.map(other => other instanceof Expr ? other : Constant.of(other) @@ -666,11 +1108,11 @@ function inAny(element: Expr | string, others: any[]): In { return new In(elementExpr, exprOthers); } -function notInAny(element: Expr, others: Expr[]): Not; -function notInAny(element: Expr, others: any[]): Not; -function notInAny(element: string, others: Expr[]): Not; // Added overload -function notInAny(element: string, others: any[]): Not; // Added overload -function notInAny(element: Expr | string, others: any[]): Not { +export function notInAny(element: Expr, others: Expr[]): Not; +export function notInAny(element: Expr, others: any[]): Not; +export function notInAny(element: string, others: Expr[]): Not; // Added overload +export function notInAny(element: string, others: any[]): Not; // Added overload +export function notInAny(element: Expr | string, others: any[]): Not { const elementExpr = element instanceof Expr ? element : Field.of(element); const exprOthers = others.map(other => other instanceof Expr ? other : Constant.of(other) @@ -686,6 +1128,18 @@ export function or(left: FilterExpr, ...right: FilterExpr[]): Or { return new Or([left, ...right]); } +export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor { + return new Xor([left, ...right]); +} + +export function ifFunction( + condition: FilterExpr, + thenExpr: Expr, + elseExpr: Expr +): If { + return new If(condition, thenExpr, elseExpr); +} + export function not(filter: FilterExpr): Not { return new Not(filter); } @@ -712,6 +1166,115 @@ export function isNan(value: Expr | string): IsNan { return new IsNan(valueExpr); } +export function length(field: string): Length; +export function length(expr: Expr): Length; +export function length(value: Expr | string): Length { + const valueExpr = value instanceof Expr ? value : Field.of(value); + return new Length(valueExpr); +} + +export function like(left: Expr, pattern: Expr): Like; +export function like(left: Expr, pattern: string): Like; +export function like(left: string, pattern: Expr): Like; +export function like(left: string, pattern: string): 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 regexContains(left: Expr, pattern: Expr): RegexContains; +export function regexContains(left: Expr, pattern: string): RegexContains; +export function regexContains(left: string, pattern: Expr): RegexContains; +export function regexContains(left: string, pattern: string): RegexContains; +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); +} + +export function regexMatch(left: Expr, pattern: Expr): RegexMatch; +export function regexMatch(left: Expr, pattern: string): RegexMatch; +export function regexMatch(left: string, pattern: Expr): RegexMatch; +export function regexMatch(left: string, pattern: string): RegexMatch; +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); +} + +export function startsWith(expr: Expr, prefix: Expr): StartsWith; +export function startsWith(expr: Expr, prefix: string): StartsWith; +export function startsWith(expr: string, prefix: Expr): StartsWith; +export function startsWith(expr: string, prefix: string): StartsWith; +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); +} + +export function endsWith(expr: Expr, suffix: Expr): EndsWith; +export function endsWith(expr: Expr, suffix: string): EndsWith; +export function endsWith(expr: string, suffix: Expr): EndsWith; +export function endsWith(expr: string, suffix: string): 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 toLowercase(expr: Expr): ToLowercase; +export function toLowercase(expr: string): ToLowercase; +export function toLowercase(expr: Expr | string): ToLowercase { + return new ToLowercase(expr instanceof Expr ? expr : Field.of(expr)); +} + +export function toUppercase(expr: Expr): ToUppercase; +export function toUppercase(expr: string): ToUppercase; +export function toUppercase(expr: Expr | string): ToUppercase { + return new ToUppercase(expr instanceof Expr ? expr : Field.of(expr)); +} + +export function trim(expr: Expr): Trim; +export function trim(expr: string): Trim; +export function trim(expr: Expr | string): Trim { + return new Trim(expr instanceof Expr ? expr : Field.of(expr)); +} + +export function strConcat( + first: string, + ...elements: (Expr | string)[] +): StrConcat; +export function strConcat( + first: Expr, + ...elements: (Expr | string)[] +): StrConcat; +export function strConcat( + first: string | Expr, + ...elements: (string | Expr)[] +): StrConcat { + const exprs = elements.map(e => (e instanceof Expr ? e : Constant.of(e))); + return new StrConcat(first instanceof Expr ? first : Field.of(first), exprs); +} + +export function mapGet(mapField: string, subField: string): MapGet; +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 countAll(): Count { return new Count(undefined, false); } @@ -737,27 +1300,30 @@ export function avg(value: Expr | string): Avg { return new Avg(exprValue, false); } -function min(value: Expr): Min; -function min(value: string): Min; -function min(value: Expr | string): Min { +export function min(value: Expr): Min; +export function min(value: string): Min; +export function min(value: Expr | string): Min { const exprValue = value instanceof Expr ? value : Field.of(value); return new Min(exprValue, false); } -function max(value: Expr): Max; -function max(value: string): Max; -function max(value: Expr | string): Max { +export function max(value: Expr): Max; +export function max(value: string): Max; +export function max(value: Expr | string): Max { const exprValue = value instanceof Expr ? value : Field.of(value); return new Max(exprValue, false); } -function cosineDistance(expr: Expr, other: Expr): CosineDistance; -function cosineDistance(expr: Expr, other: number[]): CosineDistance; -function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; -function cosineDistance(expr: string, other: Expr): CosineDistance; -function cosineDistance(expr: string, other: number[]): CosineDistance; -function cosineDistance(expr: string, other: VectorValue): CosineDistance; -function cosineDistance( +export function cosineDistance(expr: Expr, other: Expr): CosineDistance; +export function cosineDistance(expr: Expr, other: number[]): CosineDistance; +export function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; +export function cosineDistance(expr: string, other: Expr): CosineDistance; +export function cosineDistance(expr: string, other: number[]): CosineDistance; +export function cosineDistance( + expr: string, + other: VectorValue +): CosineDistance; +export function cosineDistance( expr: Expr | string, other: Expr | number[] | VectorValue ): CosineDistance { @@ -766,16 +1332,28 @@ function cosineDistance( return new CosineDistance(expr1, expr2); } -function dotProductDistance(expr: Expr, other: Expr): DotProductDistance; // Fixed return type -function dotProductDistance(expr: Expr, other: number[]): DotProductDistance; -function dotProductDistance(expr: Expr, other: VectorValue): DotProductDistance; -function dotProductDistance(expr: string, other: Expr): DotProductDistance; -function dotProductDistance(expr: string, other: number[]): DotProductDistance; -function dotProductDistance( +export function dotProductDistance(expr: Expr, other: Expr): DotProductDistance; // Fixed return type +export function dotProductDistance( + expr: Expr, + other: number[] +): DotProductDistance; +export function dotProductDistance( + expr: Expr, + other: VectorValue +): DotProductDistance; +export function dotProductDistance( + expr: string, + other: Expr +): DotProductDistance; +export function dotProductDistance( + expr: string, + other: number[] +): DotProductDistance; +export function dotProductDistance( expr: string, other: VectorValue ): DotProductDistance; -function dotProductDistance( +export function dotProductDistance( expr: Expr | string, other: Expr | number[] | VectorValue ): DotProductDistance { @@ -784,13 +1362,25 @@ function dotProductDistance( return new DotProductDistance(expr1, expr2); } -function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; -function euclideanDistance(expr: Expr, other: number[]): EuclideanDistance; -function euclideanDistance(expr: Expr, other: VectorValue): EuclideanDistance; -function euclideanDistance(expr: string, other: Expr): EuclideanDistance; -function euclideanDistance(expr: string, other: number[]): EuclideanDistance; -function euclideanDistance(expr: string, other: VectorValue): EuclideanDistance; -function euclideanDistance( +export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; +export function euclideanDistance( + expr: Expr, + other: number[] +): EuclideanDistance; +export function euclideanDistance( + expr: Expr, + other: VectorValue +): EuclideanDistance; +export function euclideanDistance(expr: string, other: Expr): EuclideanDistance; +export function euclideanDistance( + expr: string, + other: number[] +): EuclideanDistance; +export function euclideanDistance( + expr: string, + other: VectorValue +): EuclideanDistance; +export function euclideanDistance( expr: Expr | string, other: Expr | number[] | VectorValue ): EuclideanDistance { @@ -799,7 +1389,7 @@ function euclideanDistance( return new EuclideanDistance(expr1, expr2); } -function genericFunction(name: string, params: Expr[]): Function { +export function genericFunction(name: string, params: Expr[]): Function { return new Function(name, params); } diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index 8b814e0a6..21ba1e77d 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -90,11 +90,7 @@ export class ExecutionUtil< } ) .on('end', () => { - // Only return a snapshot when we have a readTime - // explain queries with analyze !== true will return no documents and no read time - const result = output.executionTime ? results : undefined; - - resolve(result); + resolve(results); }); }); } @@ -181,7 +177,7 @@ export class ExecutionUtil< output.result = new PipelineResult( this._serializer, ref, - result.fields, + result.fields || undefined, Timestamp.fromProto(proto.executionTime!), result.createTime ? Timestamp.fromProto(result.createTime!) @@ -405,17 +401,17 @@ export function toPipelineFilterCondition( : serializer.encodeValue(f.value); switch (f.op) { case 'LESS_THAN': - return and(field.exists(), field.lessThan(value)); + return and(field.exists(), field.lt(value)); case 'LESS_THAN_OR_EQUAL': - return and(field.exists(), field.lessThanOrEqual(value)); + return and(field.exists(), field.lte(value)); case 'GREATER_THAN': - return and(field.exists(), field.greaterThan(value)); + return and(field.exists(), field.gt(value)); case 'GREATER_THAN_OR_EQUAL': - return and(field.exists(), field.greaterThanOrEqual(value)); + return and(field.exists(), field.gte(value)); case 'EQUAL': - return and(field.exists(), field.equal(value)); + return and(field.exists(), field.eq(value)); case 'NOT_EQUAL': - return and(field.exists(), field.notEqual(value)); + return and(field.exists(), field.neq(value)); case 'ARRAY_CONTAINS': return and(field.exists(), field.arrayContains(value)); case 'IN': { diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 7ab677218..2976a8272 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -1,16 +1,19 @@ import * as firestore from '@google-cloud/firestore'; +import * as deepEqual from 'fast-deep-equal'; import {google} from '../protos/firestore_v1_proto_api'; import { Accumulator, - AggregateTarget, + AccumulatorTarget, Expr, + ExprWithAlias, Field, Fields, FilterCondition, Ordering, Selectable, } from './expression'; -import Firestore, {Timestamp} from './index'; +import Firestore, {FieldPath, QueryDocumentSnapshot, Timestamp} from './index'; +import {validateFieldPath} from './path'; import {ExecutionUtil} from './pipeline-util'; import {DocumentReference} from './reference/document-reference'; import {Serializer} from './serializer'; @@ -30,13 +33,15 @@ import { Select, Sort, Stage, + Distinct, } from './stage'; -import {ApiMapValue} from './types'; +import {ApiMapValue, defaultConverter} from './types'; import * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; import IStructuredPipeline = google.firestore.v1.IStructuredPipeline; import IStage = google.firestore.v1.Pipeline.IStage; import {QueryCursor} from './reference/types'; +import {isOptionalEqual} from './util'; export class PipelineSource { constructor(private db: Firestore) {} @@ -90,14 +95,14 @@ export class Pipeline< result.set(selectable as string, Field.of(selectable)); } else if (selectable instanceof Field) { result.set((selectable as Field).fieldName(), selectable); - } else if (selectable instanceof AggregateTarget) { - const target = selectable as AggregateTarget; - result.set(target.field.fieldName(), target.accumulator); } 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; @@ -121,16 +126,49 @@ export class Pipeline< return new Pipeline(this.db, copy); } - aggregate(...targets: AggregateTarget[]): Pipeline { + distinct(...groups: (string | Selectable)[]): Pipeline { const copy = this.stages.map(s => s); - copy.push( - new Aggregate( - new Map(), - new Map( - targets.map(target => [target.field.fieldName(), target.accumulator]) + copy.push(new Distinct(this.selectablesToMap(groups || []))); + return new Pipeline(this.db, copy); + } + + aggregate(...accumulators: AccumulatorTarget[]): Pipeline; + aggregate(options: { + accumulators: AccumulatorTarget[]; + groups?: (string | Selectable)[]; + }): Pipeline; + aggregate( + optionsOrTarget: + | AccumulatorTarget + | {accumulators: AccumulatorTarget[]; groups?: (string | Selectable)[]}, + ...rest: AccumulatorTarget[] + ): Pipeline { + const copy = this.stages.map(s => s); + if ('accumulators' in optionsOrTarget) { + copy.push( + new Aggregate( + new Map( + optionsOrTarget.accumulators.map((target: AccumulatorTarget) => [ + target.alias, + target.expr, + ]) + ), + this.selectablesToMap(optionsOrTarget.groups || []) ) - ) - ); + ); + } else { + copy.push( + new Aggregate( + new Map( + [optionsOrTarget, ...rest].map(target => [ + target.alias, + target.expr, + ]) + ), + new Map() + ) + ); + } return new Pipeline(this.db, copy); } @@ -160,21 +198,39 @@ export class Pipeline< return new Pipeline(this.db, copy); } - sort(orderings: Ordering[]): Pipeline; - sort( - orderings: Ordering[], - density?: 'unspecified' | 'required', - truncation?: 'unspecified' | 'disabled' - ): Pipeline; + sort(...orderings: Ordering[]): Pipeline; + sort(options: { + orderings: Ordering[]; + density?: 'unspecified' | 'required'; + truncation?: 'unspecified' | 'disabled'; + }): Pipeline; sort( - orderings: Ordering[], - density?: 'unspecified' | 'required', - truncation?: 'unspecified' | 'disabled' + optionsOrOrderings: + | Ordering + | { + orderings: Ordering[]; + density?: 'unspecified' | 'required'; + truncation?: 'unspecified' | 'disabled'; + }, + ...rest: Ordering[] ): Pipeline { const copy = this.stages.map(s => s); - copy.push( - new Sort(orderings, density ?? 'unspecified', truncation ?? 'unspecified') - ); + // Option object + if ('orderings' in optionsOrOrderings) { + copy.push( + new Sort( + optionsOrOrderings.orderings, + optionsOrOrderings.density ?? 'unspecified', + optionsOrOrderings.truncation ?? 'unspecified' + ) + ); + } else { + // Ordering object + copy.push( + new Sort([optionsOrOrderings, ...rest], 'unspecified', 'unspecified') + ); + } + return new Pipeline(this.db, copy); } @@ -283,7 +339,9 @@ export class PipelineResult< AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData, > { - private _ref: DocumentReference | undefined; + private readonly _ref: + | DocumentReference + | undefined; private _serializer: Serializer; public readonly _readTime: Timestamp | undefined; public readonly _createTime: Timestamp | undefined; @@ -311,7 +369,7 @@ export class PipelineResult< * @internal * @private **/ - readonly _fieldsProto?: ApiMapValue | null, + readonly _fieldsProto?: ApiMapValue, readTime?: Timestamp, createTime?: Timestamp, updateTime?: Timestamp @@ -322,4 +380,233 @@ export class PipelineResult< this._createTime = createTime; this._updateTime = updateTime; } + + get ref(): DocumentReference | undefined { + return this._ref; + } + + /** + * The ID of the document for which this DocumentSnapshot contains data. + * + * @type {string} + * @name DocumentSnapshot#id + * @readonly + * + * @example + * ``` + * let documentRef = firestore.doc('col/doc'); + * + * documentRef.get().then((documentSnapshot) => { + * if (documentSnapshot.exists) { + * console.log(`Document found with name '${documentSnapshot.id}'`); + * } + * }); + * ``` + */ + get id(): string | undefined { + return this._ref?.id; + } + + /** + * The time the document was created. Undefined for documents that don't + * exist. + * + * @type {Timestamp|undefined} + * @name DocumentSnapshot#createTime + * @readonly + * + * @example + * ``` + * let documentRef = firestore.doc('col/doc'); + * + * documentRef.get().then(documentSnapshot => { + * if (documentSnapshot.exists) { + * let createTime = documentSnapshot.createTime; + * console.log(`Document created at '${createTime.toDate()}'`); + * } + * }); + * ``` + */ + get createTime(): Timestamp | undefined { + return this._createTime; + } + + /** + * The time the document was last updated (at the time the snapshot was + * generated). Undefined for documents that don't exist. + * + * @type {Timestamp|undefined} + * @name DocumentSnapshot#updateTime + * @readonly + * + * @example + * ``` + * let documentRef = firestore.doc('col/doc'); + * + * documentRef.get().then(documentSnapshot => { + * if (documentSnapshot.exists) { + * let updateTime = documentSnapshot.updateTime; + * console.log(`Document updated at '${updateTime.toDate()}'`); + * } + * }); + * ``` + */ + get updateTime(): Timestamp | undefined { + return this._updateTime; + } + + /** + * The time this snapshot was read. + * + * @type {Timestamp} + * @name DocumentSnapshot#readTime + * @readonly + * + * @example + * ``` + * let documentRef = firestore.doc('col/doc'); + * + * documentRef.get().then(documentSnapshot => { + * let readTime = documentSnapshot.readTime; + * console.log(`Document read at '${readTime.toDate()}'`); + * }); + * ``` + */ + get readTime(): Timestamp { + if (this._readTime === undefined) { + throw new Error("Called 'readTime' on a local document"); + } + return this._readTime; + } + + /** + * Retrieves all fields in the document as an object. Returns 'undefined' if + * the document doesn't exist. + * + * @returns {T|undefined} An object containing all fields in the document or + * 'undefined' if the document doesn't exist. + * + * @example + * ``` + * let documentRef = firestore.doc('col/doc'); + * + * documentRef.get().then(documentSnapshot => { + * let data = documentSnapshot.data(); + * console.log(`Retrieved data: ${JSON.stringify(data)}`); + * }); + * ``` + */ + data(): AppModelType | undefined { + const fields = this._fieldsProto; + + if (fields === undefined) { + return undefined; + } + + // We only want to use the converter and create a new QueryDocumentSnapshot + // if a converter has been provided. + if (!!this.ref && this.ref._converter !== defaultConverter()) { + const untypedReference = new DocumentReference( + this.ref.firestore, + this.ref._path + ); + return this.ref._converter.fromFirestore( + new QueryDocumentSnapshot( + untypedReference, + this._fieldsProto!, + this.readTime, + this.createTime!, + this.updateTime! + ) + ); + } else { + const obj: firestore.DocumentData = {}; + for (const prop of Object.keys(fields)) { + obj[prop] = this._serializer.decodeValue(fields[prop]); + } + return obj as AppModelType; + } + } + + /** + * Retrieves the field specified by `field`. + * + * @param {string|FieldPath} 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. + * + * @example + * ``` + * let documentRef = firestore.doc('col/doc'); + * + * documentRef.set({ a: { b: 'c' }}).then(() => { + * return documentRef.get(); + * }).then(documentSnapshot => { + * let field = documentSnapshot.get('a.b'); + * console.log(`Retrieved field value: ${field}`); + * }); + * ``` + */ + // 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(field: string | FieldPath): any { + validateFieldPath('field', field); + + const protoField = this.protoField(field); + + if (protoField === undefined) { + return undefined; + } + + return this._serializer.decodeValue(protoField); + } + + /** + * Retrieves the field specified by 'fieldPath' in its Protobuf JS + * representation. + * + * @private + * @internal + * @param field The path (e.g. 'foo' or 'foo.bar') to a specific field. + * @returns The Protobuf-encoded data at the specified field location or + * undefined if no such field exists. + */ + protoField(field: string | FieldPath): api.IValue | undefined { + let fields: ApiMapValue | api.IValue | undefined = this._fieldsProto; + + if (fields === undefined) { + return undefined; + } + + const components = FieldPath.fromArgument(field).toArray(); + while (components.length > 1) { + fields = (fields as ApiMapValue)[components.shift()!]; + + if (!fields || !fields.mapValue) { + return undefined; + } + + fields = fields.mapValue.fields!; + } + + return (fields as ApiMapValue)[components[0]]; + } + + /** + * Returns true if the document's data and path in this `DocumentSnapshot` is + * equal to the provided value. + * + * @param {*} other The value to compare against. + * @return {boolean} true if this `DocumentSnapshot` is equal to the provided + * value. + */ + isEqual(other: PipelineResult): boolean { + return ( + this === other || + (isOptionalEqual(this._ref, other._ref) && + deepEqual(this._fieldsProto, other._fieldsProto)) + ); + } } diff --git a/dev/src/reference/aggregate-query.ts b/dev/src/reference/aggregate-query.ts index a4c497571..f614e0d4e 100644 --- a/dev/src/reference/aggregate-query.ts +++ b/dev/src/reference/aggregate-query.ts @@ -22,7 +22,14 @@ import * as deepEqual from 'fast-deep-equal'; import * as firestore from '@google-cloud/firestore'; import {Aggregate, AggregateSpec} from '../aggregate'; -import {AggregateTarget, avg, count, countAll, Field, sum} from '../expression'; +import { + AccumulatorTarget, + avg, + count, + countAll, + Field, + sum, +} from '../expression'; import {Pipeline} from '../pipeline'; import {Timestamp} from '../timestamp'; import {mapToArray, requestTag, wrapError} from '../util'; @@ -339,17 +346,17 @@ export class AggregateQuery< (aggregate, clientAlias) => { if (aggregate.aggregateType === 'count') { if (aggregate._field === undefined) { - return countAll().toField(clientAlias); + return countAll().as(clientAlias); } - return count(Field.of(aggregate._field)).toField(clientAlias); + return count(Field.of(aggregate._field)).as(clientAlias); } else if (aggregate.aggregateType === 'avg') { - return avg(Field.of(aggregate._field!)).toField(clientAlias); + return avg(Field.of(aggregate._field!)).as(clientAlias); } else { - return sum(Field.of(aggregate._field!)).toField(clientAlias); + return sum(Field.of(aggregate._field!)).as(clientAlias); } } ); - return this._query.toPipeline().aggregate(...aggregates); + return this._query.pipeline().aggregate(...aggregates); } /** diff --git a/dev/src/reference/query.ts b/dev/src/reference/query.ts index 75f6706b8..e335ae2d1 100644 --- a/dev/src/reference/query.ts +++ b/dev/src/reference/query.ts @@ -666,7 +666,7 @@ export class Query< ); } - toPipeline(): Pipeline { + pipeline(): Pipeline { let pipeline; if (this._queryOptions.allDescendants) { pipeline = this.firestore @@ -701,7 +701,7 @@ export class Query< if (exists.length > 1) { const [first, ...rest] = exists; pipeline = pipeline.where(and(first, ...rest)); - } else if (exists.length == 1) { + } else if (exists.length === 1) { pipeline = pipeline.where(exists[0]); } @@ -720,7 +720,11 @@ export class Query< return Ordering.of(Field.of(fieldOrder.field), dir); }); if (orderings.length > 0) { - pipeline = pipeline.sort(orderings, 'required', 'unspecified'); + pipeline = pipeline.sort({ + orderings: orderings, + density: 'required', + truncation: 'unspecified', + }); } // Cursors, Limit and Offset @@ -729,18 +733,9 @@ export class Query< !!this._queryOptions.endAt || this._queryOptions.limitType === LimitType.Last ) { - let paginating = pipeline.paginate(this._queryOptions.limit || 10); - if (this._queryOptions.startAt) { - paginating = paginating.withStartCursor(this._queryOptions.startAt!); - } - if (this._queryOptions.endAt) { - paginating = paginating.withEndCursor(this._queryOptions.endAt!); - } - if (this._queryOptions.limit === LimitType.Last) { - return paginating.lastPage(); - } else { - return paginating.firstPage(); - } + throw new Error( + 'Query to Pipeline conversion: cursors and limitToLast is not supported yet.' + ); } else { if (this._queryOptions.offset) { pipeline = pipeline.offset(this._queryOptions.offset); diff --git a/dev/src/reference/vector-query.ts b/dev/src/reference/vector-query.ts index ad853c73b..39bcc7c82 100644 --- a/dev/src/reference/vector-query.ts +++ b/dev/src/reference/vector-query.ts @@ -137,7 +137,7 @@ export class VectorQuery< distanceMeasure: this.options.distanceMeasure.toLowerCase(), } as FindNearestOptions; return this.query - .toPipeline() + .pipeline() .where(Field.of(this.vectorField).exists()) .findNearest(Field.of(this.vectorField), this.queryVector, options); } diff --git a/dev/src/stage.ts b/dev/src/stage.ts index ca8d5f39b..842b09cca 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -34,21 +34,34 @@ export class Aggregate implements Stage { name = 'aggregate'; constructor( - private groups: Map, - private accumulators: Map + private accumulators: Map, + private groups: Map ) {} _toProto(serializer: Serializer): api.Pipeline.IStage { return { name: this.name, args: [ - serializer.encodeValue(this.groups)!, serializer.encodeValue(this.accumulators)!, + serializer.encodeValue(this.groups)!, ], }; } } +export class Distinct implements Stage { + name = 'distinct'; + + constructor(private groups: Map) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.groups)!], + }; + } +} + export class Collection implements Stage { name = 'collection'; diff --git a/dev/src/util.ts b/dev/src/util.ts index c4b4754c1..c5a0de214 100644 --- a/dev/src/util.ts +++ b/dev/src/util.ts @@ -316,6 +316,30 @@ export function isArrayEqual boolean}>( return true; } +/** + * Verifies equality for an optional type using the `isEqual` interface. + * + * @private + * @internal + * @param left Optional object supporting `isEqual`. + * @param right Optional object supporting `isEqual`. + * @return True if equal. + */ +export function isOptionalEqual boolean}>( + left: T | undefined, + right: T | undefined +): boolean { + if (left === undefined && right === undefined) { + return true; + } + + if (left === undefined || right === undefined) { + return false; + } + + return left.isEqual(right); +} + /** * Verifies equality for an array of primitives. * diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 8dcfe7006..97d46eb38 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -18,11 +18,41 @@ import { Query, QueryDocumentSnapshot, } from '../src'; +import { + add, + and, + arrayContains, + arrayContainsAny, + arrayElement, + arrayFilter, + arrayTransform, + avg, + countAll, + endsWith, + eq, + Field, + gt, + like, + lt, + neq, + not, + isNull, + or, + regexContains, + regexMatch, + startsWith, + strConcat, + subtract, + cosineDistance, + dotProductDistance, + euclideanDistance, + Constant, +} from '../src/expression'; +import {PipelineResult} from '../src/pipeline'; import {verifyInstance} from '../test/util/helpers'; import {DeferredPromise, getTestRoot} from './firestore'; -import {IndexTestHelper} from './index_test_helper'; -describe('Pipeline class', () => { +describe.only('Pipeline class', () => { let firestore: Firestore; let randomCol: CollectionReference; @@ -49,22 +79,25 @@ describe('Pipeline class', () => { return randomCol; } - function expectDocs(result: QuerySnapshot, ...docs: string[]): void; - function expectDocs(result: QuerySnapshot, ...data: DocumentData[]): void; + function expectResults(result: PipelineResult[], ...docs: string[]): void; + function expectResults( + result: PipelineResult[], + ...data: DocumentData[] + ): void; - function expectDocs( - result: QuerySnapshot, + function expectResults( + result: PipelineResult[], ...data: DocumentData[] | string[] ): void { - expect(result.size).to.equal(data.length); + expect(result.length).to.equal(data.length); if (data.length > 0) { if (typeof data[0] === 'string') { - const actualIds = result.docs.map(docSnapshot => docSnapshot.id); + const actualIds = result.map(result => result.ref?.id); expect(actualIds).to.deep.equal(data); } else { - result.forEach(doc => { - expect(doc.data()).to.deep.equal(data.shift()); + result.forEach(r => { + expect(r.data()).to.deep.equal(data.shift()); }); } } @@ -72,7 +105,7 @@ describe('Pipeline class', () => { async function compareQueryAndPipeline(query: Query): Promise { const queryResults = await query.get(); - const pipeline = query.toPipeline(); + const pipeline = query.pipeline(); const pipelineResults = await pipeline.execute(); expect(queryResults.docs.map(s => s._fieldsProto)).to.deep.equal( @@ -81,14 +114,111 @@ describe('Pipeline class', () => { return queryResults; } - beforeEach(() => { + 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}, + }, + book2: { + title: 'Pride and Prejudice', + author: 'Jane Austen', + genre: 'Romance', + published: 1813, + rating: 4.5, + tags: ['classic', 'social commentary', 'love'], + awards: {none: true}, + }, + book3: { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + genre: 'Magical Realism', + published: 1967, + rating: 4.3, + tags: ['family', 'history', 'fantasy'], + awards: {nobel: true, nebula: false}, + }, + book4: { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: {hugo: false, nebula: false}, + }, + book5: { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + genre: 'Dystopian', + published: 1985, + rating: 4.1, + tags: ['feminism', 'totalitarianism', 'resistance'], + awards: {'arthur c. clarke': true, 'booker prize': false}, + }, + book6: { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + genre: 'Psychological Thriller', + published: 1866, + rating: 4.3, + tags: ['philosophy', 'crime', 'redemption'], + awards: {none: true}, + }, + book7: { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + genre: 'Southern Gothic', + published: 1960, + rating: 4.2, + tags: ['racism', 'injustice', 'coming-of-age'], + awards: {pulitzer: true}, + }, + book8: { + title: '1984', + author: 'George Orwell', + genre: 'Dystopian', + published: 1949, + rating: 4.2, + tags: ['surveillance', 'totalitarianism', 'propaganda'], + awards: {prometheus: true}, + }, + book9: { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + genre: 'Modernist', + published: 1925, + rating: 4.0, + tags: ['wealth', 'american dream', 'love'], + awards: {none: true}, + }, + 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); + } + + before(async () => { randomCol = getTestRoot(); + await setupBookDocs(); firestore = randomCol.firestore; }); afterEach(() => verifyInstance(firestore)); - it('basic collection', async () => { + it('empty results as expected', async () => { const result = await firestore .pipeline() .collection(randomCol.path) @@ -96,4 +226,508 @@ describe('Pipeline class', () => { .execute(); expect(result).to.be.empty; }); + + it('returns aggregate results as expected', async () => { + let result = await firestore + .pipeline() + .collection(randomCol.path) + .aggregate(countAll().as('count')) + .execute(); + expectResults(result, {count: 10}); + + result = await randomCol + .pipeline() + .where(eq('genre', 'Science Fiction')) + .aggregate( + countAll().as('count'), + avg('rating').as('avg_rating'), + Field.of('rating').max().as('max_rating') + ) + .execute(); + expectResults(result, {count: 2, avg_rating: 4.4, max_rating: 4.6}); + }); + + it('rejects groups without accumulators', async () => { + await expect( + randomCol + .pipeline() + .where(lt('published', 1900)) + .aggregate({ + accumulators: [], + groups: ['genre'], + }) + .execute() + ).to.be.rejected; + }); + + it('returns distinct values as expected', async () => { + const results = await randomCol + .pipeline() + .where(lt('published', 1900)) + .distinct(Field.of('genre').toLowercase().as('lower_genre')) + .execute(); + expectResults( + results, + {lower_genre: 'romance'}, + {lower_genre: 'psychological thriller'} + ); + }); + + it('returns group and accumulate results', async () => { + const results = await randomCol + .pipeline() + .where(lt('published', 1984)) + .aggregate({ + accumulators: [avg('rating').as('avg_rating')], + groups: ['genre'], + }) + .where(gt('avg_rating', 4.3)) + .execute(); + expectResults( + results, + {avg_rating: 4.7, genre: 'Fantasy'}, + {avg_rating: 4.5, genre: 'Romance'}, + {avg_rating: 4.4, genre: 'Science Fiction'} + ); + }); + + it('returns min and max accumulations', async () => { + const results = await randomCol + .pipeline() + .aggregate( + countAll().as('count'), + Field.of('rating').max().as('max_rating'), + Field.of('published').min().as('min_published') + ) + .execute(); + expectResults(results, { + count: 10, + max_rating: 4.7, + min_published: 1813, + }); + }); + + it('can select fields', async () => { + const results = await randomCol + .pipeline() + .select('title', 'author') + .sort(Field.of('author').ascending()) + .execute(); + expectResults( + results, + {title: "The Hitchhiker's Guide to the Galaxy", author: 'Douglas Adams'}, + {title: 'The Great Gatsby', author: 'F. Scott Fitzgerald'}, + {title: 'Dune', author: 'Frank Herbert'}, + {title: 'Crime and Punishment', author: 'Fyodor Dostoevsky'}, + { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + }, + {title: '1984', author: 'George Orwell'}, + {title: 'To Kill a Mockingbird', author: 'Harper Lee'}, + {title: 'The Lord of the Rings', author: 'J.R.R. Tolkien'}, + {title: 'Pride and Prejudice', author: 'Jane Austen'}, + {title: "The Handmaid's Tale", author: 'Margaret Atwood'} + ); + }); + + it('where with and', async () => { + const results = await randomCol + .pipeline() + .where(and(gt('rating', 4.5), eq('genre', 'Science Fiction'))) + .execute(); + expectResults(results, 'book10'); + }); + + it('where with or', async () => { + const results = await randomCol + .pipeline() + .where(or(eq('genre', 'Romance'), eq('genre', 'Dystopian'))) + .select('title') + .execute(); + expectResults( + results, + {title: 'Pride and Prejudice'}, + {title: "The Handmaid's Tale"}, + {title: '1984'} + ); + }); + + it('offset and limits', async () => { + const results = await randomCol + .pipeline() + .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'} + ); + }); + + it('arrayContains works', async () => { + const results = await randomCol + .pipeline() + .where(arrayContains('tags', 'comedy')) + .select('title') + .execute(); + expectResults(results, {title: "The Hitchhiker's Guide to the Galaxy"}); + }); + + it('arrayContainsAny works', async () => { + const results = await randomCol + .pipeline() + .where(arrayContainsAny('tags', ['comedy', 'classic'])) + .select('title') + .execute(); + expectResults( + results, + {title: "The Hitchhiker's Guide to the Galaxy"}, + {title: 'Pride and Prejudice'} + ); + }); + + it('arrayContainsAll works', async () => { + const results = await randomCol + .pipeline() + .where(Field.of('tags').arrayContainsAll('adventure', 'magic')) + .select('title') + .execute(); + expectResults(results, {title: 'The Lord of the Rings'}); + }); + + it('arrayLength works', async () => { + const results = await randomCol + .pipeline() + .select(Field.of('tags').arrayLength().as('tagsCount')) + .where(eq('tagsCount', 3)) + .execute(); + expect(results.length).to.equal(10); + }); + + it('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('arrayFilter works', async () => { + const results = await randomCol + .pipeline() + .select( + arrayFilter(Field.of('tags'), arrayElement().eq('comedy')).as( + 'filteredTags' + ) + ) + .limit(1) + .execute(); + + expectResults(results, { + filteredTags: ['comedy'], + }); + }); + + it('arrayTransform works', async () => { + const results = await randomCol + .pipeline() + .select( + arrayTransform( + Field.of('tags'), + arrayElement().strConcat('transformed') + ).as('transformedTags') + ) + .limit(1) + .execute(); + expectResults(results, { + transformedTags: [ + 'comedytransformed', + 'spacetransformed', + 'adventuretransformed', + ], + }); + }); + + it('testStrConcat', async () => { + const results = await randomCol + .pipeline() + .select( + Field.of('author').strConcat(' - ', Field.of('title')).as('bookInfo') + ) + .limit(1) + .execute(); + expectResults(results, { + bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy", + }); + }); + + it('testStartsWith', async () => { + const results = await randomCol + .pipeline() + .where(startsWith('title', 'The')) + .select('title') + .sort(Field.of('title').ascending()) + .execute(); + expectResults( + results, + {title: 'The Great Gatsby'}, + {title: "The Handmaid's Tale"}, + {title: "The Hitchhiker's Guide to the Galaxy"}, + {title: 'The Lord of the Rings'} + ); + }); + + it('testEndsWith', async () => { + const results = await randomCol + .pipeline() + .where(endsWith('title', 'y')) + .select('title') + .sort(Field.of('title').descending()) + .execute(); + expectResults( + results, + {title: "The Hitchhiker's Guide to the Galaxy"}, + {title: 'The Great Gatsby'} + ); + }); + + it('testLength', async () => { + const results = await randomCol + .pipeline() + .select(Field.of('title').length().as('titleLength'), Field.of('title')) + .where(gt('titleLength', 20)) + .execute(); + expectResults( + results, + {titleLength: 32, title: "The Hitchhiker's Guide to the Galaxy"}, + { + titleLength: 27, + title: 'One Hundred Years of Solitude', + } + ); + }); + + it('testToLowercase', async () => { + const results = await randomCol + .pipeline() + .select(Field.of('title').toLowercase().as('lowercaseTitle')) + .limit(1) + .execute(); + expectResults(results, { + lowercaseTitle: "the hitchhiker's guide to the galaxy", + }); + }); + + it('testToUppercase', async () => { + const results = await randomCol + .pipeline() + .select(Field.of('author').toUppercase().as('uppercaseAuthor')) + .limit(1) + .execute(); + expectResults(results, {uppercaseAuthor: 'DOUGLAS ADAMS'}); + }); + + it('testTrim', async () => { + const results = await randomCol + .pipeline() + .addFields(strConcat(' ', Field.of('title'), ' ').as('spacedTitle')) + .select( + Field.of('spacedTitle').trim().as('trimmedTitle'), + Field.of('spacedTitle') + ) + .limit(1) + .execute(); + expectResults(results, { + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + trimmedTitle: "The Hitchhiker's Guide to the Galaxy", + }); + }); + + it('testLike', async () => { + const results = await randomCol + .pipeline() + .where(like('title', '%Guide%')) + .select('title') + .execute(); + expectResults(results, {title: "The Hitchhiker's Guide to the Galaxy"}); + }); + + it('testRegexContains', async () => { + const results = await randomCol + .pipeline() + .where(regexContains('title', '(?i)(the|of)')) + .execute(); + expect(results.length).to.equal(5); + }); + + it('testRegexMatches', async () => { + const results = await randomCol + .pipeline() + .where(regexMatch('title', '.*(?i)(the|of).*')) + .execute(); + expect(results.length).to.equal(5); + }); + + it('testArithmeticOperations', async () => { + const results = await randomCol + .pipeline() + .select( + add(Field.of('rating'), 1).as('ratingPlusOne'), + subtract(Field.of('published'), 1900).as('yearsSince1900'), + Field.of('rating').multiply(10).as('ratingTimesTen'), + Field.of('rating').divide(2).as('ratingDividedByTwo') + ) + .limit(1) + .execute(); + expectResults(results, { + ratingPlusOne: 5.2, + yearsSince1900: 79, + ratingTimesTen: 42, + ratingDividedByTwo: 2.1, + }); + }); + + it('testComparisonOperators', async () => { + const results = await randomCol + .pipeline() + .where( + and( + gt('rating', 4.2), + lt(Field.of('rating'), 4.5), + neq('genre', 'Science Fiction') + ) + ) + .select('rating', 'title') + .sort(Field.of('title').ascending()) + .execute(); + expectResults( + results, + {rating: 4.3, title: 'Crime and Punishment'}, + { + rating: 4.3, + title: 'One Hundred Years of Solitude', + }, + {rating: 4.5, title: 'Pride and Prejudice'} + ); + }); + + it('testLogicalOperators', async () => { + const results = await randomCol + .pipeline() + .where( + or( + and(gt('rating', 4.5), eq('genre', 'Science Fiction')), + lt('published', 1900) + ) + ) + .select('title') + .sort(Field.of('title').ascending()) + .execute(); + expectResults( + results, + {title: 'Crime and Punishment'}, + {title: 'Dune'}, + {title: 'Pride and Prejudice'} + ); + }); + + it('testChecks', async () => { + const results = await randomCol + .pipeline() + .where(not(Field.of('rating').isNaN())) + .select( + isNull('rating').as('ratingIsNull'), + not(Field.of('rating').isNaN()).as('ratingIsNotNaN') + ) + .limit(1) + .execute(); + expectResults(results, {ratingIsNull: false, ratingIsNotNaN: true}); + }); + + it('testMapGet', async () => { + const results = await randomCol + .pipeline() + .select( + Field.of('awards').mapGet('hugo').as('hugoAward'), + Field.of('title') + ) + .where(eq('hugoAward', true)) + .execute(); + expectResults( + results, + {hugoAward: true, title: "The Hitchhiker's Guide to the Galaxy"}, + {hugoAward: true, title: 'Dune'} + ); + }); + + // it('testParent', async () => { + // const results = await randomCol + // .pipeline() + // .select( + // parent(randomCol.doc('chile').collection('subCollection').path).as( + // 'parent' + // ) + // ) + // .limit(1) + // .execute(); + // expect(results[0].data().parent.endsWith('/books')).to.be.true; + // }); + // + // it('testCollectionId', async () => { + // const results = await randomCol + // .pipeline() + // .select(collectionId(randomCol.doc('chile')).as('collectionId')) + // .limit(1) + // .execute(); + // expectResults(results, {collectionId: 'books'}); + // }); + + it('testDistanceFunctions', async () => { + const sourceVector = [0.1, 0.1]; + const targetVector = [0.5, 0.8]; + const results = await randomCol + .pipeline() + .select( + cosineDistance(Constant.ofVector(sourceVector), targetVector).as( + 'cosineDistance' + ), + dotProductDistance(Constant.ofVector(sourceVector), targetVector).as( + 'dotProductDistance' + ), + euclideanDistance(Constant.ofVector(sourceVector), targetVector).as( + 'euclideanDistance' + ) + ) + .limit(1) + .execute(); + + expectResults(results, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855, + }); + }); + + it('testNestedFields', async () => { + const results = await randomCol + .pipeline() + .where(eq('awards.hugo', true)) + .select('title', 'awards.hugo') + .execute(); + expectResults( + results, + {title: "The Hitchhiker's Guide to the Galaxy", 'awards.hugo': true}, + {title: 'Dune', 'awards.hugo': true} + ); + }); }); diff --git a/dev/system-test/query.ts b/dev/system-test/query.ts index efc42be03..cc0b877d2 100644 --- a/dev/system-test/query.ts +++ b/dev/system-test/query.ts @@ -23,7 +23,7 @@ import {verifyInstance} from '../test/util/helpers'; import {DeferredPromise, getTestRoot} from './firestore'; import {IndexTestHelper} from './index_test_helper'; -describe.only('Query class', () => { +describe('Query class', () => { interface PaginatedResults { pages: number; docs: QueryDocumentSnapshot[]; @@ -101,7 +101,7 @@ describe.only('Query class', () => { async function compareQueryAndPipeline(query: Query): Promise { const queryResults = await query.get(); - const pipeline = query.toPipeline(); + const pipeline = query.pipeline(); const pipelineResults = await pipeline.execute(); expect(pipelineResults.map(r => r._fieldsProto)).to.deep.equal( @@ -204,7 +204,7 @@ describe.only('Query class', () => { }); }); - it.skip('supports findNearest by EUCLIDEAN distance', async () => { + it('supports findNearest by EUCLIDEAN distance', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionReference = await indexTestHelper.createTestDocs([ @@ -234,7 +234,7 @@ describe.only('Query class', () => { .be.true; }); - it.skip('supports findNearest by COSINE distance', async () => { + it('supports findNearest by COSINE distance', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionReference = await indexTestHelper.setTestDocs({ @@ -276,7 +276,7 @@ describe.only('Query class', () => { ).to.be.true; }); - it.skip('supports findNearest by DOT_PRODUCT distance', async () => { + it('supports findNearest by DOT_PRODUCT distance', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionReference = await indexTestHelper.createTestDocs([ @@ -306,7 +306,7 @@ describe.only('Query class', () => { .be.true; }); - it.skip('findNearest works with converters', async () => { + it('findNearest works with converters', async () => { const indexTestHelper = new IndexTestHelper(firestore); class FooDistance { @@ -346,7 +346,7 @@ describe.only('Query class', () => { expect(res.docs[0].data().embedding).to.deep.equal([5, 5]); }); - it.skip('supports findNearest skipping fields of wrong types', async () => { + it('supports findNearest skipping fields of wrong types', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionRef = await indexTestHelper.createTestDocs([ @@ -381,7 +381,7 @@ describe.only('Query class', () => { .to.be.true; }); - it.skip('findNearest ignores mismatching dimensions', async () => { + it('findNearest ignores mismatching dimensions', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionRef = await indexTestHelper.createTestDocs([ @@ -411,7 +411,7 @@ describe.only('Query class', () => { .be.true; }); - it.skip('supports findNearest on non-existent field', async () => { + it('supports findNearest on non-existent field', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionRef = await indexTestHelper.createTestDocs([ @@ -434,7 +434,7 @@ describe.only('Query class', () => { expect(res.size).to.equal(0); }); - it.skip('supports findNearest on vector nested in a map', async () => { + it('supports findNearest on vector nested in a map', async () => { const indexTestHelper = new IndexTestHelper(firestore); const collectionReference = await indexTestHelper.createTestDocs([ @@ -498,7 +498,7 @@ describe.only('Query class', () => { res.docs.forEach(ds => expect(ds.get('embedding')).to.be.undefined); }); - it.skip('supports findNearest limits', async () => { + it('supports findNearest limits', async () => { const indexTestHelper = new IndexTestHelper(firestore); const embeddingVector = []; @@ -583,7 +583,7 @@ describe.only('Query class', () => { expectDocs(res, {count: 2}, {count: 3}); }); - it.skip('supports not-in', async () => { + it('supports not-in', async () => { await addDocs( {zip: 98101}, {zip: 91102}, @@ -645,7 +645,7 @@ describe.only('Query class', () => { expectDocs(res, {zip: 98101}, {zip: 98103}); }); - it.only('supports "in" with document ID array', async () => { + it('supports "in" with document ID array', async () => { const refs = await addDocs({count: 1}, {count: 2}, {count: 3}); const res = await compareQueryAndPipeline( randomCol.where(FieldPath.documentId(), 'in', [refs[0].id, refs[1]]) @@ -680,7 +680,7 @@ describe.only('Query class', () => { ); }); - it.skip('can query by FieldPath.documentId()', () => { + it('can query by FieldPath.documentId()', () => { const ref = randomCol.doc('foo'); return ref @@ -695,7 +695,7 @@ describe.only('Query class', () => { }); }); - it.skip('has orderBy() method', async () => { + it('has orderBy() method', async () => { await addDocs({foo: 'a'}, {foo: 'b'}); let res = await compareQueryAndPipeline(randomCol.orderBy('foo')); @@ -739,7 +739,7 @@ describe.only('Query class', () => { expect(received).to.equal(0); }); - it.skip('has limit() method on get()', async () => { + it('has limit() method on get()', async () => { await addDocs({foo: 'a'}, {foo: 'b'}); const res = await compareQueryAndPipeline( randomCol.orderBy('foo').limit(1) @@ -760,7 +760,7 @@ describe.only('Query class', () => { expect(received).to.equal(1); }); - it.skip('can run limit(num), where num is larger than the collection size on get()', async () => { + it('can run limit(num), where num is larger than the collection size on get()', async () => { await addDocs({foo: 'a'}, {foo: 'b'}); const res = await compareQueryAndPipeline( randomCol.orderBy('foo').limit(3) @@ -781,7 +781,7 @@ describe.only('Query class', () => { expect(received).to.equal(2); }); - it.skip('has limitToLast() method', async () => { + it('has limitToLast() method', async () => { await addDocs({doc: 1}, {doc: 2}, {doc: 3}); // const res = await compareQueryAndPipeline(randomCol.orderBy('doc').limitToLast(2)); const res = await randomCol.orderBy('doc').limitToLast(2).get(); @@ -790,13 +790,16 @@ describe.only('Query class', () => { it('limitToLast() supports Query cursors', async () => { await addDocs({doc: 1}, {doc: 2}, {doc: 3}, {doc: 4}, {doc: 5}); - const res = await compareQueryAndPipeline( - randomCol.orderBy('doc').startAt(2).endAt(4).limitToLast(5) - ); + const res = await randomCol + .orderBy('doc') + .startAt(2) + .endAt(4) + .limitToLast(5) + .get(); expectDocs(res, {doc: 2}, {doc: 3}, {doc: 4}); }); - it.skip('can use offset() method with get()', async () => { + it('can use offset() method with get()', async () => { await addDocs({foo: 'a'}, {foo: 'b'}); const res = await compareQueryAndPipeline( randomCol.orderBy('foo').offset(1) @@ -817,7 +820,7 @@ describe.only('Query class', () => { expect(received).to.equal(1); }); - it.skip('can run offset(num), where num is larger than the collection size on get()', async () => { + it('can run offset(num), where num is larger than the collection size on get()', async () => { await addDocs({foo: 'a'}, {foo: 'b'}); const res = await compareQueryAndPipeline( randomCol.orderBy('foo').offset(3) @@ -961,7 +964,7 @@ describe.only('Query class', () => { expect(received).to.equal(2); }); - it.skip('can query collection groups', async () => { + it('can query collection groups', async () => { // Use `randomCol` to get a random collection group name to use but ensure // it starts with 'b' for predictable ordering. const collectionGroup = 'b' + randomCol.id; @@ -1170,7 +1173,7 @@ describe.only('Query class', () => { // Skip this test if running against production because it results in a 'missing index' error. // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( + (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it : it)( 'supports OR queries with composite indexes', async () => { const collection = await testCollectionWithDocs({ @@ -1277,7 +1280,7 @@ describe.only('Query class', () => { // Skip this test if running against production because it results in a 'missing index' error. // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( + (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it : it)( 'supports OR queries on documents with missing fields', async () => { const collection = await testCollectionWithDocs({ @@ -1373,7 +1376,7 @@ describe.only('Query class', () => { // Skip this test if running against production because it results in a 'missing index' error. // The Firestore Emulator, however, does serve these queries. - (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it.skip : it)( + (process.env.FIRESTORE_EMULATOR_HOST === undefined ? it : it)( 'supports OR queries with not-in', async () => { const collection = await testCollectionWithDocs({ @@ -1786,7 +1789,7 @@ describe.only('Query class', () => { }); (process.env.FIRESTORE_EMULATOR_HOST === undefined - ? describe.skip + ? describe : describe.only)('multiple inequality', () => { it('supports multiple inequality queries', async () => { const collection = await testCollectionWithDocs({ From a514c652197ee8ed730a9658bad3cd6de7181dae Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 22 Aug 2024 10:37:31 -0400 Subject: [PATCH 05/31] Fix routing headers --- dev/src/expression.ts | 2917 +++++++++++++++++++++++++++++++- dev/src/pipeline-util.ts | 4 +- dev/src/pipeline.ts | 567 +++++-- dev/src/reference/query.ts | 2 +- dev/src/stage.ts | 10 +- dev/src/v1/firestore_client.ts | 2 +- dev/system-test/pipeline.ts | 3 +- 7 files changed, 3273 insertions(+), 232 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 32ab6f6f1..3fde72f9d 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -12,23 +12,45 @@ import {DocumentReference} from './reference/document-reference'; import {Serializer} from './serializer'; import {Timestamp} from './timestamp'; +/** + * An interface that represents a selectable expression. + */ export interface Selectable { selectable: true; } +/** + * An interface that represents a filter condition. + */ export interface FilterCondition { filterable: true; } +/** + * An interface that represents an accumulator. + */ export interface Accumulator { accumulator: true; } + +/** + * An accumulator target, which is an expression with an alias that also implements the Accumulator interface. + */ export type AccumulatorTarget = ExprWithAlias; +/** + * A filter expression, which is an expression that also implements the FilterCondition interface. + */ export type FilterExpr = Expr & FilterCondition; +/** + * A selectable expression, which is an expression that also implements the Selectable interface. + */ export type SelectableExpr = Expr & Selectable; +/** + * An enumeration of the different types of expressions. + */ export type ExprType = | 'Field' | 'Constant' @@ -36,8 +58,46 @@ export type ExprType = | 'ListOfExprs' | 'ExprWithAlias'; +/** + * Represents an expression that can be evaluated to a value within the execution of a {@link + * Pipeline}. + * + * Expressions are the building blocks for creating complex queries and transformations in + * Firestore pipelines. They can represent: + * + * - **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 { + /** + * 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. + */ add(other: Expr): Add; + + /** + * Creates an expression that adds this expression to a constant value. + * + * ```typescript + * // Add 5 to the value of the 'age' field + * Field.of("age").add(5); + * ``` + * + * @param other The constant value to add. + * @return A new `Expr` representing the addition operation. + */ add(other: any): Add; add(other: any): Add { if (other instanceof Expr) { @@ -46,7 +106,30 @@ export abstract class Expr { return new Add(this, Constant.of(other)); } + /** + * Creates an expression that subtracts another expression from this expression. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * Field.of("price").subtract(Field.of("discount")); + * ``` + * + * @param other The expression to subtract from this expression. + * @return A new `Expr` representing the subtraction operation. + */ subtract(other: Expr): Subtract; + + /** + * 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); + * ``` + * + * @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) { @@ -55,7 +138,30 @@ export abstract class Expr { return new Subtract(this, Constant.of(other)); } + /** + * Creates an expression that multiplies this expression by another expression. + * + * ```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); + * ``` + * + * @param other The constant value to multiply by. + * @return A new `Expr` representing the multiplication operation. + */ multiply(other: any): Multiply; multiply(other: any): Multiply { if (other instanceof Expr) { @@ -64,7 +170,30 @@ export abstract class Expr { return new Multiply(this, Constant.of(other)); } + /** + * Creates an expression that divides this expression by another expression. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * Field.of("total").divide(Field.of("count")); + * ``` + * + * @param other The expression to divide by. + * @return A new `Expr` representing the division operation. + */ divide(other: Expr): Divide; + + /** + * Creates an expression that divides this expression by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * Field.of("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) { @@ -73,7 +202,30 @@ export abstract class Expr { return new Divide(this, Constant.of(other)); } + /** + * 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); + * ``` + * + * @param other The expression to compare for equality. + * @return A new `Expr` representing the equality comparison. + */ eq(other: Expr): Eq; + + /** + * 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"); + * ``` + * + * @param other 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) { @@ -82,7 +234,30 @@ export abstract class Expr { return new Eq(this, Constant.of(other)); } + /** + * Creates an expression that checks if this expression is not equal to another expression. + * + * ```typescript + * // Check if the 'status' field is not equal to "completed" + * Field.of("status").neq("completed"); + * ``` + * + * @param other The expression to compare for inequality. + * @return A new `Expr` representing the inequality comparison. + */ neq(other: Expr): Neq; + + /** + * 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"); + * ``` + * + * @param other 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) { @@ -91,7 +266,30 @@ export abstract class Expr { return new Neq(this, Constant.of(other)); } + /** + * Creates an expression that checks if this expression is less than another expression. + * + * ```typescript + * // Check if the 'age' field is less than 'limit' + * Field.of("age").lt(Field.of('limit')); + * ``` + * + * @param other The expression to compare for less than. + * @return A new `Expr` representing the less than comparison. + */ lt(other: Expr): Lt; + + /** + * 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); + * ``` + * + * @param other 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) { @@ -100,7 +298,31 @@ export abstract class Expr { return new Lt(this, Constant.of(other)); } + /** + * Creates an expression that checks if this expression is less than or equal to another + * expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * Field.of("quantity").lte(Constant.of(20)); + * ``` + * + * @param other 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; + + /** + * 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); + * ``` + * + * @param other 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) { @@ -109,7 +331,30 @@ export abstract class Expr { return new Lte(this, Constant.of(other)); } + /** + * Creates an expression that checks if this expression is greater than another expression. + * + * ```typescript + * // Check if the 'age' field is greater than the 'limit' field + * Field.of("age").gt(Field.of("limit")); + * ``` + * + * @param other The expression to compare for greater than. + * @return A new `Expr` representing the greater than comparison. + */ gt(other: Expr): Gt; + + /** + * 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); + * ``` + * + * @param other 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) { @@ -118,7 +363,32 @@ export abstract class Expr { return new Gt(this, Constant.of(other)); } + /** + * Creates an expression that checks if this expression is greater than or equal to another + * expression. + * + * ```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)); + * ``` + * + * @param other 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; + + /** + * Creates an expression that checks if this expression is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'score' field is greater than or equal to 80 + * Field.of("score").gte(80); + * ``` + * + * @param other 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) { @@ -127,7 +397,30 @@ export abstract class Expr { return new Gte(this, Constant.of(other)); } + /** + * Creates an expression that concatenates an array expression with one or more other arrays. + * + * ```typescript + * // Combine the 'items' array with another array field. + * Field.of("items").arrayConcat(Field.of("otherItems")); + * ``` + * + * @param values The array expressions to concatenate. + * @return A new `Expr` representing the concatenated array. + */ arrayConcat(...values: Expr[]): ArrayConcat; + + /** + * Creates an expression that concatenates an array expression 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 values The array expressions or values to concatenate. + * @return A new `Expr` representing the concatenated array. + */ arrayConcat(...values: any[]): ArrayConcat; arrayConcat(...values: any[]): ArrayConcat { const exprValues = values.map(value => @@ -136,7 +429,30 @@ export abstract class Expr { return new ArrayConcat(this, exprValues); } + /** + * Creates an expression that checks if an array contains a specific element. + * + * ```typescript + * // Check if the 'sizes' array contains the value from the 'selectedSize' field + * Field.of("sizes").arrayContains(Field.of("selectedSize")); + * ``` + * + * @param element The element to search for in the array. + * @return A new `Expr` representing the 'array_contains' comparison. + */ arrayContains(element: Expr): ArrayContains; + + /** + * 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"); + * ``` + * + * @param element 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) { @@ -145,7 +461,30 @@ export abstract class Expr { return new ArrayContains(this, Constant.of(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")); + * ``` + * + * @param values The elements to check for in the array. + * @return A new `Expr` representing the 'array_contains_all' comparison. + */ arrayContainsAll(...values: Expr[]): ArrayContainsAll; + + /** + * 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")); + * ``` + * + * @param values 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 => @@ -154,7 +493,31 @@ export abstract class Expr { return new ArrayContainsAll(this, exprValues); } + /** + * Creates an expression that checks if an array contains any of the specified elements. + * + * ```typescript + * // Check if the 'categories' array contains either values from field "cate1" or "cate2" + * Field.of("categories").arrayContainsAny(Field.of("cate1"), Field.of("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; + + /** + * Creates an expression that checks if an array contains any of the specified elements. + * + * ```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"); + * ``` + * + * @param values 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 => @@ -163,19 +526,80 @@ export abstract class Expr { return new ArrayContainsAny(this, exprValues); } + /** + * Creates an expression that filters elements from an array using the given {@link + * FilterCondition} and returns the filtered elements as a new array. + * + * ```typescript + * // Get items from the 'inventoryPrices' array where the array item is greater than 0 + * // Note we use {@link Function#arrayElement} to represent array elements to construct a + * // filtering condition. + * Field.of("inventoryPrices").arrayFilter(arrayElement().gt(0)); + * ``` + * + * @param filter The {@link FilterCondition} to apply to the array elements. + * @return A new `Expr` representing the filtered array. + */ arrayFilter(filter: FilterExpr): ArrayFilter { return new ArrayFilter(this, filter); } + /** + * Creates an expression that calculates the length of an array. + * + * ```typescript + * // Get the number of items in the 'cart' array + * Field.of("cart").arrayLength(); + * ``` + * + * @return A new `Expr` representing the length of the array. + */ arrayLength(): ArrayLength { return new ArrayLength(this); } + /** + * Creates an expression that applies a transformation function to each element in an array and + * returns the new array as the result of the evaluation. + * + * ```typescript + * // Convert all strings in the 'names' array to uppercase + * Field.of("names").arrayTransform(arrayElement().toUppercase()); + * ``` + * + * @param transform The {@link Function} to apply to each array element. + * @return A new `Expr` representing the transformed array. + */ arrayTransform(transform: Function): ArrayTransform { return new ArrayTransform(this, transform); } + /** + * Creates an expression that checks if this expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * Field.of("category").in("Electronics", Field.of("primaryType")); + * ``` + * + * @param others The values or expressions to check against. + * @return A new `Expr` representing the 'IN' comparison. + */ in(...others: Expr[]): In; + + /** + * Creates an expression that checks if this expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * Field.of("category").in("Electronics", Field.of("primaryType")); + * ``` + * + * @param others The values or expressions to check against. + * @return A new `Expr` representing the 'IN' comparison. + */ in(...others: any[]): In; in(...others: any[]): In { const exprOthers = others.map(other => @@ -184,23 +608,72 @@ export abstract class Expr { return new In(this, exprOthers); } + /** + * Creates an expression that checks if this expression evaluates to 'NaN' (Not a Number). + * + * ```typescript + * // Check if the result of a calculation is NaN + * Field.of("value").divide(0).isNaN(); + * ``` + * + * @return A new `Expr` representing the 'isNaN' check. + */ isNaN(): IsNan { return new IsNan(this); } - isNull(): IsNull { - return new IsNull(this); - } - + /** + * Creates an expression that checks if a field exists in the document. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * Field.of("phoneNumber").exists(); + * ``` + * + * @return A new `Expr` representing the 'exists' check. + */ exists(): Exists { return new Exists(this); } + /** + * Creates an expression that calculates the length of a string. + * + * ```typescript + * // Get the length of the 'name' field + * Field.of("name").length(); + * ``` + * + * @return A new `Expr` representing the length of the string. + */ length(): Length { return new Length(this); } + /** + * 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%"); + * ``` + * + * @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; + + /** + * 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%"); + * ``` + * + * @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 (stringOrExpr instanceof Expr) { @@ -209,7 +682,32 @@ export abstract class Expr { return new Like(this, Constant.of(stringOrExpr)); } + /** + * Creates an expression that checks if a string contains a specified regular expression as a + * substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * Field.of("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; + + /** + * Creates an expression that checks if a string contains a specified regular expression as a + * substring. + * + * ```typescript + * // Check if the 'description' field contains the regular expression stored in field 'regex' + * Field.of("description").regexContains(Field.of("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 (stringOrExpr instanceof Expr) { @@ -218,7 +716,30 @@ export abstract class Expr { return new RegexContains(this, Constant.of(stringOrExpr)); } + /** + * Creates an expression that checks if a string matches a specified regular expression. + * + * ```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,}"); + * ``` + * + * @param pattern The regular expression to use for the match. + * @return A new `Expr` representing the regular expression match. + */ regexMatch(pattern: string): RegexMatch; + + /** + * 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")); + * ``` + * + * @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 (stringOrExpr instanceof Expr) { @@ -227,7 +748,31 @@ export abstract class Expr { return new RegexMatch(this, Constant.of(stringOrExpr)); } + /** + * Creates an expression that checks if a string starts with a given prefix. + * + * ```typescript + * // Check if the 'name' field starts with "Mr." + * Field.of("name").startsWith("Mr."); + * ``` + * + * @param prefix The prefix to check for. + * @return A new `Expr` representing the 'starts with' comparison. + */ startsWith(prefix: string): StartsWith; + + /** + * Creates an expression that checks if a string starts with a given prefix (represented as an + * expression). + * + * ```typescript + * // Check if the 'fullName' field starts with the value of the 'firstName' field + * Field.of("fullName").startsWith(Field.of("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 (stringOrExpr instanceof Expr) { @@ -236,7 +781,31 @@ export abstract class Expr { return new StartsWith(this, Constant.of(stringOrExpr)); } + /** + * Creates an expression that checks if a string ends with a given postfix. + * + * ```typescript + * // Check if the 'filename' field ends with ".txt" + * Field.of("filename").endsWith(".txt"); + * ``` + * + * @param suffix The postfix to check for. + * @return A new `Expr` representing the 'ends with' comparison. + */ endsWith(suffix: string): EndsWith; + + /** + * Creates an expression that checks if a string ends with a given postfix (represented as an + * expression). + * + * ```typescript + * // Check if the 'url' field ends with the value of the 'extension' field + * Field.of("url").endsWith(Field.of("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 (stringOrExpr instanceof Expr) { @@ -245,49 +814,186 @@ export abstract class Expr { return new EndsWith(this, Constant.of(stringOrExpr)); } + /** + * Creates an expression that converts a string to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * Field.of("name").toLowerCase(); + * ``` + * + * @return A new `Expr` representing the lowercase string. + */ toLowercase(): ToLowercase { return new ToLowercase(this); } + /** + * Creates an expression that converts a string to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * Field.of("title").toUpperCase(); + * ``` + * + * @return A new `Expr` representing the uppercase string. + */ toUppercase(): ToUppercase { return new ToUppercase(this); } + /** + * Creates an expression that removes leading and trailing whitespace from a string. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * Field.of("userInput").trim(); + * ``` + * + * @return A new `Expr` representing the trimmed string. + */ trim(): Trim { return new Trim(this); } + /** + * Creates an expression that concatenates string expressions together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * Field.of("firstName").strConcat(Constant.of(" "), Field.of("lastName")); + * ``` + * + * @param elements The expressions (typically strings) to concatenate. + * @return A new `Expr` representing the concatenated string. + */ strConcat(...elements: (string | Expr)[]): StrConcat { const exprs = elements.map(e => (e instanceof Expr ? e : Constant.of(e))); return new StrConcat(this, exprs); } + /** + * Accesses a value from a map (object) field using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * Field.of("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); } + /** + * Creates an aggregation that counts the number of stage inputs with valid evaluations of the + * expression or field. + * + * ```typescript + * // Count the total number of products + * Field.of("productId").count().as("totalProducts"); + * ``` + * + * @return A new `Accumulator` representing the 'count' aggregation. + */ count(): Count { return new Count(this, false); } + /** + * Creates an aggregation that calculates the sum of a numeric field across multiple stage inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * Field.of("orderAmount").sum().as("totalRevenue"); + * ``` + * + * @return A new `Accumulator` representing the 'sum' aggregation. + */ sum(): Sum { return new Sum(this, false); } + /** + * Creates an aggregation that calculates the average (mean) of a numeric field across multiple + * stage inputs. + * + * ```typescript + * // Calculate the average age of users + * Field.of("age").avg().as("averageAge"); + * ``` + * + * @return A new `Accumulator` representing the 'avg' aggregation. + */ avg(): Avg { return new Avg(this, false); } + /** + * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the lowest price of all products + * Field.of("price").min().as("lowestPrice"); + * ``` + * + * @return A new `Accumulator` representing the 'min' aggregation. + */ min(): Min { return new Min(this, false); } + /** + * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * Field.of("score").max().as("highestScore"); + * ``` + * + * @return A new `Accumulator` representing the 'max' aggregation. + */ max(): Max { return new Max(this, false); } + /** + * Calculates the cosine distance between two vectors. + * + * ```typescript + * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field + * Field.of("userVector").cosineDistance(Field.of("itemVector")); + * ``` + * + * @param other 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; + /** + * 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])); + * ``` + * + * @param other 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) { @@ -297,8 +1003,43 @@ export abstract class Expr { } } + /** + * Calculates the dot product distance between two vectors. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * Field.of("features").dotProductDistance([0.5, 0.8, 0.2]); + * ``` + * + * @param other The other vector (as an array of numbers) to compare against. + * @return A new `Expr` representing the dot product distance between the two vectors. + */ dotProductDistance(other: Expr): DotProductDistance; + + /** + * Calculates the dot product distance between two vectors. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * Field.of("features").dotProductDistance(new VectorValue([0.5, 0.8, 0.2])); + * ``` + * + * @param other The other vector (as a VectorValue) to compare against. + * @return A new `Expr` representing the dot product distance between the two vectors. + */ dotProductDistance(other: VectorValue): DotProductDistance; + + /** + * Calculates the dot product distance between two vectors. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * Field.of("features").dotProductDistance([0.5, 0.8, 0.2]); + * ``` + * + * @param other The other vector (as an array of numbers) to compare against. + * @return A new `Expr` representing the dot product distance between the two vectors. + */ dotProductDistance(other: number[]): DotProductDistance; dotProductDistance(other: Expr | VectorValue | number[]): DotProductDistance { if (other instanceof Expr) { @@ -308,8 +1049,43 @@ export abstract class Expr { } } + /** + * 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]); + * ``` + * + * @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. + */ euclideanDistance(other: Expr): EuclideanDistance; + + /** + * Calculates the Euclidean distance between two vectors. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * Field.of("location").euclideanDistance(new VectorValue([37.7749, -122.4194])); + * ``` + * + * @param other The other vector (as a VectorValue) to compare against. + * @return A new `Expr` representing the Euclidean distance between the two vectors. + */ euclideanDistance(other: VectorValue): EuclideanDistance; + + /** + * Calculates the Euclidean distance between two vectors. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * Field.of("location").euclideanDistance([37.7749, -122.4194]); + * ``` + * + * @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. + */ euclideanDistance(other: number[]): EuclideanDistance; euclideanDistance(other: Expr | VectorValue | number[]): EuclideanDistance { if (other instanceof Expr) { @@ -319,14 +1095,52 @@ export abstract class Expr { } } + /** + * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * firestore.pipeline().collection("users") + * .sort(Field.of("name").ascending()); + * ``` + * + * @return A new `Ordering` for ascending sorting. + */ ascending(): Ordering { - return Ordering.ascending(this); + 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.of("createdAt").descending()); + * ``` + * + * @return A new `Ordering` for descending sorting. + */ descending(): Ordering { - return Ordering.descending(this); + 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.of("price").multiply(Field.of("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); } @@ -365,6 +1179,22 @@ class ListOfExprs extends Expr { } } +/** + * Represents a reference to a field in a Firestore document, or outputs of a {@link Pipeline} stage. + * + *

Field references are used to access document field values in expressions and to specify fields + * for sorting, filtering, and projecting data in Firestore pipelines. + * + *

You can create a `Field` instance using the static {@link #of} method: + * + * ```typescript + * // Create a Field instance for the 'name' field + * const nameField = Field.of("name"); + * + * // Create a Field instance for a nested field 'address.city' + * const cityField = Field.of("address.city"); + * ``` + */ export class Field extends Expr implements Selectable { exprType: ExprType = 'Field'; selectable = true as const; @@ -376,6 +1206,23 @@ export class Field extends Expr implements Selectable { 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: firestore.FieldPath): Field; static of(nameOrPath: string | firestore.FieldPath): Field; @@ -443,6 +1290,19 @@ export class Fields extends Expr implements Selectable { } } +/** + * Represents a constant value that can be used in a Firestore pipeline expression. + * + * You can create a `Constant` instance using the static {@link #of} method: + * + * ```typescript + * // Create a Constant instance for the number 10 + * const ten = Constant.of(10); + * + * // Create a Constant instance for the string "hello" + * const hello = Constant.of("hello"); + * ``` + */ export class Constant extends Expr { exprType: ExprType = 'Constant'; @@ -450,25 +1310,140 @@ export class Constant extends Expr { 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. + * + * @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 Uint8Array value. + * + * @param value The Uint8Array value. + * @return A new `Constant` instance. + */ static of(value: Uint8Array): 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. + * + * @param value The Firestore proto value. + * @return A new `Constant` instance. + */ static of(value: api.IValue): Constant; + + /** + * Creates a `Constant` instance for an array value. + * + * @param value The array value. + * @return A new `Constant` instance. + */ static of(value: Array): Constant; + + /** + * Creates a `Constant` instance for a map value. + * + * @param value The map value. + * @return A new `Constant` instance. + */ static of(value: Map): Constant; + + /** + * Creates a `Constant` instance for a VectorValue value. + * + * @param value The VectorValue value. + * @return A new `Constant` instance. + */ static of(value: VectorValue): Constant; + + /** + * Creates a `Constant` instance for a Firestore proto value. + * + * @param value The Firestore proto value. + * @return A new `Constant` instance. + */ static of(value: api.IValue): 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 ofVector(value: Array | VectorValue): Constant { if (value instanceof VectorValue) { return new Constant(value); @@ -486,6 +1461,13 @@ export class Constant extends Expr { } } +/** + * 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 Function extends Expr { exprType: ExprType = 'Function'; constructor( @@ -694,13 +1676,6 @@ class Exists extends Function implements FilterCondition { filterable = true as const; } -class IsNull extends Function implements FilterCondition { - constructor(private expr: Expr) { - super('is_null', [expr]); - } - filterable = true as const; -} - class Not extends Function implements FilterCondition { constructor(private expr: Expr) { super('not', [expr]); @@ -907,9 +1882,60 @@ class EuclideanDistance extends Function { } } +/** + * 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")); + * ``` + * + * @param left The first expression to add. + * @param right The second expression to add. + * @return A new {@code Expr} representing the addition operation. + */ export function add(left: Expr, right: Expr): Add; + +/** + * 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); + * ``` + * + * @param left The expression to add to. + * @param right The constant value to add. + * @return A new {@code Expr} representing the addition operation. + */ export function add(left: Expr, right: any): Add; + +/** + * Creates an expression that adds a field's value to an expression. + * + * ```typescript + * // Add the value of the 'quantity' field and the 'reserve' field. + * add("quantity", Field.of("reserve")); + * ``` + * + * @param left The field name to add to. + * @param right The expression to add. + * @return A new {@code Expr} representing the addition operation. + */ export function add(left: string, right: Expr): Add; + +/** + * Creates an expression that adds a field's value to a constant value. + * + * ```typescript + * // Add 5 to the value of the 'age' field + * add("age", 5); + * ``` + * + * @param left The field name to add to. + * @param right The constant value 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; @@ -917,9 +1943,60 @@ export function add(left: Expr | string, right: Expr | any): Add { return new Add(normalizedLeft, normalizedRight); } +/** + * Creates an expression that subtracts two expressions. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * subtract(Field.of("price"), Field.of("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; + +/** + * Creates an expression that subtracts a constant value from an expression. + * + * ```typescript + * // Subtract the constant value 2 from the 'value' field + * subtract(Field.of("value"), 2); + * ``` + * + * @param left The expression to subtract from. + * @param right The constant value to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ export function subtract(left: Expr, right: any): Subtract; + +/** + * Creates an expression that subtracts an expression from a field's value. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * subtract("price", Field.of("discount")); + * ``` + * + * @param left The field name to subtract from. + * @param right The expression to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ export function subtract(left: string, right: Expr): Subtract; + +/** + * Creates an expression that subtracts a constant value from a field's value. + * + * ```typescript + * // Subtract 20 from the value of the 'total' field + * subtract("total", 20); + * ``` + * + * @param left The field name to subtract from. + * @param right 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; @@ -927,9 +2004,60 @@ export function subtract(left: Expr | string, right: Expr | any): Subtract { return new Subtract(normalizedLeft, normalizedRight); } +/** + * Creates an expression that multiplies two expressions together. + * + * ```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; + +/** + * 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); + * ``` + * + * @param left The expression to multiply. + * @param right The constant value to multiply by. + * @return A new {@code Expr} representing the multiplication operation. + */ export function multiply(left: Expr, right: any): Multiply; + +/** + * Creates an expression that multiplies a field's value by an expression. + * + * ```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; + +/** + * Creates an expression that multiplies a field's value by a constant value. + * + * ```typescript + * // Multiply the 'value' field by 2 + * multiply("value", 2); + * ``` + * + * @param left The field name to multiply. + * @param right The constant value to multiply by. + * @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; @@ -937,9 +2065,60 @@ export function multiply(left: Expr | string, right: Expr | any): Multiply { return new Multiply(normalizedLeft, normalizedRight); } +/** + * Creates an expression that divides two expressions. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * divide(Field.of("total"), Field.of("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; + +/** + * Creates an expression that divides an expression by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * divide(Field.of("value"), 10); + * ``` + * + * @param left The expression to be divided. + * @param right The constant value to divide by. + * @return A new {@code Expr} representing the division operation. + */ export function divide(left: Expr, right: any): Divide; + +/** + * Creates an expression that divides a field's value by an expression. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * divide("total", Field.of("count")); + * ``` + * + * @param left The field name to be divided. + * @param right The expression to divide by. + * @return A new {@code Expr} representing the division operation. + */ export function divide(left: string, right: Expr): Divide; + +/** + * Creates an expression that divides a field's value by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * divide("value", 10); + * ``` + * + * @param left The field name to be divided. + * @param right 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; @@ -947,9 +2126,60 @@ export function divide(left: Expr | string, right: Expr | any): Divide { return new Divide(normalizedLeft, normalizedRight); } +/** + * Creates an expression that checks if two expressions are equal. + * + * ```typescript + * // Check if the 'age' field is equal to an expression + * eq(Field.of("age"), Field.of("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; + +/** + * Creates an expression that checks if an expression is equal to a constant value. + * + * ```typescript + * // Check if the 'age' field is equal to 21 + * eq(Field.of("age"), 21); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the equality comparison. + */ export function eq(left: Expr, right: any): Eq; + +/** + * Creates an expression that checks if a field's value is equal to an expression. + * + * ```typescript + * // Check if the 'age' field is equal to the 'limit' field + * eq("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the equality comparison. + */ export function eq(left: string, right: Expr): Eq; + +/** + * Creates an expression that checks if a field's value is equal to a constant value. + * + * ```typescript + * // Check if the 'city' field is equal to string constant "London" + * eq("city", "London"); + * ``` + * + * @param left The field name to compare. + * @param right 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); @@ -957,9 +2187,60 @@ export function eq(left: Expr | string, right: any): Eq { return new Eq(leftExpr, rightExpr); } +/** + * Creates an expression that checks if two expressions are not equal. + * + * ```typescript + * // Check if the 'status' field is not equal to field 'finalState' + * neq(Field.of("status"), Field.of("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; + +/** + * Creates an expression that checks if an expression is not equal to a constant value. + * + * ```typescript + * // Check if the 'status' field is not equal to "completed" + * neq(Field.of("status"), "completed"); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the inequality comparison. + */ export function neq(left: Expr, right: any): Neq; + +/** + * Creates an expression that checks if a field's value is not equal to an expression. + * + * ```typescript + * // Check if the 'status' field is not equal to the value of 'expectedStatus' + * neq("status", Field.of("expectedStatus")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the inequality comparison. + */ export function neq(left: string, right: Expr): Neq; + +/** + * Creates an expression that checks if a field's value is not equal to a constant value. + * + * ```typescript + * // Check if the 'country' field is not equal to "USA" + * neq("country", "USA"); + * ``` + * + * @param left The field name to compare. + * @param right 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); @@ -967,9 +2248,60 @@ export function neq(left: Expr | string, right: any): Neq { return new Neq(leftExpr, rightExpr); } +/** + * Creates an expression that checks if the first expression is less than the second expression. + * + * ```typescript + * // Check if the 'age' field is less than 30 + * lt(Field.of("age"), Field.of("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; + +/** + * Creates an expression that checks if an expression is less than a constant value. + * + * ```typescript + * // Check if the 'age' field is less than 30 + * lt(Field.of("age"), 30); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the less than comparison. + */ export function lt(left: Expr, right: any): Lt; + +/** + * Creates an expression that checks if a field's value is less than an expression. + * + * ```typescript + * // Check if the 'age' field is less than the 'limit' field + * lt("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the less than comparison. + */ export function lt(left: string, right: Expr): Lt; + +/** + * Creates an expression that checks if a field's value is less than a constant value. + * + * ```typescript + * // Check if the 'price' field is less than 50 + * lt("price", 50); + * ``` + * + * @param left The field name to compare. + * @param right 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); @@ -977,9 +2309,61 @@ export function lt(left: Expr | string, right: any): Lt { return new Lt(leftExpr, rightExpr); } +/** + * Creates an expression that checks if the first expression is less than or equal to the second + * expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lte(Field.of("quantity"), Field.of("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; + +/** + * Creates an expression that checks if an expression is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lte(Field.of("quantity"), 20); + * ``` + * + * @param left The expression to compare. + * @param right 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; + +/** + * 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")); + * ``` + * + * @param left The field name to compare. + * @param right 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; + +/** + * Creates an expression that checks if a field's value is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'score' field is less than or equal to 70 + * lte("score", 70); + * ``` + * + * @param left The field name to compare. + * @param right 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); @@ -987,9 +2371,61 @@ export function lte(left: Expr | string, right: any): Lte { return new Lte(leftExpr, rightExpr); } +/** + * Creates an expression that checks if the first expression is greater than the second + * expression. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * gt(Field.of("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; + +/** + * Creates an expression that checks if an expression is greater than a constant value. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * gt(Field.of("age"), 18); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the greater than comparison. + */ export function gt(left: Expr, right: any): Gt; + +/** + * Creates an expression that checks if a field's value is greater than an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than the value of field 'limit' + * gt("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the greater than comparison. + */ export function gt(left: string, right: Expr): Gt; + +/** + * Creates an expression that checks if a field's value is greater than a constant value. + * + * ```typescript + * // Check if the 'price' field is greater than 100 + * gt("price", 100); + * ``` + * + * @param left The field name to compare. + * @param right 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); @@ -997,9 +2433,63 @@ export function gt(left: Expr | string, right: any): Gt { return new Gt(leftExpr, rightExpr); } +/** + * Creates an expression that checks if the first expression is greater than or equal to the + * second expression. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to the field "threshold" + * gte(Field.of("quantity"), Field.of("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; + +/** + * Creates an expression that checks if an expression is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to 10 + * gte(Field.of("quantity"), 10); + * ``` + * + * @param left The expression to compare. + * @param right 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; + +/** + * Creates an expression that checks if a field's value is greater than or equal to an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than or equal to the value of field 'limit' + * gte("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right 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; + +/** + * Creates an expression that checks if a field's value is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'score' field is greater than or equal to 80 + * gte("score", 80); + * ``` + * + * @param left The field name to compare. + * @param right 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); @@ -1007,9 +2497,60 @@ export function gte(left: Expr | string, right: any): Gte { return new Gte(leftExpr, rightExpr); } +/** + * Creates an expression that concatenates an array expression with other arrays. + * + * ```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; + +/** + * 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; + +/** + * 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")]); + * ``` + * + * @param array The field name containing array values. + * @param elements The array expressions to concatenate. + * @return A new {@code Expr} representing the concatenated array. + */ export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; + +/** + * Creates an expression that concatenates a field's array value with other arrays and/or values. + * + * ```typescript + * // Combine the 'tags' array with a new array + * arrayConcat("tags", ["newTag1", "newTag2"]); + * ``` + * + * @param array The field name containing array values. + * @param elements The array expressions or single values 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, @@ -1022,9 +2563,60 @@ export function arrayConcat( return new ArrayConcat(arrayExpr, exprValues); } +/** + * Creates an expression that checks if an array expression contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains the value of field 'selectedColor' + * arrayContains(Field.of("colors"), Field.of("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; + +/** + * Creates an expression that checks if an array expression contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * arrayContains(Field.of("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; + +/** + * Creates an expression that checks if a field's array value contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains the value of field 'selectedColor' + * arrayContains("colors", Field.of("selectedColor")); + * ``` + * + * @param array 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; + +/** + * Creates an expression that checks if a field's array value contains a specific value. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * arrayContains("colors", "red"); + * ``` + * + * @param array 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, @@ -1035,12 +2627,69 @@ export function arrayContains( return new ArrayContains(arrayExpr, elementExpr); } +/** + * Creates an expression that checks if an array expression 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"]); + * ``` + * + * @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; + +/** + * Creates an expression that checks if an array expression 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"]); + * ``` + * + * @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: any[]): ArrayContainsAny; + +/** + * Creates an expression that checks if a field's array value 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"]); + * ``` + * + * @param array 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: string, values: Expr[] ): ArrayContainsAny; + +/** + * Creates an expression that checks if a field's array value 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"]); + * ``` + * + * @param array 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: string, values: any[] @@ -1056,12 +2705,65 @@ export function arrayContainsAny( return new ArrayContainsAny(arrayExpr, exprValues); } +/** + * 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', 'tag2' and "tag3" + * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "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; + +/** + * 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', 'tag2' and "tag3" + * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "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: any[]): ArrayContainsAll; + +/** + * 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 both of the values from field 'tag1' and "tag2" + * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * ``` + * + * @param array 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: string, values: Expr[] ): ArrayContainsAll; + +/** + * 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 both of the values from field 'tag1' and "tag2" + * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * ``` + * + * @param array 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: string, values: any[] @@ -1077,14 +2779,55 @@ export function arrayContainsAll( return new ArrayContainsAll(arrayExpr, exprValues); } +/** + * Creates an expression that filters elements from an array expression using the given {@link + * FilterExpr} and returns the filtered elements as a new array. + * + * ```typescript + * // Get items from the 'inventoryPrices' array where the array item is greater than 0 + * // Note we use {@link arrayElement} to represent array elements to construct a + * // filtering condition. + * arrayFilter(Field.of("inventoryPrices"), arrayElement().gt(0)); + * ``` + * + * @param array The array expression to filter. + * @param filter The {@link FilterExpr} to apply to the array elements. + * @return A new {@code Expr} representing the filtered array. + */ export function arrayFilter(array: Expr, filter: FilterExpr): ArrayFilter { return new ArrayFilter(array, filter); } +/** + * Creates an expression that calculates the length of an array expression. + * + * ```typescript + * // Get the number of items in the 'cart' array + * arrayLength(Field.of("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); } +/** + * Creates an expression that applies a transformation function to each element in an array + * expression and returns the new array as the result of the evaluation. + * + * ```typescript + * // Convert all strings in the 'names' array to uppercase + * // Note we use {@link arrayElement} to represent array elements to construct a + * // transforming function. + * arrayTransform(Field.of("names"), arrayElement().toUppercase()); + * ``` + * + * @param array The array expression to transform. + * @param transform The {@link Function} to apply to each array element. + * @return A new {@code Expr} representing the transformed array. + */ export function arrayTransform( array: Expr, transform: Function @@ -1092,14 +2835,80 @@ export function arrayTransform( return new ArrayTransform(array, transform); } +/** + * Returns an expression that represents an array element within an {@link ArrayFilter} or {@link + * ArrayTransform} expression. + * + * ```typescript + * // Get items from the 'inventoryPrices' array where the array item is greater than 0 + * arrayFilter(Field.of("inventoryPrices"), arrayElement().gt(0)); + * ``` + * + * @return A new {@code Expr} representing an array element. + */ export function arrayElement(): ArrayElement { return new ArrayElement(); } +/** + * Creates an expression that checks if an expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ export function inAny(element: Expr, others: Expr[]): In; + +/** + * Creates an expression that checks if an expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ export function inAny(element: Expr, others: any[]): In; -export function inAny(element: string, others: Expr[]): In; // Added overload -export function inAny(element: string, others: any[]): In; // Added overload + +/** + * Creates an expression that checks if a field's value is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); + * ``` + * + * @param element The field to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ +export function inAny(element: string, others: Expr[]): In; + +/** + * Creates an expression that checks if a field's value is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny("category", ["Electronics", Field.of("primaryType")]); + * ``` + * + * @param element The field to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ +export function inAny(element: string, others: any[]): In; export function inAny(element: Expr | string, others: any[]): In { const elementExpr = element instanceof Expr ? element : Field.of(element); const exprOthers = others.map(other => @@ -1108,10 +2917,65 @@ export function inAny(element: Expr | string, others: any[]): In { return new In(elementExpr, exprOthers); } +/** + * 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' + * notInAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ export function notInAny(element: Expr, others: Expr[]): Not; + +/** + * 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' + * notInAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ export function notInAny(element: Expr, others: any[]): Not; -export function notInAny(element: string, others: Expr[]): Not; // Added overload -export function notInAny(element: string, others: any[]): Not; // Added overload + +/** + * 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' + * notInAny("status", [Constant.of("pending"), Field.of("rejectedStatus")]); + * ``` + * + * @param element The field name to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ +export function notInAny(element: string, others: Expr[]): Not; + +/** + * 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' + * notInAny("status", ["pending", Field.of("rejectedStatus")]); + * ``` + * + * @param element The field name to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ +export function notInAny(element: string, others: any[]): Not; export function notInAny(element: Expr | string, others: any[]): Not { const elementExpr = element instanceof Expr ? element : Field.of(element); const exprOthers = others.map(other => @@ -1120,18 +2984,76 @@ export function notInAny(element: Expr | string, others: any[]): Not { return new Not(new In(elementExpr, exprOthers)); } +/** + * Creates an expression that performs a logical 'AND' operation on multiple filter conditions. + * + * ```typescript + * // Check if the 'age' field is greater than 18 AND the 'city' field is "London" AND + * // the 'status' field is "active" + * 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. + * @return A new {@code Expr} representing the logical 'AND' operation. + */ export function and(left: FilterExpr, ...right: FilterExpr[]): And { return new And([left, ...right]); } +/** + * Creates an expression that performs a logical 'OR' operation on multiple filter conditions. + * + * ```typescript + * // Check if the 'age' field is greater than 18 OR the 'city' field is "London" OR + * // the 'status' field is "active" + * 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. + * @return A new {@code Expr} representing the logical 'OR' operation. + */ export function or(left: FilterExpr, ...right: FilterExpr[]): Or { return new Or([left, ...right]); } +/** + * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple filter + * conditions. + * + * ```typescript + * // Check if only one of the conditions is true: 'age' greater than 18, 'city' is "London", + * // or 'status' is "active". + * const condition = xor( + * gt("age", 18), + * eq("city", "London"), + * eq("status", "active")); + * ``` + * + * @param left The first filter condition. + * @param right Additional filter conditions to 'XOR' together. + * @return A new {@code Expr} representing the logical 'XOR' operation. + */ export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor { return new Xor([left, ...right]); } +/** + * Creates a conditional expression that evaluates to a 'then' expression if a condition is true + * and an 'else' expression if the condition is false. + * + * ```typescript + * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". + * ifFunction( + * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); + * ``` + * + * @param condition The condition to evaluate. + * @param thenExpr The expression to evaluate if the condition is true. + * @param elseExpr The expression to evaluate if the condition is false. + * @return A new {@code Expr} representing the conditional expression. + */ export function ifFunction( condition: FilterExpr, thenExpr: Expr, @@ -1140,11 +3062,45 @@ export function ifFunction( return new If(condition, thenExpr, elseExpr); } +/** + * Creates an expression that negates a filter condition. + * + * ```typescript + * // Find documents where the 'completed' field is NOT true + * not(eq("completed", true)); + * ``` + * + * @param filter The filter condition to negate. + * @return A new {@code Expr} representing the negated filter condition. + */ export function not(filter: FilterExpr): Not { return new Not(filter); } +/** + * Creates an expression that checks if a field exists. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * exists(Field.of("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; + +/** + * Creates an expression that checks if a field exists. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * exists("phoneNumber"); + * ``` + * + * @param field 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 = @@ -1152,41 +3108,188 @@ export function exists(valueOrField: Expr | string): Exists { return new Exists(valueExpr); } -export function isNull(value: Expr): IsNull; -export function isNull(value: string): IsNull; -export function isNull(value: Expr | string): IsNull { - const valueExpr = value instanceof Expr ? value : Field.of(value); - return new IsNull(valueExpr); -} - +/** + * 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.of("value").divide(0)); + * ``` + * + * @param value The expression to check. + * @return A new {@code Expr} representing the 'isNaN' check. + */ export function isNan(value: Expr): IsNan; + +/** + * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). + * + * ```typescript + * // Check if the result of a calculation is NaN + * isNaN("value"); + * ``` + * + * @param value The name of the field to check. + * @return A new {@code Expr} representing the 'isNaN' check. + */ export function isNan(value: string): IsNan; export function isNan(value: Expr | string): IsNan { const valueExpr = value instanceof Expr ? value : Field.of(value); return new IsNan(valueExpr); } +/** + * Creates an expression that calculates the length of a string field. + * + * ```typescript + * // Get the length of the 'name' field + * length("name"); + * ``` + * + * @param field The name of the field containing the string. + * @return A new {@code Expr} representing the length of the string. + */ export function length(field: string): Length; + +/** + * Creates an expression that calculates the length of a string expression. + * + * ```typescript + * // Get the length of the 'name' field + * length(Field.of("name")); + * ``` + * + * @param expr The expression representing the string to calculate the length of. + * @return A new {@code Expr} representing the length of the string. + */ export function length(expr: Expr): Length; export function length(value: Expr | string): Length { const valueExpr = value instanceof Expr ? value : Field.of(value); return new Length(valueExpr); } -export function like(left: Expr, pattern: Expr): Like; -export function like(left: Expr, pattern: string): Like; -export function like(left: string, pattern: Expr): Like; +/** + * Creates an expression that performs a case-sensitive wildcard string comparison against a + * field. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like("title", "%guide%"); + * ``` + * + * @param left 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; + +/** + * Creates an expression that performs a case-sensitive wildcard string comparison against a + * field. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like("title", Field.of("pattern")); + * ``` + * + * @param left 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; + +/** + * Creates an expression that performs a case-sensitive wildcard string comparison. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like(Field.of("title"), "%guide%"); + * ``` + * + * @param left 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; + +/** + * Creates an expression that performs a case-sensitive wildcard string comparison. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like(Field.of("title"), Field.of("pattern")); + * ``` + * + * @param left 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 regexContains(left: Expr, pattern: Expr): RegexContains; -export function regexContains(left: Expr, pattern: string): RegexContains; -export function regexContains(left: string, pattern: Expr): RegexContains; +/** + * Creates an expression that checks if a string field contains a specified regular expression as + * a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains("description", "(?i)example"); + * ``` + * + * @param left 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; + +/** + * Creates an expression that checks if a string field contains a specified regular expression as + * a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains("description", Field.of("pattern")); + * ``` + * + * @param left 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; + +/** + * Creates an expression that checks if a string expression contains a specified regular + * expression as a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains(Field.of("description"), "(?i)example"); + * ``` + * + * @param left 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; + +/** + * Creates an expression that checks if a string expression contains a specified regular + * expression as a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains(Field.of("description"), Field.of("pattern")); + * ``` + * + * @param left 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( left: Expr | string, pattern: Expr | string @@ -1196,10 +3299,63 @@ export function regexContains( return new RegexContains(leftExpr, patternExpr); } -export function regexMatch(left: Expr, pattern: Expr): RegexMatch; -export function regexMatch(left: Expr, pattern: string): RegexMatch; -export function regexMatch(left: string, pattern: Expr): RegexMatch; +/** + * Creates an expression that checks if a string field matches a specified regular expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * 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 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; + +/** + * Creates an expression that checks if a string field matches a specified regular expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * regexMatch("email", Field.of("pattern")); + * ``` + * + * @param left 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; + +/** + * Creates an expression that checks if a string expression matches a specified regular + * expression. + * + * ```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,}"); + * ``` + * + * @param left 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; + +/** + * Creates an expression that checks if a string expression matches a specified regular + * expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * regexMatch(Field.of("email"), Field.of("pattern")); + * ``` + * + * @param left 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( left: Expr | string, pattern: Expr | string @@ -1209,10 +3365,61 @@ export function regexMatch( return new RegexMatch(leftExpr, patternExpr); } -export function startsWith(expr: Expr, prefix: Expr): StartsWith; -export function startsWith(expr: Expr, prefix: string): StartsWith; -export function startsWith(expr: string, prefix: Expr): StartsWith; +/** + * Creates an expression that checks if a field's value starts with a given prefix. + * + * ```typescript + * // Check if the 'name' field starts with "Mr." + * startsWith("name", "Mr."); + * ``` + * + * @param expr 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; + +/** + * Creates an expression that checks if a field's value starts with a given prefix. + * + * ```typescript + * // Check if the 'fullName' field starts with the value of the 'firstName' field + * startsWith("fullName", Field.of("firstName")); + * ``` + * + * @param expr 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; + +/** + * Creates an expression that checks if a string expression starts with a given prefix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." + * startsWith(Field.of("fullName"), "Mr."); + * ``` + * + * @param expr 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; + +/** + * Creates an expression that checks if a string expression starts with a given prefix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." + * startsWith(Field.of("fullName"), Field.of("prefix")); + * ``` + * + * @param expr 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( expr: Expr | string, prefix: Expr | string @@ -1222,38 +3429,183 @@ export function startsWith( return new StartsWith(exprLeft, prefixExpr); } -export function endsWith(expr: Expr, suffix: Expr): EndsWith; -export function endsWith(expr: Expr, suffix: string): EndsWith; -export function endsWith(expr: string, suffix: Expr): EndsWith; +/** + * Creates an expression that checks if a field's value ends with a given postfix. + * + * ```typescript + * // Check if the 'filename' field ends with ".txt" + * endsWith("filename", ".txt"); + * ``` + * + * @param expr 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; + +/** + * Creates an expression that checks if a field's value ends with a given postfix. + * + * ```typescript + * // Check if the 'url' field ends with the value of the 'extension' field + * endsWith("url", Field.of("extension")); + * ``` + * + * @param expr 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; + +/** + * Creates an expression that checks if a string expression ends with a given postfix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." + * endsWith(Field.of("fullName"), "Jr."); + * ``` + * + * @param expr 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; + +/** + * Creates an expression that checks if a string expression ends with a given postfix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." + * endsWith(Field.of("fullName"), Constant.of("Jr.")); + * ``` + * + * @param expr 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 toLowercase(expr: Expr): ToLowercase; +/** + * Creates an expression that converts a string field to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * toLowercase("name"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the lowercase string. + */ export function toLowercase(expr: string): ToLowercase; + +/** + * Creates an expression that converts a string expression to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * toLowercase(Field.of("name")); + * ``` + * + * @param expr The expression representing the string to convert to lowercase. + * @return A new {@code Expr} representing the lowercase string. + */ +export function toLowercase(expr: Expr): ToLowercase; export function toLowercase(expr: Expr | string): ToLowercase { return new ToLowercase(expr instanceof Expr ? expr : Field.of(expr)); } -export function toUppercase(expr: Expr): ToUppercase; +/** + * Creates an expression that converts a string field to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * toUppercase("title"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the uppercase string. + */ export function toUppercase(expr: string): ToUppercase; + +/** + * Creates an expression that converts a string expression to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * toUppercase(Field.of("title")); + * ``` + * + * @param expr The expression representing the string to convert to uppercase. + * @return A new {@code Expr} representing the uppercase string. + */ +export function toUppercase(expr: Expr): ToUppercase; export function toUppercase(expr: Expr | string): ToUppercase { return new ToUppercase(expr instanceof Expr ? expr : Field.of(expr)); } -export function trim(expr: Expr): Trim; +/** + * Creates an expression that removes leading and trailing whitespace from a string field. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * trim("userInput"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the trimmed string. + */ export function trim(expr: string): Trim; + +/** + * Creates an expression that removes leading and trailing whitespace from a string expression. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * trim(Field.of("userInput")); + * ``` + * + * @param expr 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)); } +/** + * Creates an expression that concatenates string functions, fields or constants together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * strConcat("firstName", " ", Field.of("lastName")); + * ``` + * + * @param first The field name containing the initial string value. + * @param elements The expressions (typically strings) to concatenate. + * @return A new {@code Expr} representing the concatenated string. + */ export function strConcat( first: string, ...elements: (Expr | string)[] ): StrConcat; + +/** + * Creates an expression that concatenates string expressions together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * strConcat(Field.of("firstName"), " ", Field.of("lastName")); + * ``` + * + * @param first The initial string expression to concatenate to. + * @param elements The expressions (typically strings) to concatenate. + * @return A new {@code Expr} representing the concatenated string. + */ export function strConcat( first: Expr, ...elements: (Expr | string)[] @@ -1266,7 +3618,32 @@ export function strConcat( return new StrConcat(first instanceof Expr ? first : Field.of(first), exprs); } +/** + * Accesses a value from a map (object) field using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * mapGet("address", "city"); + * ``` + * + * @param mapField 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; + +/** + * Accesses a value from a map (object) expression using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * mapGet(Field.of("address"), "city"); + * ``` + * + * @param mapExpr 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( @@ -1275,54 +3652,264 @@ 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"); + * ``` + * + * @return A new {@code Accumulator} representing the 'countAll' aggregation. + */ export function countAll(): Count { return new Count(undefined, false); } +/** + * Creates an aggregation that counts the number of stage inputs with valid evaluations of the + * provided expression. + * + * ```typescript + * // Count the number of items where the price is greater than 10 + * count(Field.of("price").gt(10)).as("expensiveItemCount"); + * ``` + * + * @param value The expression to count. + * @return A new {@code Accumulator} representing the 'count' aggregation. + */ export function count(value: Expr): Count; + +/** + * Creates an aggregation that counts the number of stage inputs with valid evaluations of the + * provided field. + * + * ```typescript + * // Count the total number of products + * count("productId").as("totalProducts"); + * ``` + * + * @param value The name of the field to count. + * @return A new {@code Accumulator} representing the 'count' aggregation. + */ export function count(value: string): Count; export function count(value: Expr | string): Count { const exprValue = value instanceof Expr ? value : Field.of(value); return new Count(exprValue, false); } +/** + * Creates an aggregation that calculates the sum of values from an expression across multiple + * stage inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * sum(Field.of("orderAmount")).as("totalRevenue"); + * ``` + * + * @param value The expression to sum up. + * @return A new {@code Accumulator} representing the 'sum' aggregation. + */ export function sum(value: Expr): Sum; + +/** + * Creates an aggregation that calculates the sum of a field's values across multiple stage + * inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * 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. + */ export function sum(value: string): Sum; export function sum(value: Expr | string): Sum { const exprValue = value instanceof Expr ? value : Field.of(value); return new Sum(exprValue, false); } +/** + * Creates an aggregation that calculates the average (mean) of values from an expression across + * multiple stage inputs. + * + * ```typescript + * // Calculate the average age of users + * avg(Field.of("age")).as("averageAge"); + * ``` + * + * @param value The expression representing the values to average. + * @return A new {@code Accumulator} representing the 'avg' aggregation. + */ export function avg(value: Expr): Avg; + +/** + * Creates an aggregation that calculates the average (mean) of a field's values across multiple + * stage inputs. + * + * ```typescript + * // Calculate the average age of users + * 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. + */ export function avg(value: string): Avg; export function avg(value: Expr | string): Avg { const exprValue = value instanceof Expr ? value : Field.of(value); return new Avg(exprValue, false); } +/** + * Creates an aggregation that finds the minimum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the lowest price of all products + * min(Field.of("price")).as("lowestPrice"); + * ``` + * + * @param value The expression to find the minimum value of. + * @return A new {@code Accumulator} representing the 'min' aggregation. + */ export function min(value: Expr): Min; + +/** + * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the lowest price of all products + * min("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. + */ export function min(value: string): Min; export function min(value: Expr | string): Min { const exprValue = value instanceof Expr ? value : Field.of(value); return new Min(exprValue, false); } +/** + * Creates an aggregation that finds the maximum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * max(Field.of("score")).as("highestScore"); + * ``` + * + * @param value The expression to find the maximum value of. + * @return A new {@code Accumulator} representing the 'max' aggregation. + */ export function max(value: Expr): Max; + +/** + * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * max("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. + */ export function max(value: string): Max; export function max(value: Expr | string): Max { const exprValue = value instanceof Expr ? value : Field.of(value); return new Max(exprValue, false); } -export function cosineDistance(expr: Expr, other: Expr): CosineDistance; -export function cosineDistance(expr: Expr, other: number[]): CosineDistance; -export function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; -export function cosineDistance(expr: string, other: Expr): CosineDistance; +/** + * Calculates the Cosine distance between a field's vector value and a double array. + * + * ```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; + +/** + * 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. + * @return A new {@code Expr} representing the Cosine distance between the two vectors. + */ export function cosineDistance( expr: string, other: VectorValue ): CosineDistance; + +/** + * Calculates the Cosine distance between a field's vector value and a vector expression. + * + * ```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; + +/** + * 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]); + * ``` + * + * @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 cosine distance between the two vectors. + */ +export function cosineDistance(expr: Expr, other: number[]): CosineDistance; + +/** + * Calculates the Cosine distance between a vector expression and a VectorValue. + * + * ```typescript + * // Calculate the cosine distance between the 'location' field and a target location + * cosineDistance(Field.of("location"), new VectorValue([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 cosine distance between the two vectors. + */ +export function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; + +/** + * Calculates the Cosine distance between two vector expressions. + * + * ```typescript + * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field + * cosineDistance(Field.of("userVector"), Field.of("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. + * @return A new {@code Expr} representing the cosine distance between the two vectors. + */ +export function cosineDistance(expr: Expr, other: Expr): CosineDistance; export function cosineDistance( expr: Expr | string, other: Expr | number[] | VectorValue @@ -1332,27 +3919,104 @@ export function cosineDistance( return new CosineDistance(expr1, expr2); } -export function dotProductDistance(expr: Expr, other: Expr): DotProductDistance; // Fixed return type +/** + * Calculates the dot product distance between a field's vector value and a double array. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * dotProductDistance("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 compare against. + * @return A new {@code Expr} representing the dot product distance between the two vectors. + */ export function dotProductDistance( - expr: Expr, + expr: string, other: number[] ): DotProductDistance; + +/** + * Calculates the dot product distance between a field's vector value and a VectorValue. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * dotProductDistance("features", new VectorValue([0.5, 0.8, 0.2])); + * ``` + * + * @param expr The name of the field containing the first vector. + * @param other The other vector (as a VectorValue) to compare against. + * @return A new {@code Expr} representing the dot product distance between the two vectors. + */ export function dotProductDistance( - expr: Expr, + expr: string, other: VectorValue ): DotProductDistance; + +/** + * Calculates the dot product distance between a field's vector value and a vector expression. + * + * ```typescript + * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' + * dotProductDistance("docVector1", Field.of("docVector2")); + * ``` + * + * @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 dot product distance between the two vectors. + */ export function dotProductDistance( expr: string, other: Expr ): DotProductDistance; + +/** + * Calculates the dot product distance between a vector expression and a double array. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * dotProductDistance(Field.of("features"), [0.5, 0.8, 0.2]); + * ``` + * + * @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 dot product distance between the two vectors. + */ export function dotProductDistance( - expr: string, + expr: Expr, other: number[] ): DotProductDistance; + +/** + * Calculates the dot product distance between a vector expression and a VectorValue. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * dotProductDistance(Field.of("features"), new VectorValue([0.5, 0.8, 0.2])); + * ``` + * + * @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 dot product distance between the two vectors. + */ export function dotProductDistance( - expr: string, + expr: Expr, other: VectorValue ): DotProductDistance; + +/** + * Calculates the dot product distance between two vector expressions. + * + * ```typescript + * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' + * dotProductDistance(Field.of("docVector1"), Field.of("docVector2")); + * ``` + * + * @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 dot product distance between the two vectors. + */ +export function dotProductDistance(expr: Expr, other: Expr): DotProductDistance; export function dotProductDistance( expr: Expr | string, other: Expr | number[] | VectorValue @@ -1362,24 +4026,102 @@ export function dotProductDistance( return new DotProductDistance(expr1, expr2); } -export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; +/** + * Calculates the Euclidean distance between a field's vector value and a double array. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * euclideanDistance("location", [37.7749, -122.4194]); + * ``` + * + * @param expr The name of the field containing the first vector. + * @param other The other vector (as an array of doubles) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ export function euclideanDistance( - expr: Expr, + expr: string, other: number[] ): EuclideanDistance; + +/** + * Calculates the Euclidean distance between a field's vector value and a VectorValue. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * euclideanDistance("location", new VectorValue([37.7749, -122.4194])); + * ``` + * + * @param expr The name of the field containing the first vector. + * @param other The other vector (as a VectorValue) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ export function euclideanDistance( - expr: Expr, + expr: string, other: VectorValue ): EuclideanDistance; + +/** + * Calculates the Euclidean distance between a field's vector value and a vector expression. + * + * ```typescript + * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' + * euclideanDistance("pointA", Field.of("pointB")); + * ``` + * + * @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. + */ export function euclideanDistance(expr: string, other: Expr): EuclideanDistance; + +/** + * 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(Field.of("location"), [37.7749, -122.4194]); + * ``` + * + * @param expr The first vector (represented as an Expr) to compare against. + * @param other The other vector (as an array of doubles) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ export function euclideanDistance( - expr: string, + expr: Expr, other: number[] ): EuclideanDistance; + +/** + * Calculates the Euclidean distance between a vector expression and a VectorValue. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * euclideanDistance(Field.of("location"), new VectorValue([37.7749, -122.4194])); + * ``` + * + * @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. + */ export function euclideanDistance( - expr: string, + expr: Expr, other: VectorValue ): EuclideanDistance; + +/** + * Calculates the Euclidean distance between two vector expressions. + * + * ```typescript + * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' + * euclideanDistance(Field.of("pointA"), Field.of("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. + */ +export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; export function euclideanDistance( expr: Expr | string, other: Expr | number[] | VectorValue @@ -1389,29 +4131,66 @@ export function euclideanDistance( return new EuclideanDistance(expr1, expr2); } +/** + * 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[]): Function { return new Function(name, params); } +/** + * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * firestore.pipeline().collection("users") + * .sort(ascending(Field.of("name"))); + * ``` + * + * @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'); +} + +/** + * 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(descending(Field.of("createdAt"))); + * ``` + * + * @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'); +} + +/** + * Represents an ordering criterion for sorting documents in a Firestore pipeline. + * + * You create `Ordering` instances using the `ascending` and `descending` helper functions. + */ export class Ordering { constructor( private expr: Expr, private direction: 'ascending' | 'descending' ) {} - static of( - expr: Expr, - direction: 'ascending' | 'descending' | undefined = undefined - ): Ordering { - return new Ordering(expr, direction || 'ascending'); - } - static ascending(expr: Expr): Ordering { - return new Ordering(expr, 'ascending'); - } - static descending(expr: Expr): Ordering { - return new Ordering(expr, 'descending'); - } - _toProto(serializer: Serializer): api.IValue { return { mapValue: { diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index 21ba1e77d..f4fdd886f 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -390,9 +390,9 @@ export function toPipelineFilterCondition( } } else if (f.isNullChecking()) { if (f.nullOp() === 'IS_NULL') { - return and(field.exists(), field.isNull()); + return and(field.exists(), field.eq(null)); } else { - return and(field.exists(), not(field.isNull())); + return and(field.exists(), not(field.eq(null))); } } else { // Comparison filters diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 2976a8272..d663267a1 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -9,6 +9,7 @@ import { Field, Fields, FilterCondition, + Function, Ordering, Selectable, } from './expression'; @@ -63,6 +64,46 @@ export class PipelineSource { } } +/** + * The Pipeline class provides a flexible and expressive framework for building complex data + * transformation and query pipelines for Firestore. + * + * A pipeline takes data sources, such as Firestore collections or collection groups, and applies + * a series of stages that are chained together. Each stage takes the output from the previous stage + * (or the data source) and produces an output for the next stage (or as the final output of the + * pipeline). + * + * Expressions can be used within each stage to filter and transform data through the stage. + * + * NOTE: The chained stages do not prescribe exactly how Firestore will execute the pipeline. + * Instead, Firestore only guarantees that the result is the same as if the chained stages were + * executed in order. + * + * Usage Examples: + * + * ```typescript + * const db: Firestore; // Assumes a valid firestore instance. + * + * // Example 1: Select specific fields and rename 'rating' to 'bookRating' + * const results1 = await db.pipeline() + * .collection("books") + * .select("title", "author", Field.of("rating").as("bookRating")) + * .execute(); + * + * // Example 2: Filter documents where 'genre' is "Science Fiction" and 'published' is after 1950 + * const results2 = await db.pipeline() + * .collection("books") + * .where(and(Field.of("genre").eq("Science Fiction"), Field.of("published").gt(1950))) + * .execute(); + * + * // Example 3: Calculate the average rating of books published after 1980 + * const results3 = await db.pipeline() + * .collection("books") + * .where(Field.of("published").gt(1980)) + * .aggregate(avg(Field.of("rating")).as("averageRating")) + * .execute(); + * ``` + */ export class Pipeline< AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData, @@ -72,13 +113,89 @@ export class Pipeline< private stages: Stage[] ) {} + /** + * Adds new fields to outputs from previous stages. + * + * This stage allows you to compute values on-the-fly based on existing data from previous + * stages or constants. You can use this to create new fields or overwrite existing ones (if there + * is name overlaps). + * + * The added fields are defined using {@link Selectable}s, which can be: + * + * - {@link Field}: References an existing document field. + * - {@link Function}: Performs a calculation using functions like `add`, `multiply` with + * assigned aliases using {@link Expr#as}. + * + * Example: + * + * ```typescript + * firestore.pipeline().collection("books") + * .addFields( + * Field.of("rating").as("bookRating"), // Rename 'rating' to 'bookRating' + * add(5, Field.of("quantity")).as("totalCost") // Calculate 'totalCost' + * ); + * ``` + * + * @param fields The 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(new AddField(this.selectablesToMap(fields))); return new Pipeline(this.db, copy); } + /** + * Selects a set of fields from the outputs of previous stages. + * + *

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 + * desired. + * + *

Example: + * + * ```typescript + * firestore.collection("books") + * .select("name", "address"); + * + * // The above is a shorthand of this: + * firestore.pipeline().collection("books") + * .select(Field.of("name"), Field.of("address")); + * ``` + * + * @param fields The name of the fields to include in the output documents. + * @return A new Pipeline object with this stage appended to the stage list. + */ select(...fields: string[]): Pipeline; + /** + * Selects or creates a set of fields from the outputs of previous stages. + * + *

The selected fields are defined using {@link Selectable} expressions, which can be: + * + *

    + *
  • {@link Field}: References an existing document field.
  • + *
  • {@link Function}: Represents the result of a function with an assigned alias name using + * {@link Expr#as}
  • + *
+ * + *

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 + * desired. + * + *

Example: + * + * ```typescript + * firestore.pipeline().collection("books") + * .select( + * Field.of("name"), + * Field.of("address").toUppercase().as("upperAddress"), + * ); + * ``` + * + * @param selections The fields to include in the output documents, specified as {@link + * Selectable} expressions. + * @return A new Pipeline object with this stage appended to the stage list. + */ select(...fields: Selectable[]): Pipeline; select(...fields: (Selectable | string)[]): Pipeline { const copy = this.stages.map(s => s); @@ -108,31 +225,208 @@ export class Pipeline< return result; } + /** + * Filters the documents from previous stages to only include those matching the specified {@link + * FilterCondition}. + * + *

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: + * + *

    + *
  • field comparators: {@link Function#eq}, {@link Function#lt} (less than), {@link + * Function#gt} (greater than), etc.
  • + *
  • logical operators: {@link Function#and}, {@link Function#or}, {@link Function#not}, etc.
  • + *
  • advanced functions: {@link Function#regexMatch}, {@link + * Function#arrayContains}, etc.
  • + *
+ * + *

Example: + * + * ```typescript + * 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") + * ) + * ); + * ``` + * + * @param condition The {@link FilterCondition} to apply. + * @return A new Pipeline object with this stage appended to the stage list. + */ where(condition: FilterCondition & Expr): Pipeline { const copy = this.stages.map(s => s); copy.push(new Where(condition)); return new Pipeline(this.db, copy); } + /** + * Skips the first `offset` number of documents from the results of previous stages. + * + *

This stage is useful for implementing pagination in your pipelines, allowing you to retrieve + * results in chunks. It is typically used in conjunction with {@link #limit} to control the + * size of each page. + * + *

Example: + * + * ```typescript + * // Retrieve the second page of 20 results + * firestore.pipeline().collection("books") + * .sort(Field.of("published").descending()) + * .offset(20) // Skip the first 20 results + * .limit(20); // Take the next 20 results + * ``` + * + * @param offset The number of documents to skip. + * @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 new Pipeline(this.db, copy); } + /** + * Limits the maximum number of documents returned by previous stages to `limit`. + * + *

This stage is particularly useful when you want to retrieve a controlled subset of data from + * a potentially large result set. It's often used for: + * + *

    + *
  • **Pagination:** In combination with {@link #offset} to retrieve specific pages of + * results.
  • + *
  • **Limiting Data Retrieval:** To prevent excessive data transfer and improve performance, + * especially when dealing with large collections.
  • + *
+ * + *

Example: + * + * ```typescript + * // Limit the results to the top 10 highest-rated books + * firestore.pipeline().collection("books") + * .sort(Field.of("rating").descending()) + * .limit(10); + * ``` + * + * @param limit The maximum number of documents to return. + * @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 new Pipeline(this.db, copy); } + /** + * Returns a set of distinct field values from the inputs to this stage. + * + *

This stage run through the results from previous stages to include only results with unique + * combinations of values for the specified fields and produce these fields as the output. + * + *

Example: + * + * ```typescript + * // Get a list of unique genres. + * firestore.pipeline().collection("books") + * .distinct("genre"); + * ``` + * + * @param fields The fields to consider when determining distinct values. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + distinct(...fields: string[]): Pipeline; + + /** + * Returns a set of distinct {@link Expr} values from the inputs to this stage. + * + *

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

The parameters to this stage are defined using {@link Selectable} expressions, which can be: + * + *

    + *
  • {@link Field}: References an existing document field.
  • + *
  • {@link Function}: Represents the result of a function with an assigned alias name using + * {@link Expr#as}
  • + *
+ * + *

Example: + * + * ```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")) + * .select("authorName"); + * ``` + * + * @param selectables The {@link Selectable} expressions to consider when determining distinct + * value combinations. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + distinct(...selectables: Selectable[]): Pipeline; distinct(...groups: (string | Selectable)[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new Distinct(this.selectablesToMap(groups || []))); return new Pipeline(this.db, 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. + * + *

Example: + * + * ```typescript + * // Calculate the average rating and the total number of books + * firestore.pipeline().collection("books") + * .aggregate( + * Field.of("rating").avg().as("averageRating"), + * countAll().as("totalBooks") + * ); + * ``` + * + * @param accumulators The {@link AccumulatorTarget} expressions, each wrapping an {@link Accumulator} + * 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; + /** + * Performs optionally grouped aggregation operations on the documents from previous stages. + * + *

This stage allows you to calculate aggregate values over a set of documents, optionally + * grouped by one or more fields or functions. You can specify: + * + *

    + *
  • **Grouping Fields or Functions:** One or more fields or functions to group the documents + * by. For each distinct combination of values in these fields, a separate group is created. + * 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 + * calculates a value (e.g., sum, average, count) based on the documents within its group.
  • + *
+ * + *

Example: + * + * ```typescript + * // Calculate the average rating for each genre. + * firestore.pipeline().collection("books") + * .aggregate({ + * accumulators: [avg(Field.of("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. + */ aggregate(options: { accumulators: AccumulatorTarget[]; groups?: (string | Selectable)[]; @@ -198,6 +492,30 @@ export class Pipeline< return new Pipeline(this.db, copy); } + /** + * Sorts the documents from previous stages based on one or more {@link Ordering} criteria. + * + *

This stage allows you to order the results of your pipeline. You can specify multiple {@link + * Ordering} instances to sort by multiple fields in ascending or descending order. If documents + * have the same value for a field used for sorting, the next specified ordering will be used. If + * all orderings result in equal comparison, the documents are considered equal and the order is + * unspecified. + * + *

Example: + * + * ```typescript + * // Sort books by rating in descending order, and then by title in ascending order for books + * // 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 + * ); + * ``` + * + * @param orders One or more {@link Ordering} instances specifying the sorting criteria. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ sort(...orderings: Ordering[]): Pipeline; sort(options: { orderings: Ordering[]; @@ -217,38 +535,72 @@ export class Pipeline< const copy = this.stages.map(s => s); // Option object if ('orderings' in optionsOrOrderings) { - copy.push( - new Sort( - optionsOrOrderings.orderings, - optionsOrOrderings.density ?? 'unspecified', - optionsOrOrderings.truncation ?? 'unspecified' - ) - ); + copy.push(new Sort(optionsOrOrderings.orderings)); } else { // Ordering object - copy.push( - new Sort([optionsOrOrderings, ...rest], 'unspecified', 'unspecified') - ); + copy.push(new Sort([optionsOrOrderings, ...rest])); } return new Pipeline(this.db, copy); } - paginate(pageSize: number, orderings?: Ordering[]): PaginatingPipeline { - const copy = this.stages.map(s => s); - return new PaginatingPipeline( - new Pipeline(this.db, copy), - pageSize, - orderings - ); - } - + /** + * Adds a generic stage to the pipeline. + * + *

This method provides a flexible way to extend the pipeline's functionality by adding custom + * stages. Each generic stage is defined by a unique `name` and a set of `params` that control its + * behavior. + * + *

Example (Assuming there is no "where" stage available in SDK): + * + * ```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 + * .select("title", "author"); + * ``` + * + * @param name The unique name of the generic stage to add. + * @param params A list of parameters to configure the generic stage's behavior. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ genericStage(name: string, params: any[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new GenerateStage(name, params)); return new Pipeline(this.db, copy); } + /** + * 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 util = new ExecutionUtil( this.db, @@ -257,6 +609,22 @@ export class Pipeline< return util._getResponse(this).then(result => result!); } + /** + * Executes this pipeline and streams the results as {@link PipelineResult}s. + * + * @returns {Stream.} A stream of + * PipelineResult. + * + * @example + * ```typescript + * firestore.pipeline().collection("books") + * .where(gt(Field.of("rating"), 4.5)) + * .select("title", "author", "rating") + * .stream() + * .on('data', (pipelineResult) => {}) + * .on('end', () => {}); + * ``` + */ stream(): NodeJS.ReadableStream { const util = new ExecutionUtil( this.db, @@ -280,61 +648,13 @@ export class Pipeline< } } -class PaginatingPipeline { - constructor( - private pipeline: Pipeline, - private pageSize: number, - private orderings?: Ordering[] - ) {} - - firstPage(): Pipeline { - return this.pipeline; - } - - lastPage(): Pipeline { - return this.pipeline; - } - - offset(): PaginatingPipeline { - return this; - } - - limit(): PaginatingPipeline { - return this; - } - - startAt(result: PipelineResult): PaginatingPipeline { - return this; - } - - startAfter(result: PipelineResult): PaginatingPipeline { - return this; - } - - endAt(result: PipelineResult): PaginatingPipeline { - return this; - } - - endBefore(result: PipelineResult): PaginatingPipeline { - return this; - } - - /** - * @internal - * @private - */ - withEndCursor(arg0: QueryCursor): PaginatingPipeline { - throw new Error('Method not implemented.'); - } - /** - * @internal - * @private - */ - withStartCursor(arg0: QueryCursor): PaginatingPipeline { - throw new Error('Method not implemented.'); - } -} - +/** + * A PipelineResult contains data read from a Firestore Pipeline. The data can be extracted with the + * {@link #data()} or {@link #get(String)} methods. + * + *

If the PipelineResult represents a non-document result, `ref` will return a undefined + * value. + */ export class PipelineResult< AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData, @@ -343,7 +663,7 @@ export class PipelineResult< | DocumentReference | undefined; private _serializer: Serializer; - public readonly _readTime: Timestamp | undefined; + public readonly _executionTime: Timestamp | undefined; public readonly _createTime: Timestamp | undefined; public readonly _updateTime: Timestamp | undefined; @@ -355,12 +675,10 @@ export class PipelineResult< * @param ref The reference to the document. * @param _fieldsProto The fields of the Firestore `Document` Protobuf backing * this document (or undefined if the document does not exist). - * @param readTime The time when this snapshot was read (or undefined if + * @param readTime The time when this result was read (or undefined if * the document exists only locally). - * @param createTime The time when the document was created (or undefined if - * the document does not exist). - * @param updateTime The time when the document was last updated (or undefined - * if the document does not exist). + * @param createTime The time when the document was created if the result is a document, undefined otherwise. + * @param updateTime The time when the document was last updated if the result is a document, undefined otherwise. */ constructor( serializer: Serializer, @@ -376,56 +694,34 @@ export class PipelineResult< ) { this._ref = ref; this._serializer = serializer; - this._readTime = readTime; + this._executionTime = readTime; this._createTime = createTime; this._updateTime = updateTime; } + /** + * The reference of the document, if it is a document; otherwise `undefined`. + */ get ref(): DocumentReference | undefined { return this._ref; } /** - * The ID of the document for which this DocumentSnapshot contains data. + * The ID of the document for which this PipelineResult contains data, if it is a document; otherwise `undefined`. * * @type {string} - * @name DocumentSnapshot#id * @readonly * - * @example - * ``` - * let documentRef = firestore.doc('col/doc'); - * - * documentRef.get().then((documentSnapshot) => { - * if (documentSnapshot.exists) { - * console.log(`Document found with name '${documentSnapshot.id}'`); - * } - * }); - * ``` */ get id(): string | undefined { return this._ref?.id; } /** - * The time the document was created. Undefined for documents that don't - * exist. + * The time the document was created. Undefined if this result is not a document. * * @type {Timestamp|undefined} - * @name DocumentSnapshot#createTime * @readonly - * - * @example - * ``` - * let documentRef = firestore.doc('col/doc'); - * - * documentRef.get().then(documentSnapshot => { - * if (documentSnapshot.exists) { - * let createTime = documentSnapshot.createTime; - * console.log(`Document created at '${createTime.toDate()}'`); - * } - * }); - * ``` */ get createTime(): Timestamp | undefined { return this._createTime; @@ -433,54 +729,31 @@ export class PipelineResult< /** * The time the document was last updated (at the time the snapshot was - * generated). Undefined for documents that don't exist. + * generated). Undefined if this result is not a document. * * @type {Timestamp|undefined} - * @name DocumentSnapshot#updateTime * @readonly - * - * @example - * ``` - * let documentRef = firestore.doc('col/doc'); - * - * documentRef.get().then(documentSnapshot => { - * if (documentSnapshot.exists) { - * let updateTime = documentSnapshot.updateTime; - * console.log(`Document updated at '${updateTime.toDate()}'`); - * } - * }); - * ``` */ get updateTime(): Timestamp | undefined { return this._updateTime; } /** - * The time this snapshot was read. + * The time at which the pipeline producing this result is executed. * * @type {Timestamp} - * @name DocumentSnapshot#readTime * @readonly * - * @example - * ``` - * let documentRef = firestore.doc('col/doc'); - * - * documentRef.get().then(documentSnapshot => { - * let readTime = documentSnapshot.readTime; - * console.log(`Document read at '${readTime.toDate()}'`); - * }); - * ``` */ - get readTime(): Timestamp { - if (this._readTime === undefined) { + get executionTime(): Timestamp { + if (this._executionTime === undefined) { throw new Error("Called 'readTime' on a local document"); } - return this._readTime; + return this._executionTime; } /** - * Retrieves all fields in the document as an object. Returns 'undefined' if + * Retrieves all fields in the result as an object. Returns 'undefined' if * the document doesn't exist. * * @returns {T|undefined} An object containing all fields in the document or @@ -488,10 +761,10 @@ export class PipelineResult< * * @example * ``` - * let documentRef = firestore.doc('col/doc'); + * let p = firestore.pipeline().collection('col'); * - * documentRef.get().then(documentSnapshot => { - * let data = documentSnapshot.data(); + * p.execute().then(results => { + * let data = results[0].data(); * console.log(`Retrieved data: ${JSON.stringify(data)}`); * }); * ``` @@ -514,7 +787,7 @@ export class PipelineResult< new QueryDocumentSnapshot( untypedReference, this._fieldsProto!, - this.readTime, + this.executionTime, this.createTime!, this.updateTime! ) @@ -538,12 +811,10 @@ export class PipelineResult< * * @example * ``` - * let documentRef = firestore.doc('col/doc'); + * let p = firestore.pipeline().collection('col'); * - * documentRef.set({ a: { b: 'c' }}).then(() => { - * return documentRef.get(); - * }).then(documentSnapshot => { - * let field = documentSnapshot.get('a.b'); + * p.execute().then(results => { + * let field = results[0].get('a.b'); * console.log(`Retrieved field value: ${field}`); * }); * ``` @@ -595,11 +866,11 @@ export class PipelineResult< } /** - * Returns true if the document's data and path in this `DocumentSnapshot` is + * Returns true if the document's data and path in this `PipelineResult` is * equal to the provided value. * * @param {*} other The value to compare against. - * @return {boolean} true if this `DocumentSnapshot` is equal to the provided + * @return {boolean} true if this `PipelineResult` is equal to the provided * value. */ isEqual(other: PipelineResult): boolean { diff --git a/dev/src/reference/query.ts b/dev/src/reference/query.ts index e335ae2d1..42fbb7be1 100644 --- a/dev/src/reference/query.ts +++ b/dev/src/reference/query.ts @@ -717,7 +717,7 @@ export class Query< break; } } - return Ordering.of(Field.of(fieldOrder.field), dir); + return new Ordering(Field.of(fieldOrder.field), dir || 'ascending'); }); if (orderings.length > 0) { pipeline = pipeline.sort({ diff --git a/dev/src/stage.ts b/dev/src/stage.ts index 842b09cca..f6cd4faeb 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -215,20 +215,12 @@ export class Select implements Stage { export class Sort implements Stage { name = 'sort'; - constructor( - private orders: Ordering[], - private density: 'unspecified' | 'required', - private truncation: 'unspecified' | 'disabled' - ) {} + constructor(private orders: Ordering[]) {} _toProto(serializer: Serializer): api.Pipeline.IStage { return { name: this.name, args: this.orders.map(o => o._toProto(serializer)), - options: { - density: serializer.encodeValue(this.density)!, - truncation: serializer.encodeValue(this.truncation)!, - }, }; } } diff --git a/dev/src/v1/firestore_client.ts b/dev/src/v1/firestore_client.ts index 9affa770c..09be77e4f 100644 --- a/dev/src/v1/firestore_client.ts +++ b/dev/src/v1/firestore_client.ts @@ -1332,7 +1332,7 @@ export class FirestoreClient { options.otherArgs.headers = options.otherArgs.headers || {}; options.otherArgs.headers['x-goog-request-params'] = this._gaxModule.routingHeader.fromParams({ - parent: '', + parent: request.database ? `${request.database}/documents` : '', }); this.initialize(); return this.innerApiCalls.executePipeline(request, options); diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 97d46eb38..283b472e7 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -36,7 +36,6 @@ import { lt, neq, not, - isNull, or, regexContains, regexMatch, @@ -646,7 +645,7 @@ describe.only('Pipeline class', () => { .pipeline() .where(not(Field.of('rating').isNaN())) .select( - isNull('rating').as('ratingIsNull'), + Field.of('rating').eq(null).as('ratingIsNull'), not(Field.of('rating').isNaN()).as('ratingIsNotNaN') ) .limit(1) From e95701770a9510e21da32479925b4383ff68377c Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 22 Aug 2024 15:18:11 -0400 Subject: [PATCH 06/31] Basic initial converter support --- dev/src/pipeline-util.ts | 11 ++-- dev/src/pipeline.ts | 119 +++++++++++++++++++++++++++++------- dev/src/types.ts | 19 ++++++ dev/system-test/pipeline.ts | 47 ++++++++++++-- types/firestore.d.ts | 30 +++++++++ 5 files changed, 194 insertions(+), 32 deletions(-) diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index f4fdd886f..c3fff41d3 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -1,5 +1,6 @@ import * as firestore from '@google-cloud/firestore'; import {GoogleError, serializer} from 'google-gax'; +import {converter} from 'protobufjs'; import {Duplex, Transform} from 'stream'; import {google} from '../protos/firestore_v1_proto_api'; @@ -49,7 +50,8 @@ export class ExecutionUtil< /** @private */ readonly _firestore: Firestore, /** @private */ - readonly _serializer: Serializer + readonly _serializer: Serializer, + readonly _converter: firestore.FirestorePipelineConverter ) {} _getResponse( @@ -169,12 +171,12 @@ export class ExecutionUtil< } const ref = result.name - ? new DocumentReference( + ? new DocumentReference( this._firestore, QualifiedResourcePath.fromSlashSeparatedString(result.name) ) : undefined; - output.result = new PipelineResult( + output.result = new PipelineResult( this._serializer, ref, result.fields || undefined, @@ -184,7 +186,8 @@ export class ExecutionUtil< : undefined, result.updateTime ? Timestamp.fromProto(result.updateTime!) - : undefined + : undefined, + this._converter ); return output; }) diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index d663267a1..a832f4a72 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -1,3 +1,4 @@ +import {DocumentData} from '@google-cloud/firestore'; import * as firestore from '@google-cloud/firestore'; import * as deepEqual from 'fast-deep-equal'; import {google} from '../protos/firestore_v1_proto_api'; @@ -36,7 +37,7 @@ import { Stage, Distinct, } from './stage'; -import {ApiMapValue, defaultConverter} from './types'; +import {ApiMapValue, defaultConverter, defaultPipelineConverter} from './types'; import * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; import IStructuredPipeline = google.firestore.v1.IStructuredPipeline; @@ -110,7 +111,8 @@ export class Pipeline< > { constructor( private db: Firestore, - private stages: Stage[] + private stages: Stage[], + private converter: firestore.FirestorePipelineConverter = defaultConverter() ) {} /** @@ -570,6 +572,77 @@ export class Pipeline< return new Pipeline(this.db, copy); } + withConverter(converter: null): Pipeline; + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData, + >( + converter: firestore.FirestorePipelineConverter + ): Pipeline; + /** + * Applies a custom data converter to this Query, allowing you to use your + * own custom model objects with Firestore. When you call get() on the + * returned Query, the provided converter will convert between Firestore + * data of type `NewDbModelType` and your custom type `NewAppModelType`. + * + * Using the converter allows you to specify generic type arguments when + * storing and retrieving objects from Firestore. + * + * Passing in `null` as the converter parameter removes the current + * converter. + * + * @example + * ``` + * class Post { + * constructor(readonly title: string, readonly author: string) {} + * + * toString(): string { + * return this.title + ', by ' + this.author; + * } + * } + * + * const postConverter = { + * toFirestore(post: Post): FirebaseFirestore.DocumentData { + * return {title: post.title, author: post.author}; + * }, + * fromFirestore( + * snapshot: FirebaseFirestore.QueryDocumentSnapshot + * ): Post { + * const data = snapshot.data(); + * return new Post(data.title, data.author); + * } + * }; + * + * const postSnap = await Firestore() + * .collection('posts') + * .withConverter(postConverter) + * .doc().get(); + * const post = postSnap.data(); + * if (post !== undefined) { + * post.title; // string + * post.toString(); // Should be defined + * post.someNonExistentProperty; // TS error + * } + * + * ``` + * @param {FirestoreDataConverter | null} converter Converts objects to and + * from Firestore. Passing in `null` removes the current converter. + * @return A Query that uses the provided converter. + */ + withConverter< + NewAppModelType, + NewDbModelType extends firestore.DocumentData = firestore.DocumentData, + >( + converter: firestore.FirestorePipelineConverter | null + ): Pipeline { + const copy = this.stages.map(s => s); + return new Pipeline( + this.db, + copy, + converter ?? defaultPipelineConverter() + ); + } + /** * Executes this pipeline and returns a Promise to represent the asynchronous operation. * @@ -604,7 +677,8 @@ export class Pipeline< execute(): Promise>> { const util = new ExecutionUtil( this.db, - this.db._serializer! + this.db._serializer!, + this.converter ); return util._getResponse(this).then(result => result!); } @@ -628,7 +702,8 @@ export class Pipeline< stream(): NodeJS.ReadableStream { const util = new ExecutionUtil( this.db, - this.db._serializer! + this.db._serializer!, + this.converter ); return util.stream(this); } @@ -658,10 +733,9 @@ export class Pipeline< export class PipelineResult< AppModelType = firestore.DocumentData, DbModelType extends firestore.DocumentData = firestore.DocumentData, -> { - private readonly _ref: - | DocumentReference - | undefined; +> implements firestore.PipelineResult +{ + private readonly _ref: DocumentReference | undefined; private _serializer: Serializer; public readonly _executionTime: Timestamp | undefined; public readonly _createTime: Timestamp | undefined; @@ -682,7 +756,7 @@ export class PipelineResult< */ constructor( serializer: Serializer, - ref?: DocumentReference, + ref?: DocumentReference, /** * @internal * @private @@ -690,7 +764,8 @@ export class PipelineResult< readonly _fieldsProto?: ApiMapValue, readTime?: Timestamp, createTime?: Timestamp, - updateTime?: Timestamp + updateTime?: Timestamp, + readonly converter: firestore.FirestorePipelineConverter = defaultConverter() ) { this._ref = ref; this._serializer = serializer; @@ -702,7 +777,7 @@ export class PipelineResult< /** * The reference of the document, if it is a document; otherwise `undefined`. */ - get ref(): DocumentReference | undefined { + get ref(): DocumentReference | undefined { return this._ref; } @@ -778,18 +853,16 @@ export class PipelineResult< // We only want to use the converter and create a new QueryDocumentSnapshot // if a converter has been provided. - if (!!this.ref && this.ref._converter !== defaultConverter()) { - const untypedReference = new DocumentReference( - this.ref.firestore, - this.ref._path - ); - return this.ref._converter.fromFirestore( - new QueryDocumentSnapshot( - untypedReference, - this._fieldsProto!, - this.executionTime, - this.createTime!, - this.updateTime! + if (!!this.converter && this.converter !== defaultPipelineConverter()) { + return this.converter.fromFirestore( + new PipelineResult( + this._serializer, + this.ref, + this._fieldsProto, + this._executionTime, + this.createTime, + this.updateTime, + defaultPipelineConverter() ) ); } else { diff --git a/dev/src/types.ts b/dev/src/types.ts index 45d926818..26e8be080 100644 --- a/dev/src/types.ts +++ b/dev/src/types.ts @@ -19,6 +19,7 @@ import { QueryDocumentSnapshot, DocumentData, WithFieldValue, + FirestorePipelineConverter, } from '@google-cloud/firestore'; import {CallOptions} from 'google-gax'; @@ -26,6 +27,7 @@ import {Duplex} from 'stream'; import {google} from '../protos/firestore_v1_proto_api'; import {FieldPath} from './path'; +import {PipelineResult} from './pipeline'; import api = google.firestore.v1; @@ -151,6 +153,23 @@ export function defaultConverter< >; } +const defaultPipelineConverterObj: FirestorePipelineConverter = { + fromFirestore(snapshot: PipelineResult): DocumentData { + return snapshot.data()!; + }, +}; + +/** + * A default converter to use when none is provided. + * @private + * @internal + */ +export function defaultPipelineConverter< + AppModelType, +>(): FirestorePipelineConverter { + return defaultConverterObj as FirestoreDataConverter; +} + /** * Update data that has been resolved to a mapping of FieldPaths to values. */ diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 283b472e7..a1b1d8f74 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -1,6 +1,7 @@ import { AggregateQuery, DocumentData, + FirestorePipelineConverter, QuerySnapshot, VectorValue, } from '@google-cloud/firestore'; @@ -78,14 +79,17 @@ describe.only('Pipeline class', () => { return randomCol; } - function expectResults(result: PipelineResult[], ...docs: string[]): void; - function expectResults( - result: PipelineResult[], + function expectResults( + result: PipelineResult[], + ...docs: string[] + ): void; + function expectResults( + result: PipelineResult[], ...data: DocumentData[] ): void; - function expectResults( - result: PipelineResult[], + function expectResults( + result: PipelineResult[], ...data: DocumentData[] | string[] ): void { expect(result.length).to.equal(data.length); @@ -729,4 +733,37 @@ describe.only('Pipeline class', () => { {title: 'Dune', 'awards.hugo': true} ); }); + + it('testPipelineConverters', async () => { + type AppModel = {myTitle: string; myAuthor: string; myPublished: number}; + const converter: FirestorePipelineConverter = { + fromFirestore(result: FirebaseFirestore.PipelineResult): AppModel { + return { + myTitle: result.data()!.title as string, + myAuthor: result.data()!.author as string, + myPublished: result.data()!.published as number, + }; + }, + }; + + const results = await firestore + .pipeline() + .collection(randomCol.path) + .sort(Field.of('published').ascending()) + .limit(2) + .withConverter(converter) + .execute(); + + const objs = results.map(r => r.data()); + expect(objs[0]).to.deep.equal({ + myAuthor: 'Jane Austen', + myPublished: 1813, + myTitle: 'Pride and Prejudice', + }); + expect(objs[1]).to.deep.equal({ + myAuthor: 'Fyodor Dostoevsky', + myPublished: 1866, + myTitle: 'Crime and Punishment', + }); + }); }); diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 76cf5e10f..b10ce3c99 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -380,6 +380,10 @@ declare namespace FirebaseFirestore { fromFirestore(snapshot: QueryDocumentSnapshot): AppModelType; } + export interface FirestorePipelineConverter { + fromFirestore(result: PipelineResult): AppModelType; + } + /** * Settings used to directly configure a `Firestore` instance. */ @@ -1659,6 +1663,32 @@ declare namespace FirebaseFirestore { data(): AppModelType; } + export class PipelineResult< + AppModelType = DocumentData, + DbModelType extends DocumentData = DocumentData, + > { + private constructor(); + + /** + * The time the document was created. + */ + readonly createTime: Timestamp | undefined; + + /** + * The time the document was last updated (at the time the snapshot was + * generated). + */ + readonly updateTime: Timestamp | undefined; + + /** + * Retrieves all fields in the document as an Object. + * + * @override + * @return An Object containing all fields in the document. + */ + data(): AppModelType | undefined; + } + /** * The direction of a `Query.orderBy()` clause is specified as 'desc' or 'asc' * (descending or ascending). From 50f061bdc9ce850662beaf5fd7ad68f2b0bb96b6 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Mon, 26 Aug 2024 09:49:19 -0400 Subject: [PATCH 07/31] Converter cleanup --- dev/src/pipeline-util.ts | 53 ++++++++++++++++--------------------- dev/src/pipeline.ts | 37 +++++++++----------------- dev/src/reference/types.ts | 2 +- dev/system-test/pipeline.ts | 2 +- types/firestore.d.ts | 5 +--- 5 files changed, 39 insertions(+), 60 deletions(-) diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index c3fff41d3..ce6677b9a 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -42,10 +42,7 @@ import api = protos.google.firestore.v1; * @private * @internal */ -export class ExecutionUtil< - AppModelType, - DbModelType extends firestore.DocumentData, -> { +export class ExecutionUtil { constructor( /** @private */ readonly _firestore: Firestore, @@ -55,15 +52,15 @@ export class ExecutionUtil< ) {} _getResponse( - pipeline: Pipeline, + pipeline: Pipeline, transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions - ): Promise> | undefined> { + ): Promise> | undefined> { // Capture the error stack to preserve stack tracing across async calls. const stack = Error().stack!; return new Promise((resolve, reject) => { - const results: Array> = []; + const results: Array> = []; const output: Omit, 'result'> & { executionTime?: Timestamp; } = {}; @@ -72,25 +69,22 @@ export class ExecutionUtil< .on('error', err => { reject(wrapError(err, stack)); }) - .on( - 'data', - (data: PipelineStreamElement[]) => { - for (const element of data) { - if (element.transaction) { - output.transaction = element.transaction; - } - if (element.executionTime) { - output.executionTime = element.executionTime; - } - if (element.explainMetrics) { - output.explainMetrics = element.explainMetrics; - } - if (element.result) { - results.push(element.result); - } + .on('data', (data: PipelineStreamElement[]) => { + for (const element of data) { + if (element.transaction) { + output.transaction = element.transaction; + } + if (element.executionTime) { + output.executionTime = element.executionTime; + } + if (element.explainMetrics) { + output.explainMetrics = element.explainMetrics; + } + if (element.result) { + results.push(element.result); } } - ) + }) .on('end', () => { resolve(results); }); @@ -111,7 +105,7 @@ export class ExecutionUtil< return Date.now() - startTime >= totalTimeout; } - stream(pipeline: Pipeline): NodeJS.ReadableStream { + stream(pipeline: Pipeline): NodeJS.ReadableStream { const responseStream = this._stream(pipeline); const transform = new Transform({ objectMode: true, @@ -126,7 +120,7 @@ export class ExecutionUtil< } _stream( - pipeline: Pipeline, + pipeline: Pipeline, transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions ): NodeJS.ReadableStream { @@ -149,7 +143,7 @@ export class ExecutionUtil< } if (proto.results && proto.results.length === 0) { - const output: PipelineStreamElement = {}; + const output: PipelineStreamElement = {}; if (proto.transaction?.length) { output.transaction = proto.transaction; } @@ -161,8 +155,7 @@ export class ExecutionUtil< callback( undefined, proto.results.map(result => { - const output: PipelineStreamElement = - {}; + const output: PipelineStreamElement = {}; if (proto.transaction?.length) { output.transaction = proto.transaction; } @@ -176,7 +169,7 @@ export class ExecutionUtil< QualifiedResourcePath.fromSlashSeparatedString(result.name) ) : undefined; - output.result = new PipelineResult( + output.result = new PipelineResult( this._serializer, ref, result.fields || undefined, diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index a832f4a72..dc5d14025 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -105,10 +105,7 @@ export class PipelineSource { * .execute(); * ``` */ -export class Pipeline< - AppModelType = firestore.DocumentData, - DbModelType extends firestore.DocumentData = firestore.DocumentData, -> { +export class Pipeline { constructor( private db: Firestore, private stages: Stage[], @@ -573,12 +570,9 @@ export class Pipeline< } withConverter(converter: null): Pipeline; - withConverter< - NewAppModelType, - NewDbModelType extends firestore.DocumentData = firestore.DocumentData, - >( + withConverter( converter: firestore.FirestorePipelineConverter - ): Pipeline; + ): Pipeline; /** * Applies a custom data converter to this Query, allowing you to use your * own custom model objects with Firestore. When you call get() on the @@ -629,14 +623,11 @@ export class Pipeline< * from Firestore. Passing in `null` removes the current converter. * @return A Query that uses the provided converter. */ - withConverter< - NewAppModelType, - NewDbModelType extends firestore.DocumentData = firestore.DocumentData, - >( + withConverter( converter: firestore.FirestorePipelineConverter | null - ): Pipeline { + ): Pipeline { const copy = this.stages.map(s => s); - return new Pipeline( + return new Pipeline( this.db, copy, converter ?? defaultPipelineConverter() @@ -674,8 +665,8 @@ export class Pipeline< * * @return A Promise representing the asynchronous pipeline execution. */ - execute(): Promise>> { - const util = new ExecutionUtil( + execute(): Promise>> { + const util = new ExecutionUtil( this.db, this.db._serializer!, this.converter @@ -700,7 +691,7 @@ export class Pipeline< * ``` */ stream(): NodeJS.ReadableStream { - const util = new ExecutionUtil( + const util = new ExecutionUtil( this.db, this.db._serializer!, this.converter @@ -730,10 +721,8 @@ export class Pipeline< *

If the PipelineResult represents a non-document result, `ref` will return a undefined * value. */ -export class PipelineResult< - AppModelType = firestore.DocumentData, - DbModelType extends firestore.DocumentData = firestore.DocumentData, -> implements firestore.PipelineResult +export class PipelineResult + implements firestore.PipelineResult { private readonly _ref: DocumentReference | undefined; private _serializer: Serializer; @@ -855,7 +844,7 @@ export class PipelineResult< // if a converter has been provided. if (!!this.converter && this.converter !== defaultPipelineConverter()) { return this.converter.fromFirestore( - new PipelineResult( + new PipelineResult( this._serializer, this.ref, this._fieldsProto, @@ -946,7 +935,7 @@ export class PipelineResult< * @return {boolean} true if this `PipelineResult` is equal to the provided * value. */ - isEqual(other: PipelineResult): boolean { + isEqual(other: PipelineResult): boolean { return ( this === other || (isOptionalEqual(this._ref, other._ref) && diff --git a/dev/src/reference/types.ts b/dev/src/reference/types.ts index 96aa7098e..036994ff6 100644 --- a/dev/src/reference/types.ts +++ b/dev/src/reference/types.ts @@ -67,7 +67,7 @@ export interface PipelineStreamElement< transaction?: Uint8Array; executionTime?: Timestamp; explainMetrics?: ExplainMetrics; - result?: PipelineResult; + result?: PipelineResult; } /** diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index a1b1d8f74..a5b262cbf 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -734,7 +734,7 @@ describe.only('Pipeline class', () => { ); }); - it('testPipelineConverters', async () => { + it('pipeline converter works', async () => { type AppModel = {myTitle: string; myAuthor: string; myPublished: number}; const converter: FirestorePipelineConverter = { fromFirestore(result: FirebaseFirestore.PipelineResult): AppModel { diff --git a/types/firestore.d.ts b/types/firestore.d.ts index b10ce3c99..0c4f0a93d 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -1663,10 +1663,7 @@ declare namespace FirebaseFirestore { data(): AppModelType; } - export class PipelineResult< - AppModelType = DocumentData, - DbModelType extends DocumentData = DocumentData, - > { + export class PipelineResult { private constructor(); /** From 5947337db17cb72ac760f3ae602ae8bd6de31a0e Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Mon, 26 Aug 2024 11:44:21 -0400 Subject: [PATCH 08/31] Address some feedbacks and add more nested field test. --- dev/src/expression.ts | 14 +-- dev/src/pipeline.ts | 124 +++++++++------------------ dev/src/reference/aggregate-query.ts | 6 +- dev/src/reference/query.ts | 6 +- dev/src/stage.ts | 2 +- dev/system-test/pipeline.ts | 53 +++++++++--- 6 files changed, 93 insertions(+), 112 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 3fde72f9d..32c46e063 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -999,7 +999,7 @@ export abstract class Expr { if (other instanceof Expr) { return new CosineDistance(this, other); } else { - return new CosineDistance(this, Constant.ofVector(other)); + return new CosineDistance(this, Constant.vector(other)); } } @@ -1045,7 +1045,7 @@ export abstract class Expr { if (other instanceof Expr) { return new DotProductDistance(this, other); } else { - return new DotProductDistance(this, Constant.ofVector(other)); + return new DotProductDistance(this, Constant.vector(other)); } } @@ -1091,7 +1091,7 @@ export abstract class Expr { if (other instanceof Expr) { return new EuclideanDistance(this, other); } else { - return new EuclideanDistance(this, Constant.ofVector(other)); + return new EuclideanDistance(this, Constant.vector(other)); } } @@ -1444,7 +1444,7 @@ export class Constant extends Expr { * @param value The VectorValue value. * @return A new `Constant` instance. */ - static ofVector(value: Array | VectorValue): Constant { + static vector(value: Array | VectorValue): Constant { if (value instanceof VectorValue) { return new Constant(value); } else { @@ -3915,7 +3915,7 @@ export function cosineDistance( other: Expr | number[] | VectorValue ): CosineDistance { const expr1 = expr instanceof Expr ? expr : Field.of(expr); - const expr2 = other instanceof Expr ? other : Constant.ofVector(other); + const expr2 = other instanceof Expr ? other : Constant.vector(other); return new CosineDistance(expr1, expr2); } @@ -4022,7 +4022,7 @@ export function dotProductDistance( other: Expr | number[] | VectorValue ): DotProductDistance { const expr1 = expr instanceof Expr ? expr : Field.of(expr); - const expr2 = other instanceof Expr ? other : Constant.ofVector(other); + const expr2 = other instanceof Expr ? other : Constant.vector(other); return new DotProductDistance(expr1, expr2); } @@ -4127,7 +4127,7 @@ export function euclideanDistance( other: Expr | number[] | VectorValue ): EuclideanDistance { const expr1 = expr instanceof Expr ? expr : Field.of(expr); - const expr2 = other instanceof Expr ? other : Constant.ofVector(other); + const expr2 = other instanceof Expr ? other : Constant.vector(other); return new EuclideanDistance(expr1, expr2); } diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index dc5d14025..7b0e7a285 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -29,7 +29,7 @@ import { Where, FindNearest, FindNearestOptions, - GenerateStage, + GenericStage, Limit, Offset, Select, @@ -138,41 +138,20 @@ export class Pipeline { * @param fields The 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 { + addFields(...fields: Selectable[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new AddField(this.selectablesToMap(fields))); - return new Pipeline(this.db, copy); + return new Pipeline(this.db, copy, this.converter); } - /** - * Selects a set of fields from the outputs of previous stages. - * - *

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 - * desired. - * - *

Example: - * - * ```typescript - * firestore.collection("books") - * .select("name", "address"); - * - * // The above is a shorthand of this: - * firestore.pipeline().collection("books") - * .select(Field.of("name"), Field.of("address")); - * ``` - * - * @param fields The name of the fields to include in the output documents. - * @return A new Pipeline object with this stage appended to the stage list. - */ - select(...fields: string[]): Pipeline; /** * Selects or creates a set of fields from the outputs of previous stages. * *

The selected fields are defined using {@link Selectable} expressions, which can be: * *

    - *
  • {@link Field}: References an existing document field.
  • + *
  • {@code string}: Name of an existing field
  • + *
  • {@link Field}: References an existing field.
  • *
  • {@link Function}: Represents the result of a function with an assigned alias name using * {@link Expr#as}
  • *
@@ -186,20 +165,20 @@ export class Pipeline { * ```typescript * firestore.pipeline().collection("books") * .select( - * Field.of("name"), + * "firstName", + * Field.of("lastName"), * Field.of("address").toUppercase().as("upperAddress"), * ); * ``` * * @param selections The fields to include in the output documents, specified as {@link - * Selectable} expressions. + * Selectable} expressions or {@code string} values representing field names. * @return A new Pipeline object with this stage appended to the stage list. */ - select(...fields: Selectable[]): Pipeline; - select(...fields: (Selectable | string)[]): Pipeline { + select(...fields: (Selectable | string)[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new Select(this.selectablesToMap(fields))); - return new Pipeline(this.db, copy); + return new Pipeline(this.db, copy, this.converter); } private selectablesToMap( @@ -255,10 +234,10 @@ export class Pipeline { * @param condition The {@link FilterCondition} to apply. * @return A new Pipeline object with this stage appended to the stage list. */ - where(condition: FilterCondition & Expr): Pipeline { + where(condition: FilterCondition & Expr): Pipeline { const copy = this.stages.map(s => s); copy.push(new Where(condition)); - return new Pipeline(this.db, copy); + return new Pipeline(this.db, copy, this.converter); } /** @@ -281,10 +260,10 @@ export class Pipeline { * @param offset The number of documents to skip. * @return A new Pipeline object with this stage appended to the stage list. */ - offset(offset: number): Pipeline { + offset(offset: number): Pipeline { const copy = this.stages.map(s => s); copy.push(new Offset(offset)); - return new Pipeline(this.db, copy); + return new Pipeline(this.db, copy, this.converter); } /** @@ -312,40 +291,22 @@ export class Pipeline { * @param limit The maximum number of documents to return. * @return A new Pipeline object with this stage appended to the stage list. */ - limit(limit: number): Pipeline { + limit(limit: number): Pipeline { const copy = this.stages.map(s => s); copy.push(new Limit(limit)); - return new Pipeline(this.db, copy); + return new Pipeline(this.db, copy, this.converter); } - /** - * Returns a set of distinct field values from the inputs to this stage. - * - *

This stage run through the results from previous stages to include only results with unique - * combinations of values for the specified fields and produce these fields as the output. - * - *

Example: - * - * ```typescript - * // Get a list of unique genres. - * firestore.pipeline().collection("books") - * .distinct("genre"); - * ``` - * - * @param fields The fields to consider when determining distinct values. - * @return A new {@code Pipeline} object with this stage appended to the stage list. - */ - distinct(...fields: string[]): Pipeline; - /** * Returns a set of distinct {@link Expr} values from the inputs to this stage. * *

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

The parameters to this stage are defined using {@link Selectable} expressions, which can be: + *

The parameters to this stage are defined using {@link Selectable} expressions or {@code string}s: * *

    + *
  • {@code string}: Name of an existing field
  • *
  • {@link Field}: References an existing document field.
  • *
  • {@link Function}: Represents the result of a function with an assigned alias name using * {@link Expr#as}
  • @@ -356,19 +317,18 @@ export class Pipeline { * ```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")) + * .distinct(toUppercase(Field.of("author")).as("authorName"), Field.of("genre"), "publishedAt") * .select("authorName"); * ``` * * @param selectables The {@link Selectable} expressions to consider when determining distinct - * value combinations. + * value combinations or {@code string}s representing field names. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ - distinct(...selectables: Selectable[]): Pipeline; - distinct(...groups: (string | Selectable)[]): Pipeline { + distinct(...groups: (string | Selectable)[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new Distinct(this.selectablesToMap(groups || []))); - return new Pipeline(this.db, copy); + return new Pipeline(this.db, copy, this.converter); } /** @@ -393,7 +353,7 @@ export class Pipeline { * 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(...accumulators: AccumulatorTarget[]): Pipeline; /** * Performs optionally grouped aggregation operations on the documents from previous stages. * @@ -429,13 +389,13 @@ export class Pipeline { aggregate(options: { accumulators: AccumulatorTarget[]; groups?: (string | Selectable)[]; - }): Pipeline; + }): Pipeline; aggregate( optionsOrTarget: | AccumulatorTarget | {accumulators: AccumulatorTarget[]; groups?: (string | Selectable)[]}, ...rest: AccumulatorTarget[] - ): Pipeline { + ): Pipeline { const copy = this.stages.map(s => s); if ('accumulators' in optionsOrTarget) { copy.push( @@ -462,29 +422,29 @@ export class Pipeline { ) ); } - return new Pipeline(this.db, copy); + return new Pipeline(this.db, copy, this.converter); } findNearest( field: string, vector: number[], options: FindNearestOptions - ): Pipeline; + ): Pipeline; findNearest( field: Field, vector: FirebaseFirestore.VectorValue, options: FindNearestOptions - ): Pipeline; + ): Pipeline; findNearest( field: string | Field, vector: FirebaseFirestore.VectorValue | number[], options: FindNearestOptions - ): Pipeline; + ): Pipeline; findNearest( field: string | Field, vector: number[] | FirebaseFirestore.VectorValue, options: FindNearestOptions - ): Pipeline { + ): Pipeline { const copy = this.stages.map(s => s); const fieldExpr = typeof field === 'string' ? Field.of(field) : field; copy.push(new FindNearest(fieldExpr, vector, options)); @@ -515,22 +475,16 @@ export class Pipeline { * @param orders One or more {@link Ordering} instances specifying the sorting criteria. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ - sort(...orderings: Ordering[]): Pipeline; - sort(options: { - orderings: Ordering[]; - density?: 'unspecified' | 'required'; - truncation?: 'unspecified' | 'disabled'; - }): Pipeline; + sort(...orderings: Ordering[]): Pipeline; + sort(options: {orderings: Ordering[]}): Pipeline; sort( optionsOrOrderings: | Ordering | { orderings: Ordering[]; - density?: 'unspecified' | 'required'; - truncation?: 'unspecified' | 'disabled'; }, ...rest: Ordering[] - ): Pipeline { + ): Pipeline { const copy = this.stages.map(s => s); // Option object if ('orderings' in optionsOrOrderings) { @@ -540,7 +494,7 @@ export class Pipeline { copy.push(new Sort([optionsOrOrderings, ...rest])); } - return new Pipeline(this.db, copy); + return new Pipeline(this.db, copy, this.converter); } /** @@ -563,10 +517,10 @@ export class Pipeline { * @param params A list of parameters to configure the generic stage's behavior. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ - genericStage(name: string, params: any[]): Pipeline { + genericStage(name: string, params: any[]): Pipeline { const copy = this.stages.map(s => s); - copy.push(new GenerateStage(name, params)); - return new Pipeline(this.db, copy); + copy.push(new GenericStage(name, params)); + return new Pipeline(this.db, copy, this.converter); } withConverter(converter: null): Pipeline; @@ -811,7 +765,9 @@ export class PipelineResult */ get executionTime(): Timestamp { if (this._executionTime === undefined) { - throw new Error("Called 'readTime' on a local document"); + throw new Error( + "'executionTime' is expected to exist, but it is undefined" + ); } return this._executionTime; } diff --git a/dev/src/reference/aggregate-query.ts b/dev/src/reference/aggregate-query.ts index f614e0d4e..fa88567a2 100644 --- a/dev/src/reference/aggregate-query.ts +++ b/dev/src/reference/aggregate-query.ts @@ -340,7 +340,7 @@ export class AggregateQuery< return runQueryRequest; } - toPipeline(): Pipeline { + pipeline(): Pipeline { const aggregates = mapToArray( this._aggregates, (aggregate, clientAlias) => { @@ -351,8 +351,10 @@ export class AggregateQuery< return count(Field.of(aggregate._field)).as(clientAlias); } else if (aggregate.aggregateType === 'avg') { return avg(Field.of(aggregate._field!)).as(clientAlias); - } else { + } else if (aggregate.aggregateType === 'sum') { return sum(Field.of(aggregate._field!)).as(clientAlias); + } else { + throw new Error(`Unknown aggregate type ${aggregate.aggregateType}`); } } ); diff --git a/dev/src/reference/query.ts b/dev/src/reference/query.ts index 42fbb7be1..e6f1e8d4a 100644 --- a/dev/src/reference/query.ts +++ b/dev/src/reference/query.ts @@ -720,11 +720,7 @@ export class Query< return new Ordering(Field.of(fieldOrder.field), dir || 'ascending'); }); if (orderings.length > 0) { - pipeline = pipeline.sort({ - orderings: orderings, - density: 'required', - truncation: 'unspecified', - }); + pipeline = pipeline.sort({orderings: orderings}); } // Cursors, Limit and Offset diff --git a/dev/src/stage.ts b/dev/src/stage.ts index f6cd4faeb..de656fc03 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -225,7 +225,7 @@ export class Sort implements Stage { } } -export class GenerateStage implements Stage { +export class GenericStage implements Stage { constructor( public name: string, params: any[] diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index a5b262cbf..09c98f1a3 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -47,6 +47,7 @@ import { dotProductDistance, euclideanDistance, Constant, + mapGet, } from '../src/expression'; import {PipelineResult} from '../src/pipeline'; import {verifyInstance} from '../test/util/helpers'; @@ -126,7 +127,8 @@ describe.only('Pipeline class', () => { published: 1979, rating: 4.2, tags: ['comedy', 'space', 'adventure'], - awards: {hugo: true, nebula: false}, + awards: {hugo: true, nebula: false, others: {unknown: {year: 1980}}}, + nestedField: {'level.1': {'level.2': true}}, }, book2: { title: 'Pride and Prejudice', @@ -429,9 +431,9 @@ describe.only('Pipeline class', () => { const results = await randomCol .pipeline() .select( - arrayFilter(Field.of('tags'), arrayElement().eq('comedy')).as( - 'filteredTags' - ) + Field.of('tags') + .arrayFilter(arrayElement().eq('comedy')) + .as('filteredTags') ) .limit(1) .execute(); @@ -445,10 +447,9 @@ describe.only('Pipeline class', () => { const results = await randomCol .pipeline() .select( - arrayTransform( - Field.of('tags'), - arrayElement().strConcat('transformed') - ).as('transformedTags') + Field.of('tags') + .arrayTransform(arrayElement().strConcat('transformed')) + .as('transformedTags') ) .limit(1) .execute(); @@ -662,14 +663,19 @@ describe.only('Pipeline class', () => { .pipeline() .select( Field.of('awards').mapGet('hugo').as('hugoAward'), + Field.of('awards').mapGet('others').as('others'), Field.of('title') ) .where(eq('hugoAward', true)) .execute(); expectResults( results, - {hugoAward: true, title: "The Hitchhiker's Guide to the Galaxy"}, - {hugoAward: true, title: 'Dune'} + { + hugoAward: true, + title: "The Hitchhiker's Guide to the Galaxy", + others: {unknown: {year: 1980}}, + }, + {hugoAward: true, title: 'Dune', others: null} ); }); @@ -701,13 +707,13 @@ describe.only('Pipeline class', () => { const results = await randomCol .pipeline() .select( - cosineDistance(Constant.ofVector(sourceVector), targetVector).as( + cosineDistance(Constant.vector(sourceVector), targetVector).as( 'cosineDistance' ), - dotProductDistance(Constant.ofVector(sourceVector), targetVector).as( + dotProductDistance(Constant.vector(sourceVector), targetVector).as( 'dotProductDistance' ), - euclideanDistance(Constant.ofVector(sourceVector), targetVector).as( + euclideanDistance(Constant.vector(sourceVector), targetVector).as( 'euclideanDistance' ) ) @@ -734,6 +740,27 @@ describe.only('Pipeline class', () => { ); }); + it('test mapGet with field name including . notation', async () => { + const results = await randomCol + .pipeline() + .where(eq('awards.hugo', true)) + .select( + 'title', + Field.of('nestedField.level.1'), + mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') + ) + .execute(); + expectResults( + results, + { + title: "The Hitchhiker's Guide to the Galaxy", + 'nestedField.level.`1`': null, + nested: true, + }, + {title: 'Dune', 'nestedField.level.`1`': null, nested: null} + ); + }); + it('pipeline converter works', async () => { type AppModel = {myTitle: string; myAuthor: string; myPublished: number}; const converter: FirestorePipelineConverter = { From 84e5eaba5e5c179de4498695f3a6b36b04178934 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Mon, 26 Aug 2024 15:38:28 -0400 Subject: [PATCH 09/31] License header and tests fix --- dev/src/expression.ts | 14 ++++++++++++++ dev/src/pipeline-util.ts | 14 ++++++++++++++ dev/src/pipeline.ts | 14 ++++++++++++++ dev/src/stage.ts | 14 ++++++++++++++ dev/system-test/pipeline.ts | 23 ++++++++++++++++++++--- dev/system-test/query.ts | 14 ++++++++++++++ 6 files changed, 90 insertions(+), 3 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 32c46e063..410ae5886 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -1,3 +1,17 @@ +// 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 * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index ce6677b9a..866d90cc9 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -1,3 +1,17 @@ +// 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 * as firestore from '@google-cloud/firestore'; import {GoogleError, serializer} from 'google-gax'; import {converter} from 'protobufjs'; diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 7b0e7a285..6f5d802f5 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -1,3 +1,17 @@ +// 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 {DocumentData} from '@google-cloud/firestore'; import * as firestore from '@google-cloud/firestore'; import * as deepEqual from 'fast-deep-equal'; diff --git a/dev/src/stage.ts b/dev/src/stage.ts index de656fc03..2dc941164 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -1,3 +1,17 @@ +// 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 * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 09c98f1a3..0eca6211b 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -1,3 +1,17 @@ +// 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 { AggregateQuery, DocumentData, @@ -48,6 +62,7 @@ import { euclideanDistance, Constant, mapGet, + lte, } from '../src/expression'; import {PipelineResult} from '../src/pipeline'; import {verifyInstance} from '../test/util/helpers'; @@ -313,8 +328,9 @@ describe.only('Pipeline class', () => { }); it('can select fields', async () => { - const results = await randomCol + const results = await firestore .pipeline() + .collection(randomCol.path) .select('title', 'author') .sort(Field.of('author').ascending()) .execute(); @@ -359,8 +375,9 @@ describe.only('Pipeline class', () => { }); it('offset and limits', async () => { - const results = await randomCol + const results = await firestore .pipeline() + .collection(randomCol.path) .sort(Field.of('author').ascending()) .offset(5) .limit(3) @@ -607,7 +624,7 @@ describe.only('Pipeline class', () => { .where( and( gt('rating', 4.2), - lt(Field.of('rating'), 4.5), + lte(Field.of('rating'), 4.5), neq('genre', 'Science Fiction') ) ) diff --git a/dev/system-test/query.ts b/dev/system-test/query.ts index cc0b877d2..84853f79b 100644 --- a/dev/system-test/query.ts +++ b/dev/system-test/query.ts @@ -1,3 +1,17 @@ +// 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 { DocumentData, QuerySnapshot, From eff4589a610f5d2771a3b2012e7cf0b3cfc43d48 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Mon, 26 Aug 2024 15:38:28 -0400 Subject: [PATCH 10/31] License header and tests fix --- dev/src/expression.ts | 459 ++++++++++++++++++++++++++++++++++++ dev/src/pipeline-util.ts | 14 ++ dev/src/pipeline.ts | 22 ++ dev/src/stage.ts | 14 ++ dev/system-test/pipeline.ts | 23 +- dev/system-test/query.ts | 14 ++ 6 files changed, 543 insertions(+), 3 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 32c46e063..edb7f8827 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -1,3 +1,17 @@ +// 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 * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; @@ -13,6 +27,8 @@ import {Serializer} from './serializer'; import {Timestamp} from './timestamp'; /** + * @beta + * * An interface that represents a selectable expression. */ export interface Selectable { @@ -20,6 +36,8 @@ export interface Selectable { } /** + * @beta + * * An interface that represents a filter condition. */ export interface FilterCondition { @@ -27,6 +45,8 @@ export interface FilterCondition { } /** + * @beta + * * An interface that represents an accumulator. */ export interface Accumulator { @@ -34,21 +54,29 @@ export interface Accumulator { } /** + * @beta + * * An accumulator target, which is an expression with an alias that also implements the Accumulator interface. */ export type AccumulatorTarget = ExprWithAlias; /** + * @beta + * * A filter expression, which is an expression that also implements the FilterCondition interface. */ export type FilterExpr = Expr & FilterCondition; /** + * @beta + * * A selectable expression, which is an expression that also implements the Selectable interface. */ export type SelectableExpr = Expr & Selectable; /** + * @beta + * * An enumeration of the different types of expressions. */ export type ExprType = @@ -59,6 +87,8 @@ export type ExprType = | 'ExprWithAlias'; /** + * @beta + * * Represents an expression that can be evaluated to a value within the execution of a {@link * Pipeline}. * @@ -1148,6 +1178,9 @@ export abstract class Expr { abstract _toProto(serializer: Serializer): api.IValue; } +/** + * @beta + */ export class ExprWithAlias extends Expr implements Selectable { exprType: ExprType = 'ExprWithAlias'; selectable = true as const; @@ -1164,6 +1197,9 @@ export class ExprWithAlias extends Expr implements Selectable { } } +/** + * @internal + */ class ListOfExprs extends Expr { exprType: ExprType = 'ListOfExprs'; constructor(private exprs: Expr[]) { @@ -1180,6 +1216,8 @@ class ListOfExprs extends Expr { } /** + * @beta + * * Represents a reference to a field in a Firestore document, or outputs of a {@link Pipeline} stage. * *

    Field references are used to access document field values in expressions and to specify fields @@ -1261,6 +1299,9 @@ export class Field extends Expr implements Selectable { } } +/** + * @beta + */ export class Fields extends Expr implements Selectable { exprType: ExprType = 'Field'; selectable = true as const; @@ -1291,6 +1332,8 @@ export class Fields extends Expr implements Selectable { } /** + * @beta + * * Represents a constant value that can be used in a Firestore pipeline expression. * * You can create a `Constant` instance using the static {@link #of} method: @@ -1462,6 +1505,8 @@ export class Constant extends Expr { } /** + * @beta + * * This class defines the base class for Firestore {@link Pipeline} functions, which can be evaluated within pipeline * execution. * @@ -1487,6 +1532,9 @@ export class Function extends Expr { } } +/** + * @beta + */ export class Add extends Function { constructor( private left: Expr, @@ -1496,6 +1544,9 @@ export class Add extends Function { } } +/** + * @beta + */ export class Subtract extends Function { constructor( private left: Expr, @@ -1505,6 +1556,9 @@ export class Subtract extends Function { } } +/** + * @beta + */ export class Multiply extends Function { constructor( private left: Expr, @@ -1514,6 +1568,9 @@ export class Multiply extends Function { } } +/** + * @beta + */ export class Divide extends Function { constructor( private left: Expr, @@ -1523,6 +1580,9 @@ export class Divide extends Function { } } +/** + * @beta + */ export class Eq extends Function implements FilterCondition { constructor( private left: Expr, @@ -1533,6 +1593,9 @@ export class Eq extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Neq extends Function implements FilterCondition { constructor( private left: Expr, @@ -1543,6 +1606,9 @@ class Neq extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Lt extends Function implements FilterCondition { constructor( private left: Expr, @@ -1553,6 +1619,9 @@ class Lt extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Lte extends Function implements FilterCondition { constructor( private left: Expr, @@ -1563,6 +1632,9 @@ class Lte extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Gt extends Function implements FilterCondition { constructor( private left: Expr, @@ -1573,6 +1645,9 @@ class Gt extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Gte extends Function implements FilterCondition { constructor( private left: Expr, @@ -1583,6 +1658,9 @@ class Gte extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class ArrayConcat extends Function { constructor( private array: Expr, @@ -1592,6 +1670,9 @@ class ArrayConcat extends Function { } } +/** + * @beta + */ class ArrayContains extends Function implements FilterCondition { constructor( private array: Expr, @@ -1602,6 +1683,9 @@ class ArrayContains extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class ArrayContainsAll extends Function implements FilterCondition { constructor( private array: Expr, @@ -1612,6 +1696,9 @@ class ArrayContainsAll extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class ArrayContainsAny extends Function implements FilterCondition { constructor( private array: Expr, @@ -1622,6 +1709,9 @@ class ArrayContainsAny extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class ArrayFilter extends Function { constructor( private array: Expr, @@ -1631,12 +1721,18 @@ class ArrayFilter extends Function { } } +/** + * @beta + */ class ArrayLength extends Function { constructor(private array: Expr) { super('array_length', [array]); } } +/** + * @beta + */ class ArrayTransform extends Function { constructor( private array: Expr, @@ -1646,12 +1742,18 @@ class ArrayTransform extends Function { } } +/** + * @beta + */ class ArrayElement extends Function { constructor() { super('array_element', []); } } +/** + * @beta + */ class In extends Function implements FilterCondition { constructor( private left: Expr, @@ -1662,6 +1764,9 @@ class In extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class IsNan extends Function implements FilterCondition { constructor(private expr: Expr) { super('is_nan', [expr]); @@ -1669,6 +1774,9 @@ class IsNan extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Exists extends Function implements FilterCondition { constructor(private expr: Expr) { super('exists', [expr]); @@ -1676,6 +1784,9 @@ class Exists extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Not extends Function implements FilterCondition { constructor(private expr: Expr) { super('not', [expr]); @@ -1683,6 +1794,9 @@ class Not extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class And extends Function implements FilterCondition { constructor(private conditions: FilterExpr[]) { super('and', conditions); @@ -1691,6 +1805,9 @@ class And extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Or extends Function implements FilterCondition { constructor(private conditions: FilterExpr[]) { super('or', conditions); @@ -1698,6 +1815,9 @@ class Or extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Xor extends Function implements FilterCondition { constructor(private conditions: FilterExpr[]) { super('xor', conditions); @@ -1705,6 +1825,9 @@ class Xor extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class If extends Function implements FilterCondition { constructor( private condition: FilterExpr, @@ -1716,12 +1839,18 @@ class If extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class Length extends Function { constructor(private expr: Expr) { super('length', [expr]); } } +/** + * @beta + */ class Like extends Function implements FilterCondition { constructor( private expr: Expr, @@ -1732,6 +1861,9 @@ class Like extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class RegexContains extends Function implements FilterCondition { constructor( private expr: Expr, @@ -1742,6 +1874,9 @@ class RegexContains extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class RegexMatch extends Function implements FilterCondition { constructor( private expr: Expr, @@ -1752,6 +1887,9 @@ class RegexMatch extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class StartsWith extends Function implements FilterCondition { constructor( private expr: Expr, @@ -1762,6 +1900,9 @@ class StartsWith extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class EndsWith extends Function implements FilterCondition { constructor( private expr: Expr, @@ -1772,24 +1913,36 @@ class EndsWith extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ class ToLowercase extends Function { constructor(private expr: Expr) { super('to_lowercase', [expr]); } } +/** + * @beta + */ class ToUppercase extends Function { constructor(private expr: Expr) { super('to_uppercase', [expr]); } } +/** + * @beta + */ class Trim extends Function { constructor(private expr: Expr) { super('trim', [expr]); } } +/** + * @beta + */ class StrConcat extends Function { constructor( private first: Expr, @@ -1799,12 +1952,18 @@ class StrConcat extends Function { } } +/** + * @beta + */ class MapGet extends Function { constructor(map: Expr, name: string) { super('map_get', [map, Constant.of(name)]); } } +/** + * @beta + */ class Count extends Function implements Accumulator { accumulator = true as const; constructor( @@ -1815,6 +1974,9 @@ class Count extends Function implements Accumulator { } } +/** + * @beta + */ class Sum extends Function implements Accumulator { accumulator = true as const; constructor( @@ -1825,6 +1987,9 @@ class Sum extends Function implements Accumulator { } } +/** + * @beta + */ class Avg extends Function implements Accumulator { accumulator = true as const; constructor( @@ -1835,6 +2000,9 @@ class Avg extends Function implements Accumulator { } } +/** + * @beta + */ class Min extends Function implements Accumulator { accumulator = true as const; constructor( @@ -1845,6 +2013,9 @@ class Min extends Function implements Accumulator { } } +/** + * @beta + */ class Max extends Function implements Accumulator { accumulator = true as const; constructor( @@ -1855,6 +2026,9 @@ class Max extends Function implements Accumulator { } } +/** + * @beta + */ class CosineDistance extends Function { constructor( private vector1: Expr, @@ -1864,6 +2038,9 @@ class CosineDistance extends Function { } } +/** + * @beta + */ class DotProductDistance extends Function { constructor( private vector1: Expr, @@ -1873,6 +2050,9 @@ class DotProductDistance extends Function { } } +/** + * @beta + */ class EuclideanDistance extends Function { constructor( private vector1: Expr, @@ -1883,6 +2063,8 @@ class EuclideanDistance extends Function { } /** + * @beta + * * Creates an expression that adds two expressions together. * * ```typescript @@ -1897,6 +2079,8 @@ class EuclideanDistance extends Function { export function add(left: Expr, right: Expr): Add; /** + * @beta + * * Creates an expression that adds an expression to a constant value. * * ```typescript @@ -1911,6 +2095,8 @@ export function add(left: Expr, right: Expr): Add; export function add(left: Expr, right: any): Add; /** + * @beta + * * Creates an expression that adds a field's value to an expression. * * ```typescript @@ -1925,6 +2111,8 @@ export function add(left: Expr, right: any): Add; export function add(left: string, right: Expr): Add; /** + * @beta + * * Creates an expression that adds a field's value to a constant value. * * ```typescript @@ -1944,6 +2132,8 @@ export function add(left: Expr | string, right: Expr | any): Add { } /** + * @beta + * * Creates an expression that subtracts two expressions. * * ```typescript @@ -1958,6 +2148,8 @@ export function add(left: Expr | string, right: Expr | any): Add { export function subtract(left: Expr, right: Expr): Subtract; /** + * @beta + * * Creates an expression that subtracts a constant value from an expression. * * ```typescript @@ -1972,6 +2164,8 @@ export function subtract(left: Expr, right: Expr): Subtract; export function subtract(left: Expr, right: any): Subtract; /** + * @beta + * * Creates an expression that subtracts an expression from a field's value. * * ```typescript @@ -1986,6 +2180,8 @@ export function subtract(left: Expr, right: any): Subtract; export function subtract(left: string, right: Expr): Subtract; /** + * @beta + * * Creates an expression that subtracts a constant value from a field's value. * * ```typescript @@ -2005,6 +2201,8 @@ export function subtract(left: Expr | string, right: Expr | any): Subtract { } /** + * @beta + * * Creates an expression that multiplies two expressions together. * * ```typescript @@ -2019,6 +2217,8 @@ export function subtract(left: Expr | string, right: Expr | any): Subtract { export function multiply(left: Expr, right: Expr): Multiply; /** + * @beta + * * Creates an expression that multiplies an expression by a constant value. * * ```typescript @@ -2033,6 +2233,8 @@ export function multiply(left: Expr, right: Expr): Multiply; export function multiply(left: Expr, right: any): Multiply; /** + * @beta + * * Creates an expression that multiplies a field's value by an expression. * * ```typescript @@ -2047,6 +2249,8 @@ export function multiply(left: Expr, right: any): Multiply; export function multiply(left: string, right: Expr): Multiply; /** + * @beta + * * Creates an expression that multiplies a field's value by a constant value. * * ```typescript @@ -2066,6 +2270,8 @@ export function multiply(left: Expr | string, right: Expr | any): Multiply { } /** + * @beta + * * Creates an expression that divides two expressions. * * ```typescript @@ -2080,6 +2286,8 @@ export function multiply(left: Expr | string, right: Expr | any): Multiply { export function divide(left: Expr, right: Expr): Divide; /** + * @beta + * * Creates an expression that divides an expression by a constant value. * * ```typescript @@ -2094,6 +2302,8 @@ export function divide(left: Expr, right: Expr): Divide; export function divide(left: Expr, right: any): Divide; /** + * @beta + * * Creates an expression that divides a field's value by an expression. * * ```typescript @@ -2108,6 +2318,8 @@ export function divide(left: Expr, right: any): Divide; export function divide(left: string, right: Expr): Divide; /** + * @beta + * * Creates an expression that divides a field's value by a constant value. * * ```typescript @@ -2127,6 +2339,8 @@ export function divide(left: Expr | string, right: Expr | any): Divide { } /** + * @beta + * * Creates an expression that checks if two expressions are equal. * * ```typescript @@ -2141,6 +2355,8 @@ export function divide(left: Expr | string, right: Expr | any): Divide { export function eq(left: Expr, right: Expr): Eq; /** + * @beta + * * Creates an expression that checks if an expression is equal to a constant value. * * ```typescript @@ -2155,6 +2371,8 @@ export function eq(left: Expr, right: Expr): Eq; export function eq(left: Expr, right: any): Eq; /** + * @beta + * * Creates an expression that checks if a field's value is equal to an expression. * * ```typescript @@ -2169,6 +2387,8 @@ export function eq(left: Expr, right: any): Eq; export function eq(left: string, right: Expr): Eq; /** + * @beta + * * Creates an expression that checks if a field's value is equal to a constant value. * * ```typescript @@ -2188,6 +2408,8 @@ export function eq(left: Expr | string, right: any): Eq { } /** + * @beta + * * Creates an expression that checks if two expressions are not equal. * * ```typescript @@ -2202,6 +2424,8 @@ export function eq(left: Expr | string, right: any): Eq { export function neq(left: Expr, right: Expr): Neq; /** + * @beta + * * Creates an expression that checks if an expression is not equal to a constant value. * * ```typescript @@ -2216,6 +2440,8 @@ export function neq(left: Expr, right: Expr): Neq; export function neq(left: Expr, right: any): Neq; /** + * @beta + * * Creates an expression that checks if a field's value is not equal to an expression. * * ```typescript @@ -2230,6 +2456,8 @@ export function neq(left: Expr, right: any): Neq; export function neq(left: string, right: Expr): Neq; /** + * @beta + * * Creates an expression that checks if a field's value is not equal to a constant value. * * ```typescript @@ -2249,6 +2477,8 @@ export function neq(left: Expr | string, right: any): Neq { } /** + * @beta + * * Creates an expression that checks if the first expression is less than the second expression. * * ```typescript @@ -2263,6 +2493,8 @@ export function neq(left: Expr | string, right: any): Neq { export function lt(left: Expr, right: Expr): Lt; /** + * @beta + * * Creates an expression that checks if an expression is less than a constant value. * * ```typescript @@ -2277,6 +2509,8 @@ export function lt(left: Expr, right: Expr): Lt; export function lt(left: Expr, right: any): Lt; /** + * @beta + * * Creates an expression that checks if a field's value is less than an expression. * * ```typescript @@ -2291,6 +2525,8 @@ export function lt(left: Expr, right: any): Lt; export function lt(left: string, right: Expr): Lt; /** + * @beta + * * Creates an expression that checks if a field's value is less than a constant value. * * ```typescript @@ -2310,6 +2546,8 @@ export function lt(left: Expr | string, right: any): Lt { } /** + * @beta + * * Creates an expression that checks if the first expression is less than or equal to the second * expression. * @@ -2325,6 +2563,8 @@ export function lt(left: Expr | string, right: any): Lt { export function lte(left: Expr, right: Expr): Lte; /** + * @beta + * * Creates an expression that checks if an expression is less than or equal to a constant value. * * ```typescript @@ -2353,6 +2593,8 @@ export function lte(left: Expr, right: any): Lte; export function lte(left: string, right: Expr): Lte; /** + * @beta + * * Creates an expression that checks if a field's value is less than or equal to a constant value. * * ```typescript @@ -2372,6 +2614,8 @@ export function lte(left: Expr | string, right: any): Lte { } /** + * @beta + * * Creates an expression that checks if the first expression is greater than the second * expression. * @@ -2387,6 +2631,8 @@ export function lte(left: Expr | string, right: any): Lte { export function gt(left: Expr, right: Expr): Gt; /** + * @beta + * * Creates an expression that checks if an expression is greater than a constant value. * * ```typescript @@ -2401,6 +2647,8 @@ export function gt(left: Expr, right: Expr): Gt; export function gt(left: Expr, right: any): Gt; /** + * @beta + * * Creates an expression that checks if a field's value is greater than an expression. * * ```typescript @@ -2415,6 +2663,8 @@ export function gt(left: Expr, right: any): Gt; export function gt(left: string, right: Expr): Gt; /** + * @beta + * * Creates an expression that checks if a field's value is greater than a constant value. * * ```typescript @@ -2434,6 +2684,8 @@ export function gt(left: Expr | string, right: any): Gt { } /** + * @beta + * * Creates an expression that checks if the first expression is greater than or equal to the * second expression. * @@ -2449,6 +2701,8 @@ export function gt(left: Expr | string, right: any): Gt { export function gte(left: Expr, right: Expr): Gte; /** + * @beta + * * Creates an expression that checks if an expression is greater than or equal to a constant * value. * @@ -2464,6 +2718,8 @@ export function gte(left: Expr, right: Expr): Gte; export function gte(left: Expr, right: any): Gte; /** + * @beta + * * Creates an expression that checks if a field's value is greater than or equal to an expression. * * ```typescript @@ -2478,6 +2734,8 @@ export function gte(left: Expr, right: any): Gte; export function gte(left: string, right: Expr): Gte; /** + * @beta + * * Creates an expression that checks if a field's value is greater than or equal to a constant * value. * @@ -2498,6 +2756,8 @@ export function gte(left: Expr | string, right: any): Gte { } /** + * @beta + * * Creates an expression that concatenates an array expression with other arrays. * * ```typescript @@ -2512,6 +2772,8 @@ export function gte(left: Expr | string, right: any): Gte { export function arrayConcat(array: Expr, elements: Expr[]): ArrayConcat; /** + * @beta + * * Creates an expression that concatenates an array expression with other arrays and/or values. * * ```typescript @@ -2526,6 +2788,8 @@ export function arrayConcat(array: Expr, elements: Expr[]): ArrayConcat; export function arrayConcat(array: Expr, elements: any[]): ArrayConcat; /** + * @beta + * * Creates an expression that concatenates a field's array value with other arrays. * * ```typescript @@ -2540,6 +2804,8 @@ export function arrayConcat(array: Expr, elements: any[]): ArrayConcat; export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; /** + * @beta + * * Creates an expression that concatenates a field's array value with other arrays and/or values. * * ```typescript @@ -2564,6 +2830,8 @@ export function arrayConcat( } /** + * @beta + * * Creates an expression that checks if an array expression contains a specific element. * * ```typescript @@ -2578,6 +2846,8 @@ export function arrayConcat( export function arrayContains(array: Expr, element: Expr): ArrayContains; /** + * @beta + * * Creates an expression that checks if an array expression contains a specific element. * * ```typescript @@ -2592,6 +2862,8 @@ export function arrayContains(array: Expr, element: Expr): ArrayContains; export function arrayContains(array: Expr, element: any): ArrayContains; /** + * @beta + * * Creates an expression that checks if a field's array value contains a specific element. * * ```typescript @@ -2606,6 +2878,8 @@ export function arrayContains(array: Expr, element: any): ArrayContains; export function arrayContains(array: string, element: Expr): ArrayContains; /** + * @beta + * * Creates an expression that checks if a field's array value contains a specific value. * * ```typescript @@ -2628,6 +2902,8 @@ export function arrayContains( } /** + * @beta + * * Creates an expression that checks if an array expression contains any of the specified * elements. * @@ -2643,6 +2919,8 @@ export function arrayContains( export function arrayContainsAny(array: Expr, values: Expr[]): ArrayContainsAny; /** + * @beta + * * Creates an expression that checks if an array expression contains any of the specified * elements. * @@ -2658,6 +2936,8 @@ export function arrayContainsAny(array: Expr, values: Expr[]): ArrayContainsAny; export function arrayContainsAny(array: Expr, values: any[]): ArrayContainsAny; /** + * @beta + * * Creates an expression that checks if a field's array value contains any of the specified * elements. * @@ -2677,6 +2957,8 @@ export function arrayContainsAny( ): ArrayContainsAny; /** + * @beta + * * Creates an expression that checks if a field's array value contains any of the specified * elements. * @@ -2706,6 +2988,8 @@ export function arrayContainsAny( } /** + * @beta + * * Creates an expression that checks if an array expression contains all the specified elements. * * ```typescript @@ -2720,6 +3004,8 @@ export function arrayContainsAny( export function arrayContainsAll(array: Expr, values: Expr[]): ArrayContainsAll; /** + * @beta + * * Creates an expression that checks if an array expression contains all the specified elements. * * ```typescript @@ -2734,6 +3020,8 @@ export function arrayContainsAll(array: Expr, values: Expr[]): ArrayContainsAll; export function arrayContainsAll(array: Expr, values: any[]): ArrayContainsAll; /** + * @beta + * * Creates an expression that checks if a field's array value contains all the specified values or * expressions. * @@ -2752,6 +3040,8 @@ export function arrayContainsAll( ): ArrayContainsAll; /** + * @beta + * * Creates an expression that checks if a field's array value contains all the specified values or * expressions. * @@ -2780,6 +3070,8 @@ export function arrayContainsAll( } /** + * @beta + * * Creates an expression that filters elements from an array expression using the given {@link * FilterExpr} and returns the filtered elements as a new array. * @@ -2799,6 +3091,8 @@ export function arrayFilter(array: Expr, filter: FilterExpr): ArrayFilter { } /** + * @beta + * * Creates an expression that calculates the length of an array expression. * * ```typescript @@ -2814,6 +3108,8 @@ export function arrayLength(array: Expr): ArrayLength { } /** + * @beta + * * Creates an expression that applies a transformation function to each element in an array * expression and returns the new array as the result of the evaluation. * @@ -2836,6 +3132,8 @@ export function arrayTransform( } /** + * @beta + * * Returns an expression that represents an array element within an {@link ArrayFilter} or {@link * ArrayTransform} expression. * @@ -2851,6 +3149,8 @@ export function arrayElement(): ArrayElement { } /** + * @beta + * * Creates an expression that checks if an expression is equal to any of the provided values or * expressions. * @@ -2866,6 +3166,8 @@ export function arrayElement(): ArrayElement { export function inAny(element: Expr, others: Expr[]): In; /** + * @beta + * * Creates an expression that checks if an expression is equal to any of the provided values or * expressions. * @@ -2881,6 +3183,8 @@ export function inAny(element: Expr, others: Expr[]): In; export function inAny(element: Expr, others: any[]): In; /** + * @beta + * * Creates an expression that checks if a field's value is equal to any of the provided values or * expressions. * @@ -2896,6 +3200,8 @@ export function inAny(element: Expr, others: any[]): In; export function inAny(element: string, others: Expr[]): In; /** + * @beta + * * Creates an expression that checks if a field's value is equal to any of the provided values or * expressions. * @@ -2918,6 +3224,8 @@ export function inAny(element: Expr | string, others: any[]): In { } /** + * @beta + * * Creates an expression that checks if an expression is not equal to any of the provided values * or expressions. * @@ -2933,6 +3241,8 @@ export function inAny(element: Expr | string, others: any[]): In { export function notInAny(element: Expr, others: Expr[]): Not; /** + * @beta + * * Creates an expression that checks if an expression is not equal to any of the provided values * or expressions. * @@ -2948,6 +3258,8 @@ export function notInAny(element: Expr, others: Expr[]): Not; export function notInAny(element: Expr, others: any[]): Not; /** + * @beta + * * Creates an expression that checks if a field's value is not equal to any of the provided values * or expressions. * @@ -2963,6 +3275,8 @@ export function notInAny(element: Expr, others: any[]): Not; export function notInAny(element: string, others: Expr[]): Not; /** + * @beta + * * Creates an expression that checks if a field's value is not equal to any of the provided values * or expressions. * @@ -2985,6 +3299,8 @@ export function notInAny(element: Expr | string, others: any[]): Not { } /** + * @beta + * * Creates an expression that performs a logical 'AND' operation on multiple filter conditions. * * ```typescript @@ -3002,6 +3318,8 @@ export function and(left: FilterExpr, ...right: FilterExpr[]): And { } /** + * @beta + * * Creates an expression that performs a logical 'OR' operation on multiple filter conditions. * * ```typescript @@ -3019,6 +3337,8 @@ export function or(left: FilterExpr, ...right: FilterExpr[]): Or { } /** + * @beta + * * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple filter * conditions. * @@ -3040,6 +3360,8 @@ export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor { } /** + * @beta + * * Creates a conditional expression that evaluates to a 'then' expression if a condition is true * and an 'else' expression if the condition is false. * @@ -3063,6 +3385,8 @@ export function ifFunction( } /** + * @beta + * * Creates an expression that negates a filter condition. * * ```typescript @@ -3078,6 +3402,8 @@ export function not(filter: FilterExpr): Not { } /** + * @beta + * * Creates an expression that checks if a field exists. * * ```typescript @@ -3091,6 +3417,8 @@ export function not(filter: FilterExpr): Not { export function exists(value: Expr): Exists; /** + * @beta + * * Creates an expression that checks if a field exists. * * ```typescript @@ -3109,6 +3437,8 @@ export function exists(valueOrField: Expr | string): Exists { } /** + * @beta + * * Creates an expression that checks if an expression evaluates to 'NaN' (Not a Number). * * ```typescript @@ -3122,6 +3452,8 @@ export function exists(valueOrField: Expr | string): Exists { export function isNan(value: Expr): IsNan; /** + * @beta + * * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). * * ```typescript @@ -3139,6 +3471,8 @@ export function isNan(value: Expr | string): IsNan { } /** + * @beta + * * Creates an expression that calculates the length of a string field. * * ```typescript @@ -3152,6 +3486,8 @@ export function isNan(value: Expr | string): IsNan { export function length(field: string): Length; /** + * @beta + * * Creates an expression that calculates the length of a string expression. * * ```typescript @@ -3169,6 +3505,8 @@ export function length(value: Expr | string): Length { } /** + * @beta + * * Creates an expression that performs a case-sensitive wildcard string comparison against a * field. * @@ -3184,6 +3522,8 @@ export function length(value: Expr | string): Length { export function like(left: string, pattern: string): Like; /** + * @beta + * * Creates an expression that performs a case-sensitive wildcard string comparison against a * field. * @@ -3199,6 +3539,8 @@ export function like(left: string, pattern: string): Like; export function like(left: string, pattern: Expr): Like; /** + * @beta + * * Creates an expression that performs a case-sensitive wildcard string comparison. * * ```typescript @@ -3213,6 +3555,8 @@ export function like(left: string, pattern: Expr): Like; export function like(left: Expr, pattern: string): Like; /** + * @beta + * * Creates an expression that performs a case-sensitive wildcard string comparison. * * ```typescript @@ -3232,6 +3576,8 @@ export function like(left: Expr | string, pattern: Expr | string): Like { } /** + * @beta + * * Creates an expression that checks if a string field contains a specified regular expression as * a substring. * @@ -3247,6 +3593,8 @@ export function like(left: Expr | string, pattern: Expr | string): Like { export function regexContains(left: string, pattern: string): RegexContains; /** + * @beta + * * Creates an expression that checks if a string field contains a specified regular expression as * a substring. * @@ -3262,6 +3610,8 @@ export function regexContains(left: string, pattern: string): RegexContains; export function regexContains(left: string, pattern: Expr): RegexContains; /** + * @beta + * * Creates an expression that checks if a string expression contains a specified regular * expression as a substring. * @@ -3277,6 +3627,8 @@ export function regexContains(left: string, pattern: Expr): RegexContains; export function regexContains(left: Expr, pattern: string): RegexContains; /** + * @beta + * * Creates an expression that checks if a string expression contains a specified regular * expression as a substring. * @@ -3300,6 +3652,8 @@ export function regexContains( } /** + * @beta + * * Creates an expression that checks if a string field matches a specified regular expression. * * ```typescript @@ -3314,6 +3668,8 @@ export function regexContains( export function regexMatch(left: string, pattern: string): RegexMatch; /** + * @beta + * * Creates an expression that checks if a string field matches a specified regular expression. * * ```typescript @@ -3328,6 +3684,8 @@ export function regexMatch(left: string, pattern: string): RegexMatch; export function regexMatch(left: string, pattern: Expr): RegexMatch; /** + * @beta + * * Creates an expression that checks if a string expression matches a specified regular * expression. * @@ -3343,6 +3701,8 @@ export function regexMatch(left: string, pattern: Expr): RegexMatch; export function regexMatch(left: Expr, pattern: string): RegexMatch; /** + * @beta + * * Creates an expression that checks if a string expression matches a specified regular * expression. * @@ -3366,6 +3726,8 @@ export function regexMatch( } /** + * @beta + * * Creates an expression that checks if a field's value starts with a given prefix. * * ```typescript @@ -3380,6 +3742,8 @@ export function regexMatch( export function startsWith(expr: string, prefix: string): StartsWith; /** + * @beta + * * Creates an expression that checks if a field's value starts with a given prefix. * * ```typescript @@ -3394,6 +3758,8 @@ export function startsWith(expr: string, prefix: string): StartsWith; export function startsWith(expr: string, prefix: Expr): StartsWith; /** + * @beta + * * Creates an expression that checks if a string expression starts with a given prefix. * * ```typescript @@ -3408,6 +3774,8 @@ export function startsWith(expr: string, prefix: Expr): StartsWith; export function startsWith(expr: Expr, prefix: string): StartsWith; /** + * @beta + * * Creates an expression that checks if a string expression starts with a given prefix. * * ```typescript @@ -3430,6 +3798,8 @@ export function startsWith( } /** + * @beta + * * Creates an expression that checks if a field's value ends with a given postfix. * * ```typescript @@ -3444,6 +3814,8 @@ export function startsWith( export function endsWith(expr: string, suffix: string): EndsWith; /** + * @beta + * * Creates an expression that checks if a field's value ends with a given postfix. * * ```typescript @@ -3458,6 +3830,8 @@ export function endsWith(expr: string, suffix: string): EndsWith; export function endsWith(expr: string, suffix: Expr): EndsWith; /** + * @beta + * * Creates an expression that checks if a string expression ends with a given postfix. * * ```typescript @@ -3472,6 +3846,8 @@ export function endsWith(expr: string, suffix: Expr): EndsWith; export function endsWith(expr: Expr, suffix: string): EndsWith; /** + * @beta + * * Creates an expression that checks if a string expression ends with a given postfix. * * ```typescript @@ -3491,6 +3867,8 @@ export function endsWith(expr: Expr | string, suffix: Expr | string): EndsWith { } /** + * @beta + * * Creates an expression that converts a string field to lowercase. * * ```typescript @@ -3504,6 +3882,8 @@ export function endsWith(expr: Expr | string, suffix: Expr | string): EndsWith { export function toLowercase(expr: string): ToLowercase; /** + * @beta + * * Creates an expression that converts a string expression to lowercase. * * ```typescript @@ -3520,6 +3900,8 @@ export function toLowercase(expr: Expr | string): ToLowercase { } /** + * @beta + * * Creates an expression that converts a string field to uppercase. * * ```typescript @@ -3533,6 +3915,8 @@ export function toLowercase(expr: Expr | string): ToLowercase { export function toUppercase(expr: string): ToUppercase; /** + * @beta + * * Creates an expression that converts a string expression to uppercase. * * ```typescript @@ -3549,6 +3933,8 @@ export function toUppercase(expr: Expr | string): ToUppercase { } /** + * @beta + * * Creates an expression that removes leading and trailing whitespace from a string field. * * ```typescript @@ -3562,6 +3948,8 @@ export function toUppercase(expr: Expr | string): ToUppercase { export function trim(expr: string): Trim; /** + * @beta + * * Creates an expression that removes leading and trailing whitespace from a string expression. * * ```typescript @@ -3578,6 +3966,8 @@ export function trim(expr: Expr | string): Trim { } /** + * @beta + * * Creates an expression that concatenates string functions, fields or constants together. * * ```typescript @@ -3595,6 +3985,7 @@ export function strConcat( ): StrConcat; /** + * @beta * Creates an expression that concatenates string expressions together. * * ```typescript @@ -3619,6 +4010,8 @@ export function strConcat( } /** + * @beta + * * Accesses a value from a map (object) field using the provided key. * * ```typescript @@ -3633,6 +4026,8 @@ export function strConcat( export function mapGet(mapField: string, subField: string): MapGet; /** + * @beta + * * Accesses a value from a map (object) expression using the provided key. * * ```typescript @@ -3653,6 +4048,8 @@ export function mapGet(fieldOrExpr: string | Expr, subField: string): MapGet { } /** + * @beta + * * Creates an aggregation that counts the total number of stage inputs. * * ```typescript @@ -3667,6 +4064,8 @@ export function countAll(): Count { } /** + * @beta + * * Creates an aggregation that counts the number of stage inputs with valid evaluations of the * provided expression. * @@ -3699,6 +4098,8 @@ export function count(value: Expr | string): Count { } /** + * @beta + * * Creates an aggregation that calculates the sum of values from an expression across multiple * stage inputs. * @@ -3713,6 +4114,8 @@ export function count(value: Expr | string): Count { export function sum(value: Expr): Sum; /** + * @beta + * * Creates an aggregation that calculates the sum of a field's values across multiple stage * inputs. * @@ -3731,6 +4134,8 @@ export function sum(value: Expr | string): Sum { } /** + * @beta + * * Creates an aggregation that calculates the average (mean) of values from an expression across * multiple stage inputs. * @@ -3745,6 +4150,8 @@ export function sum(value: Expr | string): Sum { export function avg(value: Expr): Avg; /** + * @beta + * * Creates an aggregation that calculates the average (mean) of a field's values across multiple * stage inputs. * @@ -3763,6 +4170,8 @@ export function avg(value: Expr | string): Avg { } /** + * @beta + * * Creates an aggregation that finds the minimum value of an expression across multiple stage * inputs. * @@ -3777,6 +4186,8 @@ export function avg(value: Expr | string): Avg { export function min(value: Expr): Min; /** + * @beta + * * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. * * ```typescript @@ -3794,6 +4205,8 @@ export function min(value: Expr | string): Min { } /** + * @beta + * * Creates an aggregation that finds the maximum value of an expression across multiple stage * inputs. * @@ -3808,6 +4221,8 @@ export function min(value: Expr | string): Min { export function max(value: Expr): Max; /** + * @beta + * * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. * * ```typescript @@ -3825,6 +4240,8 @@ export function max(value: Expr | string): Max { } /** + * @beta + * * Calculates the Cosine distance between a field's vector value and a double array. * * ```typescript @@ -3839,6 +4256,8 @@ export function max(value: Expr | string): Max { export function cosineDistance(expr: string, other: number[]): CosineDistance; /** + * @beta + * * Calculates the Cosine distance between a field's vector value and a VectorValue. * * ```typescript @@ -3856,6 +4275,8 @@ export function cosineDistance( ): CosineDistance; /** + * @beta + * * Calculates the Cosine distance between a field's vector value and a vector expression. * * ```typescript @@ -3870,6 +4291,8 @@ export function cosineDistance( export function cosineDistance(expr: string, other: Expr): CosineDistance; /** + * @beta + * * Calculates the Cosine distance between a vector expression and a double array. * * ```typescript @@ -3884,6 +4307,8 @@ export function cosineDistance(expr: string, other: Expr): CosineDistance; export function cosineDistance(expr: Expr, other: number[]): CosineDistance; /** + * @beta + * * Calculates the Cosine distance between a vector expression and a VectorValue. * * ```typescript @@ -3898,6 +4323,8 @@ export function cosineDistance(expr: Expr, other: number[]): CosineDistance; export function cosineDistance(expr: Expr, other: VectorValue): CosineDistance; /** + * @beta + * * Calculates the Cosine distance between two vector expressions. * * ```typescript @@ -3920,6 +4347,8 @@ export function cosineDistance( } /** + * @beta + * * Calculates the dot product distance between a field's vector value and a double array. * * ```typescript @@ -3937,6 +4366,8 @@ export function dotProductDistance( ): DotProductDistance; /** + * @beta + * * Calculates the dot product distance between a field's vector value and a VectorValue. * * ```typescript @@ -3954,6 +4385,8 @@ export function dotProductDistance( ): DotProductDistance; /** + * @beta + * * Calculates the dot product distance between a field's vector value and a vector expression. * * ```typescript @@ -3971,6 +4404,8 @@ export function dotProductDistance( ): DotProductDistance; /** + * @beta + * * Calculates the dot product distance between a vector expression and a double array. * * ```typescript @@ -3988,6 +4423,8 @@ export function dotProductDistance( ): DotProductDistance; /** + * @beta + * * Calculates the dot product distance between a vector expression and a VectorValue. * * ```typescript @@ -4005,6 +4442,8 @@ export function dotProductDistance( ): DotProductDistance; /** + * @beta + * * Calculates the dot product distance between two vector expressions. * * ```typescript @@ -4027,6 +4466,8 @@ export function dotProductDistance( } /** + * @beta + * * Calculates the Euclidean distance between a field's vector value and a double array. * * ```typescript @@ -4044,6 +4485,8 @@ export function euclideanDistance( ): EuclideanDistance; /** + * @beta + * * Calculates the Euclidean distance between a field's vector value and a VectorValue. * * ```typescript @@ -4061,6 +4504,8 @@ export function euclideanDistance( ): EuclideanDistance; /** + * @beta + * * Calculates the Euclidean distance between a field's vector value and a vector expression. * * ```typescript @@ -4075,6 +4520,8 @@ export function euclideanDistance( export function euclideanDistance(expr: string, other: Expr): EuclideanDistance; /** + * @beta + * * Calculates the Euclidean distance between a vector expression and a double array. * * ```typescript @@ -4093,6 +4540,8 @@ export function euclideanDistance( ): EuclideanDistance; /** + * @beta + * * Calculates the Euclidean distance between a vector expression and a VectorValue. * * ```typescript @@ -4110,6 +4559,8 @@ export function euclideanDistance( ): EuclideanDistance; /** + * @beta + * * Calculates the Euclidean distance between two vector expressions. * * ```typescript @@ -4132,6 +4583,8 @@ export function euclideanDistance( } /** + * @beta + * * Creates functions that work on the backend but do not exist in the SDK yet. * * ```typescript @@ -4149,6 +4602,8 @@ export function genericFunction(name: string, params: Expr[]): Function { } /** + * @beta + * * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. * * ```typescript @@ -4165,6 +4620,8 @@ export function ascending(expr: Expr): Ordering { } /** + * @beta + * * Creates an {@link Ordering} that sorts documents in descending order based on this expression. * * ```typescript @@ -4181,6 +4638,8 @@ export function descending(expr: Expr): Ordering { } /** + * @beta + * * Represents an ordering criterion for sorting documents in a Firestore pipeline. * * You create `Ordering` instances using the `ascending` and `descending` helper functions. diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index ce6677b9a..866d90cc9 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -1,3 +1,17 @@ +// 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 * as firestore from '@google-cloud/firestore'; import {GoogleError, serializer} from 'google-gax'; import {converter} from 'protobufjs'; diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 7b0e7a285..5abdfed54 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -1,3 +1,17 @@ +// 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 {DocumentData} from '@google-cloud/firestore'; import * as firestore from '@google-cloud/firestore'; import * as deepEqual from 'fast-deep-equal'; @@ -45,6 +59,10 @@ import IStage = google.firestore.v1.Pipeline.IStage; import {QueryCursor} from './reference/types'; import {isOptionalEqual} from './util'; +/** + * Represents the source of a Firestore {@link Pipeline}. + * @beta + */ export class PipelineSource { constructor(private db: Firestore) {} @@ -66,6 +84,8 @@ export class PipelineSource { } /** + * @beta + * * The Pipeline class provides a flexible and expressive framework for building complex data * transformation and query pipelines for Firestore. * @@ -669,6 +689,8 @@ export class Pipeline { } /** + * @beta + * * A PipelineResult contains data read from a Firestore Pipeline. The data can be extracted with the * {@link #data()} or {@link #get(String)} methods. * diff --git a/dev/src/stage.ts b/dev/src/stage.ts index de656fc03..2dc941164 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -1,3 +1,17 @@ +// 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 * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 09c98f1a3..0eca6211b 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -1,3 +1,17 @@ +// 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 { AggregateQuery, DocumentData, @@ -48,6 +62,7 @@ import { euclideanDistance, Constant, mapGet, + lte, } from '../src/expression'; import {PipelineResult} from '../src/pipeline'; import {verifyInstance} from '../test/util/helpers'; @@ -313,8 +328,9 @@ describe.only('Pipeline class', () => { }); it('can select fields', async () => { - const results = await randomCol + const results = await firestore .pipeline() + .collection(randomCol.path) .select('title', 'author') .sort(Field.of('author').ascending()) .execute(); @@ -359,8 +375,9 @@ describe.only('Pipeline class', () => { }); it('offset and limits', async () => { - const results = await randomCol + const results = await firestore .pipeline() + .collection(randomCol.path) .sort(Field.of('author').ascending()) .offset(5) .limit(3) @@ -607,7 +624,7 @@ describe.only('Pipeline class', () => { .where( and( gt('rating', 4.2), - lt(Field.of('rating'), 4.5), + lte(Field.of('rating'), 4.5), neq('genre', 'Science Fiction') ) ) diff --git a/dev/system-test/query.ts b/dev/system-test/query.ts index cc0b877d2..84853f79b 100644 --- a/dev/system-test/query.ts +++ b/dev/system-test/query.ts @@ -1,3 +1,17 @@ +// 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 { DocumentData, QuerySnapshot, From a3e62b5bd0bad7fdf2cb62bc55e7749b374cf755 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 3 Sep 2024 10:07:33 -0400 Subject: [PATCH 11/31] Addressing comments. --- dev/src/expression.ts | 146 ++++++++++++++++++------------------ dev/src/pipeline.ts | 4 +- dev/src/stage.ts | 48 ++++++++++++ dev/system-test/pipeline.ts | 6 +- types/firestore.d.ts | 2 + 5 files changed, 128 insertions(+), 78 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index edb7f8827..deec76988 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -435,10 +435,10 @@ export abstract class Expr { * Field.of("items").arrayConcat(Field.of("otherItems")); * ``` * - * @param values The array expressions to concatenate. + * @param arrays The array expressions to concatenate. * @return A new `Expr` representing the concatenated array. */ - arrayConcat(...values: Expr[]): ArrayConcat; + arrayConcat(arrays: Expr[]): ArrayConcat; /** * Creates an expression that concatenates an array expression with one or more other arrays. @@ -448,12 +448,12 @@ export abstract class Expr { * Field.of("tags").arrayConcat(Arrays.asList("newTag1", "newTag2"), Field.of("otherTag")); * ``` * - * @param values The array expressions or values to concatenate. + * @param arrays The array expressions or values to concatenate. * @return A new `Expr` representing the concatenated array. */ - arrayConcat(...values: any[]): ArrayConcat; - arrayConcat(...values: any[]): ArrayConcat { - const exprValues = values.map(value => + 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); @@ -1034,48 +1034,48 @@ export abstract class Expr { } /** - * Calculates the dot product distance between two vectors. + * Calculates the dot product between two vectors. * * ```typescript - * // Calculate the dot product distance between a feature vector and a target vector - * Field.of("features").dotProductDistance([0.5, 0.8, 0.2]); + * // Calculate the dot product between a feature vector and a target vector + * Field.of("features").dotProduct([0.5, 0.8, 0.2]); * ``` * - * @param other The other vector (as an array of numbers) to compare against. - * @return A new `Expr` representing the dot product distance between the two vectors. + * @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. */ - dotProductDistance(other: Expr): DotProductDistance; + dotProduct(other: Expr): DotProduct; /** - * Calculates the dot product distance between two vectors. + * Calculates the dot product between two vectors. * * ```typescript - * // Calculate the dot product distance between a feature vector and a target vector - * Field.of("features").dotProductDistance(new VectorValue([0.5, 0.8, 0.2])); + * // Calculate the dot product between a feature vector and a target vector + * Field.of("features").dotProduct(new VectorValue([0.5, 0.8, 0.2])); * ``` * - * @param other The other vector (as a VectorValue) to compare against. - * @return A new `Expr` representing the dot product distance between the two vectors. + * @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. */ - dotProductDistance(other: VectorValue): DotProductDistance; + dotProduct(other: VectorValue): DotProduct; /** - * Calculates the dot product distance between two vectors. + * Calculates the dot product between two vectors. * * ```typescript - * // Calculate the dot product distance between a feature vector and a target vector - * Field.of("features").dotProductDistance([0.5, 0.8, 0.2]); + * // Calculate the dot product between a feature vector and a target vector + * Field.of("features").dotProduct([0.5, 0.8, 0.2]); * ``` * - * @param other The other vector (as an array of numbers) to compare against. - * @return A new `Expr` representing the dot product distance between the two vectors. + * @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. */ - dotProductDistance(other: number[]): DotProductDistance; - dotProductDistance(other: Expr | VectorValue | number[]): DotProductDistance { + dotProduct(other: number[]): DotProduct; + dotProduct(other: Expr | VectorValue | number[]): DotProduct { if (other instanceof Expr) { - return new DotProductDistance(this, other); + return new DotProduct(this, other); } else { - return new DotProductDistance(this, Constant.vector(other)); + return new DotProduct(this, Constant.vector(other)); } } @@ -1087,7 +1087,7 @@ export abstract class Expr { * Field.of("location").euclideanDistance([37.7749, -122.4194]); * ``` * - * @param other The other vector (as an array of numbers) to compare against. + * @param other The other vector (as an array of numbers) to calculate with. * @return A new `Expr` representing the Euclidean distance between the two vectors. */ euclideanDistance(other: Expr): EuclideanDistance; @@ -2041,7 +2041,7 @@ class CosineDistance extends Function { /** * @beta */ -class DotProductDistance extends Function { +class DotProduct extends Function { constructor( private vector1: Expr, private vector2: Expr @@ -4349,120 +4349,120 @@ export function cosineDistance( /** * @beta * - * Calculates the dot product distance between a field's vector value and a double array. + * Calculates the dot product between a field's vector value and a double array. * * ```typescript * // Calculate the dot product distance between a feature vector and a target vector - * dotProductDistance("features", [0.5, 0.8, 0.2]); + * 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 compare against. - * @return A new {@code Expr} representing the dot product distance between the two vectors. + * @param other The other vector (as an array of doubles) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProductDistance( +export function dotProduct( expr: string, other: number[] -): DotProductDistance; +): DotProduct; /** * @beta * - * Calculates the dot product distance between a field's vector value and a VectorValue. + * Calculates the dot product between a field's vector value and a VectorValue. * * ```typescript * // Calculate the dot product distance between a feature vector and a target vector - * dotProductDistance("features", new VectorValue([0.5, 0.8, 0.2])); + * dotProduct("features", new VectorValue([0.5, 0.8, 0.2])); * ``` * * @param expr The name of the field containing the first vector. - * @param other The other vector (as a VectorValue) to compare against. - * @return A new {@code Expr} representing the dot product distance between the two vectors. + * @param other The other vector (as a VectorValue) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProductDistance( +export function dotProduct( expr: string, other: VectorValue -): DotProductDistance; +): DotProduct; /** * @beta * - * Calculates the dot product distance between a field's vector value and a vector expression. + * Calculates the dot product between a field's vector value and a vector expression. * * ```typescript * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' - * dotProductDistance("docVector1", Field.of("docVector2")); + * dotProduct("docVector1", Field.of("docVector2")); * ``` * * @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 dot product distance between the two vectors. + * @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. */ -export function dotProductDistance( +export function dotProduct( expr: string, other: Expr -): DotProductDistance; +): DotProduct; /** * @beta * - * Calculates the dot product distance between a vector expression and a double array. + * Calculates the dot product between a vector expression and a double array. * * ```typescript - * // Calculate the dot product distance between a feature vector and a target vector - * dotProductDistance(Field.of("features"), [0.5, 0.8, 0.2]); + * // Calculate the dot product between a feature vector and a target vector + * dotProduct(Field.of("features"), [0.5, 0.8, 0.2]); * ``` * - * @param expr The first vector (represented as an Expr) to compare against. - * @param other The other vector (as an array of doubles) to compare against. - * @return A new {@code Expr} representing the dot product distance between the two vectors. + * @param expr The first vector (represented as an Expr) to calculate with. + * @param other The other vector (as an array of doubles) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProductDistance( +export function dotProduct( expr: Expr, other: number[] -): DotProductDistance; +): DotProduct; /** * @beta * - * Calculates the dot product distance between a vector expression and a VectorValue. + * Calculates the dot product between a vector expression and a VectorValue. * * ```typescript - * // Calculate the dot product distance between a feature vector and a target vector - * dotProductDistance(Field.of("features"), new VectorValue([0.5, 0.8, 0.2])); + * // Calculate the dot product between a feature vector and a target vector + * dotProduct(Field.of("features"), new VectorValue([0.5, 0.8, 0.2])); * ``` * - * @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 dot product distance between the two vectors. + * @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. */ -export function dotProductDistance( +export function dotProduct( expr: Expr, other: VectorValue -): DotProductDistance; +): DotProduct; /** * @beta * - * Calculates the dot product distance between two vector expressions. + * Calculates the dot product between two vector expressions. * * ```typescript - * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' - * dotProductDistance(Field.of("docVector1"), Field.of("docVector2")); + * // Calculate the dot product between two document vectors: 'docVector1' and 'docVector2' + * dotProduct(Field.of("docVector1"), Field.of("docVector2")); * ``` * - * @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 dot product distance between the two vectors. + * @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. */ -export function dotProductDistance(expr: Expr, other: Expr): DotProductDistance; -export function dotProductDistance( +export function dotProduct(expr: Expr, other: Expr): DotProduct; +export function dotProduct( expr: Expr | string, other: Expr | number[] | VectorValue -): DotProductDistance { +): DotProduct { const expr1 = expr instanceof Expr ? expr : Field.of(expr); const expr2 = other instanceof Expr ? other : Constant.vector(other); - return new DotProductDistance(expr1, expr2); + return new DotProduct(expr1, expr2); } /** diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 5abdfed54..5ac3f1592 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -129,7 +129,7 @@ export class Pipeline { constructor( private db: Firestore, private stages: Stage[], - private converter: firestore.FirestorePipelineConverter = defaultConverter() + private converter: firestore.FirestorePipelineConverter = defaultPipelineConverter() ) {} /** @@ -730,7 +730,7 @@ export class PipelineResult readTime?: Timestamp, createTime?: Timestamp, updateTime?: Timestamp, - readonly converter: firestore.FirestorePipelineConverter = defaultConverter() + readonly converter: firestore.FirestorePipelineConverter = defaultPipelineConverter() ) { this._ref = ref; this._serializer = serializer; diff --git a/dev/src/stage.ts b/dev/src/stage.ts index 2dc941164..7da7170d7 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -26,11 +26,17 @@ import {VectorValue} from './field-value'; import {DocumentReference} from './reference/document-reference'; import {Serializer} from './serializer'; +/** + * @beta + */ export interface Stage { name: string; _toProto(serializer: Serializer): api.Pipeline.IStage; } +/** + * @beta + */ export class AddField implements Stage { name = 'add_field'; @@ -44,6 +50,9 @@ export class AddField implements Stage { } } +/** + * @beta + */ export class Aggregate implements Stage { name = 'aggregate'; @@ -63,6 +72,9 @@ export class Aggregate implements Stage { } } +/** + * @beta + */ export class Distinct implements Stage { name = 'distinct'; @@ -76,6 +88,9 @@ export class Distinct implements Stage { } } +/** + * @beta + */ export class Collection implements Stage { name = 'collection'; @@ -93,6 +108,9 @@ export class Collection implements Stage { } } +/** + * @beta + */ export class CollectionGroup implements Stage { name = 'collection_group'; @@ -106,6 +124,9 @@ export class CollectionGroup implements Stage { } } +/** + * @beta + */ export class Database implements Stage { name = 'database'; @@ -116,6 +137,9 @@ export class Database implements Stage { } } +/** + * @beta + */ export class Documents implements Stage { name = 'documents'; @@ -135,6 +159,9 @@ export class Documents implements Stage { } } +/** + * @beta + */ export class Where implements Stage { name = 'where'; @@ -148,12 +175,18 @@ export class Where implements Stage { } } +/** + * @beta + */ export interface FindNearestOptions { limit: number; distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; distanceField?: string; } +/** + * @beta + */ export class FindNearest implements Stage { name = 'find_nearest'; @@ -187,6 +220,9 @@ export class FindNearest implements Stage { } } +/** + * @beta + */ export class Limit implements Stage { name = 'limit'; @@ -200,6 +236,9 @@ export class Limit implements Stage { } } +/** + * @beta + */ export class Offset implements Stage { name = 'offset'; @@ -213,6 +252,9 @@ export class Offset implements Stage { } } +/** + * @beta + */ export class Select implements Stage { name = 'select'; @@ -226,6 +268,9 @@ export class Select implements Stage { } } +/** + * @beta + */ export class Sort implements Stage { name = 'sort'; @@ -239,6 +284,9 @@ export class Sort implements Stage { } } +/** + * @beta + */ export class GenericStage implements Stage { constructor( public name: string, diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 0eca6211b..4af42e931 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -58,7 +58,7 @@ import { strConcat, subtract, cosineDistance, - dotProductDistance, + dotProduct, euclideanDistance, Constant, mapGet, @@ -435,7 +435,7 @@ describe.only('Pipeline class', () => { const results = await randomCol .pipeline() .select( - Field.of('tags').arrayConcat('newTag1', 'newTag2').as('modifiedTags') + Field.of('tags').arrayConcat(['newTag1', 'newTag2']).as('modifiedTags') ) .limit(1) .execute(); @@ -727,7 +727,7 @@ describe.only('Pipeline class', () => { cosineDistance(Constant.vector(sourceVector), targetVector).as( 'cosineDistance' ), - dotProductDistance(Constant.vector(sourceVector), targetVector).as( + dotProduct(Constant.vector(sourceVector), targetVector).as( 'dotProductDistance' ), euclideanDistance(Constant.vector(sourceVector), targetVector).as( diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 0c4f0a93d..828eb1c88 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -1677,6 +1677,8 @@ declare namespace FirebaseFirestore { */ readonly updateTime: Timestamp | undefined; + readonly executionTime: Timestamp | undefined; + /** * Retrieves all fields in the document as an Object. * From 997c6c5a3f6a98095f04b4844e8dd87490393ffc Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 3 Sep 2024 11:05:54 -0400 Subject: [PATCH 12/31] DTS + some other fixes. --- dev/src/expression.ts | 25 +- dev/src/pipeline.ts | 20 +- dev/src/stage.ts | 16 +- dev/src/types.ts | 2 +- types/firestore.d.ts | 4489 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 4488 insertions(+), 64 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index deec76988..b9f3b57f5 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -4360,10 +4360,7 @@ export function cosineDistance( * @param other The other vector (as an array of doubles) to calculate with. * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProduct( - expr: string, - other: number[] -): DotProduct; +export function dotProduct(expr: string, other: number[]): DotProduct; /** * @beta @@ -4379,10 +4376,7 @@ export function dotProduct( * @param other The other vector (as a VectorValue) to calculate with. * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProduct( - expr: string, - other: VectorValue -): DotProduct; +export function dotProduct(expr: string, other: VectorValue): DotProduct; /** * @beta @@ -4398,10 +4392,7 @@ export function dotProduct( * @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. */ -export function dotProduct( - expr: string, - other: Expr -): DotProduct; +export function dotProduct(expr: string, other: Expr): DotProduct; /** * @beta @@ -4417,10 +4408,7 @@ export function dotProduct( * @param other The other vector (as an array of doubles) to calculate with. * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProduct( - expr: Expr, - other: number[] -): DotProduct; +export function dotProduct(expr: Expr, other: number[]): DotProduct; /** * @beta @@ -4436,10 +4424,7 @@ export function dotProduct( * @param other The other vector (as a VectorValue) to calculate with. * @return A new {@code Expr} representing the dot product between the two vectors. */ -export function dotProduct( - expr: Expr, - other: VectorValue -): DotProduct; +export function dotProduct(expr: Expr, other: VectorValue): DotProduct; /** * @beta diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 5ac3f1592..c39680714 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -34,12 +34,12 @@ import {ExecutionUtil} from './pipeline-util'; import {DocumentReference} from './reference/document-reference'; import {Serializer} from './serializer'; import { - AddField, + AddFields, Aggregate, - Collection, - CollectionGroup, - Database, - Documents, + CollectionSource, + CollectionGroupSource, + DatabaseSource, + DocumentsSource, Where, FindNearest, FindNearestOptions, @@ -67,19 +67,19 @@ export class PipelineSource { constructor(private db: Firestore) {} collection(collectionPath: string): Pipeline { - return new Pipeline(this.db, [new Collection(collectionPath)]); + return new Pipeline(this.db, [new CollectionSource(collectionPath)]); } collectionGroup(collectionId: string): Pipeline { - return new Pipeline(this.db, [new CollectionGroup(collectionId)]); + return new Pipeline(this.db, [new CollectionGroupSource(collectionId)]); } database(): Pipeline { - return new Pipeline(this.db, [new Database()]); + return new Pipeline(this.db, [new DatabaseSource()]); } documents(docs: DocumentReference[]): Pipeline { - return new Pipeline(this.db, [Documents.of(docs)]); + return new Pipeline(this.db, [DocumentsSource.of(docs)]); } } @@ -160,7 +160,7 @@ export class Pipeline { */ addFields(...fields: Selectable[]): Pipeline { const copy = this.stages.map(s => s); - copy.push(new AddField(this.selectablesToMap(fields))); + copy.push(new AddFields(this.selectablesToMap(fields))); return new Pipeline(this.db, copy, this.converter); } diff --git a/dev/src/stage.ts b/dev/src/stage.ts index 7da7170d7..31aeb19cd 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -37,8 +37,8 @@ export interface Stage { /** * @beta */ -export class AddField implements Stage { - name = 'add_field'; +export class AddFields implements Stage { + name = 'add_fields'; constructor(private fields: Map) {} @@ -91,7 +91,7 @@ export class Distinct implements Stage { /** * @beta */ -export class Collection implements Stage { +export class CollectionSource implements Stage { name = 'collection'; constructor(private collectionPath: string) { @@ -111,7 +111,7 @@ export class Collection implements Stage { /** * @beta */ -export class CollectionGroup implements Stage { +export class CollectionGroupSource implements Stage { name = 'collection_group'; constructor(private collectionId: string) {} @@ -127,7 +127,7 @@ export class CollectionGroup implements Stage { /** * @beta */ -export class Database implements Stage { +export class DatabaseSource implements Stage { name = 'database'; _toProto(serializer: Serializer): api.Pipeline.IStage { @@ -140,13 +140,13 @@ export class Database implements Stage { /** * @beta */ -export class Documents implements Stage { +export class DocumentsSource implements Stage { name = 'documents'; constructor(private docPaths: string[]) {} - static of(refs: DocumentReference[]): Documents { - return new Documents(refs.map(ref => '/' + ref.path)); + static of(refs: DocumentReference[]): DocumentsSource { + return new DocumentsSource(refs.map(ref => '/' + ref.path)); } _toProto(serializer: Serializer): api.Pipeline.IStage { diff --git a/dev/src/types.ts b/dev/src/types.ts index 26e8be080..e52756272 100644 --- a/dev/src/types.ts +++ b/dev/src/types.ts @@ -167,7 +167,7 @@ const defaultPipelineConverterObj: FirestorePipelineConverter = { export function defaultPipelineConverter< AppModelType, >(): FirestorePipelineConverter { - return defaultConverterObj as FirestoreDataConverter; + return defaultPipelineConverterObj as FirestorePipelineConverter; } /** diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 828eb1c88..dc35a6272 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -1663,31 +1663,6 @@ declare namespace FirebaseFirestore { data(): AppModelType; } - export class PipelineResult { - private constructor(); - - /** - * The time the document was created. - */ - readonly createTime: Timestamp | undefined; - - /** - * The time the document was last updated (at the time the snapshot was - * generated). - */ - readonly updateTime: Timestamp | undefined; - - readonly executionTime: Timestamp | undefined; - - /** - * Retrieves all fields in the document as an Object. - * - * @override - * @return An Object containing all fields in the document. - */ - data(): AppModelType | undefined; - } - /** * The direction of a `Query.orderBy()` clause is specified as 'desc' or 'asc' * (descending or ascending). @@ -3221,6 +3196,4470 @@ declare namespace FirebaseFirestore { */ readonly snapshot: T | null; } + + /** + * @beta + * + * An interface that represents a selectable expression. + */ + export interface Selectable { + selectable: true; + } + + /** + * @beta + * + * An interface that represents a filter condition. + */ + export interface FilterCondition { + filterable: true; + } + + /** + * @beta + * + * An interface that represents an accumulator. + */ + export interface Accumulator { + accumulator: true; + } + + /** + * @beta + * + * An accumulator target, which is an expression with an alias that also implements the Accumulator interface. + */ + export type AccumulatorTarget = ExprWithAlias; + + /** + * @beta + * + * A filter expression, which is an expression that also implements the FilterCondition interface. + */ + export type FilterExpr = Expr & FilterCondition; + + /** + * @beta + * + * A selectable expression, which is an expression that also implements the Selectable interface. + */ + export type SelectableExpr = Expr & Selectable; + + /** + * @beta + * + * An enumeration of the different types of expressions. + */ + export type ExprType = + | 'Field' + | 'Constant' + | 'Function' + | 'ListOfExprs' + | 'ExprWithAlias'; + + /** + * @beta + * + * Represents an expression that can be evaluated to a value within the execution of a {@link + * Pipeline}. + * + * Expressions are the building blocks for creating complex queries and transformations in + * Firestore pipelines. They can represent: + * + * - **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 { + /** + * 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. + */ + add(other: Expr): Add; + + /** + * Creates an expression that adds this expression to a constant value. + * + * ```typescript + * // Add 5 to the value of the 'age' field + * Field.of("age").add(5); + * ``` + * + * @param other The constant value to add. + * @return A new `Expr` representing the addition operation. + */ + add(other: any): Add; + + /** + * Creates an expression that subtracts another expression from this expression. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * Field.of("price").subtract(Field.of("discount")); + * ``` + * + * @param other The expression to subtract from this expression. + * @return A new `Expr` representing the subtraction operation. + */ + subtract(other: Expr): Subtract; + + /** + * 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); + * ``` + * + * @param other The constant value to subtract. + * @return A new `Expr` representing the subtraction operation. + */ + subtract(other: any): Subtract; + + /** + * Creates an expression that multiplies this expression by another expression. + * + * ```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); + * ``` + * + * @param other The constant value to multiply by. + * @return A new `Expr` representing the multiplication operation. + */ + multiply(other: any): Multiply; + + /** + * Creates an expression that divides this expression by another expression. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * Field.of("total").divide(Field.of("count")); + * ``` + * + * @param other The expression to divide by. + * @return A new `Expr` representing the division operation. + */ + divide(other: Expr): Divide; + + /** + * Creates an expression that divides this expression by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * Field.of("value").divide(10); + * ``` + * + * @param other The constant value to divide by. + * @return A new `Expr` representing the division operation. + */ + divide(other: any): Divide; + + /** + * 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); + * ``` + * + * @param other The expression to compare for equality. + * @return A new `Expr` representing the equality comparison. + */ + eq(other: Expr): Eq; + + /** + * 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"); + * ``` + * + * @param other The constant value to compare for equality. + * @return A new `Expr` representing the equality comparison. + */ + eq(other: any): Eq; + + /** + * Creates an expression that checks if this expression is not equal to another expression. + * + * ```typescript + * // Check if the 'status' field is not equal to "completed" + * Field.of("status").neq("completed"); + * ``` + * + * @param other The expression to compare for inequality. + * @return A new `Expr` representing the inequality comparison. + */ + neq(other: Expr): Neq; + + /** + * 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"); + * ``` + * + * @param other The constant value to compare for inequality. + * @return A new `Expr` representing the inequality comparison. + */ + neq(other: any): Neq; + + /** + * Creates an expression that checks if this expression is less than another expression. + * + * ```typescript + * // Check if the 'age' field is less than 'limit' + * Field.of("age").lt(Field.of('limit')); + * ``` + * + * @param other The expression to compare for less than. + * @return A new `Expr` representing the less than comparison. + */ + lt(other: Expr): Lt; + + /** + * 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); + * ``` + * + * @param other The constant value to compare for less than. + * @return A new `Expr` representing the less than comparison. + */ + lt(other: any): Lt; + + /** + * Creates an expression that checks if this expression is less than or equal to another + * expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * Field.of("quantity").lte(Constant.of(20)); + * ``` + * + * @param other 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; + + /** + * 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); + * ``` + * + * @param other 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; + + /** + * Creates an expression that checks if this expression is greater than another expression. + * + * ```typescript + * // Check if the 'age' field is greater than the 'limit' field + * Field.of("age").gt(Field.of("limit")); + * ``` + * + * @param other The expression to compare for greater than. + * @return A new `Expr` representing the greater than comparison. + */ + gt(other: Expr): Gt; + + /** + * 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); + * ``` + * + * @param other The constant value to compare for greater than. + * @return A new `Expr` representing the greater than comparison. + */ + gt(other: any): Gt; + + /** + * Creates an expression that checks if this expression is greater than or equal to another + * expression. + * + * ```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)); + * ``` + * + * @param other 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; + + /** + * Creates an expression that checks if this expression is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'score' field is greater than or equal to 80 + * Field.of("score").gte(80); + * ``` + * + * @param other 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; + + /** + * Creates an expression that concatenates an array expression with one or more other arrays. + * + * ```typescript + * // Combine the 'items' array with another array field. + * Field.of("items").arrayConcat(Field.of("otherItems")); + * ``` + * + * @param arrays The array expressions to concatenate. + * @return A new `Expr` representing the concatenated array. + */ + arrayConcat(arrays: Expr[]): ArrayConcat; + + /** + * Creates an expression that concatenates an array expression 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 array expressions or values to concatenate. + * @return A new `Expr` representing the concatenated array. + */ + arrayConcat(arrays: any[]): ArrayConcat; + + /** + * Creates an expression that checks if an array contains a specific element. + * + * ```typescript + * // Check if the 'sizes' array contains the value from the 'selectedSize' field + * Field.of("sizes").arrayContains(Field.of("selectedSize")); + * ``` + * + * @param element The element to search for in the array. + * @return A new `Expr` representing the 'array_contains' comparison. + */ + arrayContains(element: Expr): ArrayContains; + + /** + * 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"); + * ``` + * + * @param element The element to search for in the array. + * @return A new `Expr` representing the 'array_contains' comparison. + */ + arrayContains(element: any): ArrayContains; + + /** + * 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")); + * ``` + * + * @param values The elements to check for in the array. + * @return A new `Expr` representing the 'array_contains_all' comparison. + */ + arrayContainsAll(...values: Expr[]): ArrayContainsAll; + + /** + * 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")); + * ``` + * + * @param values The elements to check for in the array. + * @return A new `Expr` representing the 'array_contains_all' comparison. + */ + arrayContainsAll(...values: any[]): ArrayContainsAll; + + /** + * Creates an expression that checks if an array contains any of the specified elements. + * + * ```typescript + * // Check if the 'categories' array contains either values from field "cate1" or "cate2" + * Field.of("categories").arrayContainsAny(Field.of("cate1"), Field.of("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; + + /** + * Creates an expression that checks if an array contains any of the specified elements. + * + * ```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"); + * ``` + * + * @param values The elements to check for in the array. + * @return A new `Expr` representing the 'array_contains_any' comparison. + */ + arrayContainsAny(...values: any[]): ArrayContainsAny; + + /** + * Creates an expression that filters elements from an array using the given {@link + * FilterCondition} and returns the filtered elements as a new array. + * + * ```typescript + * // Get items from the 'inventoryPrices' array where the array item is greater than 0 + * // Note we use {@link Function#arrayElement} to represent array elements to construct a + * // filtering condition. + * Field.of("inventoryPrices").arrayFilter(arrayElement().gt(0)); + * ``` + * + * @param filter The {@link FilterCondition} to apply to the array elements. + * @return A new `Expr` representing the filtered array. + */ + arrayFilter(filter: FilterExpr): ArrayFilter; + + /** + * Creates an expression that calculates the length of an array. + * + * ```typescript + * // Get the number of items in the 'cart' array + * Field.of("cart").arrayLength(); + * ``` + * + * @return A new `Expr` representing the length of the array. + */ + arrayLength(): ArrayLength; + + /** + * Creates an expression that applies a transformation function to each element in an array and + * returns the new array as the result of the evaluation. + * + * ```typescript + * // Convert all strings in the 'names' array to uppercase + * Field.of("names").arrayTransform(arrayElement().toUppercase()); + * ``` + * + * @param transform The {@link Function} to apply to each array element. + * @return A new `Expr` representing the transformed array. + */ + arrayTransform(transform: Function): ArrayTransform; + + /** + * Creates an expression that checks if this expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * Field.of("category").in("Electronics", Field.of("primaryType")); + * ``` + * + * @param others The values or expressions to check against. + * @return A new `Expr` representing the 'IN' comparison. + */ + in(...others: Expr[]): In; + + /** + * Creates an expression that checks if this expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * Field.of("category").in("Electronics", Field.of("primaryType")); + * ``` + * + * @param others The values or expressions to check against. + * @return A new `Expr` representing the 'IN' comparison. + */ + in(...others: any[]): In; + + /** + * Creates an expression that checks if this expression evaluates to 'NaN' (Not a Number). + * + * ```typescript + * // Check if the result of a calculation is NaN + * Field.of("value").divide(0).isNaN(); + * ``` + * + * @return A new `Expr` representing the 'isNaN' check. + */ + isNaN(): IsNan; + + /** + * Creates an expression that checks if a field exists in the document. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * Field.of("phoneNumber").exists(); + * ``` + * + * @return A new `Expr` representing the 'exists' check. + */ + exists(): Exists; + + /** + * Creates an expression that calculates the length of a string. + * + * ```typescript + * // Get the length of the 'name' field + * Field.of("name").length(); + * ``` + * + * @return A new `Expr` representing the length of the string. + */ + length(): Length; + + /** + * 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%"); + * ``` + * + * @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; + + /** + * 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%"); + * ``` + * + * @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; + + /** + * Creates an expression that checks if a string contains a specified regular expression as a + * substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * Field.of("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; + + /** + * Creates an expression that checks if a string contains a specified regular expression as a + * substring. + * + * ```typescript + * // Check if the 'description' field contains the regular expression stored in field 'regex' + * Field.of("description").regexContains(Field.of("regex")); + * ``` + * + * @param pattern The regular expression to use for the search. + * @return A new `Expr` representing the 'contains' comparison. + */ + regexContains(pattern: Expr): RegexContains; + + /** + * Creates an expression that checks if a string matches a specified regular expression. + * + * ```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,}"); + * ``` + * + * @param pattern The regular expression to use for the match. + * @return A new `Expr` representing the regular expression match. + */ + regexMatch(pattern: string): RegexMatch; + + /** + * 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")); + * ``` + * + * @param pattern The regular expression to use for the match. + * @return A new `Expr` representing the regular expression match. + */ + regexMatch(pattern: Expr): RegexMatch; + + /** + * Creates an expression that checks if a string starts with a given prefix. + * + * ```typescript + * // Check if the 'name' field starts with "Mr." + * Field.of("name").startsWith("Mr."); + * ``` + * + * @param prefix The prefix to check for. + * @return A new `Expr` representing the 'starts with' comparison. + */ + startsWith(prefix: string): StartsWith; + + /** + * Creates an expression that checks if a string starts with a given prefix (represented as an + * expression). + * + * ```typescript + * // Check if the 'fullName' field starts with the value of the 'firstName' field + * Field.of("fullName").startsWith(Field.of("firstName")); + * ``` + * + * @param prefix The prefix expression to check for. + * @return A new `Expr` representing the 'starts with' comparison. + */ + startsWith(prefix: Expr): StartsWith; + + /** + * Creates an expression that checks if a string ends with a given postfix. + * + * ```typescript + * // Check if the 'filename' field ends with ".txt" + * Field.of("filename").endsWith(".txt"); + * ``` + * + * @param suffix The postfix to check for. + * @return A new `Expr` representing the 'ends with' comparison. + */ + endsWith(suffix: string): EndsWith; + + /** + * Creates an expression that checks if a string ends with a given postfix (represented as an + * expression). + * + * ```typescript + * // Check if the 'url' field ends with the value of the 'extension' field + * Field.of("url").endsWith(Field.of("extension")); + * ``` + * + * @param suffix The postfix expression to check for. + * @return A new `Expr` representing the 'ends with' comparison. + */ + endsWith(suffix: Expr): EndsWith; + + /** + * Creates an expression that converts a string to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * Field.of("name").toLowerCase(); + * ``` + * + * @return A new `Expr` representing the lowercase string. + */ + toLowercase(): ToLowercase; + + /** + * Creates an expression that converts a string to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * Field.of("title").toUpperCase(); + * ``` + * + * @return A new `Expr` representing the uppercase string. + */ + toUppercase(): ToUppercase; + + /** + * Creates an expression that removes leading and trailing whitespace from a string. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * Field.of("userInput").trim(); + * ``` + * + * @return A new `Expr` representing the trimmed string. + */ + trim(): Trim; + + /** + * Creates an expression that concatenates string expressions together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * Field.of("firstName").strConcat(Constant.of(" "), Field.of("lastName")); + * ``` + * + * @param elements The expressions (typically strings) to concatenate. + * @return A new `Expr` representing the concatenated string. + */ + strConcat(...elements: (string | Expr)[]): StrConcat; + + /** + * Accesses a value from a map (object) field using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * Field.of("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; + + /** + * Creates an aggregation that counts the number of stage inputs with valid evaluations of the + * expression or field. + * + * ```typescript + * // Count the total number of products + * Field.of("productId").count().as("totalProducts"); + * ``` + * + * @return A new `Accumulator` representing the 'count' aggregation. + */ + count(): Count; + + /** + * Creates an aggregation that calculates the sum of a numeric field across multiple stage inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * Field.of("orderAmount").sum().as("totalRevenue"); + * ``` + * + * @return A new `Accumulator` representing the 'sum' aggregation. + */ + sum(): Sum; + + /** + * Creates an aggregation that calculates the average (mean) of a numeric field across multiple + * stage inputs. + * + * ```typescript + * // Calculate the average age of users + * Field.of("age").avg().as("averageAge"); + * ``` + * + * @return A new `Accumulator` representing the 'avg' aggregation. + */ + avg(): Avg; + + /** + * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the lowest price of all products + * Field.of("price").min().as("lowestPrice"); + * ``` + * + * @return A new `Accumulator` representing the 'min' aggregation. + */ + min(): Min; + + /** + * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * Field.of("score").max().as("highestScore"); + * ``` + * + * @return A new `Accumulator` representing the 'max' aggregation. + */ + max(): Max; + + /** + * Calculates the cosine distance between two vectors. + * + * ```typescript + * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field + * Field.of("userVector").cosineDistance(Field.of("itemVector")); + * ``` + * + * @param other 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; + /** + * 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])); + * ``` + * + * @param other 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; + + /** + * Calculates the dot product between two vectors. + * + * ```typescript + * // Calculate the dot product between a feature vector and a target vector + * Field.of("features").dotProduct([0.5, 0.8, 0.2]); + * ``` + * + * @param other The other vector (as an array of numbers) to calculate with. + * @return A new `Expr` representing the dot product between the two vectors. + */ + dotProduct(other: Expr): DotProduct; + + /** + * Calculates the dot product between two vectors. + * + * ```typescript + * // Calculate the dot product between a feature vector and a target vector + * Field.of("features").dotProduct(new VectorValue([0.5, 0.8, 0.2])); + * ``` + * + * @param other The other vector (as an array of numbers) to calculate with. + * @return A new `Expr` representing the dot product between the two vectors. + */ + dotProduct(other: VectorValue): DotProduct; + + /** + * Calculates the dot product between two vectors. + * + * ```typescript + * // Calculate the dot product between a feature vector and a target vector + * Field.of("features").dotProduct([0.5, 0.8, 0.2]); + * ``` + * + * @param other The other vector (as an array of numbers) to calculate with. + * @return A new `Expr` representing the dot product between the two vectors. + */ + dotProduct(other: number[]): DotProduct; + + /** + * 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]); + * ``` + * + * @param other The other vector (as an array of numbers) to calculate with. + * @return A new `Expr` representing the Euclidean distance between the two vectors. + */ + euclideanDistance(other: Expr): EuclideanDistance; + + /** + * Calculates the Euclidean distance between two vectors. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * Field.of("location").euclideanDistance(new VectorValue([37.7749, -122.4194])); + * ``` + * + * @param other The other vector (as a VectorValue) to compare against. + * @return A new `Expr` representing the Euclidean distance between the two vectors. + */ + euclideanDistance(other: VectorValue): EuclideanDistance; + + /** + * Calculates the Euclidean distance between two vectors. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * Field.of("location").euclideanDistance([37.7749, -122.4194]); + * ``` + * + * @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. + */ + euclideanDistance(other: number[]): EuclideanDistance; + + /** + * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * firestore.pipeline().collection("users") + * .sort(Field.of("name").ascending()); + * ``` + * + * @return A new `Ordering` for ascending sorting. + */ + ascending(): Ordering; + + /** + * 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.of("createdAt").descending()); + * ``` + * + * @return A new `Ordering` for descending sorting. + */ + descending(): Ordering; + + /** + * 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.of("price").multiply(Field.of("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; + } + + /** + * @beta + */ + export class ExprWithAlias + extends Expr + implements Selectable + { + exprType: ExprType; + selectable: true; + /** + * @param expr The expression to alias. + * @param alias The alias to assign to the expression. + */ + constructor(expr: T, alias: string); + } + + /** + * @beta + * + * Represents a reference to a field in a Firestore document, or outputs of a {@link Pipeline} stage. + * + *

    Field references are used to access document field values in expressions and to specify fields + * for sorting, filtering, and projecting data in Firestore pipelines. + * + *

    You can create a `Field` instance using the static {@link #of} method: + * + * ```typescript + * // Create a Field instance for the 'name' field + * const nameField = Field.of("name"); + * + * // Create a Field instance for a nested field 'address.city' + * const cityField = Field.of("address.city"); + * ``` + */ + export class Field extends Expr implements Selectable { + exprType: ExprType; + selectable: true; + + /** + * 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(nameOrPath: string | FieldPath): Field; + static of(pipeline: Pipeline, name: string): Field; + /** + * Returns the field name. + * + * @return The field name. + */ + fieldName(): string; + } + + /** + * @beta + */ + export class Fields extends Expr implements Selectable { + exprType: ExprType; + selectable: true; + static of(name: string, ...others: string[]): Fields; + static ofAll(): Fields; + /** + * Returns the list of fields. + * + * @return The list of fields. + */ + fieldList(): Field[]; + } + + /** + * @beta + * + * Represents a constant value that can be used in a Firestore pipeline expression. + * + * You can create a `Constant` instance using the static {@link #of} method: + * + * ```typescript + * // Create a Constant instance for the number 10 + * const ten = Constant.of(10); + * + * // Create a Constant instance for the string "hello" + * const hello = Constant.of("hello"); + * ``` + */ + export class Constant extends Expr { + exprType: ExprType; + + /** + * 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. + * + * @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 Uint8Array value. + * + * @param value The Uint8Array value. + * @return A new `Constant` instance. + */ + static of(value: Uint8Array): 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 an array value. + * + * @param value The array value. + * @return A new `Constant` instance. + */ + static of(value: Array): Constant; + + /** + * Creates a `Constant` instance for a map value. + * + * @param value The map value. + * @return A new `Constant` instance. + */ + static of(value: Map): 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; + + /** + * 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: Array | VectorValue): Constant; + } + + /** + * @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 Function extends Expr { + exprType: ExprType; + } + + /** + * @beta + */ + export class Add extends Function {} + + /** + * @beta + */ + export class Subtract extends Function {} + + /** + * @beta + */ + export class Multiply extends Function {} + + /** + * @beta + */ + export class Divide extends Function {} + + /** + * @beta + */ + export class Eq extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Neq extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Lt extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Lte extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Gt extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Gte extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class ArrayConcat extends Function {} + + /** + * @beta + */ + export class ArrayContains extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class ArrayContainsAll extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class ArrayContainsAny extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class ArrayFilter extends Function {} + + /** + * @beta + */ + export class ArrayLength extends Function {} + + /** + * @beta + */ + export class ArrayTransform extends Function {} + + /** + * @beta + */ + export class ArrayElement extends Function {} + + /** + * @beta + */ + export class In extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class IsNan extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Exists extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Not extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class And extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Or extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Xor extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class If extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class Length extends Function {} + + /** + * @beta + */ + export class Like extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class RegexContains extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class RegexMatch extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class StartsWith extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class EndsWith extends Function implements FilterCondition { + filterable: true; + } + + /** + * @beta + */ + export class ToLowercase extends Function {} + + /** + * @beta + */ + export class ToUppercase extends Function {} + + /** + * @beta + */ + export class Trim extends Function {} + + /** + * @beta + */ + export class StrConcat extends Function {} + + /** + * @beta + */ + export class MapGet extends Function {} + + /** + * @beta + */ + export class Count extends Function implements Accumulator { + accumulator: true; + } + + /** + * @beta + */ + export class Sum extends Function implements Accumulator { + accumulator: true; + } + + /** + * @beta + */ + export class Avg extends Function implements Accumulator { + accumulator: true; + } + + /** + * @beta + */ + export class Min extends Function implements Accumulator { + accumulator: true; + } + + /** + * @beta + */ + export class Max extends Function implements Accumulator { + accumulator: true; + } + + /** + * @beta + */ + export class CosineDistance extends Function {} + + /** + * @beta + */ + export class DotProduct extends Function {} + + /** + * @beta + */ + export class EuclideanDistance extends Function {} + + /** + * @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")); + * ``` + * + * @param left The first expression to add. + * @param right The second expression to add. + * @return A new {@code Expr} representing the addition operation. + */ + export function add(left: Expr, right: Expr): Add; + + /** + * @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); + * ``` + * + * @param left The expression to add to. + * @param right The constant value to add. + * @return A new {@code Expr} representing the addition operation. + */ + export function add(left: Expr, right: any): Add; + + /** + * @beta + * + * Creates an expression that adds a field's value to an expression. + * + * ```typescript + * // Add the value of the 'quantity' field and the 'reserve' field. + * add("quantity", Field.of("reserve")); + * ``` + * + * @param left The field name to add to. + * @param right The expression to add. + * @return A new {@code Expr} representing the addition operation. + */ + export function add(left: string, right: Expr): Add; + + /** + * @beta + * + * Creates an expression that adds a field's value to a constant value. + * + * ```typescript + * // Add 5 to the value of the 'age' field + * add("age", 5); + * ``` + * + * @param left The field name to add to. + * @param right The constant value to add. + * @return A new {@code Expr} representing the addition operation. + */ + export function add(left: string, right: any): Add; + + /** + * @beta + * + * Creates an expression that subtracts two expressions. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * subtract(Field.of("price"), Field.of("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; + + /** + * @beta + * + * Creates an expression that subtracts a constant value from an expression. + * + * ```typescript + * // Subtract the constant value 2 from the 'value' field + * subtract(Field.of("value"), 2); + * ``` + * + * @param left The expression to subtract from. + * @param right The constant value to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ + export function subtract(left: Expr, right: any): Subtract; + + /** + * @beta + * + * Creates an expression that subtracts an expression from a field's value. + * + * ```typescript + * // Subtract the 'discount' field from the 'price' field + * subtract("price", Field.of("discount")); + * ``` + * + * @param left The field name to subtract from. + * @param right The expression to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ + export function subtract(left: string, right: Expr): Subtract; + + /** + * @beta + * + * Creates an expression that subtracts a constant value from a field's value. + * + * ```typescript + * // Subtract 20 from the value of the 'total' field + * subtract("total", 20); + * ``` + * + * @param left The field name to subtract from. + * @param right The constant value to subtract. + * @return A new {@code Expr} representing the subtraction operation. + */ + export function subtract(left: string, right: any): Subtract; + + /** + * @beta + * + * Creates an expression that multiplies two expressions together. + * + * ```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); + * ``` + * + * @param left The expression to multiply. + * @param right The constant value to multiply by. + * @return A new {@code Expr} representing the multiplication operation. + */ + export function multiply(left: Expr, right: any): Multiply; + + /** + * @beta + * + * Creates an expression that multiplies a field's value by an expression. + * + * ```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); + * ``` + * + * @param left The field name to multiply. + * @param right The constant value to multiply by. + * @return A new {@code Expr} representing the multiplication operation. + */ + export function multiply(left: string, right: any): Multiply; + + /** + * @beta + * + * Creates an expression that divides two expressions. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * divide(Field.of("total"), Field.of("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; + + /** + * @beta + * + * Creates an expression that divides an expression by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * divide(Field.of("value"), 10); + * ``` + * + * @param left The expression to be divided. + * @param right The constant value to divide by. + * @return A new {@code Expr} representing the division operation. + */ + export function divide(left: Expr, right: any): Divide; + + /** + * @beta + * + * Creates an expression that divides a field's value by an expression. + * + * ```typescript + * // Divide the 'total' field by the 'count' field + * divide("total", Field.of("count")); + * ``` + * + * @param left The field name to be divided. + * @param right The expression to divide by. + * @return A new {@code Expr} representing the division operation. + */ + export function divide(left: string, right: Expr): Divide; + + /** + * @beta + * + * Creates an expression that divides a field's value by a constant value. + * + * ```typescript + * // Divide the 'value' field by 10 + * divide("value", 10); + * ``` + * + * @param left The field name to be divided. + * @param right The constant value to divide by. + * @return A new {@code Expr} representing the division operation. + */ + export function divide(left: string, right: any): Divide; + + /** + * @beta + * + * Creates an expression that checks if two expressions are equal. + * + * ```typescript + * // Check if the 'age' field is equal to an expression + * eq(Field.of("age"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is equal to a constant value. + * + * ```typescript + * // Check if the 'age' field is equal to 21 + * eq(Field.of("age"), 21); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the equality comparison. + */ + export function eq(left: Expr, right: any): Eq; + + /** + * @beta + * + * Creates an expression that checks if a field's value is equal to an expression. + * + * ```typescript + * // Check if the 'age' field is equal to the 'limit' field + * eq("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the equality comparison. + */ + export function eq(left: string, right: Expr): Eq; + + /** + * @beta + * + * Creates an expression that checks if a field's value is equal to a constant value. + * + * ```typescript + * // Check if the 'city' field is equal to string constant "London" + * eq("city", "London"); + * ``` + * + * @param left The field name to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the equality comparison. + */ + export function eq(left: string, right: any): Eq; + + /** + * @beta + * + * Creates an expression that checks if two expressions are not equal. + * + * ```typescript + * // Check if the 'status' field is not equal to field 'finalState' + * neq(Field.of("status"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is not equal to a constant value. + * + * ```typescript + * // Check if the 'status' field is not equal to "completed" + * neq(Field.of("status"), "completed"); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the inequality comparison. + */ + export function neq(left: Expr, right: any): Neq; + + /** + * @beta + * + * Creates an expression that checks if a field's value is not equal to an expression. + * + * ```typescript + * // Check if the 'status' field is not equal to the value of 'expectedStatus' + * neq("status", Field.of("expectedStatus")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the inequality comparison. + */ + export function neq(left: string, right: Expr): Neq; + + /** + * @beta + * + * Creates an expression that checks if a field's value is not equal to a constant value. + * + * ```typescript + * // Check if the 'country' field is not equal to "USA" + * neq("country", "USA"); + * ``` + * + * @param left The field name to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the inequality comparison. + */ + export function neq(left: string, right: any): Neq; + + /** + * @beta + * + * Creates an expression that checks if the first expression is less than the second expression. + * + * ```typescript + * // Check if the 'age' field is less than 30 + * lt(Field.of("age"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is less than a constant value. + * + * ```typescript + * // Check if the 'age' field is less than 30 + * lt(Field.of("age"), 30); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the less than comparison. + */ + export function lt(left: Expr, right: any): Lt; + + /** + * @beta + * + * Creates an expression that checks if a field's value is less than an expression. + * + * ```typescript + * // Check if the 'age' field is less than the 'limit' field + * lt("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the less than comparison. + */ + export function lt(left: string, right: Expr): Lt; + + /** + * @beta + * + * Creates an expression that checks if a field's value is less than a constant value. + * + * ```typescript + * // Check if the 'price' field is less than 50 + * lt("price", 50); + * ``` + * + * @param left The field name to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the less than comparison. + */ + export function lt(left: string, right: any): Lt; + + /** + * @beta + * + * Creates an expression that checks if the first expression is less than or equal to the second + * expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lte(Field.of("quantity"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lte(Field.of("quantity"), 20); + * ``` + * + * @param left The expression to compare. + * @param right 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; + + /** + * 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")); + * ``` + * + * @param left The field name to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'score' field is less than or equal to 70 + * lte("score", 70); + * ``` + * + * @param left The field name to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that checks if the first expression is greater than the second + * expression. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * gt(Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is greater than a constant value. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * gt(Field.of("age"), 18); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the greater than comparison. + */ + export function gt(left: Expr, right: any): Gt; + + /** + * @beta + * + * Creates an expression that checks if a field's value is greater than an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than the value of field 'limit' + * gt("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the greater than comparison. + */ + export function gt(left: string, right: Expr): Gt; + + /** + * @beta + * + * Creates an expression that checks if a field's value is greater than a constant value. + * + * ```typescript + * // Check if the 'price' field is greater than 100 + * gt("price", 100); + * ``` + * + * @param left The field name to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the greater than comparison. + */ + export function gt(left: string, right: any): Gt; + + /** + * @beta + * + * Creates an expression that checks if the first expression is greater than or equal to the + * second expression. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to the field "threshold" + * gte(Field.of("quantity"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to 10 + * gte(Field.of("quantity"), 10); + * ``` + * + * @param left The expression to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value is greater than or equal to an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than or equal to the value of field 'limit' + * gte("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'score' field is greater than or equal to 80 + * gte("score", 80); + * ``` + * + * @param left The field name to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that concatenates an array expression with other arrays. + * + * ```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")]); + * ``` + * + * @param array The field name containing array values. + * @param elements The array expressions to concatenate. + * @return A new {@code Expr} representing the concatenated array. + */ + export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; + + /** + * @beta + * + * Creates an expression that concatenates a field's array value with other arrays and/or values. + * + * ```typescript + * // Combine the 'tags' array with a new array + * arrayConcat("tags", ["newTag1", "newTag2"]); + * ``` + * + * @param array The field name containing array values. + * @param elements The array expressions or single values to concatenate. + * @return A new {@code Expr} representing the concatenated array. + */ + export function arrayConcat(array: string, elements: any[]): ArrayConcat; + + /** + * @beta + * + * Creates an expression that checks if an array expression contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains the value of field 'selectedColor' + * arrayContains(Field.of("colors"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an array expression contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * arrayContains(Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if a field's array value contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains the value of field 'selectedColor' + * arrayContains("colors", Field.of("selectedColor")); + * ``` + * + * @param array 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; + + /** + * @beta + * + * Creates an expression that checks if a field's array value contains a specific value. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * arrayContains("colors", "red"); + * ``` + * + * @param array 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; + + /** + * @beta + * + * Creates an expression that checks if an array expression 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"]); + * ``` + * + * @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; + + /** + * @beta + * + * Creates an expression that checks if an array expression 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"]); + * ``` + * + * @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: any[] + ): ArrayContainsAny; + + /** + * @beta + * + * Creates an expression that checks if a field's array value 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"]); + * ``` + * + * @param array 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: string, + values: Expr[] + ): ArrayContainsAny; + + /** + * @beta + * + * Creates an expression that checks if a field's array value 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"]); + * ``` + * + * @param array 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: string, + values: any[] + ): ArrayContainsAny; + + /** + * @beta + * + * 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', 'tag2' and "tag3" + * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "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; + + /** + * @beta + * + * 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', 'tag2' and "tag3" + * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "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: any[] + ): ArrayContainsAll; + + /** + * @beta + * + * 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 both of the values from field 'tag1' and "tag2" + * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * ``` + * + * @param array 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: string, + values: Expr[] + ): ArrayContainsAll; + + /** + * @beta + * + * 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 both of the values from field 'tag1' and "tag2" + * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * ``` + * + * @param array 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: string, + values: any[] + ): ArrayContainsAll; + + /** + * @beta + * + * Creates an expression that filters elements from an array expression using the given {@link + * FilterExpr} and returns the filtered elements as a new array. + * + * ```typescript + * // Get items from the 'inventoryPrices' array where the array item is greater than 0 + * // Note we use {@link arrayElement} to represent array elements to construct a + * // filtering condition. + * arrayFilter(Field.of("inventoryPrices"), arrayElement().gt(0)); + * ``` + * + * @param array The array expression to filter. + * @param filter The {@link FilterExpr} to apply to the array elements. + * @return A new {@code Expr} representing the filtered array. + */ + export function arrayFilter(array: Expr, filter: FilterExpr): ArrayFilter; + + /** + * @beta + * + * Creates an expression that calculates the length of an array expression. + * + * ```typescript + * // Get the number of items in the 'cart' array + * arrayLength(Field.of("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; + + /** + * @beta + * + * Creates an expression that applies a transformation function to each element in an array + * expression and returns the new array as the result of the evaluation. + * + * ```typescript + * // Convert all strings in the 'names' array to uppercase + * // Note we use {@link arrayElement} to represent array elements to construct a + * // transforming function. + * arrayTransform(Field.of("names"), arrayElement().toUppercase()); + * ``` + * + * @param array The array expression to transform. + * @param transform The {@link Function} to apply to each array element. + * @return A new {@code Expr} representing the transformed array. + */ + export function arrayTransform( + array: Expr, + transform: Function + ): ArrayTransform; + + /** + * @beta + * + * Returns an expression that represents an array element within an {@link ArrayFilter} or {@link + * ArrayTransform} expression. + * + * ```typescript + * // Get items from the 'inventoryPrices' array where the array item is greater than 0 + * arrayFilter(Field.of("inventoryPrices"), arrayElement().gt(0)); + * ``` + * + * @return A new {@code Expr} representing an array element. + */ + export function arrayElement(): ArrayElement; + + /** + * @beta + * + * Creates an expression that checks if an expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ + export function inAny(element: Expr, others: Expr[]): In; + + /** + * @beta + * + * Creates an expression that checks if an expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ + export function inAny(element: Expr, others: any[]): In; + + /** + * @beta + * + * Creates an expression that checks if a field's value is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); + * ``` + * + * @param element The field to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ + export function inAny(element: string, others: Expr[]): In; + + /** + * @beta + * + * Creates an expression that checks if a field's value is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny("category", ["Electronics", Field.of("primaryType")]); + * ``` + * + * @param element The field to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ + export function inAny(element: string, others: any[]): In; + + /** + * @beta + * + * 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' + * notInAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ + export function notInAny(element: Expr, others: Expr[]): Not; + + /** + * @beta + * + * 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' + * notInAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ + export function notInAny(element: Expr, others: any[]): Not; + + /** + * @beta + * + * 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' + * notInAny("status", [Constant.of("pending"), Field.of("rejectedStatus")]); + * ``` + * + * @param element The field name to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ + export function notInAny(element: string, others: Expr[]): Not; + + /** + * @beta + * + * 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' + * notInAny("status", ["pending", Field.of("rejectedStatus")]); + * ``` + * + * @param element The field name to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ + export function notInAny(element: string, others: any[]): Not; + + /** + * @beta + * + * Creates an expression that performs a logical 'AND' operation on multiple filter conditions. + * + * ```typescript + * // Check if the 'age' field is greater than 18 AND the 'city' field is "London" AND + * // the 'status' field is "active" + * 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. + * @return A new {@code Expr} representing the logical 'AND' operation. + */ + export function and(left: FilterExpr, ...right: FilterExpr[]): And; + + /** + * @beta + * + * Creates an expression that performs a logical 'OR' operation on multiple filter conditions. + * + * ```typescript + * // Check if the 'age' field is greater than 18 OR the 'city' field is "London" OR + * // the 'status' field is "active" + * 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. + * @return A new {@code Expr} representing the logical 'OR' operation. + */ + export function or(left: FilterExpr, ...right: FilterExpr[]): Or; + + /** + * @beta + * + * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple filter + * conditions. + * + * ```typescript + * // Check if only one of the conditions is true: 'age' greater than 18, 'city' is "London", + * // or 'status' is "active". + * const condition = xor( + * gt("age", 18), + * eq("city", "London"), + * eq("status", "active")); + * ``` + * + * @param left The first filter condition. + * @param right Additional filter conditions to 'XOR' together. + * @return A new {@code Expr} representing the logical 'XOR' operation. + */ + export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor; + + /** + * @beta + * + * Creates a conditional expression that evaluates to a 'then' expression if a condition is true + * and an 'else' expression if the condition is false. + * + * ```typescript + * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". + * ifFunction( + * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); + * ``` + * + * @param condition The condition to evaluate. + * @param thenExpr The expression to evaluate if the condition is true. + * @param elseExpr The expression to evaluate if the condition is false. + * @return A new {@code Expr} representing the conditional expression. + */ + export function ifFunction( + condition: FilterExpr, + thenExpr: Expr, + elseExpr: Expr + ): If; + + /** + * @beta + * + * Creates an expression that negates a filter condition. + * + * ```typescript + * // Find documents where the 'completed' field is NOT true + * not(eq("completed", true)); + * ``` + * + * @param filter The filter condition to negate. + * @return A new {@code Expr} representing the negated filter condition. + */ + export function not(filter: FilterExpr): Not; + + /** + * @beta + * + * Creates an expression that checks if a field exists. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * exists(Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if a field exists. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * exists("phoneNumber"); + * ``` + * + * @param field The field name to check. + * @return A new {@code Expr} representing the 'exists' check. + */ + export function exists(field: string): Exists; + + /** + * @beta + * + * Creates an expression that checks if an expression evaluates to 'NaN' (Not a Number). + * + * ```typescript + * // Check if the result of a calculation is NaN + * isNaN(Field.of("value").divide(0)); + * ``` + * + * @param value The expression to check. + * @return A new {@code Expr} representing the 'isNaN' check. + */ + export function isNaN(value: Expr): IsNan; + + /** + * @beta + * + * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). + * + * ```typescript + * // Check if the result of a calculation is NaN + * isNaN("value"); + * ``` + * + * @param value The name of the field to check. + * @return A new {@code Expr} representing the 'isNaN' check. + */ + export function isNaN(value: string): IsNan; + + /** + * @beta + * + * Creates an expression that calculates the length of a string field. + * + * ```typescript + * // Get the length of the 'name' field + * length("name"); + * ``` + * + * @param field The name of the field containing the string. + * @return A new {@code Expr} representing the length of the string. + */ + export function length(field: string): Length; + + /** + * @beta + * + * Creates an expression that calculates the length of a string expression. + * + * ```typescript + * // Get the length of the 'name' field + * length(Field.of("name")); + * ``` + * + * @param expr The expression representing the string to calculate the length of. + * @return A new {@code Expr} representing the length of the string. + */ + export function length(expr: Expr): Length; + + /** + * @beta + * + * Creates an expression that performs a case-sensitive wildcard string comparison against a + * field. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like("title", "%guide%"); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that performs a case-sensitive wildcard string comparison against a + * field. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like("title", Field.of("pattern")); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that performs a case-sensitive wildcard string comparison. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like(Field.of("title"), "%guide%"); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that performs a case-sensitive wildcard string comparison. + * + * ```typescript + * // Check if the 'title' field contains the string "guide" + * like(Field.of("title"), Field.of("pattern")); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string field contains a specified regular expression as + * a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains("description", "(?i)example"); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string field contains a specified regular expression as + * a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains("description", Field.of("pattern")); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression contains a specified regular + * expression as a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains(Field.of("description"), "(?i)example"); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression contains a specified regular + * expression as a substring. + * + * ```typescript + * // Check if the 'description' field contains "example" (case-insensitive) + * regexContains(Field.of("description"), Field.of("pattern")); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string field matches a specified regular expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * 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 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; + + /** + * @beta + * + * Creates an expression that checks if a string field matches a specified regular expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * regexMatch("email", Field.of("pattern")); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression matches a specified regular + * expression. + * + * ```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,}"); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression matches a specified regular + * expression. + * + * ```typescript + * // Check if the 'email' field matches a valid email pattern + * regexMatch(Field.of("email"), Field.of("pattern")); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value starts with a given prefix. + * + * ```typescript + * // Check if the 'name' field starts with "Mr." + * startsWith("name", "Mr."); + * ``` + * + * @param expr 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value starts with a given prefix. + * + * ```typescript + * // Check if the 'fullName' field starts with the value of the 'firstName' field + * startsWith("fullName", Field.of("firstName")); + * ``` + * + * @param expr 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression starts with a given prefix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." + * startsWith(Field.of("fullName"), "Mr."); + * ``` + * + * @param expr 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression starts with a given prefix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields starts with "Mr." + * startsWith(Field.of("fullName"), Field.of("prefix")); + * ``` + * + * @param expr 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value ends with a given postfix. + * + * ```typescript + * // Check if the 'filename' field ends with ".txt" + * endsWith("filename", ".txt"); + * ``` + * + * @param expr 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value ends with a given postfix. + * + * ```typescript + * // Check if the 'url' field ends with the value of the 'extension' field + * endsWith("url", Field.of("extension")); + * ``` + * + * @param expr 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression ends with a given postfix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." + * endsWith(Field.of("fullName"), "Jr."); + * ``` + * + * @param expr 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression ends with a given postfix. + * + * ```typescript + * // Check if the result of concatenating 'firstName' and 'lastName' fields ends with "Jr." + * endsWith(Field.of("fullName"), Constant.of("Jr.")); + * ``` + * + * @param expr 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; + + /** + * @beta + * + * Creates an expression that converts a string field to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * toLowercase("name"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the lowercase string. + */ + export function toLowercase(expr: string): ToLowercase; + + /** + * @beta + * + * Creates an expression that converts a string expression to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * toLowercase(Field.of("name")); + * ``` + * + * @param expr The expression representing the string to convert to lowercase. + * @return A new {@code Expr} representing the lowercase string. + */ + export function toLowercase(expr: Expr): ToLowercase; + + /** + * @beta + * + * Creates an expression that converts a string field to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * toUppercase("title"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the uppercase string. + */ + export function toUppercase(expr: string): ToUppercase; + + /** + * @beta + * + * Creates an expression that converts a string expression to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * toUppercase(Field.of("title")); + * ``` + * + * @param expr The expression representing the string to convert to uppercase. + * @return A new {@code Expr} representing the uppercase string. + */ + export function toUppercase(expr: Expr): ToUppercase; + + /** + * @beta + * + * Creates an expression that removes leading and trailing whitespace from a string field. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * trim("userInput"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the trimmed string. + */ + export function trim(expr: string): Trim; + + /** + * @beta + * + * Creates an expression that removes leading and trailing whitespace from a string expression. + * + * ```typescript + * // Trim whitespace from the 'userInput' field + * trim(Field.of("userInput")); + * ``` + * + * @param expr The expression representing the string to trim. + * @return A new {@code Expr} representing the trimmed string. + */ + export function trim(expr: Expr): Trim; + + /** + * @beta + * + * Creates an expression that concatenates string functions, fields or constants together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * strConcat("firstName", " ", Field.of("lastName")); + * ``` + * + * @param first The field name containing the initial string value. + * @param elements The expressions (typically strings) to concatenate. + * @return A new {@code Expr} representing the concatenated string. + */ + export function strConcat( + first: string, + ...elements: (Expr | string)[] + ): StrConcat; + + /** + * @beta + * Creates an expression that concatenates string expressions together. + * + * ```typescript + * // Combine the 'firstName', " ", and 'lastName' fields into a single string + * strConcat(Field.of("firstName"), " ", Field.of("lastName")); + * ``` + * + * @param first The initial string expression to concatenate to. + * @param elements The expressions (typically strings) to concatenate. + * @return A new {@code Expr} representing the concatenated string. + */ + export function strConcat( + first: Expr, + ...elements: (Expr | string)[] + ): StrConcat; + + /** + * @beta + * + * Accesses a value from a map (object) field using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * mapGet("address", "city"); + * ``` + * + * @param mapField 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; + + /** + * @beta + * + * Accesses a value from a map (object) expression using the provided key. + * + * ```typescript + * // Get the 'city' value from the 'address' map field + * mapGet(Field.of("address"), "city"); + * ``` + * + * @param mapExpr 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; + + /** + * @beta + * + * Creates an aggregation that counts the total number of stage inputs. + * + * ```typescript + * // Count the total number of users + * countAll().as("totalUsers"); + * ``` + * + * @return A new {@code Accumulator} representing the 'countAll' aggregation. + */ + export function countAll(): Count; + + /** + * @beta + * + * Creates an aggregation that counts the number of stage inputs with valid evaluations of the + * provided expression. + * + * ```typescript + * // Count the number of items where the price is greater than 10 + * count(Field.of("price").gt(10)).as("expensiveItemCount"); + * ``` + * + * @param value The expression to count. + * @return A new {@code Accumulator} representing the 'count' aggregation. + */ + export function count(value: Expr): Count; + + /** + * Creates an aggregation that counts the number of stage inputs with valid evaluations of the + * provided field. + * + * ```typescript + * // Count the total number of products + * count("productId").as("totalProducts"); + * ``` + * + * @param value The name of the field to count. + * @return A new {@code Accumulator} representing the 'count' aggregation. + */ + export function count(value: string): Count; + + /** + * @beta + * + * Creates an aggregation that calculates the sum of values from an expression across multiple + * stage inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * sum(Field.of("orderAmount")).as("totalRevenue"); + * ``` + * + * @param value The expression to sum up. + * @return A new {@code Accumulator} representing the 'sum' aggregation. + */ + export function sum(value: Expr): Sum; + + /** + * @beta + * + * Creates an aggregation that calculates the sum of a field's values across multiple stage + * inputs. + * + * ```typescript + * // Calculate the total revenue from a set of orders + * 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. + */ + export function sum(value: string): Sum; + + /** + * @beta + * + * Creates an aggregation that calculates the average (mean) of values from an expression across + * multiple stage inputs. + * + * ```typescript + * // Calculate the average age of users + * avg(Field.of("age")).as("averageAge"); + * ``` + * + * @param value The expression representing the values to average. + * @return A new {@code Accumulator} representing the 'avg' aggregation. + */ + export function avg(value: Expr): Avg; + + /** + * @beta + * + * Creates an aggregation that calculates the average (mean) of a field's values across multiple + * stage inputs. + * + * ```typescript + * // Calculate the average age of users + * 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. + */ + export function avg(value: string): Avg; + + /** + * @beta + * + * Creates an aggregation that finds the minimum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the lowest price of all products + * min(Field.of("price")).as("lowestPrice"); + * ``` + * + * @param value The expression to find the minimum value of. + * @return A new {@code Accumulator} representing the 'min' aggregation. + */ + export function min(value: Expr): Min; + + /** + * @beta + * + * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the lowest price of all products + * min("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. + */ + export function min(value: string): Min; + + /** + * @beta + * + * Creates an aggregation that finds the maximum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * max(Field.of("score")).as("highestScore"); + * ``` + * + * @param value The expression to find the maximum value of. + * @return A new {@code Accumulator} representing the 'max' aggregation. + */ + export function max(value: Expr): Max; + + /** + * @beta + * + * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * max("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. + */ + export function max(value: string): Max; + + /** + * @beta + * + * Calculates the Cosine distance between a field's vector value and a double array. + * + * ```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. + * @return A new {@code Expr} representing the Cosine distance between the two vectors. + */ + export function cosineDistance( + expr: string, + other: VectorValue + ): CosineDistance; + + /** + * @beta + * + * Calculates the Cosine distance between a field's vector value and a vector expression. + * + * ```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]); + * ``` + * + * @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 cosine distance between the two vectors. + */ + export function cosineDistance(expr: Expr, other: number[]): CosineDistance; + + /** + * @beta + * + * Calculates the Cosine distance between a vector expression and a VectorValue. + * + * ```typescript + * // Calculate the cosine distance between the 'location' field and a target location + * cosineDistance(Field.of("location"), new VectorValue([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 cosine distance between the two vectors. + */ + export function cosineDistance( + expr: Expr, + other: VectorValue + ): CosineDistance; + + /** + * @beta + * + * Calculates the Cosine distance between two vector expressions. + * + * ```typescript + * // Calculate the cosine distance between the 'userVector' field and the 'itemVector' field + * cosineDistance(Field.of("userVector"), Field.of("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. + * @return A new {@code Expr} representing the cosine distance between the two vectors. + */ + export function cosineDistance(expr: Expr, other: Expr): CosineDistance; + + /** + * @beta + * + * Calculates the dot product between a field's vector value and a double array. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * dotProduct("features", [0.5, 0.8, 0.2]); + * ``` + * + * @param expr The name of the field containing the first vector. + * @param other The other vector (as an array of doubles) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. + */ + export function dotProduct(expr: string, other: number[]): DotProduct; + + /** + * @beta + * + * Calculates the dot product between a field's vector value and a VectorValue. + * + * ```typescript + * // Calculate the dot product distance between a feature vector and a target vector + * dotProduct("features", new VectorValue([0.5, 0.8, 0.2])); + * ``` + * + * @param expr The name of the field containing the first vector. + * @param other The other vector (as a VectorValue) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. + */ + export function dotProduct(expr: string, other: VectorValue): DotProduct; + + /** + * @beta + * + * Calculates the dot product between a field's vector value and a vector expression. + * + * ```typescript + * // Calculate the dot product distance between two document vectors: 'docVector1' and 'docVector2' + * dotProduct("docVector1", Field.of("docVector2")); + * ``` + * + * @param expr The name of the field containing the first vector. + * @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. + */ + export function dotProduct(expr: string, other: Expr): DotProduct; + + /** + * @beta + * + * Calculates the dot product between a vector expression and a double array. + * + * ```typescript + * // Calculate the dot product between a feature vector and a target vector + * dotProduct(Field.of("features"), [0.5, 0.8, 0.2]); + * ``` + * + * @param expr The first vector (represented as an Expr) to calculate with. + * @param other The other vector (as an array of doubles) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. + */ + export function dotProduct(expr: Expr, other: number[]): DotProduct; + + /** + * @beta + * + * Calculates the dot product between a vector expression and a VectorValue. + * + * ```typescript + * // Calculate the dot product between a feature vector and a target vector + * dotProduct(Field.of("features"), new VectorValue([0.5, 0.8, 0.2])); + * ``` + * + * @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. + */ + export function dotProduct(expr: Expr, other: VectorValue): DotProduct; + + /** + * @beta + * + * Calculates the dot product between two vector expressions. + * + * ```typescript + * // Calculate the dot product between two document vectors: 'docVector1' and 'docVector2' + * dotProduct(Field.of("docVector1"), Field.of("docVector2")); + * ``` + * + * @param expr The first vector (represented as an Expr) to calculate with. + * @param other The other vector (represented as an Expr) to calculate with. + * @return A new {@code Expr} representing the dot product between the two vectors. + */ + export function dotProduct(expr: Expr, other: Expr): DotProduct; + + /** + * @beta + * + * Calculates the Euclidean distance between a field's vector value and a double array. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * euclideanDistance("location", [37.7749, -122.4194]); + * ``` + * + * @param expr The name of the field containing the first vector. + * @param other The other vector (as an array of doubles) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ + export function euclideanDistance( + expr: string, + other: number[] + ): EuclideanDistance; + + /** + * @beta + * + * Calculates the Euclidean distance between a field's vector value and a VectorValue. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * euclideanDistance("location", new VectorValue([37.7749, -122.4194])); + * ``` + * + * @param expr The name of the field containing the first vector. + * @param other The other vector (as a VectorValue) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ + export function euclideanDistance( + expr: string, + other: VectorValue + ): EuclideanDistance; + + /** + * @beta + * + * Calculates the Euclidean distance between a field's vector value and a vector expression. + * + * ```typescript + * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' + * euclideanDistance("pointA", Field.of("pointB")); + * ``` + * + * @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. + */ + export function euclideanDistance( + expr: string, + other: Expr + ): EuclideanDistance; + + /** + * @beta + * + * 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(Field.of("location"), [37.7749, -122.4194]); + * ``` + * + * @param expr The first vector (represented as an Expr) to compare against. + * @param other The other vector (as an array of doubles) to compare against. + * @return A new {@code Expr} representing the Euclidean distance between the two vectors. + */ + export function euclideanDistance( + expr: Expr, + other: number[] + ): EuclideanDistance; + + /** + * @beta + * + * Calculates the Euclidean distance between a vector expression and a VectorValue. + * + * ```typescript + * // Calculate the Euclidean distance between the 'location' field and a target location + * euclideanDistance(Field.of("location"), new VectorValue([37.7749, -122.4194])); + * ``` + * + * @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. + */ + export function euclideanDistance( + expr: Expr, + other: VectorValue + ): EuclideanDistance; + + /** + * @beta + * + * Calculates the Euclidean distance between two vector expressions. + * + * ```typescript + * // Calculate the Euclidean distance between two vector fields: 'pointA' and 'pointB' + * euclideanDistance(Field.of("pointA"), Field.of("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. + */ + export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; + + /** + * @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[]): Function; + + /** + * @beta + * + * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * firestore.pipeline().collection("users") + * .sort(ascending(Field.of("name"))); + * ``` + * + * @param expr The expression to create an ascending ordering for. + * @return A new `Ordering` for ascending sorting. + */ + export function ascending(expr: Expr): Ordering; + + /** + * @beta + * + * 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(descending(Field.of("createdAt"))); + * ``` + * + * @param expr The expression to create a descending ordering for. + * @return A new `Ordering` for descending sorting. + */ + export function descending(expr: Expr): Ordering; + + /** + * @beta + * + * Represents an ordering criterion for sorting documents in a Firestore pipeline. + * + * You create `Ordering` instances using the `ascending` and `descending` helper functions. + */ + export class Ordering { + /** + * @param expr The expression to order by. + * @param direction The direction to order by. + */ + constructor(expr: Expr, direction: 'ascending' | 'descending'); + } + + /** + * @beta + */ + export interface Stage { + name: string; + } + + /** + * @beta + */ + export class AddFields implements Stage { + name: string; + } + + /** + * @beta + */ + export class Aggregate implements Stage { + name: string; + } + + /** + * @beta + */ + export class Distinct implements Stage { + name: string; + } + + /** + * @beta + */ + export class CollectionSource implements Stage { + name: string; + } + + /** + * @beta + */ + export class CollectionGroupSource implements Stage { + name: string; + } + + /** + * @beta + */ + export class DatabaseSource implements Stage { + name: string; + } + + /** + * @beta + */ + export class DocumentsSource implements Stage { + name: string; + + static of(refs: DocumentReference[]): DocumentsSource; + } + + /** + * @beta + */ + export class Where implements Stage { + name: string; + } + + /** + * @beta + */ + export interface FindNearestOptions { + limit: number; + distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + distanceField?: string; + } + + /** + * @beta + */ + export class FindNearest implements Stage { + name: string; + } + + /** + * @beta + */ + export class Limit implements Stage { + name: string; + } + + /** + * @beta + */ + export class Offset implements Stage { + name: string; + } + + /** + * @beta + */ + export class Select implements Stage { + name: string; + } + + /** + * @beta + */ + export class Sort implements Stage { + name: string; + } + + /** + * @beta + */ + export class GenericStage implements Stage { + name: string; + } + + /** + * Represents the source of a Firestore {@link Pipeline}. + * @beta + */ + export class PipelineSource { + /** + * Specifies the source as a collection. + * + * @param collectionPath The path to the collection. + * @return A new Pipeline object with the collection as the source. + */ + collection(collectionPath: string): Pipeline; + + /** + * Specifies the source as a collection group. + * + * @param collectionId The ID of the collection group. + * @return A new Pipeline object with the collection group as the source. + */ + collectionGroup(collectionId: string): Pipeline; + + /** + * Specifies the source as a database. + * + * @return A new Pipeline object with the database as the source. + */ + database(): Pipeline; + + /** + * Specifies the source as a set of documents. + * + * @param docs The document references. + * @return A new Pipeline object with the documents as the source. + */ + documents(docs: DocumentReference[]): Pipeline; + } + + /** + * @beta + * + * The Pipeline class provides a flexible and expressive framework for building complex data + * transformation and query pipelines for Firestore. + * + * A pipeline takes data sources, such as Firestore collections or collection groups, and applies + * a series of stages that are chained together. Each stage takes the output from the previous stage + * (or the data source) and produces an output for the next stage (or as the final output of the + * pipeline). + * + * Expressions can be used within each stage to filter and transform data through the stage. + * + * NOTE: The chained stages do not prescribe exactly how Firestore will execute the pipeline. + * Instead, Firestore only guarantees that the result is the same as if the chained stages were + * executed in order. + * + * Usage Examples: + * + * ```typescript + * const db: Firestore; // Assumes a valid firestore instance. + * + * // Example 1: Select specific fields and rename 'rating' to 'bookRating' + * const results1 = await db.pipeline() + * .collection("books") + * .select("title", "author", Field.of("rating").as("bookRating")) + * .execute(); + * + * // Example 2: Filter documents where 'genre' is "Science Fiction" and 'published' is after 1950 + * const results2 = await db.pipeline() + * .collection("books") + * .where(and(Field.of("genre").eq("Science Fiction"), Field.of("published").gt(1950))) + * .execute(); + * + * // Example 3: Calculate the average rating of books published after 1980 + * const results3 = await db.pipeline() + * .collection("books") + * .where(Field.of("published").gt(1980)) + * .aggregate(avg(Field.of("rating")).as("averageRating")) + * .execute(); + * ``` + */ + export class Pipeline { + /** + * Adds new fields to outputs from previous stages. + * + * This stage allows you to compute values on-the-fly based on existing data from previous + * stages or constants. You can use this to create new fields or overwrite existing ones (if there + * is name overlaps). + * + * The added fields are defined using {@link Selectable}s, which can be: + * + * - {@link Field}: References an existing document field. + * - {@link Function}: Performs a calculation using functions like `add`, `multiply` with + * assigned aliases using {@link Expr#as}. + * + * Example: + * + * ```typescript + * firestore.pipeline().collection("books") + * .addFields( + * Field.of("rating").as("bookRating"), // Rename 'rating' to 'bookRating' + * add(5, Field.of("quantity")).as("totalCost") // Calculate 'totalCost' + * ); + * ``` + * + * @param fields The 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; + + /** + * Selects or creates a set of fields from the outputs of previous stages. + * + *

    The selected fields are defined using {@link Selectable} expressions, which can be: + * + *

      + *
    • {@code string}: Name of an existing field
    • + *
    • {@link Field}: References an existing field.
    • + *
    • {@link Function}: Represents the result of a function with an assigned alias name using + * {@link Expr#as}
    • + *
    + * + *

    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 + * desired. + * + *

    Example: + * + * ```typescript + * firestore.pipeline().collection("books") + * .select( + * "firstName", + * Field.of("lastName"), + * Field.of("address").toUppercase().as("upperAddress"), + * ); + * ``` + * + * @param selections The 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(...fields: (Selectable | string)[]): Pipeline; + + /** + * Filters the documents from previous stages to only include those matching the specified {@link + * FilterCondition}. + * + *

    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: + * + *

      + *
    • field comparators: {@link Function#eq}, {@link Function#lt} (less than), {@link + * Function#gt} (greater than), etc.
    • + *
    • logical operators: {@link Function#and}, {@link Function#or}, {@link Function#not}, etc.
    • + *
    • advanced functions: {@link Function#regexMatch}, {@link + * Function#arrayContains}, etc.
    • + *
    + * + *

    Example: + * + * ```typescript + * 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") + * ) + * ); + * ``` + * + * @param condition The {@link FilterCondition} to apply. + * @return A new Pipeline object with this stage appended to the stage list. + */ + where(condition: FilterCondition & Expr): Pipeline; + + /** + * Skips the first `offset` number of documents from the results of previous stages. + * + *

    This stage is useful for implementing pagination in your pipelines, allowing you to retrieve + * results in chunks. It is typically used in conjunction with {@link #limit} to control the + * size of each page. + * + *

    Example: + * + * ```typescript + * // Retrieve the second page of 20 results + * firestore.pipeline().collection("books") + * .sort(Field.of("published").descending()) + * .offset(20) // Skip the first 20 results + * .limit(20); // Take the next 20 results + * ``` + * + * @param offset The number of documents to skip. + * @return A new Pipeline object with this stage appended to the stage list. + */ + offset(offset: number): Pipeline; + + /** + * Limits the maximum number of documents returned by previous stages to `limit`. + * + *

    This stage is particularly useful when you want to retrieve a controlled subset of data from + * a potentially large result set. It's often used for: + * + *

      + *
    • **Pagination:** In combination with {@link #offset} to retrieve specific pages of + * results.
    • + *
    • **Limiting Data Retrieval:** To prevent excessive data transfer and improve performance, + * especially when dealing with large collections.
    • + *
    + * + *

    Example: + * + * ```typescript + * // Limit the results to the top 10 highest-rated books + * firestore.pipeline().collection("books") + * .sort(Field.of("rating").descending()) + * .limit(10); + * ``` + * + * @param limit The maximum number of documents to return. + * @return A new Pipeline object with this stage appended to the stage list. + */ + limit(limit: number): Pipeline; + + /** + * Returns a set of distinct {@link Expr} values from the inputs to this stage. + * + *

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

    The parameters to this stage are defined using {@link Selectable} expressions or {@code string}s: + * + *

      + *
    • {@code string}: Name of an existing field
    • + *
    • {@link Field}: References an existing document field.
    • + *
    • {@link Function}: Represents the result of a function with an assigned alias name using + * {@link Expr#as}
    • + *
    + * + *

    Example: + * + * ```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") + * .select("authorName"); + * ``` + * + * @param selectables The {@link Selectable} expressions to consider when determining distinct + * value combinations or {@code string}s representing field names. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + distinct(...groups: (string | Selectable)[]): Pipeline; + + /** + * 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. + * + *

    Example: + * + * ```typescript + * // Calculate the average rating and the total number of books + * firestore.pipeline().collection("books") + * .aggregate( + * Field.of("rating").avg().as("averageRating"), + * countAll().as("totalBooks") + * ); + * ``` + * + * @param accumulators The {@link AccumulatorTarget} expressions, each wrapping an {@link Accumulator} + * 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; + /** + * Performs optionally grouped aggregation operations on the documents from previous stages. + * + *

    This stage allows you to calculate aggregate values over a set of documents, optionally + * grouped by one or more fields or functions. You can specify: + * + *

      + *
    • **Grouping Fields or Functions:** One or more fields or functions to group the documents + * by. For each distinct combination of values in these fields, a separate group is created. + * 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 + * calculates a value (e.g., sum, average, count) based on the documents within its group.
    • + *
    + * + *

    Example: + * + * ```typescript + * // Calculate the average rating for each genre. + * firestore.pipeline().collection("books") + * .aggregate({ + * accumulators: [avg(Field.of("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. + */ + aggregate(options: { + accumulators: AccumulatorTarget[]; + groups?: (string | Selectable)[]; + }): Pipeline; + + findNearest( + field: string, + vector: number[], + options: FindNearestOptions + ): Pipeline; + findNearest( + field: Field, + vector: FirebaseFirestore.VectorValue, + options: FindNearestOptions + ): Pipeline; + findNearest( + field: string | Field, + vector: FirebaseFirestore.VectorValue | number[], + options: FindNearestOptions + ): Pipeline; + + /** + * Sorts the documents from previous stages based on one or more {@link Ordering} criteria. + * + *

    This stage allows you to order the results of your pipeline. You can specify multiple {@link + * Ordering} instances to sort by multiple fields in ascending or descending order. If documents + * have the same value for a field used for sorting, the next specified ordering will be used. If + * all orderings result in equal comparison, the documents are considered equal and the order is + * unspecified. + * + *

    Example: + * + * ```typescript + * // Sort books by rating in descending order, and then by title in ascending order for books + * // 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 + * ); + * ``` + * + * @param orders One or more {@link Ordering} instances specifying the sorting criteria. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + sort(...orderings: Ordering[]): Pipeline; + sort(options: {orderings: Ordering[]}): Pipeline; + + /** + * Adds a generic stage to the pipeline. + * + *

    This method provides a flexible way to extend the pipeline's functionality by adding custom + * stages. Each generic stage is defined by a unique `name` and a set of `params` that control its + * behavior. + * + *

    Example (Assuming there is no "where" stage available in SDK): + * + * ```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 + * .select("title", "author"); + * ``` + * + * @param name The unique name of the generic stage to add. + * @param params A list of parameters to configure the generic stage's behavior. + * @return A new {@code Pipeline} object with this stage appended to the stage list. + */ + genericStage(name: string, params: any[]): Pipeline; + withConverter(converter: null): Pipeline; + withConverter( + converter: FirestorePipelineConverter + ): Pipeline; + /** + * 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>>; + + /** + * Executes this pipeline and streams the results as {@link PipelineResult}s. + * + * @returns {Stream.} A stream of + * PipelineResult. + * + * @example + * ```typescript + * firestore.pipeline().collection("books") + * .where(gt(Field.of("rating"), 4.5)) + * .select("title", "author", "rating") + * .stream() + * .on('data', (pipelineResult) => {}) + * .on('end', () => {}); + * ``` + */ + stream(): NodeJS.ReadableStream; + } + + /** + * @beta + * + * A PipelineResult contains data read from a Firestore Pipeline. The data can be extracted with the + * {@link #data()} or {@link #get(String)} methods. + * + *

    If the PipelineResult represents a non-document result, `ref` will return a undefined + * value. + */ + export class PipelineResult { + readonly executionTime: Timestamp; + readonly createTime: Timestamp | undefined; + readonly updateTime: Timestamp | undefined; + + /** + * The reference of the document, if it is a document; otherwise `undefined`. + */ + get ref(): DocumentReference | undefined; + + /** + * The ID of the document for which this PipelineResult contains data, if it is a document; otherwise `undefined`. + * + * @type {string} + * @readonly + * + */ + get id(): string | undefined; + + /** + * Retrieves all fields in the result as an object. Returns 'undefined' if + * the document doesn't exist. + * + * @returns {T|undefined} An object containing all fields in the document or + * 'undefined' if the document doesn't exist. + * + * @example + * ``` + * let p = firestore.pipeline().collection('col'); + * + * p.execute().then(results => { + * let data = results[0].data(); + * console.log(`Retrieved data: ${JSON.stringify(data)}`); + * }); + * ``` + */ + data(): AppModelType | undefined; + + /** + * Retrieves the field specified by `field`. + * + * @param {string|FieldPath} 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. + * + * @example + * ``` + * let p = firestore.pipeline().collection('col'); + * + * p.execute().then(results => { + * let field = results[0].get('a.b'); + * console.log(`Retrieved field value: ${field}`); + * }); + * ``` + */ + // 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(field: string | FieldPath): any; + + /** + * Returns true if the document's data and path in this `PipelineResult` is + * equal to the provided value. + * + * @param {*} other The value to compare against. + * @return {boolean} true if this `PipelineResult` is equal to the provided + * value. + */ + isEqual(other: PipelineResult): boolean; + } } declare module '@google-cloud/firestore' { From f0fdad5e58e10717dcc55a1a2cf7f9570373f489 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 5 Sep 2024 14:41:19 -0400 Subject: [PATCH 13/31] FindNearest update --- dev/src/pipeline.ts | 28 ++++------------------------ dev/src/reference/vector-query.ts | 13 +++++++++---- dev/src/stage.ts | 27 +++++++++++++-------------- types/firestore.d.ts | 20 ++++---------------- 4 files changed, 30 insertions(+), 58 deletions(-) diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index c39680714..71c2a7b45 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {DocumentData} from '@google-cloud/firestore'; import * as firestore from '@google-cloud/firestore'; import * as deepEqual from 'fast-deep-equal'; import {google} from '../protos/firestore_v1_proto_api'; @@ -445,29 +444,10 @@ export class Pipeline { return new Pipeline(this.db, copy, this.converter); } - findNearest( - field: string, - vector: number[], - options: FindNearestOptions - ): Pipeline; - findNearest( - field: Field, - vector: FirebaseFirestore.VectorValue, - options: FindNearestOptions - ): Pipeline; - findNearest( - field: string | Field, - vector: FirebaseFirestore.VectorValue | number[], - options: FindNearestOptions - ): Pipeline; - findNearest( - field: string | Field, - vector: number[] | FirebaseFirestore.VectorValue, - options: FindNearestOptions - ): Pipeline { + findNearest(options: FindNearestOptions): Pipeline; + findNearest(options: FindNearestOptions): Pipeline { const copy = this.stages.map(s => s); - const fieldExpr = typeof field === 'string' ? Field.of(field) : field; - copy.push(new FindNearest(fieldExpr, vector, options)); + copy.push(new FindNearest(options)); return new Pipeline(this.db, copy); } @@ -822,7 +802,7 @@ export class PipelineResult // if a converter has been provided. if (!!this.converter && this.converter !== defaultPipelineConverter()) { return this.converter.fromFirestore( - new PipelineResult( + new PipelineResult( this._serializer, this.ref, this._fieldsProto, diff --git a/dev/src/reference/vector-query.ts b/dev/src/reference/vector-query.ts index 39bcc7c82..c1d783ea8 100644 --- a/dev/src/reference/vector-query.ts +++ b/dev/src/reference/vector-query.ts @@ -132,14 +132,19 @@ export class VectorQuery< } toPipeline(): Pipeline { - const options = { + const options: FindNearestOptions = { + field: Field.of(this.vectorField), + vectorValue: this.queryVector, limit: this.options.limit, - distanceMeasure: this.options.distanceMeasure.toLowerCase(), - } as FindNearestOptions; + distanceMeasure: this.options.distanceMeasure.toLowerCase() as + | 'cosine' + | 'euclidean' + | 'dot_product', + }; return this.query .pipeline() .where(Field.of(this.vectorField).exists()) - .findNearest(Field.of(this.vectorField), this.queryVector, options); + .findNearest(options); } _getResponse( diff --git a/dev/src/stage.ts b/dev/src/stage.ts index 31aeb19cd..f7be929ca 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import * as firestore from '@google-cloud/firestore'; import * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; @@ -179,8 +180,10 @@ export class Where implements Stage { * @beta */ export interface FindNearestOptions { - limit: number; + field: Field; + vectorValue: firestore.VectorValue | number[]; distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + limit?: number; distanceField?: string; } @@ -190,18 +193,14 @@ export interface FindNearestOptions { export class FindNearest implements Stage { name = 'find_nearest'; - constructor( - private property: Field, - private vector: FirebaseFirestore.VectorValue | number[], - private options: FindNearestOptions - ) {} + constructor(private _options: FindNearestOptions) {} _toProto(serializer: Serializer): api.Pipeline.IStage { const options: {[k: string]: api.IValue} = { - limit: serializer.encodeValue(this.options.limit)!, + limit: serializer.encodeValue(this._options.limit)!, }; - if (this.options.distanceField) { - options.distance_field = Field.of(this.options.distanceField)._toProto( + if (this._options.distanceField) { + options.distance_field = Field.of(this._options.distanceField)._toProto( serializer ); } @@ -209,11 +208,11 @@ export class FindNearest implements Stage { return { name: this.name, args: [ - this.property._toProto(serializer), - this.vector instanceof VectorValue - ? serializer.encodeValue(this.vector)! - : serializer.encodeVector(this.vector as number[]), - serializer.encodeValue(this.options.distanceMeasure)!, + this._options.field._toProto(serializer), + this._options.vectorValue instanceof VectorValue + ? serializer.encodeValue(this._options.vectorValue)! + : serializer.encodeVector(this._options.vectorValue as number[]), + serializer.encodeValue(this._options.distanceMeasure)!, ], options, }; diff --git a/types/firestore.d.ts b/types/firestore.d.ts index dc35a6272..db4865253 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -7100,8 +7100,10 @@ declare namespace FirebaseFirestore { * @beta */ export interface FindNearestOptions { - limit: number; + field: Field; + vectorValue: VectorValue | number[]; distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + limit?: number; distanceField?: string; } @@ -7460,21 +7462,7 @@ declare namespace FirebaseFirestore { groups?: (string | Selectable)[]; }): Pipeline; - findNearest( - field: string, - vector: number[], - options: FindNearestOptions - ): Pipeline; - findNearest( - field: Field, - vector: FirebaseFirestore.VectorValue, - options: FindNearestOptions - ): Pipeline; - findNearest( - field: string | Field, - vector: FirebaseFirestore.VectorValue | number[], - options: FindNearestOptions - ): Pipeline; + findNearest(options: FindNearestOptions): Pipeline; /** * Sorts the documents from previous stages based on one or more {@link Ordering} criteria. From f0ba8daf05123d837bba19638b6617334852d216 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 10 Sep 2024 16:04:21 -0400 Subject: [PATCH 14/31] pipeline and stage dts fix done. expr to go. --- dev/src/expression.ts | 64 ++++++++++++++++++++-------------------- dev/src/pipeline-util.ts | 3 +- dev/src/pipeline.ts | 41 +++++++++++++------------ dev/src/stage.ts | 8 ++--- types/firestore.d.ts | 2 ++ 5 files changed, 60 insertions(+), 58 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index b9f3b57f5..c894fcbeb 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -103,7 +103,7 @@ export type ExprType = * The `Expr` class provides a fluent API for building expressions. You can chain together * method calls to create complex expressions. */ -export abstract class Expr { +export abstract class Expr implements firestore.Expr{ /** * Creates an expression that adds this expression to another expression. * @@ -115,7 +115,7 @@ export abstract class Expr { * @param other The expression to add to this expression. * @return A new `Expr` representing the addition operation. */ - add(other: Expr): Add; + add(other: firestore.Expr): Add; /** * Creates an expression that adds this expression to a constant value. @@ -147,7 +147,7 @@ export abstract class Expr { * @param other The expression to subtract from this expression. * @return A new `Expr` representing the subtraction operation. */ - subtract(other: Expr): Subtract; + subtract(other: firestore.Expr): Subtract; /** * Creates an expression that subtracts a constant value from this expression. @@ -179,7 +179,7 @@ export abstract class Expr { * @param other The expression to multiply by. * @return A new `Expr` representing the multiplication operation. */ - multiply(other: Expr): Multiply; + multiply(other: firestore.Expr): Multiply; /** * Creates an expression that multiplies this expression by a constant value. @@ -211,7 +211,7 @@ export abstract class Expr { * @param other The expression to divide by. * @return A new `Expr` representing the division operation. */ - divide(other: Expr): Divide; + divide(other: firestore.Expr): Divide; /** * Creates an expression that divides this expression by a constant value. @@ -243,7 +243,7 @@ export abstract class Expr { * @param other The expression to compare for equality. * @return A new `Expr` representing the equality comparison. */ - eq(other: Expr): Eq; + eq(other: firestore.Expr): Eq; /** * Creates an expression that checks if this expression is equal to a constant value. @@ -275,7 +275,7 @@ export abstract class Expr { * @param other The expression to compare for inequality. * @return A new `Expr` representing the inequality comparison. */ - neq(other: Expr): Neq; + neq(other: firestore.Expr): Neq; /** * Creates an expression that checks if this expression is not equal to a constant value. @@ -307,7 +307,7 @@ export abstract class Expr { * @param other The expression to compare for less than. * @return A new `Expr` representing the less than comparison. */ - lt(other: Expr): Lt; + lt(other: firestore.Expr): Lt; /** * Creates an expression that checks if this expression is less than a constant value. @@ -340,7 +340,7 @@ export abstract class Expr { * @param other 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(other: firestore.Expr): Lte; /** * Creates an expression that checks if this expression is less than or equal to a constant value. @@ -372,7 +372,7 @@ export abstract class Expr { * @param other The expression to compare for greater than. * @return A new `Expr` representing the greater than comparison. */ - gt(other: Expr): Gt; + gt(other: firestore.Expr): Gt; /** * Creates an expression that checks if this expression is greater than a constant value. @@ -405,7 +405,7 @@ export abstract class Expr { * @param other 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(other: firestore.Expr): Gte; /** * Creates an expression that checks if this expression is greater than or equal to a constant @@ -438,7 +438,7 @@ export abstract class Expr { * @param arrays The array expressions to concatenate. * @return A new `Expr` representing the concatenated array. */ - arrayConcat(arrays: Expr[]): ArrayConcat; + arrayConcat(arrays: firestore.Expr[]): ArrayConcat; /** * Creates an expression that concatenates an array expression with one or more other arrays. @@ -470,7 +470,7 @@ export abstract class Expr { * @param element The element to search for in the array. * @return A new `Expr` representing the 'array_contains' comparison. */ - arrayContains(element: Expr): ArrayContains; + arrayContains(element: firestore.Expr): ArrayContains; /** * Creates an expression that checks if an array contains a specific value. @@ -502,7 +502,7 @@ export abstract class Expr { * @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: firestore.Expr[]): ArrayContainsAll; /** * Creates an expression that checks if an array contains all the specified elements. @@ -534,7 +534,7 @@ export abstract class Expr { * @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: firestore.Expr[]): ArrayContainsAny; /** * Creates an expression that checks if an array contains any of the specified elements. @@ -616,7 +616,7 @@ export abstract class Expr { * @param others The values or expressions to check against. * @return A new `Expr` representing the 'IN' comparison. */ - in(...others: Expr[]): In; + in(...others: firestore.Expr[]): In; /** * Creates an expression that checks if this expression is equal to any of the provided values or @@ -704,8 +704,8 @@ export abstract class Expr { * @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 { + like(pattern: firestore.Expr): Like; + like(stringOrExpr: string | firestore.Expr): Like { if (stringOrExpr instanceof Expr) { return new Like(this, stringOrExpr); } @@ -738,8 +738,8 @@ export abstract class Expr { * @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 { + regexContains(pattern: firestore.Expr): RegexContains; + regexContains(stringOrExpr: string | firestore.Expr): RegexContains { if (stringOrExpr instanceof Expr) { return new RegexContains(this, stringOrExpr); } @@ -770,8 +770,8 @@ export abstract class Expr { * @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 { + regexMatch(pattern: firestore.Expr): RegexMatch; + regexMatch(stringOrExpr: string | firestore.Expr): RegexMatch { if (stringOrExpr instanceof Expr) { return new RegexMatch(this, stringOrExpr); } @@ -803,8 +803,8 @@ export abstract class Expr { * @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 { + startsWith(prefix: firestore.Expr): StartsWith; + startsWith(stringOrExpr: string | firestore.Expr): StartsWith { if (stringOrExpr instanceof Expr) { return new StartsWith(this, stringOrExpr); } @@ -836,8 +836,8 @@ export abstract class Expr { * @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 { + endsWith(suffix: firestore.Expr): EndsWith; + endsWith(stringOrExpr: string | firestore.Expr): EndsWith { if (stringOrExpr instanceof Expr) { return new EndsWith(this, stringOrExpr); } @@ -1000,7 +1000,7 @@ export abstract class Expr { * @param other 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(other: firestore.Expr): CosineDistance; /** * Calculates the Cosine distance between two vectors. * @@ -1025,7 +1025,7 @@ export abstract class Expr { * @return A new `Expr` representing the Cosine distance between the two vectors. */ cosineDistance(other: number[]): CosineDistance; - cosineDistance(other: Expr | VectorValue | number[]): CosineDistance { + cosineDistance(other: firestore.Expr | firestore.VectorValue | number[]): CosineDistance { if (other instanceof Expr) { return new CosineDistance(this, other); } else { @@ -1044,7 +1044,7 @@ export abstract class Expr { * @param other The other vector (as an array of numbers) to calculate with. * @return A new `Expr` representing the dot product between the two vectors. */ - dotProduct(other: Expr): DotProduct; + dotProduct(other: firestore.Expr): DotProduct; /** * Calculates the dot product between two vectors. @@ -1071,7 +1071,7 @@ export abstract class Expr { * @return A new `Expr` representing the dot product between the two vectors. */ dotProduct(other: number[]): DotProduct; - dotProduct(other: Expr | VectorValue | number[]): DotProduct { + dotProduct(other: firestore.Expr | firestore.VectorValue | number[]): DotProduct { if (other instanceof Expr) { return new DotProduct(this, other); } else { @@ -1090,7 +1090,7 @@ export abstract class Expr { * @param other The other vector (as an array of numbers) to calculate with. * @return A new `Expr` representing the Euclidean distance between the two vectors. */ - euclideanDistance(other: Expr): EuclideanDistance; + euclideanDistance(other: firestore.Expr): EuclideanDistance; /** * Calculates the Euclidean distance between two vectors. @@ -1117,7 +1117,7 @@ export abstract class Expr { * @return A new `Expr` representing the Euclidean distance between the two vectors. */ euclideanDistance(other: number[]): EuclideanDistance; - euclideanDistance(other: Expr | VectorValue | number[]): EuclideanDistance { + euclideanDistance(other: firestore.Expr | firestore.VectorValue | number[]): EuclideanDistance { if (other instanceof Expr) { return new EuclideanDistance(this, other); } else { diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index 866d90cc9..0da348da2 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -27,7 +27,7 @@ import { isNan, Field, not, - Constant, + Constant, AccumulatorTarget, ExprWithAlias, Accumulator, } from './expression'; import Firestore, {DocumentReference, Timestamp} from './index'; import {logger} from './logger'; @@ -47,6 +47,7 @@ import { requestTag, wrapError, } from './util'; +import {invalidArgumentMessage} from "./validate"; import api = protos.google.firestore.v1; /** diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 71c2a7b45..1239458c0 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -50,19 +50,18 @@ import { Stage, Distinct, } from './stage'; -import {ApiMapValue, defaultConverter, defaultPipelineConverter} from './types'; +import {ApiMapValue, defaultPipelineConverter} from './types'; import * as protos from '../protos/firestore_v1_proto_api'; import api = protos.google.firestore.v1; import IStructuredPipeline = google.firestore.v1.IStructuredPipeline; import IStage = google.firestore.v1.Pipeline.IStage; -import {QueryCursor} from './reference/types'; import {isOptionalEqual} from './util'; /** * Represents the source of a Firestore {@link Pipeline}. * @beta */ -export class PipelineSource { +export class PipelineSource implements firestore.PipelineSource{ constructor(private db: Firestore) {} collection(collectionPath: string): Pipeline { @@ -124,7 +123,7 @@ export class PipelineSource { * .execute(); * ``` */ -export class Pipeline { +export class Pipeline implements firestore.Pipeline { constructor( private db: Firestore, private stages: Stage[], @@ -157,7 +156,7 @@ export class Pipeline { * @param fields The 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 { + addFields(...fields: firestore.Selectable[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new AddFields(this.selectablesToMap(fields))); return new Pipeline(this.db, copy, this.converter); @@ -194,9 +193,9 @@ export class Pipeline { * Selectable} expressions or {@code string} values representing field names. * @return A new Pipeline object with this stage appended to the stage list. */ - select(...fields: (Selectable | string)[]): Pipeline { + select(...selections: (firestore.Selectable | string)[]): Pipeline { const copy = this.stages.map(s => s); - copy.push(new Select(this.selectablesToMap(fields))); + copy.push(new Select(this.selectablesToMap(selections))); return new Pipeline(this.db, copy, this.converter); } @@ -253,7 +252,7 @@ export class Pipeline { * @param condition The {@link FilterCondition} to apply. * @return A new Pipeline object with this stage appended to the stage list. */ - where(condition: FilterCondition & Expr): Pipeline { + where(condition: FilterCondition & firestore.Expr): Pipeline { const copy = this.stages.map(s => s); copy.push(new Where(condition)); return new Pipeline(this.db, copy, this.converter); @@ -344,7 +343,7 @@ export class Pipeline { * value combinations or {@code string}s representing field names. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ - distinct(...groups: (string | Selectable)[]): Pipeline { + distinct(...groups: (string | firestore.Selectable)[]): Pipeline { const copy = this.stages.map(s => s); copy.push(new Distinct(this.selectablesToMap(groups || []))); return new Pipeline(this.db, copy, this.converter); @@ -372,7 +371,7 @@ export class Pipeline { * 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(...accumulators: firestore.AccumulatorTarget[]): Pipeline; /** * Performs optionally grouped aggregation operations on the documents from previous stages. * @@ -406,23 +405,23 @@ export class Pipeline { * @return A new {@code Pipeline} object with this stage appended to the stage list. */ aggregate(options: { - accumulators: AccumulatorTarget[]; + accumulators: firestore.AccumulatorTarget[]; groups?: (string | Selectable)[]; }): Pipeline; aggregate( optionsOrTarget: - | AccumulatorTarget - | {accumulators: AccumulatorTarget[]; groups?: (string | Selectable)[]}, - ...rest: AccumulatorTarget[] + | firestore.AccumulatorTarget + | {accumulators: firestore.AccumulatorTarget[]; groups?: (string | firestore.Selectable)[]}, + ...rest: firestore.AccumulatorTarget[] ): Pipeline { const copy = this.stages.map(s => s); if ('accumulators' in optionsOrTarget) { copy.push( new Aggregate( new Map( - optionsOrTarget.accumulators.map((target: AccumulatorTarget) => [ - target.alias, - target.expr, + optionsOrTarget.accumulators.map((target: firestore.AccumulatorTarget) => [ + (target as unknown as AccumulatorTarget).alias, + (target as unknown as AccumulatorTarget).expr, ]) ), this.selectablesToMap(optionsOrTarget.groups || []) @@ -433,8 +432,8 @@ export class Pipeline { new Aggregate( new Map( [optionsOrTarget, ...rest].map(target => [ - target.alias, - target.expr, + (target as unknown as AccumulatorTarget).alias, + (target as unknown as AccumulatorTarget).expr, ]) ), new Map() @@ -444,8 +443,8 @@ export class Pipeline { return new Pipeline(this.db, copy, this.converter); } - findNearest(options: FindNearestOptions): Pipeline; - findNearest(options: FindNearestOptions): Pipeline { + findNearest(options: firestore.FindNearestOptions): Pipeline; + findNearest(options: firestore.FindNearestOptions): Pipeline { const copy = this.stages.map(s => s); copy.push(new FindNearest(options)); return new Pipeline(this.db, copy); diff --git a/dev/src/stage.ts b/dev/src/stage.ts index f7be929ca..c498e20ae 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -166,12 +166,12 @@ export class DocumentsSource implements Stage { export class Where implements Stage { name = 'where'; - constructor(private condition: FilterCondition & Expr) {} + constructor(private condition: firestore.FilterCondition & firestore.Expr) {} _toProto(serializer: Serializer): api.Pipeline.IStage { return { name: this.name, - args: [this.condition._toProto(serializer)], + args: [(this.condition as unknown as Expr)._toProto(serializer)], }; } } @@ -180,7 +180,7 @@ export class Where implements Stage { * @beta */ export interface FindNearestOptions { - field: Field; + field: firestore.Field; vectorValue: firestore.VectorValue | number[]; distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; limit?: number; @@ -208,7 +208,7 @@ export class FindNearest implements Stage { return { name: this.name, args: [ - this._options.field._toProto(serializer), + (this._options.field as unknown as Field)._toProto(serializer), this._options.vectorValue instanceof VectorValue ? serializer.encodeValue(this._options.vectorValue)! : serializer.encodeVector(this._options.vectorValue as number[]), diff --git a/types/firestore.d.ts b/types/firestore.d.ts index db4865253..f44c9056b 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -549,6 +549,8 @@ declare namespace FirebaseFirestore { */ collectionGroup(collectionId: string): CollectionGroup; + pipeline(): PipelineSource; + /** * Retrieves multiple documents from Firestore. * From 38b39814340419c3f2e943347707d5369d6b896e Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 11 Sep 2024 16:50:49 -0400 Subject: [PATCH 15/31] fixing expression.ts type decl --- dev/src/expression.ts | 59 ++++++++++++++++++++------------ dev/src/index.ts | 68 +++++++++++++++++++++++++++++++++++++ dev/src/pipeline-util.ts | 7 ++-- dev/src/pipeline.ts | 33 ++++++++++++------ dev/system-test/pipeline.ts | 2 +- 5 files changed, 134 insertions(+), 35 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index c894fcbeb..0a7117ae8 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -103,7 +103,7 @@ export type ExprType = * 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 firestore.Expr{ +export abstract class Expr implements firestore.Expr { /** * Creates an expression that adds this expression to another expression. * @@ -570,8 +570,8 @@ export abstract class Expr implements firestore.Expr{ * @param filter The {@link FilterCondition} to apply to the array elements. * @return A new `Expr` representing the filtered array. */ - arrayFilter(filter: FilterExpr): ArrayFilter { - return new ArrayFilter(this, filter); + arrayFilter(filter: firestore.FilterExpr): ArrayFilter { + return new ArrayFilter(this, filter as unknown as FilterExpr); } /** @@ -709,7 +709,7 @@ export abstract class Expr implements firestore.Expr{ if (stringOrExpr instanceof Expr) { return new Like(this, stringOrExpr); } - return new Like(this, Constant.of(stringOrExpr)); + return new Like(this, Constant.of(stringOrExpr as string)); } /** @@ -743,7 +743,7 @@ export abstract class Expr implements firestore.Expr{ if (stringOrExpr instanceof Expr) { return new RegexContains(this, stringOrExpr); } - return new RegexContains(this, Constant.of(stringOrExpr)); + return new RegexContains(this, Constant.of(stringOrExpr as string)); } /** @@ -775,7 +775,7 @@ export abstract class Expr implements firestore.Expr{ if (stringOrExpr instanceof Expr) { return new RegexMatch(this, stringOrExpr); } - return new RegexMatch(this, Constant.of(stringOrExpr)); + return new RegexMatch(this, Constant.of(stringOrExpr as string)); } /** @@ -808,7 +808,7 @@ export abstract class Expr implements firestore.Expr{ if (stringOrExpr instanceof Expr) { return new StartsWith(this, stringOrExpr); } - return new StartsWith(this, Constant.of(stringOrExpr)); + return new StartsWith(this, Constant.of(stringOrExpr as string)); } /** @@ -841,7 +841,7 @@ export abstract class Expr implements firestore.Expr{ if (stringOrExpr instanceof Expr) { return new EndsWith(this, stringOrExpr); } - return new EndsWith(this, Constant.of(stringOrExpr)); + return new EndsWith(this, Constant.of(stringOrExpr as string)); } /** @@ -1025,11 +1025,16 @@ export abstract class Expr implements firestore.Expr{ * @return A new `Expr` representing the Cosine distance between the two vectors. */ cosineDistance(other: number[]): CosineDistance; - cosineDistance(other: firestore.Expr | firestore.VectorValue | number[]): CosineDistance { + cosineDistance( + other: firestore.Expr | firestore.VectorValue | number[] + ): CosineDistance { if (other instanceof Expr) { return new CosineDistance(this, other); } else { - return new CosineDistance(this, Constant.vector(other)); + return new CosineDistance( + this, + Constant.vector(other as VectorValue | number[]) + ); } } @@ -1071,11 +1076,16 @@ export abstract class Expr implements firestore.Expr{ * @return A new `Expr` representing the dot product between the two vectors. */ dotProduct(other: number[]): DotProduct; - dotProduct(other: firestore.Expr | firestore.VectorValue | number[]): DotProduct { + dotProduct( + other: firestore.Expr | firestore.VectorValue | number[] + ): DotProduct { if (other instanceof Expr) { return new DotProduct(this, other); } else { - return new DotProduct(this, Constant.vector(other)); + return new DotProduct( + this, + Constant.vector(other as VectorValue | number[]) + ); } } @@ -1117,11 +1127,16 @@ export abstract class Expr implements firestore.Expr{ * @return A new `Expr` representing the Euclidean distance between the two vectors. */ euclideanDistance(other: number[]): EuclideanDistance; - euclideanDistance(other: firestore.Expr | firestore.VectorValue | number[]): EuclideanDistance { + euclideanDistance( + other: firestore.Expr | firestore.VectorValue | number[] + ): EuclideanDistance { if (other instanceof Expr) { return new EuclideanDistance(this, other); } else { - return new EuclideanDistance(this, Constant.vector(other)); + return new EuclideanDistance( + this, + Constant.vector(other as VectorValue | number[]) + ); } } @@ -1264,9 +1279,9 @@ export class Field extends Expr implements Selectable { static of(name: string): Field; static of(path: firestore.FieldPath): Field; static of(nameOrPath: string | firestore.FieldPath): Field; - static of(pipeline: Pipeline, name: string): Field; + static of(pipeline: firestore.Pipeline, name: string): Field; static of( - pipelineOrName: Pipeline | string | firestore.FieldPath, + pipelineOrName: firestore.Pipeline | string | firestore.FieldPath, name?: string ): Field { if (typeof pipelineOrName === 'string') { @@ -1399,7 +1414,7 @@ export class Constant extends Expr { * @param value The GeoPoint value. * @return A new `Constant` instance. */ - static of(value: GeoPoint): Constant; + static of(value: firestore.GeoPoint): Constant; /** * Creates a `Constant` instance for a Timestamp value. @@ -1407,7 +1422,7 @@ export class Constant extends Expr { * @param value The Timestamp value. * @return A new `Constant` instance. */ - static of(value: Timestamp): Constant; + static of(value: firestore.Timestamp): Constant; /** * Creates a `Constant` instance for a Date value. @@ -1431,7 +1446,7 @@ export class Constant extends Expr { * @param value The DocumentReference value. * @return A new `Constant` instance. */ - static of(value: DocumentReference): Constant; + static of(value: firestore.DocumentReference): Constant; /** * Creates a `Constant` instance for a Firestore proto value. @@ -1463,7 +1478,7 @@ export class Constant extends Expr { * @param value The VectorValue value. * @return A new `Constant` instance. */ - static of(value: VectorValue): Constant; + static of(value: firestore.VectorValue): Constant; /** * Creates a `Constant` instance for a Firestore proto value. @@ -1487,11 +1502,11 @@ export class Constant extends Expr { * @param value The VectorValue value. * @return A new `Constant` instance. */ - static vector(value: Array | VectorValue): Constant { + static vector(value: Array | firestore.VectorValue): Constant { if (value instanceof VectorValue) { return new Constant(value); } else { - return new Constant(new VectorValue(value)); + return new Constant(new VectorValue(value as Array)); } } diff --git a/dev/src/index.ts b/dev/src/index.ts index 61982a432..25fc1390f 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -123,6 +123,74 @@ export type { ExplainMetrics, ExplainResults, } from './query-profile'; +export {Pipeline, PipelineResult, PipelineSource} from './pipeline'; +export type {FindNearestOptions} from './stage'; +export type { + FilterCondition, + FilterExpr, + AccumulatorTarget, + Accumulator, + Selectable, + SelectableExpr, +} from './expression'; +export { + Expr, + ExprWithAlias, + Field, + Fields, + Constant, + Function, + Ordering, + add, + subtract, + multiply, + divide, + eq, + neq, + lt, + lte, + gt, + gte, + arrayConcat, + arrayContains, + arrayContainsAny, + arrayContainsAll, + arrayFilter, + arrayLength, + arrayTransform, + arrayElement, + inAny, + notInAny, + and, + or, + xor, + ifFunction, + not, + exists, + isNan, + like, + regexContains, + regexMatch, + startsWith, + endsWith, + toLowercase, + toUppercase, + trim, + strConcat, + mapGet, + countAll, + count, + sum, + avg, + min, + max, + cosineDistance, + dotProduct, + euclideanDistance, + genericFunction, + ascending, + descending, +} from './expression'; const libVersion = require('../../package.json').version; setLibVersion(libVersion); diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index 0da348da2..59ebbcfad 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -27,7 +27,10 @@ import { isNan, Field, not, - Constant, AccumulatorTarget, ExprWithAlias, Accumulator, + Constant, + AccumulatorTarget, + ExprWithAlias, + Accumulator, } from './expression'; import Firestore, {DocumentReference, Timestamp} from './index'; import {logger} from './logger'; @@ -47,7 +50,7 @@ import { requestTag, wrapError, } from './util'; -import {invalidArgumentMessage} from "./validate"; +import {invalidArgumentMessage} from './validate'; import api = protos.google.firestore.v1; /** diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 1239458c0..aec5790b6 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -61,7 +61,7 @@ import {isOptionalEqual} from './util'; * Represents the source of a Firestore {@link Pipeline}. * @beta */ -export class PipelineSource implements firestore.PipelineSource{ +export class PipelineSource implements firestore.PipelineSource { constructor(private db: Firestore) {} collection(collectionPath: string): Pipeline { @@ -123,7 +123,9 @@ export class PipelineSource implements firestore.PipelineSource{ * .execute(); * ``` */ -export class Pipeline implements firestore.Pipeline { +export class Pipeline + implements firestore.Pipeline +{ constructor( private db: Firestore, private stages: Stage[], @@ -193,7 +195,9 @@ export class Pipeline implements firestor * Selectable} expressions or {@code string} values representing field names. * @return A new Pipeline object with this stage appended to the stage list. */ - select(...selections: (firestore.Selectable | string)[]): Pipeline { + select( + ...selections: (firestore.Selectable | string)[] + ): Pipeline { const copy = this.stages.map(s => s); copy.push(new Select(this.selectablesToMap(selections))); return new Pipeline(this.db, copy, this.converter); @@ -343,7 +347,9 @@ export class Pipeline implements firestor * value combinations or {@code string}s representing field names. * @return A new {@code Pipeline} object with this stage appended to the stage list. */ - distinct(...groups: (string | firestore.Selectable)[]): Pipeline { + distinct( + ...groups: (string | firestore.Selectable)[] + ): Pipeline { const copy = this.stages.map(s => s); copy.push(new Distinct(this.selectablesToMap(groups || []))); return new Pipeline(this.db, copy, this.converter); @@ -371,7 +377,9 @@ export class Pipeline implements firestor * and provide a name for the accumulated results. * @return A new Pipeline object with this stage appended to the stage list. */ - aggregate(...accumulators: firestore.AccumulatorTarget[]): Pipeline; + aggregate( + ...accumulators: firestore.AccumulatorTarget[] + ): Pipeline; /** * Performs optionally grouped aggregation operations on the documents from previous stages. * @@ -411,7 +419,10 @@ export class Pipeline implements firestor aggregate( optionsOrTarget: | firestore.AccumulatorTarget - | {accumulators: firestore.AccumulatorTarget[]; groups?: (string | firestore.Selectable)[]}, + | { + accumulators: firestore.AccumulatorTarget[]; + groups?: (string | firestore.Selectable)[]; + }, ...rest: firestore.AccumulatorTarget[] ): Pipeline { const copy = this.stages.map(s => s); @@ -419,10 +430,12 @@ export class Pipeline implements firestor copy.push( new Aggregate( new Map( - optionsOrTarget.accumulators.map((target: firestore.AccumulatorTarget) => [ - (target as unknown as AccumulatorTarget).alias, - (target as unknown as AccumulatorTarget).expr, - ]) + optionsOrTarget.accumulators.map( + (target: firestore.AccumulatorTarget) => [ + (target as unknown as AccumulatorTarget).alias, + (target as unknown as AccumulatorTarget).expr, + ] + ) ), this.selectablesToMap(optionsOrTarget.groups || []) ) diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 4af42e931..f143953bf 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -296,7 +296,7 @@ describe.only('Pipeline class', () => { it('returns group and accumulate results', async () => { const results = await randomCol .pipeline() - .where(lt('published', 1984)) + .where(lt(Field.of('published'), 1984)) .aggregate({ accumulators: [avg('rating').as('avg_rating')], groups: ['genre'], From 3aa8edd0be57573a29a2c308140ae5e589e68d67 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 11 Sep 2024 19:47:35 -0400 Subject: [PATCH 16/31] length to strLength --- dev/src/expression.ts | 20 ++++++++++---------- dev/src/index.ts | 1 + dev/system-test/pipeline.ts | 2 +- types/firestore.d.ts | 14 +++++++------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 0a7117ae8..a55dbd776 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -671,13 +671,13 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Get the length of the 'name' field - * Field.of("name").length(); + * Field.of("name").strLength(); * ``` * * @return A new `Expr` representing the length of the string. */ - length(): Length { - return new Length(this); + strLength(): StrLength { + return new StrLength(this); } /** @@ -1857,7 +1857,7 @@ class If extends Function implements FilterCondition { /** * @beta */ -class Length extends Function { +class StrLength extends Function { constructor(private expr: Expr) { super('length', [expr]); } @@ -3492,13 +3492,13 @@ export function isNan(value: Expr | string): IsNan { * * ```typescript * // Get the length of the 'name' field - * length("name"); + * strLength("name"); * ``` * * @param field The name of the field containing the string. * @return A new {@code Expr} representing the length of the string. */ -export function length(field: string): Length; +export function strLength(field: string): StrLength; /** * @beta @@ -3507,16 +3507,16 @@ export function length(field: string): Length; * * ```typescript * // Get the length of the 'name' field - * length(Field.of("name")); + * strLength(Field.of("name")); * ``` * * @param expr The expression representing the string to calculate the length of. * @return A new {@code Expr} representing the length of the string. */ -export function length(expr: Expr): Length; -export function length(value: Expr | string): Length { +export function strLength(expr: Expr): StrLength; +export function strLength(value: Expr | string): StrLength { const valueExpr = value instanceof Expr ? value : Field.of(value); - return new Length(valueExpr); + return new StrLength(valueExpr); } /** diff --git a/dev/src/index.ts b/dev/src/index.ts index 25fc1390f..e2ad0d76a 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -168,6 +168,7 @@ export { not, exists, isNan, + strLength, like, regexContains, regexMatch, diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index f143953bf..b3ef9b2b0 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -525,7 +525,7 @@ describe.only('Pipeline class', () => { it('testLength', async () => { const results = await randomCol .pipeline() - .select(Field.of('title').length().as('titleLength'), Field.of('title')) + .select(Field.of('title').strLength().as('titleLength'), Field.of('title')) .where(gt('titleLength', 20)) .execute(); expectResults( diff --git a/types/firestore.d.ts b/types/firestore.d.ts index f44c9056b..5e13aa930 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -3744,12 +3744,12 @@ declare namespace FirebaseFirestore { * * ```typescript * // Get the length of the 'name' field - * Field.of("name").length(); + * Field.of("name").strLength(); * ``` * * @return A new `Expr` representing the length of the string. */ - length(): Length; + strLength(): StrLength; /** * Creates an expression that performs a case-sensitive string comparison. @@ -4569,7 +4569,7 @@ declare namespace FirebaseFirestore { /** * @beta */ - export class Length extends Function {} + export class StrLength extends Function {} /** * @beta @@ -5979,13 +5979,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Get the length of the 'name' field - * length("name"); + * strLength("name"); * ``` * * @param field The name of the field containing the string. * @return A new {@code Expr} representing the length of the string. */ - export function length(field: string): Length; + export function strLength(field: string): StrLength; /** * @beta @@ -5994,13 +5994,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Get the length of the 'name' field - * length(Field.of("name")); + * strLength(Field.of("name")); * ``` * * @param expr The expression representing the string to calculate the length of. * @return A new {@code Expr} representing the length of the string. */ - export function length(expr: Expr): Length; + export function length(expr: Expr): StrLength; /** * @beta From c97ed975a2dbe6e8e041265e9450e66129e2c381 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 12 Sep 2024 15:01:44 -0400 Subject: [PATCH 17/31] remove __path__ hack --- dev/src/expression.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index a55dbd776..555d17bc0 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -1286,7 +1286,7 @@ export class Field extends Expr implements Selectable { ): Field { if (typeof pipelineOrName === 'string') { if (FieldPath.documentId().formattedName === pipelineOrName) { - return new Field(new FieldPath('__path__')); + return new Field(FieldPath.documentId()); } return new Field(FieldPath.fromArgument(pipelineOrName)); From 9116488c7b504d8250eeaf03cfd78d6f0df9c87c Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 25 Sep 2024 15:57:09 -0400 Subject: [PATCH 18/31] resync with expressions catelog --- dev/src/expression.ts | 2934 +++++++++++++++++++++++++++++------ dev/src/index.ts | 31 +- dev/system-test/pipeline.ts | 49 +- types/firestore.d.ts | 2694 +++++++++++++++++++++++++------- 4 files changed, 4619 insertions(+), 1089 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 555d17bc0..a35134c1e 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -18,13 +18,10 @@ import api = protos.google.firestore.v1; import * as firestore from '@google-cloud/firestore'; import {VectorValue} from './field-value'; -import {GeoPoint} from './geo-point'; import {FieldPath} from './path'; import {Pipeline} from './pipeline'; import {isFirestoreValue} from './pipeline-util'; -import {DocumentReference} from './reference/document-reference'; import {Serializer} from './serializer'; -import {Timestamp} from './timestamp'; /** * @beta @@ -232,6 +229,212 @@ export abstract class Expr implements firestore.Expr { return new Divide(this, Constant.of(other)); } + /** + * Creates an expression that calculates the modulo (remainder) of dividing this expression by another expression. + * + * ```typescript + * // Calculate the remainder of dividing the 'value' field by the 'divisor' field + * Field.of("value").mod(Field.of("divisor")); + * ``` + * + * @param other The expression to divide by. + * @return A new `Expr` representing the modulo operation. + */ + mod(other: firestore.Expr): Mod; + + /** + * 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); + * ``` + * + * @param other 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)); + } + + /** + * 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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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. * @@ -556,24 +759,6 @@ export abstract class Expr implements firestore.Expr { return new ArrayContainsAny(this, exprValues); } - /** - * Creates an expression that filters elements from an array using the given {@link - * FilterCondition} and returns the filtered elements as a new array. - * - * ```typescript - * // Get items from the 'inventoryPrices' array where the array item is greater than 0 - * // Note we use {@link Function#arrayElement} to represent array elements to construct a - * // filtering condition. - * Field.of("inventoryPrices").arrayFilter(arrayElement().gt(0)); - * ``` - * - * @param filter The {@link FilterCondition} to apply to the array elements. - * @return A new `Expr` representing the filtered array. - */ - arrayFilter(filter: firestore.FilterExpr): ArrayFilter { - return new ArrayFilter(this, filter as unknown as FilterExpr); - } - /** * Creates an expression that calculates the length of an array. * @@ -588,22 +773,6 @@ export abstract class Expr implements firestore.Expr { return new ArrayLength(this); } - /** - * Creates an expression that applies a transformation function to each element in an array and - * returns the new array as the result of the evaluation. - * - * ```typescript - * // Convert all strings in the 'names' array to uppercase - * Field.of("names").arrayTransform(arrayElement().toUppercase()); - * ``` - * - * @param transform The {@link Function} to apply to each array element. - * @return A new `Expr` representing the transformed array. - */ - arrayTransform(transform: Function): ArrayTransform { - return new ArrayTransform(this, transform); - } - /** * Creates an expression that checks if this expression is equal to any of the provided values or * expressions. @@ -667,17 +836,17 @@ export abstract class Expr implements firestore.Expr { } /** - * Creates an expression that calculates the length of a string. + * Creates an expression that calculates the character length of a string in UTF-8. * * ```typescript - * // Get the length of the 'name' field - * Field.of("name").strLength(); + * // Get the character length of the 'name' field in its UTF-8 form. + * Field.of("name").charLength(); * ``` * * @return A new `Expr` representing the length of the string. */ - strLength(): StrLength { - return new StrLength(this); + charLength(): CharLength { + return new CharLength(this); } /** @@ -706,10 +875,10 @@ export abstract class Expr implements firestore.Expr { */ like(pattern: firestore.Expr): Like; like(stringOrExpr: string | firestore.Expr): Like { - if (stringOrExpr instanceof Expr) { - return new Like(this, stringOrExpr); + if (typeof stringOrExpr === 'string') { + return new Like(this, Constant.of(stringOrExpr)); } - return new Like(this, Constant.of(stringOrExpr as string)); + return new Like(this, stringOrExpr as Expr); } /** @@ -740,10 +909,10 @@ export abstract class Expr implements firestore.Expr { */ regexContains(pattern: firestore.Expr): RegexContains; regexContains(stringOrExpr: string | firestore.Expr): RegexContains { - if (stringOrExpr instanceof Expr) { - return new RegexContains(this, stringOrExpr); + if (typeof stringOrExpr === 'string') { + return new RegexContains(this, Constant.of(stringOrExpr)); } - return new RegexContains(this, Constant.of(stringOrExpr as string)); + return new RegexContains(this, stringOrExpr as Expr); } /** @@ -772,10 +941,10 @@ export abstract class Expr implements firestore.Expr { */ regexMatch(pattern: firestore.Expr): RegexMatch; regexMatch(stringOrExpr: string | firestore.Expr): RegexMatch { - if (stringOrExpr instanceof Expr) { - return new RegexMatch(this, stringOrExpr); + if (typeof stringOrExpr === 'string') { + return new RegexMatch(this, Constant.of(stringOrExpr)); } - return new RegexMatch(this, Constant.of(stringOrExpr as string)); + return new RegexMatch(this, stringOrExpr as Expr); } /** @@ -805,10 +974,10 @@ export abstract class Expr implements firestore.Expr { */ startsWith(prefix: firestore.Expr): StartsWith; startsWith(stringOrExpr: string | firestore.Expr): StartsWith { - if (stringOrExpr instanceof Expr) { - return new StartsWith(this, stringOrExpr); + if (typeof stringOrExpr === 'string') { + return new StartsWith(this, Constant.of(stringOrExpr)); } - return new StartsWith(this, Constant.of(stringOrExpr as string)); + return new StartsWith(this, stringOrExpr as Expr); } /** @@ -838,10 +1007,10 @@ export abstract class Expr implements firestore.Expr { */ endsWith(suffix: firestore.Expr): EndsWith; endsWith(stringOrExpr: string | firestore.Expr): EndsWith { - if (stringOrExpr instanceof Expr) { - return new EndsWith(this, stringOrExpr); + if (typeof stringOrExpr === 'string') { + return new EndsWith(this, Constant.of(stringOrExpr)); } - return new EndsWith(this, Constant.of(stringOrExpr as string)); + return new EndsWith(this, stringOrExpr as Expr); } /** @@ -849,13 +1018,13 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Convert the 'name' field to lowercase - * Field.of("name").toLowerCase(); + * Field.of("name").toLower(); * ``` * * @return A new `Expr` representing the lowercase string. */ - toLowercase(): ToLowercase { - return new ToLowercase(this); + toLower(): ToLower { + return new ToLower(this); } /** @@ -863,13 +1032,13 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Convert the 'title' field to uppercase - * Field.of("title").toUpperCase(); + * Field.of("title").toUpper(); * ``` * * @return A new `Expr` representing the uppercase string. */ - toUppercase(): ToUppercase { - return new ToUppercase(this); + toUpper(): ToUpper { + return new ToUpper(this); } /** @@ -897,11 +1066,131 @@ export abstract class Expr implements firestore.Expr { * @param elements The expressions (typically strings) to concatenate. * @return A new `Expr` representing the concatenated string. */ - strConcat(...elements: (string | Expr)[]): StrConcat { - const exprs = elements.map(e => (e instanceof Expr ? e : Constant.of(e))); + strConcat(...elements: (string | firestore.Expr)[]): StrConcat { + const exprs = elements.map(e => + typeof e === 'string' ? Constant.of(e) : (e as Expr) + ); return new StrConcat(this, exprs); } + /** + * Creates an expression that reverses this string expression. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * Field.of("myString").reverse(); + * ``` + * + * @return A new {@code Expr} representing the reversed string. + */ + reverse(): Reverse { + return new Reverse(this); + } + + /** + * Creates an expression that replaces the first occurrence of a substring within this string expression with another substring. + * + * ```typescript + * // Replace the first occurrence of "hello" with "hi" in the 'message' field + * Field.of("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): firestore.ReplaceFirst; + + /** + * Creates an expression that replaces the first occurrence of a substring within this string expression with another substring, + * where the substring to find and the replacement substring are specified by expressions. + * + * ```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")); + * ``` + * + * @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: firestore.Expr, + replace: firestore.Expr + ): firestore.ReplaceFirst; + replaceFirst( + find: firestore.Expr | string, + replace: firestore.Expr | string + ): firestore.ReplaceFirst { + const normalizedFind = typeof find === 'string' ? Constant.of(find) : find; + const normalizedReplace = + typeof replace === 'string' ? Constant.of(replace) : replace; + return new ReplaceFirst( + this, + normalizedFind as Expr, + normalizedReplace as Expr + ); + } + + /** + * Creates an expression that replaces all occurrences of a substring within this string expression with another substring. + * + * ```typescript + * // Replace all occurrences of "hello" with "hi" in the 'message' field + * Field.of("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): firestore.ReplaceAll; + + /** + * Creates an expression that replaces all occurrences of a substring within this string expression with another substring, + * where the substring to find and the replacement substring are specified by expressions. + * + * ```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")); + * ``` + * + * @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: firestore.Expr, + replace: firestore.Expr + ): firestore.ReplaceAll; + replaceAll( + find: firestore.Expr | string, + replace: firestore.Expr | string + ): firestore.ReplaceAll { + const normalizedFind = typeof find === 'string' ? Constant.of(find) : find; + const normalizedReplace = + typeof replace === 'string' ? Constant.of(replace) : replace; + return new ReplaceAll( + this, + normalizedFind as Expr, + normalizedReplace as Expr + ); + } + + /** + * Creates an expression that calculates the length of this string expression in bytes. + * + * ```typescript + * // Calculate the length of the 'myString' field in bytes. + * Field.of("myString").byteLength(); + * ``` + * + * @return A new {@code Expr} representing the length of the string in bytes. + */ + byteLength(): firestore.ByteLength { + return new ByteLength(this); + } + /** * Accesses a value from a map (object) field using the provided key. * @@ -989,6 +1278,84 @@ export abstract class Expr implements firestore.Expr { return new Max(this, false); } + /** + * Creates an expression that returns the larger value between this expression and another expression, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'timestamp' field and the current timestamp. + * Field.of("timestamp").logicalMax(Function.currentTimestamp()); + * ``` + * + * @param other The expression to compare with. + * @return A new {@code Expr} representing the logical max operation. + */ + logicalMax(other: firestore.Expr): firestore.LogicalMax; + + /** + * 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").logicalMax(10); + * ``` + * + * @param other The constant value to compare with. + * @return A new {@code Expr} representing the logical max operation. + */ + logicalMax(other: any): firestore.LogicalMax; + logicalMax(other: any): firestore.LogicalMax { + if (other instanceof firestore.Expr) { + return new LogicalMax(this, other as Expr); + } + return new LogicalMax(this, Constant.of(other)); + } + + /** + * Creates an expression that returns the smaller value between this expression and another expression, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'timestamp' field and the current timestamp. + * Field.of("timestamp").logicalMin(Function.currentTimestamp()); + * ``` + * + * @param other The expression to compare with. + * @return A new {@code Expr} representing the logical min operation. + */ + logicalMin(other: firestore.Expr): firestore.LogicalMin; + + /** + * 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").logicalMin(10); + * ``` + * + * @param other The constant value to compare with. + * @return A new {@code Expr} representing the logical min operation. + */ + logicalMin(other: any): firestore.LogicalMin; + logicalMin(other: any): firestore.LogicalMin { + if (other instanceof firestore.Expr) { + return new LogicalMin(this, other as Expr); + } + return new LogicalMin(this, Constant.of(other)); + } + + /** + * Creates an expression that calculates the length (number of dimensions) of this Firestore Vector expression. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * Field.of("embedding").vectorLength(); + * ``` + * + * @return A new {@code Expr} representing the length of the vector. + */ + vectorLength(): VectorLength { + return new VectorLength(this); + } + /** * Calculates the cosine distance between two vectors. * @@ -1028,8 +1395,8 @@ export abstract class Expr implements firestore.Expr { cosineDistance( other: firestore.Expr | firestore.VectorValue | number[] ): CosineDistance { - if (other instanceof Expr) { - return new CosineDistance(this, other); + if (other instanceof firestore.Expr) { + return new CosineDistance(this, other as Expr); } else { return new CosineDistance( this, @@ -1079,8 +1446,8 @@ export abstract class Expr implements firestore.Expr { dotProduct( other: firestore.Expr | firestore.VectorValue | number[] ): DotProduct { - if (other instanceof Expr) { - return new DotProduct(this, other); + if (other instanceof firestore.Expr) { + return new DotProduct(this, other as Expr); } else { return new DotProduct( this, @@ -1130,8 +1497,8 @@ export abstract class Expr implements firestore.Expr { euclideanDistance( other: firestore.Expr | firestore.VectorValue | number[] ): EuclideanDistance { - if (other instanceof Expr) { - return new EuclideanDistance(this, other); + if (other instanceof firestore.Expr) { + return new EuclideanDistance(this, other as Expr); } else { return new EuclideanDistance( this, @@ -1141,61 +1508,256 @@ export abstract class Expr implements firestore.Expr { } /** - * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * Creates an expression that interprets this expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. * * ```typescript - * // Sort documents by the 'name' field in ascending order - * firestore.pipeline().collection("users") - * .sort(Field.of("name").ascending()); + * // Interpret the 'microseconds' field as microseconds since epoch. + * Field.of("microseconds").unixMicrosToTimestamp(); * ``` * - * @return A new `Ordering` for ascending sorting. + * @return A new {@code Expr} representing the timestamp. */ - ascending(): Ordering { - return ascending(this); + unixMicrosToTimestamp(): firestore.UnixMicrosToTimestamp { + return new UnixMicrosToTimestamp(this); } /** - * Creates an {@link Ordering} that sorts documents in descending order based on this expression. + * Creates an expression that converts this timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). * * ```typescript - * // Sort documents by the 'createdAt' field in descending order - * firestore.pipeline().collection("users") - * .sort(Field.of("createdAt").descending()); + * // Convert the 'timestamp' field to microseconds since epoch. + * Field.of("timestamp").timestampToUnixMicros(); * ``` * - * @return A new `Ordering` for descending sorting. + * @return A new {@code Expr} representing the number of microseconds since epoch. */ - descending(): Ordering { - return descending(this); + timestampToUnixMicros(): firestore.TimestampToUnixMicros { + return new TimestampToUnixMicros(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. + * Creates an expression that interprets this expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. * * ```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")); + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * Field.of("milliseconds").unixMillisToTimestamp(); * ``` * - * @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. + * @return A new {@code Expr} representing the timestamp. */ - as(name: string): ExprWithAlias { - return new ExprWithAlias(this, name); + unixMillisToTimestamp(): firestore.UnixMillisToTimestamp { + return new UnixMillisToTimestamp(this); } - abstract _toProto(serializer: Serializer): api.IValue; -} + /** + * Creates an expression that converts this timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * Field.of("timestamp").timestampToUnixMillis(); + * ``` + * + * @return A new {@code Expr} representing the number of milliseconds since epoch. + */ + timestampToUnixMillis(): firestore.TimestampToUnixMillis { + return new TimestampToUnixMillis(this); + } -/** - * @beta - */ + /** + * Creates an expression that interprets this expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * Field.of("seconds").unixSecondsToTimestamp(); + * ``` + * + * @return A new {@code Expr} representing the timestamp. + */ + unixSecondsToTimestamp(): firestore.UnixSecondsToTimestamp { + return new UnixSecondsToTimestamp(this); + } + + /** + * Creates an expression that converts this timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * Field.of("timestamp").timestampToUnixSeconds(); + * ``` + * + * @return A new {@code Expr} representing the number of seconds since epoch. + */ + timestampToUnixSeconds(): firestore.TimestampToUnixSeconds { + return new TimestampToUnixSeconds(this); + } + + /** + * Creates an expression that adds a specified amount of time to this timestamp expression. + * + * ```typescript + * // Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. + * Field.of("timestamp").timestampAdd(Field.of("unit"), Field.of("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: firestore.Expr, + amount: firestore.Expr + ): firestore.TimestampAdd; + + /** + * 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); + * ``` + * + * @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. + */ + timestampAdd( + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number + ): firestore.TimestampAdd; + timestampAdd( + unit: + | firestore.Expr + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: firestore.Expr | number + ): firestore.TimestampAdd { + const normalizedUnit = typeof unit === 'string' ? Constant.of(unit) : unit; + const normalizedAmount = + typeof amount === 'number' ? Constant.of(amount) : amount; + return new TimestampAdd( + this, + normalizedUnit as Expr, + normalizedAmount as Expr + ); + } + + /** + * Creates an expression that subtracts a specified amount of time from this timestamp expression. + * + * ```typescript + * // Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. + * Field.of("timestamp").timestampSub(Field.of("unit"), Field.of("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: firestore.Expr, + amount: firestore.Expr + ): firestore.TimestampSub; + + /** + * 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); + * ``` + * + * @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. + */ + timestampSub( + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number + ): firestore.TimestampSub; + timestampSub( + unit: + | firestore.Expr + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: firestore.Expr | number + ): firestore.TimestampSub { + const normalizedUnit = typeof unit === 'string' ? Constant.of(unit) : unit; + const normalizedAmount = + typeof amount === 'number' ? Constant.of(amount) : amount; + return new TimestampSub( + this, + normalizedUnit as Expr, + normalizedAmount as Expr + ); + } + + /** + * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. + * + * ```typescript + * // Sort documents by the 'name' field in ascending order + * firestore.pipeline().collection("users") + * .sort(Field.of("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.of("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.of("price").multiply(Field.of("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); + } + + abstract _toProto(serializer: Serializer): api.IValue; +} + +/** + * @beta + */ export class ExprWithAlias extends Expr implements Selectable { exprType: ExprType = 'ExprWithAlias'; selectable = true as const; @@ -1595,6 +2157,87 @@ export class Divide extends Function { } } +/** + * @beta + */ +export class Mod extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('mod', [left, right]); + } +} + +/** + * @beta + */ +export class BitAnd extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('bit_and', [left, right]); + } +} + +/** + * @beta + */ +export class BitOr extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('bit_or', [left, right]); + } +} + +/** + * @beta + */ +export class BitXor extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('bit_xor', [left, right]); + } +} + +/** + * @beta + */ +export class BitNot extends Function { + constructor(private operand: Expr) { + super('bit_not', [operand]); + } +} + +/** + * @beta + */ +export class BitLeftShift extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('bit_left_shift', [left, right]); + } +} + +/** + * @beta + */ +export class BitRightShift extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('bit_right_shift', [left, right]); + } +} + /** * @beta */ @@ -1685,6 +2328,15 @@ class ArrayConcat extends Function { } } +/** + * @beta + */ +class ArrayReverse extends Function { + constructor(private array: Expr) { + super('array_reverse', [array]); + } +} + /** * @beta */ @@ -1724,18 +2376,6 @@ class ArrayContainsAny extends Function implements FilterCondition { filterable = true as const; } -/** - * @beta - */ -class ArrayFilter extends Function { - constructor( - private array: Expr, - private filter: FilterExpr - ) { - super('array_filter', [array, filter]); - } -} - /** * @beta */ @@ -1745,18 +2385,6 @@ class ArrayLength extends Function { } } -/** - * @beta - */ -class ArrayTransform extends Function { - constructor( - private array: Expr, - private transform: Function - ) { - super('array_transform', [array, transform]); - } -} - /** * @beta */ @@ -1857,9 +2485,77 @@ class If extends Function implements FilterCondition { /** * @beta */ -class StrLength extends Function { - constructor(private expr: Expr) { - super('length', [expr]); +class LogicalMax extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('logical_max', [left, right]); + } +} + +/** + * @beta + */ +class LogicalMin extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('logical_min', [left, right]); + } +} + +/** + * @beta + */ +export class Reverse extends Function { + constructor(private value: Expr) { + super('reverse', [value]); + } +} + +/** + * @beta + */ +export class ReplaceFirst extends Function { + constructor( + private value: Expr, + private find: Expr, + private replace: Expr + ) { + super('replace_first', [value, find, replace]); + } +} + +/** + * @beta + */ +export class ReplaceAll extends Function { + constructor( + private value: Expr, + private find: Expr, + private replace: Expr + ) { + super('replace_all', [value, find, replace]); + } +} + +/** + * @beta + */ +export class CharLength extends Function { + constructor(private value: Expr) { + super('char_length', [value]); + } +} + +/** + * @beta + */ +export class ByteLength extends Function { + constructor(private value: Expr) { + super('byte_length', [value]); } } @@ -1931,18 +2627,18 @@ class EndsWith extends Function implements FilterCondition { /** * @beta */ -class ToLowercase extends Function { +class ToLower extends Function { constructor(private expr: Expr) { - super('to_lowercase', [expr]); + super('to_lower', [expr]); } } /** * @beta */ -class ToUppercase extends Function { +class ToUpper extends Function { constructor(private expr: Expr) { - super('to_uppercase', [expr]); + super('to_upper', [expr]); } } @@ -2079,16 +2775,105 @@ class EuclideanDistance extends Function { /** * @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")); - * ``` - * - * @param left The first expression to add. - * @param right The second expression to add. + */ +export class VectorLength extends Function { + constructor(private value: Expr) { + super('vector_length', [value]); + } +} + +/** + * @beta + */ +export class UnixMicrosToTimestamp extends Function { + constructor(private input: Expr) { + super('unix_micros_to_timestamp', [input]); + } +} + +/** + * @beta + */ +export class TimestampToUnixMicros extends Function { + constructor(private input: Expr) { + super('timestamp_to_unix_micros', [input]); + } +} + +/** + * @beta + */ +export class UnixMillisToTimestamp extends Function { + constructor(private input: Expr) { + super('unix_millis_to_timestamp', [input]); + } +} + +/** + * @beta + */ +export class TimestampToUnixMillis extends Function { + constructor(private input: Expr) { + super('timestamp_to_unix_millis', [input]); + } +} + +/** + * @beta + */ +export class UnixSecondsToTimestamp extends Function { + constructor(private input: Expr) { + super('unix_seconds_to_timestamp', [input]); + } +} + +/** + * @beta + */ +export class TimestampToUnixSeconds extends Function { + constructor(private input: Expr) { + super('timestamp_to_unix_seconds', [input]); + } +} + +/** + * @beta + */ +export class TimestampAdd extends Function { + constructor( + private timestamp: Expr, + private unit: Expr, + private amount: Expr + ) { + super('timestamp_add', [timestamp, unit, amount]); + } +} + +/** + * @beta + */ +export class TimestampSub extends Function { + constructor( + private timestamp: Expr, + private unit: Expr, + private amount: Expr + ) { + super('timestamp_sub', [timestamp, unit, amount]); + } +} + +/** + * @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")); + * ``` + * + * @param left The first expression to add. + * @param right The second expression to add. * @return A new {@code Expr} representing the addition operation. */ export function add(left: Expr, right: Expr): Add; @@ -2356,378 +3141,833 @@ export function divide(left: Expr | string, right: Expr | any): Divide { /** * @beta * - * Creates an expression that checks if two expressions are equal. + * Creates an expression that calculates the modulo (remainder) of dividing two expressions. * * ```typescript - * // Check if the 'age' field is equal to an expression - * eq(Field.of("age"), Field.of("minAge").add(10)); + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod(Field.of("field1"), Field.of("field2")); * ``` * - * @param left The first expression to compare. - * @param right The second expression to compare. - * @return A new `Expr` representing the equality comparison. + * @param left The dividend expression. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. */ -export function eq(left: Expr, right: Expr): Eq; +export function mod(left: Expr, right: Expr): Mod; /** * @beta * - * Creates an expression that checks if an expression is equal to a constant value. + * Creates an expression that calculates the modulo (remainder) of dividing an expression by a constant. * * ```typescript - * // Check if the 'age' field is equal to 21 - * eq(Field.of("age"), 21); + * // Calculate the remainder of dividing 'field1' by 5. + * mod(Field.of("field1"), 5); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the equality comparison. + * @param left The dividend expression. + * @param right The divisor constant. + * @return A new {@code Expr} representing the modulo operation. */ -export function eq(left: Expr, right: any): Eq; +export function mod(left: Expr, right: any): Mod; /** * @beta * - * Creates an expression that checks if a field's value is equal to an expression. + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by an expression. * * ```typescript - * // Check if the 'age' field is equal to the 'limit' field - * eq("age", Field.of("limit")); + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod("field1", Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the equality comparison. + * @param left The dividend field name. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. */ -export function eq(left: string, right: Expr): Eq; +export function mod(left: string, right: Expr): Mod; /** * @beta * - * Creates an expression that checks if a field's value is equal to a constant value. + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by a constant. * * ```typescript - * // Check if the 'city' field is equal to string constant "London" - * eq("city", "London"); + * // Calculate the remainder of dividing 'field1' by 5. + * mod("field1", 5); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the equality comparison. + * @param left The dividend field name. + * @param right The divisor constant. + * @return A new {@code Expr} representing the modulo operation. */ -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 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); } /** * @beta * - * Creates an expression that checks if two expressions are not equal. + * Creates an expression that applies a bitwise AND operation between two expressions. * * ```typescript - * // Check if the 'status' field is not equal to field 'finalState' - * neq(Field.of("status"), Field.of("finalState")); + * // Calculate the bitwise AND of 'field1' and 'field2'. + * bitAnd(Field.of("field1"), Field.of("field2")); * ``` * - * @param left The first expression to compare. - * @param right The second expression to compare. - * @return A new `Expr` representing the inequality comparison. + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the bitwise AND operation. */ -export function neq(left: Expr, right: Expr): Neq; +export function bitAnd(left: Expr, right: Expr): BitAnd; /** * @beta * - * Creates an expression that checks if an expression is not equal to a constant value. + * Creates an expression that applies a bitwise AND operation between an expression and a constant. * * ```typescript - * // Check if the 'status' field is not equal to "completed" - * neq(Field.of("status"), "completed"); + * // Calculate the bitwise AND of 'field1' and 0xFF. + * bitAnd(Field.of("field1"), 0xFF); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the inequality comparison. + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the bitwise AND operation. */ -export function neq(left: Expr, right: any): Neq; +export function bitAnd(left: Expr, right: any): BitAnd; /** * @beta * - * Creates an expression that checks if a field's value is not equal to an expression. + * Creates an expression that applies a bitwise AND operation between a field and an expression. * * ```typescript - * // Check if the 'status' field is not equal to the value of 'expectedStatus' - * neq("status", Field.of("expectedStatus")); + * // Calculate the bitwise AND of 'field1' and 'field2'. + * bitAnd("field1", Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the inequality comparison. + * @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 neq(left: string, right: Expr): Neq; +export function bitAnd(left: string, right: Expr): BitAnd; /** * @beta * - * Creates an expression that checks if a field's value is not equal to a constant value. + * Creates an expression that applies a bitwise AND operation between a field and a constant. * * ```typescript - * // Check if the 'country' field is not equal to "USA" - * neq("country", "USA"); + * // Calculate the bitwise AND of 'field1' and 0xFF. + * bitAnd("field1", 0xFF); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the inequality comparison. + * @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 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 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 checks if the first expression is less than the second expression. + * Creates an expression that applies a bitwise OR operation between two expressions. * * ```typescript - * // Check if the 'age' field is less than 30 - * lt(Field.of("age"), Field.of("limit")); + * // Calculate the bitwise OR of 'field1' and 'field2'. + * bitOr(Field.of("field1"), Field.of("field2")); * ``` * - * @param left The first expression to compare. - * @param right The second expression to compare. - * @return A new `Expr` representing the less than comparison. + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the bitwise OR operation. */ -export function lt(left: Expr, right: Expr): Lt; +export function bitOr(left: Expr, right: Expr): BitOr; /** * @beta * - * Creates an expression that checks if an expression is less than a constant value. + * Creates an expression that applies a bitwise OR operation between an expression and a constant. * * ```typescript - * // Check if the 'age' field is less than 30 - * lt(Field.of("age"), 30); + * // Calculate the bitwise OR of 'field1' and 0xFF. + * bitOr(Field.of("field1"), 0xFF); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the less than comparison. + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the bitwise OR operation. */ -export function lt(left: Expr, right: any): Lt; +export function bitOr(left: Expr, right: any): BitOr; /** * @beta * - * Creates an expression that checks if a field's value is less than an expression. + * Creates an expression that applies a bitwise OR operation between a field and an expression. * * ```typescript - * // Check if the 'age' field is less than the 'limit' field - * lt("age", Field.of("limit")); + * // Calculate the bitwise OR of 'field1' and 'field2'. + * bitOr("field1", Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the less than comparison. + * @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 lt(left: string, right: Expr): Lt; +export function bitOr(left: string, right: Expr): BitOr; /** * @beta * - * Creates an expression that checks if a field's value is less than a constant value. + * Creates an expression that applies a bitwise OR operation between a field and a constant. * * ```typescript - * // Check if the 'price' field is less than 50 - * lt("price", 50); + * // Calculate the bitwise OR of 'field1' and 0xFF. + * bitOr("field1", 0xFF); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the less than comparison. + * @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 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 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 checks if the first expression is less than or equal to the second - * expression. + * Creates an expression that applies a bitwise XOR operation between two expressions. * * ```typescript - * // Check if the 'quantity' field is less than or equal to 20 - * lte(Field.of("quantity"), Field.of("limit")); + * // Calculate the bitwise XOR of 'field1' and 'field2'. + * bitXor(Field.of("field1"), Field.of("field2")); * ``` * - * @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. + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the bitwise XOR operation. */ -export function lte(left: Expr, right: Expr): Lte; +export function bitXor(left: Expr, right: Expr): BitXor; /** * @beta * - * Creates an expression that checks if an expression is less than or equal to a constant value. + * Creates an expression that applies a bitwise XOR operation between an expression and a constant. * * ```typescript - * // Check if the 'quantity' field is less than or equal to 20 - * lte(Field.of("quantity"), 20); + * // Calculate the bitwise XOR of 'field1' and 0xFF. + * bitXor(Field.of("field1"), 0xFF); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the less than or equal to comparison. + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the bitwise XOR operation. */ -export function lte(left: Expr, right: any): Lte; +export function bitXor(left: Expr, right: any): BitXor; /** - * Creates an expression that checks if a field's value is less than or equal to an expression. + * @beta + * + * Creates an expression that applies a bitwise XOR operation between a field and an expression. * * ```typescript - * // Check if the 'quantity' field is less than or equal to the 'limit' field - * lte("quantity", Field.of("limit")); + * // Calculate the bitwise XOR of 'field1' and 'field2'. + * bitXor("field1", Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the less than or equal to comparison. + * @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 lte(left: string, right: Expr): Lte; +export function bitXor(left: string, right: Expr): BitXor; /** * @beta * - * Creates an expression that checks if a field's value is less than or equal to a constant value. + * Creates an expression that applies a bitwise XOR operation between a field and a constant. * * ```typescript - * // Check if the 'score' field is less than or equal to 70 - * lte("score", 70); + * // Calculate the bitwise XOR of 'field1' and 0xFF. + * bitXor("field1", 0xFF); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the less than or equal to comparison. + * @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 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 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 checks if the first expression is greater than the second - * expression. + * Creates an expression that applies a bitwise NOT operation to an expression. * * ```typescript - * // Check if the 'age' field is greater than 18 - * gt(Field.of("age"), Constant(9).add(9)); + * // Calculate the bitwise NOT of 'field1'. + * bitNot(Field.of("field1")); * ``` * - * @param left The first expression to compare. - * @param right The second expression to compare. - * @return A new `Expr` representing the greater than comparison. + * @param operand The operand expression. + * @return A new {@code Expr} representing the bitwise NOT operation. */ -export function gt(left: Expr, right: Expr): Gt; +export function bitNot(operand: Expr): BitNot; /** * @beta * - * Creates an expression that checks if an expression is greater than a constant value. + * Creates an expression that applies a bitwise NOT operation to a field. * * ```typescript - * // Check if the 'age' field is greater than 18 - * gt(Field.of("age"), 18); + * // Calculate the bitwise NOT of 'field1'. + * bitNot("field1"); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the greater than comparison. + * @param operand The operand field name. + * @return A new {@code Expr} representing the bitwise NOT operation. */ -export function gt(left: Expr, right: any): Gt; +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 checks if a field's value is greater than an expression. + * Creates an expression that applies a bitwise left shift operation between two expressions. * * ```typescript - * // Check if the value of field 'age' is greater than the value of field 'limit' - * gt("age", Field.of("limit")); + * // Calculate the bitwise left shift of 'field1' by 'field2' bits. + * bitLeftShift(Field.of("field1"), Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the greater than comparison. + * @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 gt(left: string, right: Expr): Gt; +export function bitLeftShift(left: Expr, right: Expr): BitLeftShift; /** * @beta * - * Creates an expression that checks if a field's value is greater than a constant value. + * Creates an expression that applies a bitwise left shift operation between an expression and a constant. * * ```typescript - * // Check if the 'price' field is greater than 100 - * gt("price", 100); + * // Calculate the bitwise left shift of 'field1' by 2 bits. + * bitLeftShift(Field.of("field1"), 2); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the greater than comparison. + * @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 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 bitLeftShift(left: Expr, right: any): BitLeftShift; /** * @beta * - * Creates an expression that checks if the first expression is greater than or equal to the - * second expression. + * Creates an expression that applies a bitwise left shift operation between a field and an expression. * * ```typescript - * // Check if the 'quantity' field is greater than or equal to the field "threshold" - * gte(Field.of("quantity"), Field.of("threshold")); + * // Calculate the bitwise left shift of 'field1' by 'field2' bits. + * bitLeftShift("field1", Field.of("field2")); * ``` * - * @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. + * @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 gte(left: Expr, right: Expr): Gte; +export function bitLeftShift(left: string, right: Expr): BitLeftShift; /** * @beta * - * Creates an expression that checks if an expression is greater than or equal to a constant - * value. + * Creates an expression that applies a bitwise left shift operation between a field and a constant. * * ```typescript - * // Check if the 'quantity' field is greater than or equal to 10 - * gte(Field.of("quantity"), 10); + * // Calculate the bitwise left shift of 'field1' by 2 bits. + * bitLeftShift("field1", 2); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. + * @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 checks if two expressions are equal. + * + * ```typescript + * // Check if the 'age' field is equal to an expression + * eq(Field.of("age"), Field.of("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; + +/** + * @beta + * + * Creates an expression that checks if an expression is equal to a constant value. + * + * ```typescript + * // Check if the 'age' field is equal to 21 + * eq(Field.of("age"), 21); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the equality comparison. + */ +export function eq(left: Expr, right: any): Eq; + +/** + * @beta + * + * Creates an expression that checks if a field's value is equal to an expression. + * + * ```typescript + * // Check if the 'age' field is equal to the 'limit' field + * eq("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the equality comparison. + */ +export function eq(left: string, right: Expr): Eq; + +/** + * @beta + * + * Creates an expression that checks if a field's value is equal to a constant value. + * + * ```typescript + * // Check if the 'city' field is equal to string constant "London" + * eq("city", "London"); + * ``` + * + * @param left The field name to compare. + * @param right 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); +} + +/** + * @beta + * + * Creates an expression that checks if two expressions are not equal. + * + * ```typescript + * // Check if the 'status' field is not equal to field 'finalState' + * neq(Field.of("status"), Field.of("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; + +/** + * @beta + * + * Creates an expression that checks if an expression is not equal to a constant value. + * + * ```typescript + * // Check if the 'status' field is not equal to "completed" + * neq(Field.of("status"), "completed"); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the inequality comparison. + */ +export function neq(left: Expr, right: any): Neq; + +/** + * @beta + * + * Creates an expression that checks if a field's value is not equal to an expression. + * + * ```typescript + * // Check if the 'status' field is not equal to the value of 'expectedStatus' + * neq("status", Field.of("expectedStatus")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the inequality comparison. + */ +export function neq(left: string, right: Expr): Neq; + +/** + * @beta + * + * Creates an expression that checks if a field's value is not equal to a constant value. + * + * ```typescript + * // Check if the 'country' field is not equal to "USA" + * neq("country", "USA"); + * ``` + * + * @param left The field name to compare. + * @param right 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); +} + +/** + * @beta + * + * Creates an expression that checks if the first expression is less than the second expression. + * + * ```typescript + * // Check if the 'age' field is less than 30 + * lt(Field.of("age"), Field.of("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; + +/** + * @beta + * + * Creates an expression that checks if an expression is less than a constant value. + * + * ```typescript + * // Check if the 'age' field is less than 30 + * lt(Field.of("age"), 30); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the less than comparison. + */ +export function lt(left: Expr, right: any): Lt; + +/** + * @beta + * + * Creates an expression that checks if a field's value is less than an expression. + * + * ```typescript + * // Check if the 'age' field is less than the 'limit' field + * lt("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the less than comparison. + */ +export function lt(left: string, right: Expr): Lt; + +/** + * @beta + * + * Creates an expression that checks if a field's value is less than a constant value. + * + * ```typescript + * // Check if the 'price' field is less than 50 + * lt("price", 50); + * ``` + * + * @param left The field name to compare. + * @param right 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); +} + +/** + * @beta + * + * Creates an expression that checks if the first expression is less than or equal to the second + * expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lte(Field.of("quantity"), Field.of("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; + +/** + * @beta + * + * Creates an expression that checks if an expression is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lte(Field.of("quantity"), 20); + * ``` + * + * @param left The expression to compare. + * @param right 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; + +/** + * 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")); + * ``` + * + * @param left The field name to compare. + * @param right 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; + +/** + * @beta + * + * Creates an expression that checks if a field's value is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'score' field is less than or equal to 70 + * lte("score", 70); + * ``` + * + * @param left The field name to compare. + * @param right 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); +} + +/** + * @beta + * + * Creates an expression that checks if the first expression is greater than the second + * expression. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * gt(Field.of("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; + +/** + * @beta + * + * Creates an expression that checks if an expression is greater than a constant value. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * gt(Field.of("age"), 18); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the greater than comparison. + */ +export function gt(left: Expr, right: any): Gt; + +/** + * @beta + * + * Creates an expression that checks if a field's value is greater than an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than the value of field 'limit' + * gt("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the greater than comparison. + */ +export function gt(left: string, right: Expr): Gt; + +/** + * @beta + * + * Creates an expression that checks if a field's value is greater than a constant value. + * + * ```typescript + * // Check if the 'price' field is greater than 100 + * gt("price", 100); + * ``` + * + * @param left The field name to compare. + * @param right 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); +} + +/** + * @beta + * + * Creates an expression that checks if the first expression is greater than or equal to the + * second expression. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to the field "threshold" + * gte(Field.of("quantity"), Field.of("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; + +/** + * @beta + * + * Creates an expression that checks if an expression is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to 10 + * gte(Field.of("quantity"), 10); + * ``` + * + * @param left The expression to compare. + * @param right 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; @@ -3057,110 +4297,48 @@ export function arrayContainsAll( /** * @beta * - * 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 both of the values from field 'tag1' and "tag2" - * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); - * ``` - * - * @param array 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: string, - values: any[] -): ArrayContainsAll; -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); -} - -/** - * @beta - * - * Creates an expression that filters elements from an array expression using the given {@link - * FilterExpr} and returns the filtered elements as a new array. - * - * ```typescript - * // Get items from the 'inventoryPrices' array where the array item is greater than 0 - * // Note we use {@link arrayElement} to represent array elements to construct a - * // filtering condition. - * arrayFilter(Field.of("inventoryPrices"), arrayElement().gt(0)); - * ``` - * - * @param array The array expression to filter. - * @param filter The {@link FilterExpr} to apply to the array elements. - * @return A new {@code Expr} representing the filtered array. - */ -export function arrayFilter(array: Expr, filter: FilterExpr): ArrayFilter { - return new ArrayFilter(array, filter); -} - -/** - * @beta - * - * Creates an expression that calculates the length of an array expression. - * - * ```typescript - * // Get the number of items in the 'cart' array - * arrayLength(Field.of("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); -} - -/** - * @beta - * - * Creates an expression that applies a transformation function to each element in an array - * expression and returns the new array as the result of the evaluation. + * Creates an expression that checks if a field's array value contains all the specified values or + * expressions. * * ```typescript - * // Convert all strings in the 'names' array to uppercase - * // Note we use {@link arrayElement} to represent array elements to construct a - * // transforming function. - * arrayTransform(Field.of("names"), arrayElement().toUppercase()); + * // Check if the 'tags' array contains both of the values from field 'tag1' and "tag2" + * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); * ``` * - * @param array The array expression to transform. - * @param transform The {@link Function} to apply to each array element. - * @return A new {@code Expr} representing the transformed array. + * @param array 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 arrayTransform( - array: Expr, - transform: Function -): ArrayTransform { - return new ArrayTransform(array, transform); +export function arrayContainsAll( + array: string, + values: any[] +): ArrayContainsAll; +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); } /** * @beta * - * Returns an expression that represents an array element within an {@link ArrayFilter} or {@link - * ArrayTransform} expression. + * Creates an expression that calculates the length of an array expression. * * ```typescript - * // Get items from the 'inventoryPrices' array where the array item is greater than 0 - * arrayFilter(Field.of("inventoryPrices"), arrayElement().gt(0)); + * // Get the number of items in the 'cart' array + * arrayLength(Field.of("cart")); * ``` * - * @return A new {@code Expr} representing an array element. + * @param array The array expression to calculate the length of. + * @return A new {@code Expr} representing the length of the array. */ -export function arrayElement(): ArrayElement { - return new ArrayElement(); +export function arrayLength(array: Expr): ArrayLength { + return new ArrayLength(array); } /** @@ -3366,157 +4544,509 @@ export function or(left: FilterExpr, ...right: FilterExpr[]): Or { * eq("status", "active")); * ``` * - * @param left The first filter condition. - * @param right Additional filter conditions to 'XOR' together. - * @return A new {@code Expr} representing the logical 'XOR' operation. + * @param left The first filter condition. + * @param right Additional filter conditions to 'XOR' together. + * @return A new {@code Expr} representing the logical 'XOR' operation. + */ +export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor { + return new Xor([left, ...right]); +} + +/** + * @beta + * + * Creates a conditional expression that evaluates to a 'then' expression if a condition is true + * and an 'else' expression if the condition is false. + * + * ```typescript + * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". + * ifFunction( + * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); + * ``` + * + * @param condition The condition to evaluate. + * @param thenExpr The expression to evaluate if the condition is true. + * @param elseExpr The expression to evaluate if the condition is false. + * @return A new {@code Expr} representing the conditional expression. + */ +export function ifFunction( + condition: FilterExpr, + thenExpr: Expr, + elseExpr: Expr +): If { + return new If(condition, thenExpr, elseExpr); +} + +/** + * @beta + * + * Creates an expression that negates a filter condition. + * + * ```typescript + * // Find documents where the 'completed' field is NOT true + * not(eq("completed", true)); + * ``` + * + * @param filter The filter condition to negate. + * @return A new {@code Expr} representing the negated filter condition. + */ +export function not(filter: FilterExpr): Not { + return new Not(filter); +} + +/** + * @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. + * logicalMax(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 logicalMax(left: Expr, right: Expr): LogicalMax; + +/** + * @beta + * + * Creates an expression that returns the larger value between an expression and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'value' field and 10. + * logicalMax(Field.of("value"), 10); + * ``` + * + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the logical max operation. + */ +export function logicalMax(left: Expr, right: any): LogicalMax; + +/** + * @beta + * + * Creates an expression that returns the larger value between a field and an expression, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'field1' field and the 'field2' field. + * logicalMax("field1", Field.of('field2')); + * ``` + * + * @param left The left operand field name. + * @param right The right operand expression. + * @return A new {@code Expr} representing the logical max operation. + */ +export function logicalMax(left: string, right: Expr): LogicalMax; + +/** + * @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. + * logicalMax("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 logicalMax(left: string, right: any): LogicalMax; +export function logicalMax(left: Expr | string, right: Expr | any): LogicalMax { + const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; + const normalizedRight = right instanceof Expr ? right : Constant.of(right); + return new LogicalMax(normalizedLeft, normalizedRight); +} + +/** + * @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. + * logicalMin(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 logicalMin(left: Expr, right: Expr): LogicalMin; + +/** + * @beta + * + * Creates an expression that returns the smaller value between an expression and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'value' field and 10. + * logicalMin(Field.of("value"), 10); + * ``` + * + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the logical min operation. + */ +export function logicalMin(left: Expr, right: any): LogicalMin; + +/** + * @beta + * + * Creates an expression that returns the smaller value between a field and an expression, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'field1' field and the 'field2' field. + * logicalMin("field1", Field.of("field2")); + * ``` + * + * @param left The left operand field name. + * @param right The right operand expression. + * @return A new {@code Expr} representing the logical min operation. + */ +export function logicalMin(left: string, right: Expr): LogicalMin; + +/** + * @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. + * logicalMin("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 logicalMin(left: string, right: any): LogicalMin; +export function logicalMin(left: Expr | string, right: Expr | any): LogicalMin { + const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; + const normalizedRight = right instanceof Expr ? right : Constant.of(right); + return new LogicalMin(normalizedLeft, normalizedRight); +} + +/** + * @beta + * + * Creates an expression that checks if a field exists. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * exists(Field.of("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; + +/** + * @beta + * + * Creates an expression that checks if a field exists. + * + * ```typescript + * // Check if the document has a field named "phoneNumber" + * exists("phoneNumber"); + * ``` + * + * @param field 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); +} + +/** + * @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.of("value").divide(0)); + * ``` + * + * @param value The expression to check. + * @return A new {@code Expr} representing the 'isNaN' check. + */ +export function isNan(value: Expr): IsNan; + +/** + * @beta + * + * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). + * + * ```typescript + * // Check if the result of a calculation is NaN + * isNaN("value"); + * ``` + * + * @param value The name of the field to check. + * @return A new {@code Expr} representing the 'isNaN' check. + */ +export function isNan(value: string): IsNan; +export function isNan(value: Expr | string): IsNan { + const valueExpr = value instanceof Expr ? value : Field.of(value); + return new IsNan(valueExpr); +} + +/** + * @beta + * + * Creates an expression that reverses a string. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * reverse(Field.of("myString")); + * ``` + * + * @param expr The expression representing the string to reverse. + * @return A new {@code Expr} representing the reversed string. + */ +export function reverse(expr: Expr): Reverse; + +/** + * @beta + * + * Creates an expression that reverses a string represented by a field. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * reverse("myString"); + * ``` + * + * @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); +} + +/** + * @beta + * + * Creates an expression that replaces the first occurrence of a substring within a string with another substring. + * + * ```typescript + * // Replace the first occurrence of "hello" with "hi" in the 'message' field. + * replaceFirst(Field.of("message"), "hello", "hi"); + * ``` + * + * @param value The expression 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( + value: Expr, + find: string, + replace: string +): ReplaceFirst; + +/** + * @beta + * + * Creates an expression that replaces the first occurrence of a substring within a string with another substring, + * where the substring to find and the replacement substring are specified by expressions. + * + * ```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")); + * ``` + * + * @param value The expression representing the string to perform the replacement on. + * @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. */ -export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor { - return new Xor([left, ...right]); -} +export function replaceFirst( + value: Expr, + find: Expr, + replace: Expr +): ReplaceFirst; /** * @beta * - * Creates a conditional expression that evaluates to a 'then' expression if a condition is true - * and an 'else' expression if the condition is false. + * Creates an expression that replaces the first occurrence of a substring within a string represented by a field with another substring. * * ```typescript - * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". - * ifFunction( - * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); + * // Replace the first occurrence of "hello" with "hi" in the 'message' field. + * replaceFirst("message", "hello", "hi"); * ``` * - * @param condition The condition to evaluate. - * @param thenExpr The expression to evaluate if the condition is true. - * @param elseExpr The expression to evaluate if the condition is false. - * @return A new {@code Expr} representing the conditional expression. + * @param field 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 ifFunction( - condition: FilterExpr, - thenExpr: Expr, - elseExpr: Expr -): If { - return new If(condition, thenExpr, elseExpr); +export function replaceFirst( + field: string, + find: string, + replace: string +): ReplaceFirst; +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); } /** * @beta * - * Creates an expression that negates a filter condition. + * Creates an expression that replaces all occurrences of a substring within a string with another substring. * * ```typescript - * // Find documents where the 'completed' field is NOT true - * not(eq("completed", true)); + * // Replace all occurrences of "hello" with "hi" in the 'message' field. + * replaceAll(Field.of("message"), "hello", "hi"); * ``` * - * @param filter The filter condition to negate. - * @return A new {@code Expr} representing the negated filter condition. + * @param value The expression 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 not(filter: FilterExpr): Not { - return new Not(filter); -} +export function replaceAll( + value: Expr, + find: string, + replace: string +): ReplaceAll; /** * @beta * - * Creates an expression that checks if a field exists. + * Creates an expression that replaces all occurrences of a substring within a string with another substring, + * where the substring to find and the replacement substring are specified by expressions. * * ```typescript - * // Check if the document has a field named "phoneNumber" - * exists(Field.of("phoneNumber")); + * // 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")); * ``` * - * @param value An expression evaluates to the name of the field to check. - * @return A new {@code Expr} representing the 'exists' check. + * @param value The expression representing the string to perform the replacement on. + * @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. */ -export function exists(value: Expr): Exists; +export function replaceAll(value: Expr, find: Expr, replace: Expr): ReplaceAll; /** * @beta * - * Creates an expression that checks if a field exists. + * Creates an expression that replaces all occurrences of a substring within a string represented by a field with another substring. * * ```typescript - * // Check if the document has a field named "phoneNumber" - * exists("phoneNumber"); + * // Replace all occurrences of "hello" with "hi" in the 'message' field. + * replaceAll("message", "hello", "hi"); * ``` * - * @param field The field name to check. - * @return A new {@code Expr} representing the 'exists' check. + * @param field 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 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 replaceAll( + field: string, + find: string, + replace: string +): ReplaceAll; +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); } /** * @beta * - * Creates an expression that checks if an expression evaluates to 'NaN' (Not a Number). + * Creates an expression that calculates the byte length of a string in UTF-8, or just the length of a Blob. * * ```typescript - * // Check if the result of a calculation is NaN - * isNaN(Field.of("value").divide(0)); + * // Calculate the length of the 'myString' field in bytes. + * byteLength(Field.of("myString")); * ``` * - * @param value The expression to check. - * @return A new {@code Expr} representing the 'isNaN' check. + * @param expr The expression representing the string. + * @return A new {@code Expr} representing the length of the string in bytes. */ -export function isNan(value: Expr): IsNan; +export function byteLength(expr: Expr): ByteLength; /** * @beta * - * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). + * Creates an expression that calculates the length of a string represented by a field in UTF-8 bytes, or just the length of a Blob. * * ```typescript - * // Check if the result of a calculation is NaN - * isNaN("value"); + * // Calculate the length of the 'myString' field in bytes. + * byteLength("myString"); * ``` * - * @param value The name of the field to check. - * @return A new {@code Expr} representing the 'isNaN' check. + * @param field The name of the field representing the string. + * @return A new {@code Expr} representing the length of the string in bytes. */ -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 byteLength(field: string): ByteLength; +export function byteLength(expr: Expr | string): ByteLength { + const normalizedExpr = typeof expr === 'string' ? Field.of(expr) : expr; + return new ByteLength(normalizedExpr); } /** * @beta * - * Creates an expression that calculates the length of a string field. + * Creates an expression that calculates the character length of a string field in UTF8. * * ```typescript - * // Get the length of the 'name' field + * // Get the character length of the 'name' field in UTF-8. * strLength("name"); * ``` * * @param field The name of the field containing the string. * @return A new {@code Expr} representing the length of the string. */ -export function strLength(field: string): StrLength; +export function charLength(field: string): CharLength; /** * @beta * - * Creates an expression that calculates the length of a string expression. + * Creates an expression that calculates the character length of a string expression in UTF-8. * * ```typescript - * // Get the length of the 'name' field + * // Get the character length of the 'name' field in UTF-8. * strLength(Field.of("name")); * ``` * * @param expr The expression representing the string to calculate the length of. * @return A new {@code Expr} representing the length of the string. */ -export function strLength(expr: Expr): StrLength; -export function strLength(value: Expr | string): StrLength { +export function charLength(expr: Expr): CharLength; +export function charLength(value: Expr | string): CharLength { const valueExpr = value instanceof Expr ? value : Field.of(value); - return new StrLength(valueExpr); + return new CharLength(valueExpr); } /** @@ -3888,13 +5418,13 @@ export function endsWith(expr: Expr | string, suffix: Expr | string): EndsWith { * * ```typescript * // Convert the 'name' field to lowercase - * toLowercase("name"); + * toLower("name"); * ``` * * @param expr The name of the field containing the string. * @return A new {@code Expr} representing the lowercase string. */ -export function toLowercase(expr: string): ToLowercase; +export function toLower(expr: string): ToLower; /** * @beta @@ -3903,15 +5433,15 @@ export function toLowercase(expr: string): ToLowercase; * * ```typescript * // Convert the 'name' field to lowercase - * toLowercase(Field.of("name")); + * toLower(Field.of("name")); * ``` * * @param expr The expression representing the string to convert to lowercase. * @return A new {@code Expr} representing the lowercase string. */ -export function toLowercase(expr: Expr): ToLowercase; -export function toLowercase(expr: Expr | string): ToLowercase { - return new ToLowercase(expr instanceof Expr ? expr : Field.of(expr)); +export function toLower(expr: Expr): ToLower; +export function toLower(expr: Expr | string): ToLower { + return new ToLower(expr instanceof Expr ? expr : Field.of(expr)); } /** @@ -3921,13 +5451,13 @@ export function toLowercase(expr: Expr | string): ToLowercase { * * ```typescript * // Convert the 'title' field to uppercase - * toUppercase("title"); + * toUpper("title"); * ``` * * @param expr The name of the field containing the string. * @return A new {@code Expr} representing the uppercase string. */ -export function toUppercase(expr: string): ToUppercase; +export function toUpper(expr: string): ToUpper; /** * @beta @@ -3942,9 +5472,9 @@ export function toUppercase(expr: string): ToUppercase; * @param expr The expression representing the string to convert to uppercase. * @return A new {@code Expr} representing the uppercase string. */ -export function toUppercase(expr: Expr): ToUppercase; -export function toUppercase(expr: Expr | string): ToUppercase { - return new ToUppercase(expr instanceof Expr ? expr : Field.of(expr)); +export function toUpper(expr: Expr): ToUpper; +export function toUpper(expr: Expr | string): ToUpper { + return new ToUpper(expr instanceof Expr ? expr : Field.of(expr)); } /** @@ -4582,6 +6112,434 @@ export function euclideanDistance( return new EuclideanDistance(expr1, expr2); } +/** + * @beta + * + * Creates an expression that calculates the length of a Firestore Vector. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * vectorLength(Field.of("embedding")); + * ``` + * + * @param expr The expression representing the Firestore Vector. + * @return A new {@code Expr} representing the length of the array. + */ +export function vectorLength(expr: Expr): VectorLength; + +/** + * @beta + * + * Creates an expression that calculates the length of a Firestore Vector represented by a field. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * vectorLength("embedding"); + * ``` + * + * @param field 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); +} + +/** + * @beta + * + * Creates an expression that interprets an expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'microseconds' field as microseconds since epoch. + * unixMicrosToTimestamp(Field.of("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; + +/** + * @beta + * + * Creates an expression that interprets a field's value as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'microseconds' field as microseconds since epoch. + * unixMicrosToTimestamp("microseconds"); + * ``` + * + * @param field 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); +} + +/** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to microseconds since epoch. + * timestampToUnixMicros(Field.of("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; + +/** + * @beta + * + * Creates an expression that converts a timestamp field to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to microseconds since epoch. + * timestampToUnixMicros("timestamp"); + * ``` + * + * @param field 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); +} + +/** + * @beta + * + * Creates an expression that interprets an expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * unixMillisToTimestamp(Field.of("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; + +/** + * @beta + * + * Creates an expression that interprets a field's value as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * unixMillisToTimestamp("milliseconds"); + * ``` + * + * @param field 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); +} + +/** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * timestampToUnixMillis(Field.of("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; + +/** + * @beta + * + * Creates an expression that converts a timestamp field to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * timestampToUnixMillis("timestamp"); + * ``` + * + * @param field 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); +} + +/** + * @beta + * + * Creates an expression that interprets an expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * unixSecondsToTimestamp(Field.of("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; + +/** + * @beta + * + * Creates an expression that interprets a field's value as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * unixSecondsToTimestamp("seconds"); + * ``` + * + * @param field 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); +} + +/** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * timestampToUnixSeconds(Field.of("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; + +/** + * @beta + * + * Creates an expression that converts a timestamp field to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * timestampToUnixSeconds("timestamp"); + * ``` + * + * @param field 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); +} + +/** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp. + * + * ```typescript + * // Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. + * timestampAdd(Field.of("timestamp"), Field.of("unit"), Field.of("amount")); + * ``` + * + * @param timestamp The expression representing the timestamp. + * @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. + */ +export function timestampAdd( + timestamp: Expr, + unit: Expr, + amount: Expr +): TimestampAdd; + +/** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp. + * + * ```typescript + * // Add 1 day to the 'timestamp' field. + * timestampAdd(Field.of("timestamp"), "day", 1); + * ``` + * + * @param timestamp The expression 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( + timestamp: Expr, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number +): TimestampAdd; + +/** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp represented by a field. + * + * ```typescript + * // Add 1 day to the 'timestamp' field. + * timestampAdd("timestamp", "day", 1); + * ``` + * + * @param field 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, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number +): TimestampAdd; +export function timestampAdd( + timestamp: Expr | string, + unit: + | Expr + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | '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 + ); +} + +/** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp. + * + * ```typescript + * // Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. + * timestampSub(Field.of("timestamp"), Field.of("unit"), Field.of("amount")); + * ``` + * + * @param timestamp The expression representing the timestamp. + * @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. + */ +export function timestampSub( + timestamp: Expr, + unit: Expr, + amount: Expr +): TimestampSub; + +/** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp. + * + * ```typescript + * // Subtract 1 day from the 'timestamp' field. + * timestampSub(Field.of("timestamp"), "day", 1); + * ``` + * + * @param timestamp The expression 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( + timestamp: Expr, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number +): TimestampSub; + +/** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp represented by a field. + * + * ```typescript + * // Subtract 1 day from the 'timestamp' field. + * timestampSub("timestamp", "day", 1); + * ``` + * + * @param field 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, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number +): TimestampSub; +export function timestampSub( + timestamp: Expr | string, + unit: + | Expr + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | '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 * diff --git a/dev/src/index.ts b/dev/src/index.ts index e2ad0d76a..cfca3b879 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -155,10 +155,7 @@ export { arrayContains, arrayContainsAny, arrayContainsAll, - arrayFilter, arrayLength, - arrayTransform, - arrayElement, inAny, notInAny, and, @@ -168,14 +165,14 @@ export { not, exists, isNan, - strLength, + charLength, like, regexContains, regexMatch, startsWith, endsWith, - toLowercase, - toUppercase, + toLower, + toUpper, trim, strConcat, mapGet, @@ -191,6 +188,28 @@ export { genericFunction, ascending, descending, + bitLeftShift, + bitOr, + bitRightShift, + bitXor, + bitAnd, + bitNot, + timestampAdd, + timestampSub, + timestampToUnixMicros, + timestampToUnixMillis, + timestampToUnixSeconds, + unixMicrosToTimestamp, + unixMillisToTimestamp, + unixSecondsToTimestamp, + logicalMax, + logicalMin, + vectorLength, + byteLength, + reverse, + replaceAll, + replaceFirst, + mod, } from './expression'; const libVersion = require('../../package.json').version; diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index b3ef9b2b0..4bc990b92 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -38,9 +38,6 @@ import { and, arrayContains, arrayContainsAny, - arrayElement, - arrayFilter, - arrayTransform, avg, countAll, endsWith, @@ -284,7 +281,7 @@ describe.only('Pipeline class', () => { const results = await randomCol .pipeline() .where(lt('published', 1900)) - .distinct(Field.of('genre').toLowercase().as('lower_genre')) + .distinct(Field.of('genre').toLower().as('lower_genre')) .execute(); expectResults( results, @@ -444,41 +441,6 @@ describe.only('Pipeline class', () => { }); }); - it('arrayFilter works', async () => { - const results = await randomCol - .pipeline() - .select( - Field.of('tags') - .arrayFilter(arrayElement().eq('comedy')) - .as('filteredTags') - ) - .limit(1) - .execute(); - - expectResults(results, { - filteredTags: ['comedy'], - }); - }); - - it('arrayTransform works', async () => { - const results = await randomCol - .pipeline() - .select( - Field.of('tags') - .arrayTransform(arrayElement().strConcat('transformed')) - .as('transformedTags') - ) - .limit(1) - .execute(); - expectResults(results, { - transformedTags: [ - 'comedytransformed', - 'spacetransformed', - 'adventuretransformed', - ], - }); - }); - it('testStrConcat', async () => { const results = await randomCol .pipeline() @@ -525,7 +487,10 @@ describe.only('Pipeline class', () => { it('testLength', async () => { const results = await randomCol .pipeline() - .select(Field.of('title').strLength().as('titleLength'), Field.of('title')) + .select( + Field.of('title').charLength().as('titleLength'), + Field.of('title') + ) .where(gt('titleLength', 20)) .execute(); expectResults( @@ -541,7 +506,7 @@ describe.only('Pipeline class', () => { it('testToLowercase', async () => { const results = await randomCol .pipeline() - .select(Field.of('title').toLowercase().as('lowercaseTitle')) + .select(Field.of('title').toLower().as('lowercaseTitle')) .limit(1) .execute(); expectResults(results, { @@ -552,7 +517,7 @@ describe.only('Pipeline class', () => { it('testToUppercase', async () => { const results = await randomCol .pipeline() - .select(Field.of('author').toUppercase().as('uppercaseAuthor')) + .select(Field.of('author').toUpper().as('uppercaseAuthor')) .limit(1) .execute(); expectResults(results, {uppercaseAuthor: 'DOUGLAS ADAMS'}); diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 5e13aa930..b8e95db59 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -3381,6 +3381,174 @@ declare namespace FirebaseFirestore { */ divide(other: any): Divide; + /** + * Creates an expression that calculates the modulo (remainder) of dividing this expression by another expression. + * + * ```typescript + * // Calculate the remainder of dividing the 'value' field by the 'divisor' field + * Field.of("value").mod(Field.of("divisor")); + * ``` + * + * @param other The expression to divide by. + * @return A new `Expr` representing the modulo operation. + */ + mod(other: Expr): Mod; + + /** + * 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); + * ``` + * + * @param other The constant value to divide by. + * @return A new `Expr` representing the modulo operation. + */ + mod(other: any): Mod; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + + /** + * 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; + /** * Creates an expression that checks if this expression is equal to another expression. * @@ -3645,22 +3813,6 @@ declare namespace FirebaseFirestore { */ arrayContainsAny(...values: any[]): ArrayContainsAny; - /** - * Creates an expression that filters elements from an array using the given {@link - * FilterCondition} and returns the filtered elements as a new array. - * - * ```typescript - * // Get items from the 'inventoryPrices' array where the array item is greater than 0 - * // Note we use {@link Function#arrayElement} to represent array elements to construct a - * // filtering condition. - * Field.of("inventoryPrices").arrayFilter(arrayElement().gt(0)); - * ``` - * - * @param filter The {@link FilterCondition} to apply to the array elements. - * @return A new `Expr` representing the filtered array. - */ - arrayFilter(filter: FilterExpr): ArrayFilter; - /** * Creates an expression that calculates the length of an array. * @@ -3673,20 +3825,6 @@ declare namespace FirebaseFirestore { */ arrayLength(): ArrayLength; - /** - * Creates an expression that applies a transformation function to each element in an array and - * returns the new array as the result of the evaluation. - * - * ```typescript - * // Convert all strings in the 'names' array to uppercase - * Field.of("names").arrayTransform(arrayElement().toUppercase()); - * ``` - * - * @param transform The {@link Function} to apply to each array element. - * @return A new `Expr` representing the transformed array. - */ - arrayTransform(transform: Function): ArrayTransform; - /** * Creates an expression that checks if this expression is equal to any of the provided values or * expressions. @@ -3740,16 +3878,16 @@ declare namespace FirebaseFirestore { exists(): Exists; /** - * Creates an expression that calculates the length of a string. + * Creates an expression that calculates the character length of a string in UTF-8. * * ```typescript - * // Get the length of the 'name' field + * // Get the character length of the 'name' field of UTF-8. * Field.of("name").strLength(); * ``` * * @return A new `Expr` representing the length of the string. */ - strLength(): StrLength; + charLength(): CharLength; /** * Creates an expression that performs a case-sensitive string comparison. @@ -3890,24 +4028,24 @@ declare namespace FirebaseFirestore { * * ```typescript * // Convert the 'name' field to lowercase - * Field.of("name").toLowerCase(); + * Field.of("name").toLower(); * ``` * * @return A new `Expr` representing the lowercase string. */ - toLowercase(): ToLowercase; + toLower(): ToLower; /** * Creates an expression that converts a string to uppercase. * * ```typescript * // Convert the 'title' field to uppercase - * Field.of("title").toUpperCase(); + * Field.of("title").toUpper(); * ``` * * @return A new `Expr` representing the uppercase string. */ - toUppercase(): ToUppercase; + toUpper(): ToUpper; /** * Creates an expression that removes leading and trailing whitespace from a string. @@ -3934,6 +4072,88 @@ declare namespace FirebaseFirestore { */ strConcat(...elements: (string | Expr)[]): StrConcat; + /** + * Creates an expression that reverses this string expression. + * + * ```typescript + * // Reverse the value of the 'myString' field. + * Field.of("myString").reverse(); + * ``` + * + * @return A new {@code Expr} representing the reversed string. + */ + reverse(): Reverse; + + /** + * Creates an expression that replaces the first occurrence of a substring within this string expression with another substring. + * + * ```typescript + * // Replace the first occurrence of "hello" with "hi" in the 'message' field + * Field.of("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; + + /** + * Creates an expression that replaces the first occurrence of a substring within this string expression with another substring, + * where the substring to find and the replacement substring are specified by expressions. + * + * ```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")); + * ``` + * + * @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; + + /** + * Creates an expression that replaces all occurrences of a substring within this string expression with another substring. + * + * ```typescript + * // Replace all occurrences of "hello" with "hi" in the 'message' field + * Field.of("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; + + /** + * Creates an expression that replaces all occurrences of a substring within this string expression with another substring, + * where the substring to find and the replacement substring are specified by expressions. + * + * ```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")); + * ``` + * + * @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; + + /** + * Creates an expression that calculates the length of this string expression in bytes. + * + * ```typescript + * // Calculate the length of the 'myString' field in bytes. + * Field.of("myString").byteLength(); + * ``` + * + * @return A new {@code Expr} representing the length of the string in bytes. + */ + byteLength(): ByteLength; + /** * Accesses a value from a map (object) field using the provided key. * @@ -4009,6 +4229,70 @@ declare namespace FirebaseFirestore { */ max(): Max; + /** + * Creates an expression that returns the larger value between this expression and another expression, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'timestamp' field and the current timestamp. + * Field.of("timestamp").logicalMax(Function.currentTimestamp()); + * ``` + * + * @param other The expression to compare with. + * @return A new {@code Expr} representing the logical max operation. + */ + logicalMax(other: Expr): LogicalMax; + + /** + * 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").logicalMax(10); + * ``` + * + * @param other The constant value to compare with. + * @return A new {@code Expr} representing the logical max operation. + */ + logicalMax(other: any): LogicalMax; + + /** + * Creates an expression that returns the smaller value between this expression and another expression, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'timestamp' field and the current timestamp. + * Field.of("timestamp").logicalMin(Function.currentTimestamp()); + * ``` + * + * @param other The expression to compare with. + * @return A new {@code Expr} representing the logical min operation. + */ + logicalMin(other: Expr): LogicalMin; + + /** + * 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").logicalMin(10); + * ``` + * + * @param other The constant value to compare with. + * @return A new {@code Expr} representing the logical min operation. + */ + logicalMin(other: any): LogicalMin; + + /** + * Creates an expression that calculates the length (number of dimensions) of this Firestore Vector expression. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * Field.of("embedding").vectorLength(); + * ``` + * + * @return A new {@code Expr} representing the length of the vector. + */ + vectorLength(): VectorLength; + /** * Calculates the cosine distance between two vectors. * @@ -4124,6 +4408,155 @@ declare namespace FirebaseFirestore { */ euclideanDistance(other: number[]): EuclideanDistance; + /** + * Creates an expression that interprets this expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'microseconds' field as microseconds since epoch. + * Field.of("microseconds").unixMicrosToTimestamp(); + * ``` + * + * @return A new {@code Expr} representing the timestamp. + */ + unixMicrosToTimestamp(): UnixMicrosToTimestamp; + + /** + * Creates an expression that converts this timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to microseconds since epoch. + * Field.of("timestamp").timestampToUnixMicros(); + * ``` + * + * @return A new {@code Expr} representing the number of microseconds since epoch. + */ + timestampToUnixMicros(): TimestampToUnixMicros; + + /** + * Creates an expression that interprets this expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * Field.of("milliseconds").unixMillisToTimestamp(); + * ``` + * + * @return A new {@code Expr} representing the timestamp. + */ + unixMillisToTimestamp(): UnixMillisToTimestamp; + + /** + * Creates an expression that converts this timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * Field.of("timestamp").timestampToUnixMillis(); + * ``` + * + * @return A new {@code Expr} representing the number of milliseconds since epoch. + */ + timestampToUnixMillis(): TimestampToUnixMillis; + + /** + * Creates an expression that interprets this expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * Field.of("seconds").unixSecondsToTimestamp(); + * ``` + * + * @return A new {@code Expr} representing the timestamp. + */ + unixSecondsToTimestamp(): UnixSecondsToTimestamp; + + /** + * Creates an expression that converts this timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * Field.of("timestamp").timestampToUnixSeconds(); + * ``` + * + * @return A new {@code Expr} representing the number of seconds since epoch. + */ + timestampToUnixSeconds(): TimestampToUnixSeconds; + + /** + * Creates an expression that adds a specified amount of time to this timestamp expression. + * + * ```typescript + * // Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. + * Field.of("timestamp").timestampAdd(Field.of("unit"), Field.of("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; + + /** + * 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); + * ``` + * + * @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. + */ + timestampAdd( + unit: + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: number + ): TimestampAdd; + + /** + * Creates an expression that subtracts a specified amount of time from this timestamp expression. + * + * ```typescript + * // Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. + * Field.of("timestamp").timestampSub(Field.of("unit"), Field.of("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; + + /** + * 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); + * ``` + * + * @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. + */ + timestampSub( + unit: + | 'microsecond' + | 'millisecond' + | 'second' + | 'minute' + | 'hour' + | 'day', + amount: number + ): TimestampSub; + /** * Creates an {@link Ordering} that sorts documents in ascending order based on this expression. * @@ -4425,8 +4858,43 @@ declare namespace FirebaseFirestore { /** * @beta */ - export class Eq extends Function implements FilterCondition { - filterable: true; + export class Mod extends Function {} + + /** + * @beta + */ + export class BitAnd extends Function {} + + /** + * @beta + */ + export class BitOr extends Function {} + + /** + * @beta + */ + export class BitXor extends Function {} + + /** + * @beta + */ + export class BitNot extends Function {} + + /** + * @beta + */ + export class BitLeftShift extends Function {} + + /** + * @beta + */ + export class BitRightShift extends Function {} + + /** + * @beta + */ + export class Eq extends Function implements FilterCondition { + filterable: true; } /** @@ -4490,26 +4958,11 @@ declare namespace FirebaseFirestore { filterable: true; } - /** - * @beta - */ - export class ArrayFilter extends Function {} - /** * @beta */ export class ArrayLength extends Function {} - /** - * @beta - */ - export class ArrayTransform extends Function {} - - /** - * @beta - */ - export class ArrayElement extends Function {} - /** * @beta */ @@ -4569,7 +5022,17 @@ declare namespace FirebaseFirestore { /** * @beta */ - export class StrLength extends Function {} + export class LogicalMax extends Function {} + + /** + * @beta + */ + export class LogicalMin extends Function {} + + /** + * @beta + */ + export class CharLength extends Function {} /** * @beta @@ -4609,12 +5072,12 @@ declare namespace FirebaseFirestore { /** * @beta */ - export class ToLowercase extends Function {} + export class ToLower extends Function {} /** * @beta */ - export class ToUppercase extends Function {} + export class ToUpper extends Function {} /** * @beta @@ -4626,6 +5089,26 @@ declare namespace FirebaseFirestore { */ export class StrConcat extends Function {} + /** + * @beta + */ + export class Reverse extends Function {} + + /** + * @beta + */ + export class ReplaceFirst extends Function {} + + /** + * @beta + */ + export class ReplaceAll extends Function {} + + /** + * @beta + */ + export class ByteLength extends Function {} + /** * @beta */ @@ -4681,6 +5164,51 @@ declare namespace FirebaseFirestore { */ export class EuclideanDistance extends Function {} + /** + * @beta + */ + export class VectorLength extends Function {} + + /** + * @beta + */ + export class UnixMicrosToTimestamp extends Function {} + + /** + * @beta + */ + export class TimestampToUnixMicros extends Function {} + + /** + * @beta + */ + export class UnixMillisToTimestamp extends Function {} + + /** + * @beta + */ + export class TimestampToUnixMillis extends Function {} + + /** + * @beta + */ + export class UnixSecondsToTimestamp extends Function {} + + /** + * @beta + */ + export class TimestampToUnixSeconds extends Function {} + + /** + * @beta + */ + export class TimestampAdd extends Function {} + + /** + * @beta + */ + export class TimestampSub extends Function {} + /** * @beta * @@ -4940,1067 +5468,1785 @@ declare namespace FirebaseFirestore { /** * @beta * - * Creates an expression that checks if two expressions are equal. + * Creates an expression that calculates the modulo (remainder) of dividing two expressions. * * ```typescript - * // Check if the 'age' field is equal to an expression - * eq(Field.of("age"), Field.of("minAge").add(10)); + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod(Field.of("field1"), Field.of("field2")); * ``` * - * @param left The first expression to compare. - * @param right The second expression to compare. - * @return A new `Expr` representing the equality comparison. + * @param left The dividend expression. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. */ - export function eq(left: Expr, right: Expr): Eq; + export function mod(left: Expr, right: Expr): Mod; /** * @beta * - * Creates an expression that checks if an expression is equal to a constant value. + * Creates an expression that calculates the modulo (remainder) of dividing an expression by a constant. * * ```typescript - * // Check if the 'age' field is equal to 21 - * eq(Field.of("age"), 21); + * // Calculate the remainder of dividing 'field1' by 5. + * mod(Field.of("field1"), 5); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the equality comparison. + * @param left The dividend expression. + * @param right The divisor constant. + * @return A new {@code Expr} representing the modulo operation. */ - export function eq(left: Expr, right: any): Eq; + export function mod(left: Expr, right: any): Mod; /** * @beta * - * Creates an expression that checks if a field's value is equal to an expression. + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by an expression. * * ```typescript - * // Check if the 'age' field is equal to the 'limit' field - * eq("age", Field.of("limit")); + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod("field1", Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the equality comparison. + * @param left The dividend field name. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. */ - export function eq(left: string, right: Expr): Eq; + export function mod(left: string, right: Expr): Mod; /** * @beta * - * Creates an expression that checks if a field's value is equal to a constant value. + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by a constant. * * ```typescript - * // Check if the 'city' field is equal to string constant "London" - * eq("city", "London"); + * // Calculate the remainder of dividing 'field1' by 5. + * mod("field1", 5); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the equality comparison. + * @param left The dividend field name. + * @param right The divisor constant. + * @return A new {@code Expr} representing the modulo operation. */ - export function eq(left: string, right: any): Eq; + export function mod(left: string, right: any): Mod; /** * @beta * - * Creates an expression that checks if two expressions are not equal. + * Creates an expression that applies a bitwise AND operation between two expressions. * * ```typescript - * // Check if the 'status' field is not equal to field 'finalState' - * neq(Field.of("status"), Field.of("finalState")); + * // Calculate the bitwise AND of 'field1' and 'field2'. + * bitAnd(Field.of("field1"), Field.of("field2")); * ``` * - * @param left The first expression to compare. - * @param right The second expression to compare. - * @return A new `Expr` representing the inequality comparison. + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the bitwise AND operation. */ - export function neq(left: Expr, right: Expr): Neq; + export function bitAnd(left: Expr, right: Expr): BitAnd; /** * @beta * - * Creates an expression that checks if an expression is not equal to a constant value. + * Creates an expression that applies a bitwise AND operation between an expression and a constant. * * ```typescript - * // Check if the 'status' field is not equal to "completed" - * neq(Field.of("status"), "completed"); + * // Calculate the bitwise AND of 'field1' and 0xFF. + * bitAnd(Field.of("field1"), 0xFF); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the inequality comparison. + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the bitwise AND operation. */ - export function neq(left: Expr, right: any): Neq; + export function bitAnd(left: Expr, right: any): BitAnd; /** * @beta * - * Creates an expression that checks if a field's value is not equal to an expression. + * Creates an expression that applies a bitwise AND operation between a field and an expression. * * ```typescript - * // Check if the 'status' field is not equal to the value of 'expectedStatus' - * neq("status", Field.of("expectedStatus")); + * // Calculate the bitwise AND of 'field1' and 'field2'. + * bitAnd("field1", Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the inequality comparison. + * @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 neq(left: string, right: Expr): Neq; + export function bitAnd(left: string, right: Expr): BitAnd; /** * @beta * - * Creates an expression that checks if a field's value is not equal to a constant value. + * Creates an expression that applies a bitwise AND operation between a field and a constant. * * ```typescript - * // Check if the 'country' field is not equal to "USA" - * neq("country", "USA"); + * // Calculate the bitwise AND of 'field1' and 0xFF. + * bitAnd("field1", 0xFF); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the inequality comparison. + * @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 neq(left: string, right: any): Neq; + export function bitAnd(left: string, right: any): BitAnd; /** * @beta * - * Creates an expression that checks if the first expression is less than the second expression. + * Creates an expression that applies a bitwise OR operation between two expressions. * * ```typescript - * // Check if the 'age' field is less than 30 - * lt(Field.of("age"), Field.of("limit")); + * // Calculate the bitwise OR of 'field1' and 'field2'. + * bitOr(Field.of("field1"), Field.of("field2")); * ``` * - * @param left The first expression to compare. - * @param right The second expression to compare. - * @return A new `Expr` representing the less than comparison. + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the bitwise OR operation. */ - export function lt(left: Expr, right: Expr): Lt; + export function bitOr(left: Expr, right: Expr): BitOr; /** * @beta * - * Creates an expression that checks if an expression is less than a constant value. + * Creates an expression that applies a bitwise OR operation between an expression and a constant. * * ```typescript - * // Check if the 'age' field is less than 30 - * lt(Field.of("age"), 30); + * // Calculate the bitwise OR of 'field1' and 0xFF. + * bitOr(Field.of("field1"), 0xFF); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the less than comparison. + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the bitwise OR operation. */ - export function lt(left: Expr, right: any): Lt; + export function bitOr(left: Expr, right: any): BitOr; /** * @beta * - * Creates an expression that checks if a field's value is less than an expression. + * Creates an expression that applies a bitwise OR operation between a field and an expression. * * ```typescript - * // Check if the 'age' field is less than the 'limit' field - * lt("age", Field.of("limit")); + * // Calculate the bitwise OR of 'field1' and 'field2'. + * bitOr("field1", Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the less than comparison. + * @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 lt(left: string, right: Expr): Lt; + export function bitOr(left: string, right: Expr): BitOr; /** * @beta * - * Creates an expression that checks if a field's value is less than a constant value. + * Creates an expression that applies a bitwise OR operation between a field and a constant. * * ```typescript - * // Check if the 'price' field is less than 50 - * lt("price", 50); + * // Calculate the bitwise OR of 'field1' and 0xFF. + * bitOr("field1", 0xFF); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the less than comparison. + * @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 lt(left: string, right: any): Lt; + export function bitOr(left: string, right: any): BitOr; /** * @beta * - * Creates an expression that checks if the first expression is less than or equal to the second - * expression. + * Creates an expression that applies a bitwise XOR operation between two expressions. * * ```typescript - * // Check if the 'quantity' field is less than or equal to 20 - * lte(Field.of("quantity"), Field.of("limit")); + * // Calculate the bitwise XOR of 'field1' and 'field2'. + * bitXor(Field.of("field1"), Field.of("field2")); * ``` * - * @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. + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the bitwise XOR operation. */ - export function lte(left: Expr, right: Expr): Lte; + export function bitXor(left: Expr, right: Expr): BitXor; /** * @beta * - * Creates an expression that checks if an expression is less than or equal to a constant value. + * Creates an expression that applies a bitwise XOR operation between an expression and a constant. * * ```typescript - * // Check if the 'quantity' field is less than or equal to 20 - * lte(Field.of("quantity"), 20); + * // Calculate the bitwise XOR of 'field1' and 0xFF. + * bitXor(Field.of("field1"), 0xFF); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the less than or equal to comparison. + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the bitwise XOR operation. */ - export function lte(left: Expr, right: any): Lte; + export function bitXor(left: Expr, right: any): BitXor; /** - * Creates an expression that checks if a field's value is less than or equal to an expression. + * @beta + * + * Creates an expression that applies a bitwise XOR operation between a field and an expression. * * ```typescript - * // Check if the 'quantity' field is less than or equal to the 'limit' field - * lte("quantity", Field.of("limit")); + * // Calculate the bitwise XOR of 'field1' and 'field2'. + * bitXor("field1", Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the less than or equal to comparison. + * @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 lte(left: string, right: Expr): Lte; + export function bitXor(left: string, right: Expr): BitXor; /** * @beta * - * Creates an expression that checks if a field's value is less than or equal to a constant value. + * Creates an expression that applies a bitwise XOR operation between a field and a constant. * * ```typescript - * // Check if the 'score' field is less than or equal to 70 - * lte("score", 70); + * // Calculate the bitwise XOR of 'field1' and 0xFF. + * bitXor("field1", 0xFF); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the less than or equal to comparison. + * @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 lte(left: string, right: any): Lte; + export function bitXor(left: string, right: any): BitXor; /** * @beta * - * Creates an expression that checks if the first expression is greater than the second - * expression. + * Creates an expression that applies a bitwise NOT operation to an expression. * * ```typescript - * // Check if the 'age' field is greater than 18 - * gt(Field.of("age"), Constant(9).add(9)); + * // Calculate the bitwise NOT of 'field1'. + * bitNot(Field.of("field1")); * ``` * - * @param left The first expression to compare. - * @param right The second expression to compare. - * @return A new `Expr` representing the greater than comparison. + * @param operand The operand expression. + * @return A new {@code Expr} representing the bitwise NOT operation. */ - export function gt(left: Expr, right: Expr): Gt; + export function bitNot(operand: Expr): BitNot; /** * @beta * - * Creates an expression that checks if an expression is greater than a constant value. + * Creates an expression that applies a bitwise NOT operation to a field. * * ```typescript - * // Check if the 'age' field is greater than 18 - * gt(Field.of("age"), 18); + * // Calculate the bitwise NOT of 'field1'. + * bitNot("field1"); * ``` * - * @param left The expression to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the greater than comparison. + * @param operand The operand field name. + * @return A new {@code Expr} representing the bitwise NOT operation. */ - export function gt(left: Expr, right: any): Gt; + export function bitNot(operand: string): BitNot; /** * @beta * - * Creates an expression that checks if a field's value is greater than an expression. + * Creates an expression that applies a bitwise left shift operation between two expressions. * * ```typescript - * // Check if the value of field 'age' is greater than the value of field 'limit' - * gt("age", Field.of("limit")); + * // Calculate the bitwise left shift of 'field1' by 'field2' bits. + * bitLeftShift(Field.of("field1"), Field.of("field2")); * ``` * - * @param left The field name to compare. - * @param right The expression to compare to. - * @return A new `Expr` representing the greater than comparison. + * @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 gt(left: string, right: Expr): Gt; + export function bitLeftShift(left: Expr, right: Expr): BitLeftShift; /** * @beta * - * Creates an expression that checks if a field's value is greater than a constant value. + * Creates an expression that applies a bitwise left shift operation between an expression and a constant. * * ```typescript - * // Check if the 'price' field is greater than 100 - * gt("price", 100); + * // Calculate the bitwise left shift of 'field1' by 2 bits. + * bitLeftShift(Field.of("field1"), 2); * ``` * - * @param left The field name to compare. - * @param right The constant value to compare to. - * @return A new `Expr` representing the greater than comparison. + * @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 gt(left: string, right: any): Gt; + export function bitLeftShift(left: Expr, right: any): BitLeftShift; /** * @beta * - * Creates an expression that checks if the first expression is greater than or equal to the - * second expression. + * Creates an expression that applies a bitwise left shift operation between a field and an expression. * * ```typescript - * // Check if the 'quantity' field is greater than or equal to the field "threshold" - * gte(Field.of("quantity"), Field.of("threshold")); + * // Calculate the bitwise left shift of 'field1' by 'field2' bits. + * bitLeftShift("field1", Field.of("field2")); * ``` * - * @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. + * @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 gte(left: Expr, right: Expr): Gte; + export function bitLeftShift(left: string, right: Expr): BitLeftShift; /** * @beta * - * Creates an expression that checks if an expression is greater than or equal to a constant - * value. + * Creates an expression that applies a bitwise left shift operation between a field and a constant. * * ```typescript - * // Check if the 'quantity' field is greater than or equal to 10 - * gte(Field.of("quantity"), 10); + * // 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; + + /** + * @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; + + /** + * @beta + * + * Creates an expression that checks if two expressions are equal. + * + * ```typescript + * // Check if the 'age' field is equal to an expression + * eq(Field.of("age"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is equal to a constant value. + * + * ```typescript + * // Check if the 'age' field is equal to 21 + * eq(Field.of("age"), 21); * ``` * * @param left The expression to compare. * @param right The constant value to compare to. - * @return A new `Expr` representing the greater than or equal to comparison. + * @return A new `Expr` representing the equality comparison. */ - export function gte(left: Expr, right: any): Gte; + export function eq(left: Expr, right: any): Eq; /** * @beta * - * Creates an expression that checks if a field's value is greater than or equal to an expression. + * Creates an expression that checks if a field's value is equal to an expression. * * ```typescript - * // Check if the value of field 'age' is greater than or equal to the value of field 'limit' - * gte("age", Field.of("limit")); + * // Check if the 'age' field is equal to the 'limit' field + * eq("age", Field.of("limit")); * ``` * * @param left The field name to compare. * @param right The expression to compare to. - * @return A new `Expr` representing the greater than or equal to comparison. + * @return A new `Expr` representing the equality comparison. */ - export function gte(left: string, right: Expr): Gte; + export function eq(left: string, right: Expr): Eq; /** * @beta * - * Creates an expression that checks if a field's value is greater than or equal to a constant - * value. + * Creates an expression that checks if a field's value is equal to a constant value. * * ```typescript - * // Check if the 'score' field is greater than or equal to 80 - * gte("score", 80); + * // Check if the 'city' field is equal to string constant "London" + * eq("city", "London"); * ``` * * @param left The field name to compare. * @param right The constant value to compare to. - * @return A new `Expr` representing the greater than or equal to comparison. + * @return A new `Expr` representing the equality comparison. */ - export function gte(left: string, right: any): Gte; + export function eq(left: string, right: any): Eq; /** * @beta * - * Creates an expression that concatenates an array expression with other arrays. + * Creates an expression that checks if two expressions are not equal. * * ```typescript - * // Combine the 'items' array with two new item arrays - * arrayConcat(Field.of("items"), [Field.of("newItems"), Field.of("otherItems")]); + * // Check if the 'status' field is not equal to field 'finalState' + * neq(Field.of("status"), Field.of("finalState")); * ``` * - * @param array The array expression to concatenate to. - * @param elements The array expressions to concatenate. - * @return A new {@code Expr} representing the concatenated array. + * @param left The first expression to compare. + * @param right The second expression to compare. + * @return A new `Expr` representing the inequality comparison. */ - export function arrayConcat(array: Expr, elements: Expr[]): ArrayConcat; + export function neq(left: Expr, right: Expr): Neq; /** * @beta * - * Creates an expression that concatenates an array expression with other arrays and/or values. + * Creates an expression that checks if an expression is not equal to a constant value. * * ```typescript - * // Combine the 'tags' array with a new array - * arrayConcat(Field.of("tags"), ["newTag1", "newTag2"]); + * // Check if the 'status' field is not equal to "completed" + * neq(Field.of("status"), "completed"); * ``` * - * @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. + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the inequality comparison. */ - export function arrayConcat(array: Expr, elements: any[]): ArrayConcat; + export function neq(left: Expr, right: any): Neq; /** * @beta * - * Creates an expression that concatenates a field's array value with other arrays. + * Creates an expression that checks if a field's value is not equal to an expression. * * ```typescript - * // Combine the 'items' array with two new item arrays - * arrayConcat("items", [Field.of("newItems"), Field.of("otherItems")]); + * // Check if the 'status' field is not equal to the value of 'expectedStatus' + * neq("status", Field.of("expectedStatus")); * ``` * - * @param array The field name containing array values. - * @param elements The array expressions to concatenate. - * @return A new {@code Expr} representing the concatenated array. + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the inequality comparison. */ - export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; + export function neq(left: string, right: Expr): Neq; /** * @beta * - * Creates an expression that concatenates a field's array value with other arrays and/or values. + * Creates an expression that checks if a field's value is not equal to a constant value. * * ```typescript - * // Combine the 'tags' array with a new array - * arrayConcat("tags", ["newTag1", "newTag2"]); + * // Check if the 'country' field is not equal to "USA" + * neq("country", "USA"); * ``` * - * @param array The field name containing array values. - * @param elements The array expressions or single values to concatenate. - * @return A new {@code Expr} representing the concatenated array. + * @param left The field name to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the inequality comparison. */ - export function arrayConcat(array: string, elements: any[]): ArrayConcat; + export function neq(left: string, right: any): Neq; /** * @beta * - * Creates an expression that checks if an array expression contains a specific element. + * Creates an expression that checks if the first expression is less than the second expression. * * ```typescript - * // Check if the 'colors' array contains the value of field 'selectedColor' - * arrayContains(Field.of("colors"), Field.of("selectedColor")); + * // Check if the 'age' field is less than 30 + * lt(Field.of("age"), Field.of("limit")); * ``` * - * @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. + * @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 arrayContains(array: Expr, element: Expr): ArrayContains; + export function lt(left: Expr, right: Expr): Lt; /** * @beta * - * Creates an expression that checks if an array expression contains a specific element. + * Creates an expression that checks if an expression is less than a constant value. * * ```typescript - * // Check if the 'colors' array contains "red" - * arrayContains(Field.of("colors"), "red"); + * // Check if the 'age' field is less than 30 + * lt(Field.of("age"), 30); * ``` * - * @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. + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the less than comparison. */ - export function arrayContains(array: Expr, element: any): ArrayContains; + export function lt(left: Expr, right: any): Lt; /** * @beta * - * Creates an expression that checks if a field's array value contains a specific element. + * Creates an expression that checks if a field's value is less than an expression. * * ```typescript - * // Check if the 'colors' array contains the value of field 'selectedColor' - * arrayContains("colors", Field.of("selectedColor")); + * // Check if the 'age' field is less than the 'limit' field + * lt("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the less than comparison. + */ + export function lt(left: string, right: Expr): Lt; + + /** + * @beta + * + * Creates an expression that checks if a field's value is less than a constant value. + * + * ```typescript + * // Check if the 'price' field is less than 50 + * lt("price", 50); + * ``` + * + * @param left The field name to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the less than comparison. + */ + export function lt(left: string, right: any): Lt; + + /** + * @beta + * + * Creates an expression that checks if the first expression is less than or equal to the second + * expression. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lte(Field.of("quantity"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'quantity' field is less than or equal to 20 + * lte(Field.of("quantity"), 20); + * ``` + * + * @param left The expression to compare. + * @param right 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; + + /** + * 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")); + * ``` + * + * @param left The field name to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value is less than or equal to a constant value. + * + * ```typescript + * // Check if the 'score' field is less than or equal to 70 + * lte("score", 70); + * ``` + * + * @param left The field name to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that checks if the first expression is greater than the second + * expression. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * gt(Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is greater than a constant value. + * + * ```typescript + * // Check if the 'age' field is greater than 18 + * gt(Field.of("age"), 18); + * ``` + * + * @param left The expression to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the greater than comparison. + */ + export function gt(left: Expr, right: any): Gt; + + /** + * @beta + * + * Creates an expression that checks if a field's value is greater than an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than the value of field 'limit' + * gt("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right The expression to compare to. + * @return A new `Expr` representing the greater than comparison. + */ + export function gt(left: string, right: Expr): Gt; + + /** + * @beta + * + * Creates an expression that checks if a field's value is greater than a constant value. + * + * ```typescript + * // Check if the 'price' field is greater than 100 + * gt("price", 100); + * ``` + * + * @param left The field name to compare. + * @param right The constant value to compare to. + * @return A new `Expr` representing the greater than comparison. + */ + export function gt(left: string, right: any): Gt; + + /** + * @beta + * + * Creates an expression that checks if the first expression is greater than or equal to the + * second expression. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to the field "threshold" + * gte(Field.of("quantity"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'quantity' field is greater than or equal to 10 + * gte(Field.of("quantity"), 10); + * ``` + * + * @param left The expression to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value is greater than or equal to an expression. + * + * ```typescript + * // Check if the value of field 'age' is greater than or equal to the value of field 'limit' + * gte("age", Field.of("limit")); + * ``` + * + * @param left The field name to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that checks if a field's value is greater than or equal to a constant + * value. + * + * ```typescript + * // Check if the 'score' field is greater than or equal to 80 + * gte("score", 80); + * ``` + * + * @param left The field name to compare. + * @param right 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; + + /** + * @beta + * + * Creates an expression that concatenates an array expression with other arrays. + * + * ```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")]); + * ``` + * + * @param array The field name containing array values. + * @param elements The array expressions to concatenate. + * @return A new {@code Expr} representing the concatenated array. + */ + export function arrayConcat(array: string, elements: Expr[]): ArrayConcat; + + /** + * @beta + * + * Creates an expression that concatenates a field's array value with other arrays and/or values. + * + * ```typescript + * // Combine the 'tags' array with a new array + * arrayConcat("tags", ["newTag1", "newTag2"]); + * ``` + * + * @param array The field name containing array values. + * @param elements The array expressions or single values to concatenate. + * @return A new {@code Expr} representing the concatenated array. + */ + export function arrayConcat(array: string, elements: any[]): ArrayConcat; + + /** + * @beta + * + * Creates an expression that checks if an array expression contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains the value of field 'selectedColor' + * arrayContains(Field.of("colors"), Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an array expression contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * arrayContains(Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if a field's array value contains a specific element. + * + * ```typescript + * // Check if the 'colors' array contains the value of field 'selectedColor' + * arrayContains("colors", Field.of("selectedColor")); + * ``` + * + * @param array 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; + + /** + * @beta + * + * Creates an expression that checks if a field's array value contains a specific value. + * + * ```typescript + * // Check if the 'colors' array contains "red" + * arrayContains("colors", "red"); + * ``` + * + * @param array 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; + + /** + * @beta + * + * Creates an expression that checks if an array expression 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"]); + * ``` + * + * @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; + + /** + * @beta + * + * Creates an expression that checks if an array expression 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"]); + * ``` + * + * @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: any[] + ): ArrayContainsAny; + + /** + * @beta + * + * Creates an expression that checks if a field's array value 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"]); + * ``` + * + * @param array 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: string, + values: Expr[] + ): ArrayContainsAny; + + /** + * @beta + * + * Creates an expression that checks if a field's array value 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"]); + * ``` + * + * @param array 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: string, + values: any[] + ): ArrayContainsAny; + + /** + * @beta + * + * 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', 'tag2' and "tag3" + * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "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; + + /** + * @beta + * + * 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', 'tag2' and "tag3" + * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "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: any[] + ): ArrayContainsAll; + + /** + * @beta + * + * 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 both of the values from field 'tag1' and "tag2" + * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * ``` + * + * @param array 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: string, + values: Expr[] + ): ArrayContainsAll; + + /** + * @beta + * + * 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 both of the values from field 'tag1' and "tag2" + * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * ``` + * + * @param array 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: string, + values: any[] + ): ArrayContainsAll; + + /** + * @beta + * + * Creates an expression that calculates the length of an array expression. + * + * ```typescript + * // Get the number of items in the 'cart' array + * arrayLength(Field.of("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; + + /** + * @beta + * + * Creates an expression that checks if an expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ + export function inAny(element: Expr, others: Expr[]): In; + + /** + * @beta + * + * Creates an expression that checks if an expression is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ + export function inAny(element: Expr, others: any[]): In; + + /** + * @beta + * + * Creates an expression that checks if a field's value is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); + * ``` + * + * @param element The field to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ + export function inAny(element: string, others: Expr[]): In; + + /** + * @beta + * + * Creates an expression that checks if a field's value is equal to any of the provided values or + * expressions. + * + * ```typescript + * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' + * inAny("category", ["Electronics", Field.of("primaryType")]); + * ``` + * + * @param element The field to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'IN' comparison. + */ + export function inAny(element: string, others: any[]): In; + + /** + * @beta + * + * 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' + * notInAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ + export function notInAny(element: Expr, others: Expr[]): Not; + + /** + * @beta + * + * 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' + * notInAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); + * ``` + * + * @param element The expression to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. + */ + export function notInAny(element: Expr, others: any[]): Not; + + /** + * @beta + * + * 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' + * notInAny("status", [Constant.of("pending"), Field.of("rejectedStatus")]); * ``` * - * @param array 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. + * @param element The field name to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. */ - export function arrayContains(array: string, element: Expr): ArrayContains; + export function notInAny(element: string, others: Expr[]): Not; /** * @beta * - * Creates an expression that checks if a field's array value contains a specific value. + * 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 'colors' array contains "red" - * arrayContains("colors", "red"); + * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' + * notInAny("status", ["pending", Field.of("rejectedStatus")]); * ``` * - * @param array 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. + * @param element The field name to compare. + * @param others The values to check against. + * @return A new {@code Expr} representing the 'NOT IN' comparison. */ - export function arrayContains(array: string, element: any): ArrayContains; + export function notInAny(element: string, others: any[]): Not; /** * @beta * - * Creates an expression that checks if an array expression contains any of the specified - * elements. + * Creates an expression that performs a logical 'AND' operation on multiple filter conditions. * * ```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 'age' field is greater than 18 AND the 'city' field is "London" AND + * // the 'status' field is "active" + * const condition = and(gt("age", 18), eq("city", "London"), eq("status", "active")); * ``` * - * @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. + * @param left The first filter condition. + * @param right Additional filter conditions to 'AND' together. + * @return A new {@code Expr} representing the logical 'AND' operation. */ - export function arrayContainsAny( - array: Expr, - values: Expr[] - ): ArrayContainsAny; + export function and(left: FilterExpr, ...right: FilterExpr[]): And; /** * @beta * - * Creates an expression that checks if an array expression contains any of the specified - * elements. + * Creates an expression that performs a logical 'OR' operation on multiple filter conditions. * * ```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 'age' field is greater than 18 OR the 'city' field is "London" OR + * // the 'status' field is "active" + * const condition = or(gt("age", 18), eq("city", "London"), eq("status", "active")); * ``` * - * @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. + * @param left The first filter condition. + * @param right Additional filter conditions to 'OR' together. + * @return A new {@code Expr} representing the logical 'OR' operation. */ - export function arrayContainsAny( - array: Expr, - values: any[] - ): ArrayContainsAny; + export function or(left: FilterExpr, ...right: FilterExpr[]): Or; /** * @beta * - * Creates an expression that checks if a field's array value contains any of the specified - * elements. + * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple filter + * conditions. * * ```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 only one of the conditions is true: 'age' greater than 18, 'city' is "London", + * // or 'status' is "active". + * const condition = xor( + * gt("age", 18), + * eq("city", "London"), + * eq("status", "active")); * ``` * - * @param array 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. + * @param left The first filter condition. + * @param right Additional filter conditions to 'XOR' together. + * @return A new {@code Expr} representing the logical 'XOR' operation. */ - export function arrayContainsAny( - array: string, - values: Expr[] - ): ArrayContainsAny; + export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor; /** * @beta * - * Creates an expression that checks if a field's array value contains any of the specified - * elements. + * Creates a conditional expression that evaluates to a 'then' expression if a condition is true + * and an 'else' expression if the condition is false. * * ```typescript - * // Check if the 'groups' array contains either the value from the 'userGroup' field - * // or the value "guest" - * arrayContainsAny("categories", [Field.of("cate1"), "Science"]); + * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". + * ifFunction( + * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); * ``` * - * @param array 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. + * @param condition The condition to evaluate. + * @param thenExpr The expression to evaluate if the condition is true. + * @param elseExpr The expression to evaluate if the condition is false. + * @return A new {@code Expr} representing the conditional expression. */ - export function arrayContainsAny( - array: string, - values: any[] - ): ArrayContainsAny; + export function ifFunction( + condition: FilterExpr, + thenExpr: Expr, + elseExpr: Expr + ): If; /** * @beta * - * Creates an expression that checks if an array expression contains all the specified elements. + * Creates an expression that negates a filter condition. * * ```typescript - * // Check if the 'tags' array contains both of the values from field 'tag1', 'tag2' and "tag3" - * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "SciFi", "Adventure"]); + * // Find documents where the 'completed' field is NOT true + * not(eq("completed", true)); * ``` * - * @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. + * @param filter The filter condition to negate. + * @return A new {@code Expr} representing the negated filter condition. */ - export function arrayContainsAll( - array: Expr, - values: Expr[] - ): ArrayContainsAll; + export function not(filter: FilterExpr): Not; /** * @beta * - * Creates an expression that checks if an array expression contains all the specified elements. + * Creates an expression that returns the larger value between two expressions, based on Firestore's value type ordering. * * ```typescript - * // Check if the 'tags' array contains both of the values from field 'tag1', 'tag2' and "tag3" - * arrayContainsAll(Field.of("tags"), [Field.of("tag1"), "SciFi", "Adventure"]); + * // Returns the larger value between the 'timestamp' field and the current timestamp. + * logicalMax(Field.of("timestamp"), Function.currentTimestamp()); * ``` * - * @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. + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the logical max operation. */ - export function arrayContainsAll( - array: Expr, - values: any[] - ): ArrayContainsAll; + export function logicalMax(left: Expr, right: Expr): LogicalMax; /** * @beta * - * Creates an expression that checks if a field's array value contains all the specified values or - * expressions. + * Creates an expression that returns the larger value between an expression and a constant value, based on Firestore's value type ordering. * * ```typescript - * // Check if the 'tags' array contains both of the values from field 'tag1' and "tag2" - * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * // Returns the larger value between the 'value' field and 10. + * logicalMax(Field.of("value"), 10); * ``` * - * @param array 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. + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the logical max operation. */ - export function arrayContainsAll( - array: string, - values: Expr[] - ): ArrayContainsAll; + export function logicalMax(left: Expr, right: any): LogicalMax; /** * @beta * - * Creates an expression that checks if a field's array value contains all the specified values or - * expressions. + * Creates an expression that returns the larger value between a field and an expression, based on Firestore's value type ordering. * * ```typescript - * // Check if the 'tags' array contains both of the values from field 'tag1' and "tag2" - * arrayContainsAll("tags", [Field.of("tag1"), "SciFi", "Adventure"]); + * // Returns the larger value between the 'timestamp' field and the current timestamp. + * logicalMax("timestamp", Function.currentTimestamp()); * ``` * - * @param array 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. + * @param left The left operand field name. + * @param right The right operand expression. + * @return A new {@code Expr} representing the logical max operation. */ - export function arrayContainsAll( - array: string, - values: any[] - ): ArrayContainsAll; + export function logicalMax(left: string, right: Expr): LogicalMax; /** * @beta * - * Creates an expression that filters elements from an array expression using the given {@link - * FilterExpr} and returns the filtered elements as a new array. + * Creates an expression that returns the larger value between a field and a constant value, based on Firestore's value type ordering. * * ```typescript - * // Get items from the 'inventoryPrices' array where the array item is greater than 0 - * // Note we use {@link arrayElement} to represent array elements to construct a - * // filtering condition. - * arrayFilter(Field.of("inventoryPrices"), arrayElement().gt(0)); + * // Returns the larger value between the 'value' field and 10. + * logicalMax("value", 10); * ``` * - * @param array The array expression to filter. - * @param filter The {@link FilterExpr} to apply to the array elements. - * @return A new {@code Expr} representing the filtered array. + * @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 arrayFilter(array: Expr, filter: FilterExpr): ArrayFilter; + export function logicalMax(left: string, right: any): LogicalMax; /** * @beta * - * Creates an expression that calculates the length of an array expression. + * Creates an expression that returns the smaller value between two expressions, based on Firestore's value type ordering. * * ```typescript - * // Get the number of items in the 'cart' array - * arrayLength(Field.of("cart")); + * // Returns the smaller value between the 'timestamp' field and the current timestamp. + * logicalMin(Field.of("timestamp"), Function.currentTimestamp()); * ``` * - * @param array The array expression to calculate the length of. - * @return A new {@code Expr} representing the length of the array. + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the logical min operation. */ - export function arrayLength(array: Expr): ArrayLength; + export function logicalMin(left: Expr, right: Expr): LogicalMin; /** * @beta * - * Creates an expression that applies a transformation function to each element in an array - * expression and returns the new array as the result of the evaluation. + * Creates an expression that returns the smaller value between an expression and a constant value, based on Firestore's value type ordering. * * ```typescript - * // Convert all strings in the 'names' array to uppercase - * // Note we use {@link arrayElement} to represent array elements to construct a - * // transforming function. - * arrayTransform(Field.of("names"), arrayElement().toUppercase()); + * // Returns the smaller value between the 'value' field and 10. + * logicalMin(Field.of("value"), 10); * ``` * - * @param array The array expression to transform. - * @param transform The {@link Function} to apply to each array element. - * @return A new {@code Expr} representing the transformed array. + * @param left The left operand expression. + * @param right The right operand constant. + * @return A new {@code Expr} representing the logical min operation. */ - export function arrayTransform( - array: Expr, - transform: Function - ): ArrayTransform; + export function logicalMin(left: Expr, right: any): LogicalMin; /** * @beta * - * Returns an expression that represents an array element within an {@link ArrayFilter} or {@link - * ArrayTransform} expression. + * Creates an expression that returns the smaller value between a field and an expression, based on Firestore's value type ordering. * * ```typescript - * // Get items from the 'inventoryPrices' array where the array item is greater than 0 - * arrayFilter(Field.of("inventoryPrices"), arrayElement().gt(0)); + * // Returns the smaller value between the 'timestamp' field and the current timestamp. + * logicalMin("timestamp", Function.currentTimestamp()); * ``` * - * @return A new {@code Expr} representing an array element. + * @param left The left operand field name. + * @param right The right operand expression. + * @return A new {@code Expr} representing the logical min operation. */ - export function arrayElement(): ArrayElement; + export function logicalMin(left: string, right: Expr): LogicalMin; /** * @beta * - * Creates an expression that checks if an expression is equal to any of the provided values or - * expressions. + * Creates an expression that returns the smaller value between a field and a constant value, based on Firestore's value type ordering. * * ```typescript - * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); + * // Returns the smaller value between the 'value' field and 10. + * logicalMin("value", 10); * ``` * - * @param element The expression to compare. - * @param others The values to check against. - * @return A new {@code Expr} representing the 'IN' comparison. + * @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 inAny(element: Expr, others: Expr[]): In; + export function logicalMin(left: string, right: any): LogicalMin; /** * @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 a field exists. * * ```typescript - * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); + * // Check if the document has a field named "phoneNumber" + * exists(Field.of("phoneNumber")); * ``` * - * @param element The expression to compare. - * @param others The values to check against. - * @return A new {@code Expr} representing the 'IN' comparison. + * @param value An expression evaluates to the name of the field to check. + * @return A new {@code Expr} representing the 'exists' check. */ - export function inAny(element: Expr, others: any[]): In; + export function exists(value: Expr): Exists; /** * @beta * - * Creates an expression that checks if a field's value is equal to any of the provided values or - * expressions. + * Creates an expression that checks if a field exists. * * ```typescript - * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); + * // Check if the document has a field named "phoneNumber" + * exists("phoneNumber"); * ``` * - * @param element The field to compare. - * @param others The values to check against. - * @return A new {@code Expr} representing the 'IN' comparison. + * @param field The field name to check. + * @return A new {@code Expr} representing the 'exists' check. */ - export function inAny(element: string, others: Expr[]): In; + export function exists(field: string): Exists; /** * @beta * - * Creates an expression that checks if a field's value is equal to any of the provided values or - * expressions. + * Creates an expression that checks if an expression evaluates to 'NaN' (Not a Number). * * ```typescript - * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny("category", ["Electronics", Field.of("primaryType")]); + * // Check if the result of a calculation is NaN + * isNaN(Field.of("value").divide(0)); * ``` * - * @param element The field to compare. - * @param others The values to check against. - * @return A new {@code Expr} representing the 'IN' comparison. + * @param value The expression to check. + * @return A new {@code Expr} representing the 'isNaN' check. */ - export function inAny(element: string, others: any[]): In; + export function isNaN(value: Expr): IsNan; /** * @beta * - * Creates an expression that checks if an expression is not equal to any of the provided values - * or expressions. + * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). * * ```typescript - * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); + * // Check if the result of a calculation is NaN + * isNaN("value"); * ``` * - * @param element The expression to compare. - * @param others The values to check against. - * @return A new {@code Expr} representing the 'NOT IN' comparison. + * @param value The name of the field to check. + * @return A new {@code Expr} representing the 'isNaN' check. */ - export function notInAny(element: Expr, others: Expr[]): Not; + export function isNaN(value: string): IsNan; /** * @beta * - * Creates an expression that checks if an expression is not equal to any of the provided values - * or expressions. + * Creates an expression that reverses a string. * * ```typescript - * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); + * // Reverse the value of the 'myString' field. + * reverse(Field.of("myString")); * ``` * - * @param element The expression to compare. - * @param others The values to check against. - * @return A new {@code Expr} representing the 'NOT IN' comparison. + * @param expr The expression representing the string to reverse. + * @return A new {@code Expr} representing the reversed string. */ - export function notInAny(element: Expr, others: any[]): Not; + export function reverse(expr: Expr): Reverse; /** * @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 reverses a string represented by a field. * * ```typescript - * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny("status", [Constant.of("pending"), Field.of("rejectedStatus")]); + * // Reverse the value of the 'myString' field. + * reverse("myString"); * ``` * - * @param element The field name to compare. - * @param others The values to check against. - * @return A new {@code Expr} representing the 'NOT IN' comparison. + * @param field The name of the field representing the string to reverse. + * @return A new {@code Expr} representing the reversed string. */ - export function notInAny(element: string, others: Expr[]): Not; + export function reverse(field: string): Reverse; /** * @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 replaces the first occurrence of a substring within a string with another substring. * * ```typescript - * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny("status", ["pending", Field.of("rejectedStatus")]); + * // Replace the first occurrence of "hello" with "hi" in the 'message' field. + * replaceFirst(Field.of("message"), "hello", "hi"); * ``` - * - * @param element The field name to compare. - * @param others The values to check against. - * @return A new {@code Expr} representing the 'NOT IN' comparison. + * + * @param value The expression 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 notInAny(element: string, others: any[]): Not; + export function replaceFirst( + value: Expr, + find: string, + replace: string + ): ReplaceFirst; /** * @beta * - * Creates an expression that performs a logical 'AND' operation on multiple filter conditions. + * Creates an expression that replaces the first occurrence of a substring within a string with another substring, + * where the substring to find and the replacement substring are specified by expressions. * * ```typescript - * // Check if the 'age' field is greater than 18 AND the 'city' field is "London" AND - * // the 'status' field is "active" - * const condition = and(gt("age", 18), eq("city", "London"), eq("status", "active")); + * // 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")); * ``` * - * @param left The first filter condition. - * @param right Additional filter conditions to 'AND' together. - * @return A new {@code Expr} representing the logical 'AND' operation. + * @param value The expression representing the string to perform the replacement on. + * @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. */ - export function and(left: FilterExpr, ...right: FilterExpr[]): And; + export function replaceFirst( + value: Expr, + find: Expr, + replace: Expr + ): ReplaceFirst; /** * @beta * - * Creates an expression that performs a logical 'OR' operation on multiple filter conditions. + * Creates an expression that replaces the first occurrence of a substring within a string represented by a field with another substring. * * ```typescript - * // Check if the 'age' field is greater than 18 OR the 'city' field is "London" OR - * // the 'status' field is "active" - * const condition = or(gt("age", 18), eq("city", "London"), eq("status", "active")); + * // Replace the first occurrence of "hello" with "hi" in the 'message' field. + * replaceFirst("message", "hello", "hi"); * ``` * - * @param left The first filter condition. - * @param right Additional filter conditions to 'OR' together. - * @return A new {@code Expr} representing the logical 'OR' operation. + * @param field 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 or(left: FilterExpr, ...right: FilterExpr[]): Or; + export function replaceFirst( + field: string, + find: string, + replace: string + ): ReplaceFirst; /** * @beta * - * Creates an expression that performs a logical 'XOR' (exclusive OR) operation on multiple filter - * conditions. + * Creates an expression that replaces the first occurrence of a substring within a string represented by a field with another substring, + * where the substring to find and the replacement substring are specified by expressions. * * ```typescript - * // Check if only one of the conditions is true: 'age' greater than 18, 'city' is "London", - * // or 'status' is "active". - * const condition = xor( - * gt("age", 18), - * eq("city", "London"), - * eq("status", "active")); + * // Replace the first occurrence of the value in 'findField' with the value in 'replaceField' in the 'message' field. + * replaceFirst("message", Field.of("findField"), Field.of("replaceField")); * ``` * - * @param left The first filter condition. - * @param right Additional filter conditions to 'XOR' together. - * @return A new {@code Expr} representing the logical 'XOR' operation. + * @param field The name of the field representing the string to perform the replacement on. + * @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. */ - export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor; + export function replaceFirst( + field: string, + find: Expr, + replace: Expr + ): ReplaceFirst; /** * @beta * - * Creates a conditional expression that evaluates to a 'then' expression if a condition is true - * and an 'else' expression if the condition is false. + * Creates an expression that replaces all occurrences of a substring within a string with another substring. * * ```typescript - * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". - * ifFunction( - * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); + * // Replace all occurrences of "hello" with "hi" in the 'message' field. + * replaceAll(Field.of("message"), "hello", "hi"); * ``` * - * @param condition The condition to evaluate. - * @param thenExpr The expression to evaluate if the condition is true. - * @param elseExpr The expression to evaluate if the condition is false. - * @return A new {@code Expr} representing the conditional expression. + * @param value The expression 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 ifFunction( - condition: FilterExpr, - thenExpr: Expr, - elseExpr: Expr - ): If; + export function replaceAll( + value: Expr, + find: string, + replace: string + ): ReplaceAll; /** * @beta * - * Creates an expression that negates a filter condition. + * Creates an expression that replaces all occurrences of a substring within a string with another substring, + * where the substring to find and the replacement substring are specified by expressions. * * ```typescript - * // Find documents where the 'completed' field is NOT true - * not(eq("completed", true)); + * // 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")); * ``` * - * @param filter The filter condition to negate. - * @return A new {@code Expr} representing the negated filter condition. + * @param value The expression representing the string to perform the replacement on. + * @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. */ - export function not(filter: FilterExpr): Not; + export function replaceAll( + value: Expr, + find: Expr, + replace: Expr + ): ReplaceAll; /** * @beta * - * Creates an expression that checks if a field exists. + * Creates an expression that replaces all occurrences of a substring within a string represented by a field with another substring. * * ```typescript - * // Check if the document has a field named "phoneNumber" - * exists(Field.of("phoneNumber")); + * // Replace all occurrences of "hello" with "hi" in the 'message' field. + * replaceAll("message", "hello", "hi"); * ``` * - * @param value An expression evaluates to the name of the field to check. - * @return A new {@code Expr} representing the 'exists' check. + * @param field 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 exists(value: Expr): Exists; + export function replaceAll( + field: string, + find: string, + replace: string + ): ReplaceAll; /** * @beta * - * Creates an expression that checks if a field exists. + * Creates an expression that replaces all occurrences of a substring within a string represented by a field with another substring, + * where the substring to find and the replacement substring are specified by expressions. * * ```typescript - * // Check if the document has a field named "phoneNumber" - * exists("phoneNumber"); + * // Replace all occurrences of the value in 'findField' with the value in 'replaceField' in the 'message' field. + * replaceAll("message", Field.of("findField"), Field.of("replaceField")); * ``` * - * @param field The field name to check. - * @return A new {@code Expr} representing the 'exists' check. + * @param field The name of the field representing the string to perform the replacement on. + * @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. */ - export function exists(field: string): Exists; + export function replaceAll( + field: string, + find: Expr, + replace: Expr + ): ReplaceAll; /** * @beta * - * Creates an expression that checks if an expression evaluates to 'NaN' (Not a Number). + * Creates an expression that calculates the length of a string in bytes. * * ```typescript - * // Check if the result of a calculation is NaN - * isNaN(Field.of("value").divide(0)); + * // Calculate the length of the 'myString' field in bytes. + * byteLength(Field.of("myString")); * ``` * - * @param value The expression to check. - * @return A new {@code Expr} representing the 'isNaN' check. + * @param expr The expression representing the string. + * @return A new {@code Expr} representing the length of the string in bytes. */ - export function isNaN(value: Expr): IsNan; + export function byteLength(expr: Expr): ByteLength; /** * @beta * - * Creates an expression that checks if a field's value evaluates to 'NaN' (Not a Number). + * Creates an expression that calculates the length of a string represented by a field in bytes. * * ```typescript - * // Check if the result of a calculation is NaN - * isNaN("value"); + * // Calculate the length of the 'myString' field in bytes. + * byteLength("myString"); * ``` * - * @param value The name of the field to check. - * @return A new {@code Expr} representing the 'isNaN' check. + * @param field The name of the field representing the string. + * @return A new {@code Expr} representing the length of the string in bytes. */ - export function isNaN(value: string): IsNan; + export function byteLength(field: string): ByteLength; /** * @beta * - * Creates an expression that calculates the length of a string field. + * Creates an expression that calculates the character length of a string field in UTF-8. * * ```typescript - * // Get the length of the 'name' field + * // Get the character length of the 'name' field in UTF-8. * strLength("name"); * ``` * * @param field The name of the field containing the string. * @return A new {@code Expr} representing the length of the string. */ - export function strLength(field: string): StrLength; + export function charLength(field: string): CharLength; /** * @beta * - * Creates an expression that calculates the length of a string expression. + * Creates an expression that calculates the character length of a string expression in UTF-8. * * ```typescript - * // Get the length of the 'name' field + * // Get the character length of the 'name' field in UTF-8. * strLength(Field.of("name")); * ``` * * @param expr The expression representing the string to calculate the length of. * @return A new {@code Expr} representing the length of the string. */ - export function length(expr: Expr): StrLength; + export function charLength(expr: Expr): CharLength; /** * @beta @@ -6337,13 +7583,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Convert the 'name' field to lowercase - * toLowercase("name"); + * toLower("name"); * ``` * * @param expr The name of the field containing the string. * @return A new {@code Expr} representing the lowercase string. */ - export function toLowercase(expr: string): ToLowercase; + export function toLower(expr: string): ToLower; /** * @beta @@ -6352,13 +7598,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Convert the 'name' field to lowercase - * toLowercase(Field.of("name")); + * toLower(Field.of("name")); * ``` * * @param expr The expression representing the string to convert to lowercase. * @return A new {@code Expr} representing the lowercase string. */ - export function toLowercase(expr: Expr): ToLowercase; + export function toLower(expr: Expr): ToLower; /** * @beta @@ -6367,13 +7613,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Convert the 'title' field to uppercase - * toUppercase("title"); + * toUpper("title"); * ``` * * @param expr The name of the field containing the string. * @return A new {@code Expr} representing the uppercase string. */ - export function toUppercase(expr: string): ToUppercase; + export function toUpper(expr: string): ToUpper; /** * @beta @@ -6388,7 +7634,7 @@ declare namespace FirebaseFirestore { * @param expr The expression representing the string to convert to uppercase. * @return A new {@code Expr} representing the uppercase string. */ - export function toUppercase(expr: Expr): ToUppercase; + export function toUpper(expr: Expr): ToUpper; /** * @beta @@ -6969,6 +8215,348 @@ declare namespace FirebaseFirestore { */ export function euclideanDistance(expr: Expr, other: Expr): EuclideanDistance; + /** + * @beta + * + * Creates an expression that calculates the length of a Firestore Vector. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * vectorLength(Field.of("embedding")); + * ``` + * + * @param expr The expression representing the Firestore Vector. + * @return A new {@code Expr} representing the length of the array. + */ + export function vectorLength(expr: Expr): VectorLength; + + /** + * @beta + * + * Creates an expression that calculates the length of a Firestore Vector represented by a field. + * + * ```typescript + * // Get the vector length (dimension) of the field 'embedding'. + * vectorLength("embedding"); + * ``` + * + * @param field 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; + + /** + * @beta + * + * Creates an expression that interprets an expression as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'microseconds' field as microseconds since epoch. + * unixMicrosToTimestamp(Field.of("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; + + /** + * @beta + * + * Creates an expression that interprets a field's value as the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'microseconds' field as microseconds since epoch. + * unixMicrosToTimestamp("microseconds"); + * ``` + * + * @param field 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; + + /** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to microseconds since epoch. + * timestampToUnixMicros(Field.of("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; + + /** + * @beta + * + * Creates an expression that converts a timestamp field to the number of microseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to microseconds since epoch. + * timestampToUnixMicros("timestamp"); + * ``` + * + * @param field 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; + + /** + * @beta + * + * Creates an expression that interprets an expression as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * unixMillisToTimestamp(Field.of("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; + + /** + * @beta + * + * Creates an expression that interprets a field's value as the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'milliseconds' field as milliseconds since epoch. + * unixMillisToTimestamp("milliseconds"); + * ``` + * + * @param field 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; + + /** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * timestampToUnixMillis(Field.of("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; + + /** + * @beta + * + * Creates an expression that converts a timestamp field to the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to milliseconds since epoch. + * timestampToUnixMillis("timestamp"); + * ``` + * + * @param field 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; + + /** + * @beta + * + * Creates an expression that interprets an expression as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * unixSecondsToTimestamp(Field.of("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; + + /** + * @beta + * + * Creates an expression that interprets a field's value as the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC) + * and returns a timestamp. + * + * ```typescript + * // Interpret the 'seconds' field as seconds since epoch. + * unixSecondsToTimestamp("seconds"); + * ``` + * + * @param field 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; + + /** + * @beta + * + * Creates an expression that converts a timestamp expression to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * timestampToUnixSeconds(Field.of("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; + + /** + * @beta + * + * Creates an expression that converts a timestamp field to the number of seconds since the Unix epoch (1970-01-01 00:00:00 UTC). + * + * ```typescript + * // Convert the 'timestamp' field to seconds since epoch. + * timestampToUnixSeconds("timestamp"); + * ``` + * + * @param field 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; + + /** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp. + * + * ```typescript + * // Add some duration determined by field 'unit' and 'amount' to the 'timestamp' field. + * timestampAdd(Field.of("timestamp"), Field.of("unit"), Field.of("amount")); + * ``` + * + * @param timestamp The expression representing the timestamp. + * @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. + */ + export function timestampAdd( + timestamp: Expr, + unit: Expr, + amount: Expr + ): TimestampAdd; + + /** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp. + * + * ```typescript + * // Add 1 day to the 'timestamp' field. + * timestampAdd(Field.of("timestamp"), "day", 1); + * ``` + * + * @param timestamp The expression 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( + timestamp: Expr, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number + ): TimestampAdd; + + /** + * @beta + * + * Creates an expression that adds a specified amount of time to a timestamp represented by a field. + * + * ```typescript + * // Add 1 day to the 'timestamp' field. + * timestampAdd("timestamp", "day", 1); + * ``` + * + * @param field 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, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number + ): TimestampAdd; + + /** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp. + * + * ```typescript + * // Subtract some duration determined by field 'unit' and 'amount' from the 'timestamp' field. + * timestampSub(Field.of("timestamp"), Field.of("unit"), Field.of("amount")); + * ``` + * + * @param timestamp The expression representing the timestamp. + * @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. + */ + export function timestampSub( + timestamp: Expr, + unit: Expr, + amount: Expr + ): TimestampSub; + + /** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp. + * + * ```typescript + * // Subtract 1 day from the 'timestamp' field. + * timestampSub(Field.of("timestamp"), "day", 1); + * ``` + * + * @param timestamp The expression 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( + timestamp: Expr, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number + ): TimestampSub; + + /** + * @beta + * + * Creates an expression that subtracts a specified amount of time from a timestamp represented by a field. + * + * ```typescript + * // Subtract 1 day from the 'timestamp' field. + * timestampSub("timestamp", "day", 1); + * ``` + * + * @param field 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, + unit: 'microsecond' | 'millisecond' | 'second' | 'minute' | 'hour' | 'day', + amount: number + ): TimestampSub; + /** * @beta * From 670a124d0571ce46f6935046debce904fc53332b Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 25 Sep 2024 16:29:53 -0400 Subject: [PATCH 19/31] comment out bit operations --- dev/src/expression.ts | 1252 ++++++++++++++++++++--------------------- dev/src/index.ts | 12 +- types/firestore.d.ts | 1038 +++++++++++++++++----------------- 3 files changed, 1151 insertions(+), 1151 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index a35134c1e..22a75b438 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -261,179 +261,179 @@ export abstract class Expr implements firestore.Expr { return new Mod(this, Constant.of(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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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 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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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: firestore.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. @@ -2169,74 +2169,74 @@ export class Mod extends Function { } } -/** - * @beta - */ -export class BitAnd extends Function { - constructor( - private left: Expr, - private right: Expr - ) { - super('bit_and', [left, right]); - } -} - -/** - * @beta - */ -export class BitOr extends Function { - constructor( - private left: Expr, - private right: Expr - ) { - super('bit_or', [left, right]); - } -} - -/** - * @beta - */ -export class BitXor extends Function { - constructor( - private left: Expr, - private right: Expr - ) { - super('bit_xor', [left, right]); - } -} - -/** - * @beta - */ -export class BitNot extends Function { - constructor(private operand: Expr) { - super('bit_not', [operand]); - } -} - -/** - * @beta - */ -export class BitLeftShift extends Function { - constructor( - private left: Expr, - private right: Expr - ) { - super('bit_left_shift', [left, right]); - } -} - -/** - * @beta - */ -export class BitRightShift extends Function { - constructor( - private left: Expr, - private right: Expr - ) { - super('bit_right_shift', [left, right]); - } -} +// /** +// * @beta +// */ +// export class BitAnd extends Function { +// constructor( +// private left: Expr, +// private right: Expr +// ) { +// super('bit_and', [left, right]); +// } +// } +// +// /** +// * @beta +// */ +// export class BitOr extends Function { +// constructor( +// private left: Expr, +// private right: Expr +// ) { +// super('bit_or', [left, right]); +// } +// } +// +// /** +// * @beta +// */ +// export class BitXor extends Function { +// constructor( +// private left: Expr, +// private right: Expr +// ) { +// super('bit_xor', [left, right]); +// } +// } +// +// /** +// * @beta +// */ +// export class BitNot extends Function { +// constructor(private operand: Expr) { +// super('bit_not', [operand]); +// } +// } +// +// /** +// * @beta +// */ +// export class BitLeftShift extends Function { +// constructor( +// private left: Expr, +// private right: Expr +// ) { +// super('bit_left_shift', [left, right]); +// } +// } +// +// /** +// * @beta +// */ +// export class BitRightShift extends Function { +// constructor( +// private left: Expr, +// private right: Expr +// ) { +// super('bit_right_shift', [left, right]); +// } +// } /** * @beta @@ -3207,391 +3207,391 @@ export function mod(left: Expr | string, right: Expr | any): Mod { return new Mod(normalizedLeft, normalizedRight); } -/** - * @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 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 diff --git a/dev/src/index.ts b/dev/src/index.ts index cfca3b879..54af20aba 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -188,12 +188,12 @@ export { genericFunction, ascending, descending, - bitLeftShift, - bitOr, - bitRightShift, - bitXor, - bitAnd, - bitNot, + // bitLeftShift, + // bitOr, + // bitRightShift, + // bitXor, + // bitAnd, + // bitNot, timestampAdd, timestampSub, timestampToUnixMicros, diff --git a/types/firestore.d.ts b/types/firestore.d.ts index b8e95db59..16941d12b 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -3407,147 +3407,147 @@ declare namespace FirebaseFirestore { */ mod(other: any): Mod; - /** - * 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; - - /** - * 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; - - /** - * 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; - - /** - * 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; - - /** - * 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; - - /** - * 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; + // /** + // * 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; + // + // /** + // * 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; + // + // /** + // * 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; + // + // /** + // * 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; + // + // /** + // * 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; + // + // /** + // * 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; /** * Creates an expression that checks if this expression is equal to another expression. @@ -4860,35 +4860,35 @@ declare namespace FirebaseFirestore { */ export class Mod extends Function {} - /** - * @beta - */ - export class BitAnd extends Function {} - - /** - * @beta - */ - export class BitOr extends Function {} - - /** - * @beta - */ - export class BitXor extends Function {} - - /** - * @beta - */ - export class BitNot extends Function {} - - /** - * @beta - */ - export class BitLeftShift extends Function {} - - /** - * @beta - */ - export class BitRightShift extends Function {} + // /** + // * @beta + // */ + // export class BitAnd extends Function {} + // + // /** + // * @beta + // */ + // export class BitOr extends Function {} + // + // /** + // * @beta + // */ + // export class BitXor extends Function {} + // + // /** + // * @beta + // */ + // export class BitNot extends Function {} + // + // /** + // * @beta + // */ + // export class BitLeftShift extends Function {} + // + // /** + // * @beta + // */ + // export class BitRightShift extends Function {} /** * @beta @@ -5529,355 +5529,355 @@ declare namespace FirebaseFirestore { */ export function mod(left: string, right: any): Mod; - /** - * @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; - - /** - * @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; - - /** - * @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; - - /** - * @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; - - /** - * @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; - - /** - * @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; + // /** + // * @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; + // + // /** + // * @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; + // + // /** + // * @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; + // + // /** + // * @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; + // + // /** + // * @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; + // + // /** + // * @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; /** * @beta From 773bcd76659c9715a7d39276efe50848eaf572af Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 25 Sep 2024 18:29:53 -0400 Subject: [PATCH 20/31] more syncs --- dev/src/expression.ts | 118 ++++++++++++++++++++++++++++++++++++++++++ dev/src/index.ts | 1 + types/firestore.d.ts | 97 ++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 22a75b438..12675bc50 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -947,6 +947,38 @@ export abstract class Expr implements firestore.Expr { return new RegexMatch(this, stringOrExpr as Expr); } + /** + * Creates an expression that checks if a string contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * Field.of("description").strContains("example"); + * ``` + * + * @param substring The substring to search for. + * @return A new `Expr` representing the 'contains' comparison. + */ + strContains(substring: string): StrContains; + + /** + * 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")); + * ``` + * + * @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); + } + /** * Creates an expression that checks if a string starts with a given prefix. * @@ -2598,6 +2630,19 @@ class RegexMatch extends Function implements FilterCondition { filterable = true as const; } +/** + * @beta + */ +class StrContains extends Function implements FilterCondition { + constructor( + private expr: Expr, + private substring: Expr + ) { + super('str_contains', [expr, substring]); + } + filterable = true as const; +} + /** * @beta */ @@ -5270,6 +5315,79 @@ export function regexMatch( return new RegexMatch(leftExpr, patternExpr); } +/** + * @beta + * + * Creates an expression that checks if a string field contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * strContains("description", "example"); + * ``` + * + * @param left 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; + +/** + * @beta + * + * Creates an expression that checks if a string field contains a substring specified by an expression. + * + * ```typescript + * // Check if the 'description' field contains the value of the 'keyword' field. + * strContains("description", Field.of("keyword")); + * ``` + * + * @param left 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; + +/** + * @beta + * + * Creates an expression that checks if a string expression contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * strContains(Field.of("description"), "example"); + * ``` + * + * @param left 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; + +/** + * @beta + * + * Creates an expression that checks if a string expression contains a substring specified by another expression. + * + * ```typescript + * // Check if the 'description' field contains the value of the 'keyword' field. + * strContains(Field.of("description"), Field.of("keyword")); + * ``` + * + * @param left 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( + 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); +} + /** * @beta * diff --git a/dev/src/index.ts b/dev/src/index.ts index 54af20aba..0e0274bc6 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -169,6 +169,7 @@ export { like, regexContains, regexMatch, + strContains, startsWith, endsWith, toLower, diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 16941d12b..a92a3b31f 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -3969,6 +3969,32 @@ declare namespace FirebaseFirestore { */ regexMatch(pattern: Expr): RegexMatch; + /** + * Creates an expression that checks if this string expression contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * Field.of("description").strContains("example"); + * ``` + * + * @param substring The substring to search for. + * @return A new {@code Expr} representing the 'contains' comparison. + */ + strContains(substring: string): StrContains; + + /** + * Creates an expression that checks if this string expression 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")); + * ``` + * + * @param expr The expression representing the substring to search for. + * @return A new {@code Expr} representing the 'contains' comparison. + */ + strContains(expr: Expr): StrContains; + /** * Creates an expression that checks if a string starts with a given prefix. * @@ -5055,6 +5081,13 @@ declare namespace FirebaseFirestore { filterable: true; } + /** + * @beta + */ + export class StrContains extends Function implements FilterCondition { + filterable: true; + } + /** * @beta */ @@ -7448,6 +7481,70 @@ declare namespace FirebaseFirestore { */ export function regexMatch(left: Expr, pattern: Expr): RegexMatch; + /** + * @beta + * + * Creates an expression that checks if a string field contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * strContains("description", "example"); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string field contains a substring specified by an expression. + * + * ```typescript + * // Check if the 'description' field contains the value of the 'keyword' field. + * strContains("description", Field.of("keyword")); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression contains a specified substring. + * + * ```typescript + * // Check if the 'description' field contains "example". + * strContains(Field.of("description"), "example"); + * ``` + * + * @param left 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; + + /** + * @beta + * + * Creates an expression that checks if a string expression contains a substring specified by another expression. + * + * ```typescript + * // Check if the 'description' field contains the value of the 'keyword' field. + * strContains(Field.of("description"), Field.of("keyword")); + * ``` + * + * @param left 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; + /** * @beta * From c22a2dbe1459576baf42a897bc068daf95a5fc4a Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 1 Oct 2024 10:30:03 -0400 Subject: [PATCH 21/31] Fix rest connection by correct url endpoint. --- dev/protos/google/firestore/v1/firestore.proto | 2 +- dev/protos/v1.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/protos/google/firestore/v1/firestore.proto b/dev/protos/google/firestore/v1/firestore.proto index f8718bb67..8479e7779 100644 --- a/dev/protos/google/firestore/v1/firestore.proto +++ b/dev/protos/google/firestore/v1/firestore.proto @@ -145,7 +145,7 @@ service Firestore { rpc ExecutePipeline(ExecutePipelineRequest) returns (stream ExecutePipelineResponse) { option (google.api.http) = { - post: "/v1beta1/{database=projects/*/databases/*}:executePipeline" + post: "/v1/{database=projects/*/databases/*}/documents:executePipeline" body: "*" }; } diff --git a/dev/protos/v1.json b/dev/protos/v1.json index 7d3336323..35e6ad599 100644 --- a/dev/protos/v1.json +++ b/dev/protos/v1.json @@ -1965,13 +1965,13 @@ "responseType": "ExecutePipelineResponse", "responseStream": true, "options": { - "(google.api.http).post": "/v1beta1/{database=projects/*/databases/*}:executePipeline", + "(google.api.http).post": "/v1/{database=projects/*/databases/*}/documents:executePipeline", "(google.api.http).body": "*" }, "parsedOptions": [ { "(google.api.http)": { - "post": "/v1beta1/{database=projects/*/databases/*}:executePipeline", + "post": "/v1/{database=projects/*/databases/*}/documents:executePipeline", "body": "*" } } From a0fb269b430e62f0709c23a3754f66bec9c7b732 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 1 Oct 2024 10:41:02 -0400 Subject: [PATCH 22/31] documentation fix --- dev/src/pipeline.ts | 4 ++-- types/firestore.d.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index aec5790b6..30b8332f3 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -479,8 +479,8 @@ export class Pipeline * // 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 + * Field.of("rating").descending(), + * Field.of("title").ascending() * ); * ``` * diff --git a/types/firestore.d.ts b/types/firestore.d.ts index a92a3b31f..a615218eb 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -9167,8 +9167,8 @@ declare namespace FirebaseFirestore { * // 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 + * Field.of("rating").descending(), + * Field.of("title").ascending() * ); * ``` * From b43de5708e0754f23b0493331f61bdabf6b4759b Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 1 Oct 2024 12:09:59 -0400 Subject: [PATCH 23/31] Fix system tests build error by not using `instanceof firestore.Expr` in expression.ts --- dev/src/expression.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 12675bc50..877c5a0fa 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -1336,7 +1336,7 @@ export abstract class Expr implements firestore.Expr { */ logicalMax(other: any): firestore.LogicalMax; logicalMax(other: any): firestore.LogicalMax { - if (other instanceof firestore.Expr) { + if (other instanceof Expr) { return new LogicalMax(this, other as Expr); } return new LogicalMax(this, Constant.of(other)); @@ -1368,7 +1368,7 @@ export abstract class Expr implements firestore.Expr { */ logicalMin(other: any): firestore.LogicalMin; logicalMin(other: any): firestore.LogicalMin { - if (other instanceof firestore.Expr) { + if (other instanceof Expr) { return new LogicalMin(this, other as Expr); } return new LogicalMin(this, Constant.of(other)); @@ -1427,7 +1427,7 @@ export abstract class Expr implements firestore.Expr { cosineDistance( other: firestore.Expr | firestore.VectorValue | number[] ): CosineDistance { - if (other instanceof firestore.Expr) { + if (other instanceof Expr) { return new CosineDistance(this, other as Expr); } else { return new CosineDistance( @@ -1478,7 +1478,7 @@ export abstract class Expr implements firestore.Expr { dotProduct( other: firestore.Expr | firestore.VectorValue | number[] ): DotProduct { - if (other instanceof firestore.Expr) { + if (other instanceof Expr) { return new DotProduct(this, other as Expr); } else { return new DotProduct( @@ -1529,7 +1529,7 @@ export abstract class Expr implements firestore.Expr { euclideanDistance( other: firestore.Expr | firestore.VectorValue | number[] ): EuclideanDistance { - if (other instanceof firestore.Expr) { + if (other instanceof Expr) { return new EuclideanDistance(this, other as Expr); } else { return new EuclideanDistance( From 1d9c585e2482bb53c5926ed7a7f54d22707d8fb8 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 2 Oct 2024 18:22:23 -0400 Subject: [PATCH 24/31] remove __path__ --- dev/src/expression.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 877c5a0fa..5b5f5678d 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -1886,7 +1886,7 @@ export class Field extends Expr implements Selectable { return new Field(FieldPath.fromArgument(pipelineOrName)); } else if (pipelineOrName instanceof FieldPath) { if (FieldPath.documentId().isEqual(pipelineOrName)) { - return new Field(new FieldPath('__path__')); + return new Field(FieldPath.documentId()); } return new Field(pipelineOrName); } else { From d9194b71e22694caa5817956df48cfb875686656 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 3 Oct 2024 10:11:52 -0400 Subject: [PATCH 25/31] add reference test --- dev/system-test/pipeline.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 4bc990b92..96934dd5f 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -564,6 +564,15 @@ describe.only('Pipeline class', () => { expect(results.length).to.equal(5); }); + it('testQueryByDocumentReference', async () => { + const results = await randomCol + .pipeline() + .where(eq(Field.of(FieldPath.documentId()), randomCol.doc('book1'))) + .select('title') + .execute(); + expectResults(results, {title: "The Hitchhiker's Guide to the Galaxy"}); + }); + it('testArithmeticOperations', async () => { const results = await randomCol .pipeline() From 091ddf0f4d1b39381a1d96ccb39b540af0e659a4 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Fri, 4 Oct 2024 10:35:38 -0400 Subject: [PATCH 26/31] added transaction support. --- dev/src/pipeline-util.ts | 20 +++++++++++--------- dev/src/pipeline.ts | 25 +++++++++++++++++++++++-- dev/src/reference/types.ts | 7 +++++++ dev/src/transaction.ts | 27 +++++++++++++++++++++++++++ dev/system-test/pipeline.ts | 25 +++++++++++++++++++++++++ types/firestore.d.ts | 4 ++++ 6 files changed, 97 insertions(+), 11 deletions(-) diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index 59ebbcfad..db24449af 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -40,7 +40,11 @@ import {CompositeFilterInternal} from './reference/composite-filter-internal'; import {NOOP_MESSAGE} from './reference/constants'; import {FieldFilterInternal} from './reference/field-filter-internal'; import {FilterInternal} from './reference/filter-internal'; -import {PipelineStreamElement, QueryResponse} from './reference/types'; +import { + PipelineResponse, + PipelineStreamElement, + QueryResponse, +} from './reference/types'; import {Serializer} from './serializer'; import { Deferred, @@ -50,7 +54,6 @@ import { requestTag, wrapError, } from './util'; -import {invalidArgumentMessage} from './validate'; import api = protos.google.firestore.v1; /** @@ -73,15 +76,13 @@ export class ExecutionUtil { pipeline: Pipeline, transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, explainOptions?: firestore.ExplainOptions - ): Promise> | undefined> { + ): Promise> { // Capture the error stack to preserve stack tracing across async calls. const stack = Error().stack!; return new Promise((resolve, reject) => { - const results: Array> = []; - const output: Omit, 'result'> & { - executionTime?: Timestamp; - } = {}; + const result: Array> = []; + const output: PipelineResponse = {}; this._stream(pipeline, transactionOrReadTime, explainOptions) .on('error', err => { @@ -99,12 +100,13 @@ export class ExecutionUtil { output.explainMetrics = element.explainMetrics; } if (element.result) { - results.push(element.result); + result.push(element.result); } } }) .on('end', () => { - resolve(results); + output.result = result; + resolve(output); }); }); } diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index 30b8332f3..c28918213 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -31,6 +31,7 @@ import Firestore, {FieldPath, QueryDocumentSnapshot, Timestamp} from './index'; import {validateFieldPath} from './path'; import {ExecutionUtil} from './pipeline-util'; import {DocumentReference} from './reference/document-reference'; +import {PipelineResponse} from './reference/types'; import {Serializer} from './serializer'; import { AddFields, @@ -632,12 +633,21 @@ export class Pipeline * @return A Promise representing the asynchronous pipeline execution. */ execute(): Promise>> { + return this._execute().then(response => response.result || []); + } + + _execute( + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + explainOptions?: FirebaseFirestore.ExplainOptions + ): Promise> { const util = new ExecutionUtil( this.db, this.db._serializer!, this.converter ); - return util._getResponse(this).then(result => result!); + return util + ._getResponse(this, transactionOrReadTime, explainOptions) + .then(result => result!); } /** @@ -673,10 +683,21 @@ export class Pipeline stage._toProto(this.db._serializer!) ); const structuredPipeline: IStructuredPipeline = {pipeline: {stages}}; - return { + const executePipelineRequest: api.IExecutePipelineRequest = { database: this.db.formattedName, structuredPipeline, }; + + if (transactionOrReadTime instanceof Uint8Array) { + executePipelineRequest.transaction = transactionOrReadTime; + } else if (transactionOrReadTime instanceof Timestamp) { + executePipelineRequest.readTime = + transactionOrReadTime.toProto().timestampValue; + } else if (transactionOrReadTime) { + executePipelineRequest.newTransaction = transactionOrReadTime; + } + + return executePipelineRequest; } } diff --git a/dev/src/reference/types.ts b/dev/src/reference/types.ts index 036994ff6..d282b507f 100644 --- a/dev/src/reference/types.ts +++ b/dev/src/reference/types.ts @@ -70,6 +70,13 @@ export interface PipelineStreamElement< result?: PipelineResult; } +export interface PipelineResponse { + transaction?: Uint8Array; + executionTime?: Timestamp; + explainMetrics?: ExplainMetrics; + result?: Array>; +} + /** * onSnapshot() callback that receives a QuerySnapshot. * diff --git a/dev/src/transaction.ts b/dev/src/transaction.ts index f29fccbe5..c11bcc78b 100644 --- a/dev/src/transaction.ts +++ b/dev/src/transaction.ts @@ -22,6 +22,7 @@ import * as proto from '../protos/firestore_v1_proto_api'; import {ExponentialBackoff} from './backoff'; import {DocumentSnapshot} from './document'; import {DEFAULT_MAX_TRANSACTION_ATTEMPTS, Firestore, WriteBatch} from './index'; +import {Pipeline, PipelineResult} from './pipeline'; import {Timestamp} from './timestamp'; import {logger} from './logger'; import {FieldPath, validateFieldPath} from './path'; @@ -260,6 +261,20 @@ export class Transaction implements firestore.Transaction { ); } + execute( + pipeline: firestore.Pipeline + ): Promise>> { + if (this._writeBatch && !this._writeBatch.isEmpty) { + throw new Error(READ_AFTER_WRITE_ERROR_MSG); + } + + if (pipeline instanceof Pipeline) { + return this.withLazyStartedTransaction(pipeline, this.executePipelineFn); + } + + throw new Error('Value for argument "pipeline" must be a Pipeline'); + } + /** * Create the document referred to by the provided * [DocumentReference]{@link DocumentReference}. The operation will @@ -737,6 +752,18 @@ export class Transaction implements firestore.Transaction { }> { return query._get(opts); } + + private async executePipelineFn( + pipeline: Pipeline, + opts: Uint8Array | api.ITransactionOptions | Timestamp + ): Promise<{ + transaction?: Uint8Array; + result: Array>; + }> { + const {transaction, result, explainMetrics, executionTime} = + await pipeline._execute(opts); + return {transaction, result: result || []}; + } } /** diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 96934dd5f..e2ef43c0d 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -784,4 +784,29 @@ describe.only('Pipeline class', () => { myTitle: 'Crime and Punishment', }); }); + + it('run pipeline as part of a transaction', async () => { + const pipeline = randomCol + .pipeline() + .where(eq('awards.hugo', true)) + .select('title', 'awards.hugo'); + + await firestore.runTransaction(async transaction => { + const results = await transaction.execute(pipeline); + expectResults( + results, + {title: "The Hitchhiker's Guide to the Galaxy", 'awards.hugo': true}, + {title: 'Dune', 'awards.hugo': true} + ); + + transaction.update(randomCol.doc('book1'), {foo: 'bar'}); + }); + + const result = await randomCol + .pipeline() + .where(eq('foo', 'bar')) + .select('title', Field.of(FieldPath.documentId())) + .execute(); + expectResults(result, {title: "The Hitchhiker's Guide to the Galaxy"}); + }); }); diff --git a/types/firestore.d.ts b/types/firestore.d.ts index a615218eb..dd0b804b4 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -808,6 +808,10 @@ declare namespace FirebaseFirestore { > ): Promise>>; + execute( + pipeline: Pipeline + ): Promise>>; + /** * Create the document referred to by the provided `DocumentReference`. * The operation will fail the transaction if a document exists at the From 14b67ddddc8f312b9377ca9e2c53b8540d9d3ad7 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Fri, 4 Oct 2024 11:24:09 -0400 Subject: [PATCH 27/31] Add comments --- dev/src/transaction.ts | 34 ++++++++++++++++++++++++++++++++++ types/firestore.d.ts | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/dev/src/transaction.ts b/dev/src/transaction.ts index c11bcc78b..abc101a13 100644 --- a/dev/src/transaction.ts +++ b/dev/src/transaction.ts @@ -261,6 +261,40 @@ export class Transaction implements firestore.Transaction { ); } + /** + * @beta + * + * 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 transaction + * .execute( + * firestore.pipeline().collection("books") + * .where(gt(Field.of("rating"), 4.5)) + * .select("title", "author", "rating")); + * ``` + * + * @return A Promise representing the asynchronous pipeline execution. + */ execute( pipeline: firestore.Pipeline ): Promise>> { diff --git a/types/firestore.d.ts b/types/firestore.d.ts index dd0b804b4..991302a3a 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -808,8 +808,42 @@ declare namespace FirebaseFirestore { > ): Promise>>; + /** + * @beta + * + * 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 transaction + * .execute( + * firestore.pipeline().collection("books") + * .where(gt(Field.of("rating"), 4.5)) + * .select("title", "author", "rating")); + * ``` + * + * @return A Promise representing the asynchronous pipeline execution. + */ execute( - pipeline: Pipeline + pipeline: Pipeline ): Promise>>; /** From 0c04b48ec5043bb1251f561bf9b0101935e5e082 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 24 Oct 2024 14:46:27 -0400 Subject: [PATCH 28/31] add remove_fields support --- dev/src/pipeline.ts | 30 ++++++++++++++++++++ dev/src/stage.ts | 16 +++++++++++ dev/system-test/pipeline.ts | 55 +++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/dev/src/pipeline.ts b/dev/src/pipeline.ts index c28918213..4fc32d402 100644 --- a/dev/src/pipeline.ts +++ b/dev/src/pipeline.ts @@ -50,6 +50,7 @@ import { Sort, Stage, Distinct, + RemoveFields, } from './stage'; import {ApiMapValue, defaultPipelineConverter} from './types'; import * as protos from '../protos/firestore_v1_proto_api'; @@ -165,6 +166,35 @@ export class Pipeline return new Pipeline(this.db, copy, this.converter); } + /** + * 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.of("rating"), + * "cost" + * ); + * ``` + * + * @param fields The fields to remove. + * @return A new Pipeline object with this stage appended to the stage list. + */ + removeFields( + ...fields: (firestore.Field | string)[] + ): Pipeline { + const copy = this.stages.map(s => s); + copy.push( + new RemoveFields( + fields.map(f => (typeof f === 'string' ? Field.of(f) : (f as Field))) + ) + ); + return new Pipeline(this.db, copy, this.converter); + } + /** * Selects or creates a set of fields from the outputs of previous stages. * diff --git a/dev/src/stage.ts b/dev/src/stage.ts index c498e20ae..5be9be513 100644 --- a/dev/src/stage.ts +++ b/dev/src/stage.ts @@ -51,6 +51,22 @@ export class AddFields implements Stage { } } +/** + * @beta + */ +export class RemoveFields implements Stage { + name = 'remove_fields'; + + constructor(private fields: Field[]) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.fields.map(f => serializer.encodeValue(f)!), + }; + } +} + /** * @beta */ diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index e2ef43c0d..82d380b11 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -324,6 +324,61 @@ describe.only('Pipeline class', () => { }); }); + it('can add and remove fields', async () => { + const results = await firestore + .pipeline() + .collection(randomCol.path) + .addFields( + strConcat(Field.of('author'), '_', Field.of('title')).as( + 'author_title' + ), + strConcat(Field.of('title'), '_', Field.of('author')).as('title_author') + ) + .removeFields( + 'title_author', + 'tags', + 'awards', + 'rating', + 'title', + 'published', + 'genre', + 'nestedField' + ) + .sort(Field.of('author_title').ascending()) + .execute(); + expectResults( + results, + { + author_title: "Douglas Adams_The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + }, + { + author_title: 'F. Scott Fitzgerald_The Great Gatsby', + author: 'F. Scott Fitzgerald', + }, + {author_title: 'Frank Herbert_Dune', author: 'Frank Herbert'}, + { + author_title: 'Fyodor Dostoevsky_Crime and Punishment', + author: 'Fyodor Dostoevsky', + }, + { + author_title: 'Gabriel García Márquez_One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + }, + {author_title: 'George Orwell_1984', author: 'George Orwell'}, + {author_title: 'Harper Lee_To Kill a Mockingbird', author: 'Harper Lee'}, + { + author_title: 'J.R.R. Tolkien_The Lord of the Rings', + author: 'J.R.R. Tolkien', + }, + {author_title: 'Jane Austen_Pride and Prejudice', author: 'Jane Austen'}, + { + author_title: "Margaret Atwood_The Handmaid's Tale", + author: 'Margaret Atwood', + } + ); + }); + it('can select fields', async () => { const results = await firestore .pipeline() From 91bd76e6528772d8b80ec7b793a1313e73d0f8a8 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 24 Oct 2024 15:09:04 -0400 Subject: [PATCH 29/31] add d.ts change for removeFields --- types/firestore.d.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 991302a3a..fce440cd2 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -8770,6 +8770,13 @@ declare namespace FirebaseFirestore { name: string; } + /** + * @beta + */ + export class RemoveFields implements Stage { + name: string; + } + /** * @beta */ @@ -8982,6 +8989,25 @@ declare namespace FirebaseFirestore { */ addFields(...fields: Selectable[]): Pipeline; + /** + * 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.of("rating"), + * "cost" + * ); + * ``` + * + * @param fields The fields to remove. + * @return A new Pipeline object with this stage appended to the stage list. + */ + removeFields(...fields: (Field | string)[]): Pipeline; + /** * Selects or creates a set of fields from the outputs of previous stages. * From cb9d69224ba6c472b68e47a2f521e7f73e1b0a49 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:49:20 -0700 Subject: [PATCH 30/31] fix build --- CODE_OF_CONDUCT.md | 2 +- dev/conformance/runner.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 2add2547a..a044123a3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -91,4 +91,4 @@ harassment or threats to anyone's safety, we may take action without notice. This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at -https://www.contributor-covenant.org/version/1/4/code-of-conduct.html \ No newline at end of file +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/dev/conformance/runner.ts b/dev/conformance/runner.ts index 3ade3a1f5..82a117809 100644 --- a/dev/conformance/runner.ts +++ b/dev/conformance/runner.ts @@ -530,6 +530,7 @@ function normalizeTimestamp(obj: {[key: string]: {}}) { if (fieldNames.includes(key) && typeof obj[key] === 'string') { obj[key] = convertTimestamp(obj[key] as string); } else if (typeof obj[key] === 'object') { + // @ts-ignore normalizeTimestamp(obj[key]); } } @@ -576,6 +577,7 @@ function normalizeInt32Value(obj: {[key: string]: {}}, parent = '') { value: obj[key], }; } else if (typeof obj[key] === 'object') { + // @ts-ignore normalizeInt32Value(obj[key], key); } } From 5db7307d7ab1d09f845603fb269ca3b09647b1d4 Mon Sep 17 00:00:00 2001 From: Mark Duckworth <1124037+MarkDuckworth@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:36:10 -0700 Subject: [PATCH 31/31] Rename cond, *minimum, *maximum, eqAny, and notEqAny. New tests. Implement NotEqAny. --- dev/src/expression.ts | 297 +++++++++++++++++++++--------------- dev/src/index.ts | 14 +- dev/src/pipeline-util.ts | 4 +- dev/system-test/pipeline.ts | 154 ++++++++++++++++--- types/firestore.d.ts | 156 +++++++++---------- 5 files changed, 393 insertions(+), 232 deletions(-) diff --git a/dev/src/expression.ts b/dev/src/expression.ts index 5b5f5678d..399433096 100644 --- a/dev/src/expression.ts +++ b/dev/src/expression.ts @@ -779,13 +779,13 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * Field.of("category").in("Electronics", Field.of("primaryType")); + * Field.of("category").eqAny("Electronics", Field.of("primaryType")); * ``` * * @param others The values or expressions to check against. - * @return A new `Expr` representing the 'IN' comparison. + * @return A new `Expr` representing the 'EqAny' comparison. */ - in(...others: firestore.Expr[]): In; + eqAny(...others: firestore.Expr[]): EqAny; /** * Creates an expression that checks if this expression is equal to any of the provided values or @@ -793,18 +793,52 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * Field.of("category").in("Electronics", Field.of("primaryType")); + * Field.of("category").eqAny("Electronics", Field.of("primaryType")); * ``` * * @param others The values or expressions to check against. - * @return A new `Expr` representing the 'IN' comparison. + * @return A new `Expr` representing the 'EqAny' comparison. */ - in(...others: any[]): In; - in(...others: any[]): In { + eqAny(...others: any[]): EqAny; + eqAny(...others: any[]): EqAny { const exprOthers = others.map(other => other instanceof Expr ? other : Constant.of(other) ); - return new In(this, exprOthers); + return new EqAny(this, exprOthers); + } + + /** + * Creates an expression that checks if this 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' + * Field.of("status").notEqAny("pending", Field.of("rejectedStatus")); + * ``` + * + * @param others The values or expressions to check against. + * @return A new `Expr` representing the 'NotEqAny' comparison. + */ + notEqAny(...others: firestore.Expr[]): NotEqAny; + + /** + * Creates an expression that checks if this 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' + * Field.of("status").notEqAny("pending", Field.of("rejectedStatus")); + * ``` + * + * @param others 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); } /** @@ -1287,13 +1321,13 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Find the lowest price of all products - * Field.of("price").min().as("lowestPrice"); + * Field.of("price").minimum().as("lowestPrice"); * ``` * - * @return A new `Accumulator` representing the 'min' aggregation. + * @return A new `Accumulator` representing the 'minimum' aggregation. */ - min(): Min { - return new Min(this, false); + minimum(): Minimum { + return new Minimum(this, false); } /** @@ -1301,13 +1335,13 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Find the highest score in a leaderboard - * Field.of("score").max().as("highestScore"); + * Field.of("score").maximum().as("highestScore"); * ``` * - * @return A new `Accumulator` representing the 'max' aggregation. + * @return A new `Accumulator` representing the 'maximum' aggregation. */ - max(): Max { - return new Max(this, false); + maximum(): Maximum { + return new Maximum(this, false); } /** @@ -1315,31 +1349,31 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Returns the larger value between the 'timestamp' field and the current timestamp. - * Field.of("timestamp").logicalMax(Function.currentTimestamp()); + * Field.of("timestamp").logicalMaximum(Function.currentTimestamp()); * ``` * * @param other The expression to compare with. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ - logicalMax(other: firestore.Expr): firestore.LogicalMax; + logicalMaximum(other: firestore.Expr): firestore.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").logicalMax(10); + * Field.of("value").logicalMaximum(10); * ``` * * @param other The constant value to compare with. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ - logicalMax(other: any): firestore.LogicalMax; - logicalMax(other: any): firestore.LogicalMax { + logicalMaximum(other: any): firestore.LogicalMaximum; + logicalMaximum(other: any): firestore.LogicalMaximum { if (other instanceof Expr) { - return new LogicalMax(this, other as Expr); + return new LogicalMaximum(this, other as Expr); } - return new LogicalMax(this, Constant.of(other)); + return new LogicalMaximum(this, Constant.of(other)); } /** @@ -1347,31 +1381,31 @@ export abstract class Expr implements firestore.Expr { * * ```typescript * // Returns the smaller value between the 'timestamp' field and the current timestamp. - * Field.of("timestamp").logicalMin(Function.currentTimestamp()); + * Field.of("timestamp").logicalMinimum(Function.currentTimestamp()); * ``` * * @param other The expression to compare with. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ - logicalMin(other: firestore.Expr): firestore.LogicalMin; + logicalMinimum(other: firestore.Expr): firestore.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").logicalMin(10); + * Field.of("value").logicalMinimum(10); * ``` * * @param other The constant value to compare with. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ - logicalMin(other: any): firestore.LogicalMin; - logicalMin(other: any): firestore.LogicalMin { + logicalMinimum(other: any): firestore.LogicalMinimum; + logicalMinimum(other: any): firestore.LogicalMinimum { if (other instanceof Expr) { - return new LogicalMin(this, other as Expr); + return new LogicalMinimum(this, other as Expr); } - return new LogicalMin(this, Constant.of(other)); + return new LogicalMinimum(this, Constant.of(other)); } /** @@ -2429,12 +2463,25 @@ class ArrayElement extends Function { /** * @beta */ -class In extends Function implements FilterCondition { +class EqAny extends Function implements FilterCondition { constructor( private left: Expr, private others: Expr[] ) { - super('in', [left, new ListOfExprs(others)]); + super('eq_any', [left, new ListOfExprs(others)]); + } + filterable = true as const; +} + +/** + * @beta + */ +class NotEqAny extends Function implements FilterCondition { + constructor( + private left: Expr, + private others: Expr[] + ) { + super('not_eq_any', [left, new ListOfExprs(others)]); } filterable = true as const; } @@ -2503,13 +2550,13 @@ class Xor extends Function implements FilterCondition { /** * @beta */ -class If extends Function implements FilterCondition { +class Cond extends Function implements FilterCondition { constructor( private condition: FilterExpr, private thenExpr: Expr, private elseExpr: Expr ) { - super('if', [condition, thenExpr, elseExpr]); + super('cond', [condition, thenExpr, elseExpr]); } filterable = true as const; } @@ -2517,24 +2564,24 @@ class If extends Function implements FilterCondition { /** * @beta */ -class LogicalMax extends Function { +class LogicalMaximum extends Function { constructor( private left: Expr, private right: Expr ) { - super('logical_max', [left, right]); + super('logical_maximum', [left, right]); } } /** * @beta */ -class LogicalMin extends Function { +class LogicalMinimum extends Function { constructor( private left: Expr, private right: Expr ) { - super('logical_min', [left, right]); + super('logical_minimum', [left, right]); } } @@ -2759,26 +2806,26 @@ class Avg extends Function implements Accumulator { /** * @beta */ -class Min extends Function implements Accumulator { +class Minimum extends Function implements Accumulator { accumulator = true as const; constructor( private value: Expr, private distinct: boolean ) { - super('min', [value]); + super('minimum', [value]); } } /** * @beta */ -class Max extends Function implements Accumulator { +class Maximum extends Function implements Accumulator { accumulator = true as const; constructor( private value: Expr, private distinct: boolean ) { - super('max', [value]); + super('maximum', [value]); } } @@ -4394,14 +4441,14 @@ export function arrayLength(array: Expr): ArrayLength { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); + * eqAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); * ``` * * @param element The expression to compare. * @param others The values to check against. - * @return A new {@code Expr} representing the 'IN' comparison. + * @return A new {@code Expr} representing the 'eqAny' comparison. */ -export function inAny(element: Expr, others: Expr[]): In; +export function eqAny(element: Expr, others: Expr[]): EqAny; /** * @beta @@ -4411,14 +4458,14 @@ export function inAny(element: Expr, others: Expr[]): In; * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); + * eqAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); * ``` * * @param element The expression to compare. * @param others The values to check against. - * @return A new {@code Expr} representing the 'IN' comparison. + * @return A new {@code Expr} representing the 'eqAny' comparison. */ -export function inAny(element: Expr, others: any[]): In; +export function eqAny(element: Expr, others: any[]): EqAny; /** * @beta @@ -4428,14 +4475,14 @@ export function inAny(element: Expr, others: any[]): In; * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); + * eqAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); * ``` * * @param element The field to compare. * @param others The values to check against. - * @return A new {@code Expr} representing the 'IN' comparison. + * @return A new {@code Expr} representing the 'eqAny' comparison. */ -export function inAny(element: string, others: Expr[]): In; +export function eqAny(element: string, others: Expr[]): EqAny; /** * @beta @@ -4445,20 +4492,20 @@ export function inAny(element: string, others: Expr[]): In; * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny("category", ["Electronics", Field.of("primaryType")]); + * eqAny("category", ["Electronics", Field.of("primaryType")]); * ``` * * @param element The field to compare. * @param others The values to check against. - * @return A new {@code Expr} representing the 'IN' comparison. + * @return A new {@code Expr} representing the 'eqAny' comparison. */ -export function inAny(element: string, others: any[]): In; -export function inAny(element: Expr | string, others: any[]): In { +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 In(elementExpr, exprOthers); + return new EqAny(elementExpr, exprOthers); } /** @@ -4469,14 +4516,14 @@ export function inAny(element: Expr | string, others: any[]): In { * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); + * notEqAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); * ``` * * @param element The expression to compare. * @param others The values to check against. - * @return A new {@code Expr} representing the 'NOT IN' comparison. + * @return A new {@code Expr} representing the 'NotEqAny' comparison. */ -export function notInAny(element: Expr, others: Expr[]): Not; +export function notEqAny(element: Expr, others: Expr[]): NotEqAny; /** * @beta @@ -4486,14 +4533,14 @@ export function notInAny(element: Expr, others: Expr[]): Not; * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); + * notEqAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); * ``` * * @param element The expression to compare. * @param others The values to check against. - * @return A new {@code Expr} representing the 'NOT IN' comparison. + * @return A new {@code Expr} representing the 'NotEqAny' comparison. */ -export function notInAny(element: Expr, others: any[]): Not; +export function notEqAny(element: Expr, others: any[]): NotEqAny; /** * @beta @@ -4503,14 +4550,14 @@ export function notInAny(element: Expr, others: any[]): Not; * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny("status", [Constant.of("pending"), Field.of("rejectedStatus")]); + * notEqAny("status", ["pending", Field.of("rejectedStatus")]); * ``` * * @param element The field name to compare. * @param others The values to check against. - * @return A new {@code Expr} representing the 'NOT IN' comparison. + * @return A new {@code Expr} representing the 'NotEqAny' comparison. */ -export function notInAny(element: string, others: Expr[]): Not; +export function notEqAny(element: string, others: Expr[]): NotEqAny; /** * @beta @@ -4520,20 +4567,20 @@ export function notInAny(element: string, others: Expr[]): Not; * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny("status", ["pending", Field.of("rejectedStatus")]); + * notEqAny("status", ["pending", Field.of("rejectedStatus")]); * ``` * * @param element The field name to compare. * @param others The values to check against. - * @return A new {@code Expr} representing the 'NOT IN' comparison. + * @return A new {@code Expr} representing the 'NotEqAny' comparison. */ -export function notInAny(element: string, others: any[]): Not; -export function notInAny(element: Expr | string, others: any[]): Not { +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 Not(new In(elementExpr, exprOthers)); + return new NotEqAny(elementExpr, exprOthers); } /** @@ -4605,7 +4652,7 @@ export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor { * * ```typescript * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". - * ifFunction( + * cond( * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); * ``` * @@ -4614,12 +4661,12 @@ export function xor(left: FilterExpr, ...right: FilterExpr[]): Xor { * @param elseExpr The expression to evaluate if the condition is false. * @return A new {@code Expr} representing the conditional expression. */ -export function ifFunction( +export function cond( condition: FilterExpr, thenExpr: Expr, elseExpr: Expr -): If { - return new If(condition, thenExpr, elseExpr); +): Cond { + return new Cond(condition, thenExpr, elseExpr); } /** @@ -4646,14 +4693,14 @@ export function not(filter: FilterExpr): Not { * * ```typescript * // Returns the larger value between the 'field1' field and the 'field2' field. - * logicalMax(Field.of("field1"), Field.of("field2")); + * 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. + * @return A new {@code Expr} representing the logical maximum operation. */ -export function logicalMax(left: Expr, right: Expr): LogicalMax; +export function logicalMaximum(left: Expr, right: Expr): LogicalMaximum; /** * @beta @@ -4662,14 +4709,14 @@ export function logicalMax(left: Expr, right: Expr): LogicalMax; * * ```typescript * // Returns the larger value between the 'value' field and 10. - * logicalMax(Field.of("value"), 10); + * logicalMaximum(Field.of("value"), 10); * ``` * * @param left The left operand expression. * @param right The right operand constant. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ -export function logicalMax(left: Expr, right: any): LogicalMax; +export function logicalMaximum(left: Expr, right: any): LogicalMaximum; /** * @beta @@ -4678,14 +4725,14 @@ export function logicalMax(left: Expr, right: any): LogicalMax; * * ```typescript * // Returns the larger value between the 'field1' field and the 'field2' field. - * logicalMax("field1", Field.of('field2')); + * logicalMaximum("field1", Field.of('field2')); * ``` * * @param left The left operand field name. * @param right The right operand expression. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ -export function logicalMax(left: string, right: Expr): LogicalMax; +export function logicalMaximum(left: string, right: Expr): LogicalMaximum; /** * @beta @@ -4694,18 +4741,21 @@ export function logicalMax(left: string, right: Expr): LogicalMax; * * ```typescript * // Returns the larger value between the 'value' field and 10. - * logicalMax("value", 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. + * @return A new {@code Expr} representing the logical maximum operation. */ -export function logicalMax(left: string, right: any): LogicalMax; -export function logicalMax(left: Expr | string, right: Expr | any): LogicalMax { +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 LogicalMax(normalizedLeft, normalizedRight); + return new LogicalMaximum(normalizedLeft, normalizedRight); } /** @@ -4715,14 +4765,14 @@ export function logicalMax(left: Expr | string, right: Expr | any): LogicalMax { * * ```typescript * // Returns the smaller value between the 'field1' field and the 'field2' field. - * logicalMin(Field.of("field1"), Field.of("field2")); + * 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. + * @return A new {@code Expr} representing the logical minimum operation. */ -export function logicalMin(left: Expr, right: Expr): LogicalMin; +export function logicalMinimum(left: Expr, right: Expr): LogicalMinimum; /** * @beta @@ -4731,14 +4781,14 @@ export function logicalMin(left: Expr, right: Expr): LogicalMin; * * ```typescript * // Returns the smaller value between the 'value' field and 10. - * logicalMin(Field.of("value"), 10); + * logicalMinimum(Field.of("value"), 10); * ``` * * @param left The left operand expression. * @param right The right operand constant. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ -export function logicalMin(left: Expr, right: any): LogicalMin; +export function logicalMinimum(left: Expr, right: any): LogicalMinimum; /** * @beta @@ -4747,14 +4797,14 @@ export function logicalMin(left: Expr, right: any): LogicalMin; * * ```typescript * // Returns the smaller value between the 'field1' field and the 'field2' field. - * logicalMin("field1", Field.of("field2")); + * logicalMinimum("field1", Field.of("field2")); * ``` * * @param left The left operand field name. * @param right The right operand expression. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ -export function logicalMin(left: string, right: Expr): LogicalMin; +export function logicalMinimum(left: string, right: Expr): LogicalMinimum; /** * @beta @@ -4763,18 +4813,21 @@ export function logicalMin(left: string, right: Expr): LogicalMin; * * ```typescript * // Returns the smaller value between the 'value' field and 10. - * logicalMin("value", 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. + * @return A new {@code Expr} representing the logical minimum operation. */ -export function logicalMin(left: string, right: any): LogicalMin; -export function logicalMin(left: Expr | string, right: Expr | any): LogicalMin { +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 LogicalMin(normalizedLeft, normalizedRight); + return new LogicalMinimum(normalizedLeft, normalizedRight); } /** @@ -5840,13 +5893,13 @@ export function avg(value: Expr | string): Avg { * * ```typescript * // Find the lowest price of all products - * min(Field.of("price")).as("lowestPrice"); + * minimum(Field.of("price")).as("lowestPrice"); * ``` * * @param value The expression to find the minimum value of. - * @return A new {@code Accumulator} representing the 'min' aggregation. + * @return A new {@code Accumulator} representing the 'minimum' aggregation. */ -export function min(value: Expr): Min; +export function minimum(value: Expr): Minimum; /** * @beta @@ -5855,16 +5908,16 @@ export function min(value: Expr): Min; * * ```typescript * // Find the lowest price of all products - * min("price").as("lowestPrice"); + * 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. + * @return A new {@code Accumulator} representing the 'minimum' aggregation. */ -export function min(value: string): Min; -export function min(value: Expr | string): Min { +export function minimum(value: string): Minimum; +export function minimum(value: Expr | string): Minimum { const exprValue = value instanceof Expr ? value : Field.of(value); - return new Min(exprValue, false); + return new Minimum(exprValue, false); } /** @@ -5875,13 +5928,13 @@ export function min(value: Expr | string): Min { * * ```typescript * // Find the highest score in a leaderboard - * max(Field.of("score")).as("highestScore"); + * maximum(Field.of("score")).as("highestScore"); * ``` * * @param value The expression to find the maximum value of. - * @return A new {@code Accumulator} representing the 'max' aggregation. + * @return A new {@code Accumulator} representing the 'maximum' aggregation. */ -export function max(value: Expr): Max; +export function maximum(value: Expr): Maximum; /** * @beta @@ -5890,16 +5943,16 @@ export function max(value: Expr): Max; * * ```typescript * // Find the highest score in a leaderboard - * max("score").as("highestScore"); + * 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. + * @return A new {@code Accumulator} representing the 'maximum' aggregation. */ -export function max(value: string): Max; -export function max(value: Expr | string): Max { +export function maximum(value: string): Maximum; +export function maximum(value: Expr | string): Maximum { const exprValue = value instanceof Expr ? value : Field.of(value); - return new Max(exprValue, false); + return new Maximum(exprValue, false); } /** diff --git a/dev/src/index.ts b/dev/src/index.ts index 0e0274bc6..5b83d596c 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -156,12 +156,12 @@ export { arrayContainsAny, arrayContainsAll, arrayLength, - inAny, - notInAny, + eqAny, + notEqAny, and, or, xor, - ifFunction, + cond, not, exists, isNan, @@ -181,8 +181,8 @@ export { count, sum, avg, - min, - max, + minimum, + maximum, cosineDistance, dotProduct, euclideanDistance, @@ -203,8 +203,8 @@ export { unixMicrosToTimestamp, unixMillisToTimestamp, unixSecondsToTimestamp, - logicalMax, - logicalMin, + logicalMaximum, + logicalMinimum, vectorLength, byteLength, reverse, diff --git a/dev/src/pipeline-util.ts b/dev/src/pipeline-util.ts index db24449af..1bb77b482 100644 --- a/dev/src/pipeline-util.ts +++ b/dev/src/pipeline-util.ts @@ -434,7 +434,7 @@ export function toPipelineFilterCondition( const values = value?.arrayValue?.values?.map(val => Constant.of(val) ); - return and(field.exists(), field.in(...values!)); + return and(field.exists(), field.eqAny(...values!)); } case 'ARRAY_CONTAINS_ANY': { const values = value?.arrayValue?.values?.map(val => @@ -446,7 +446,7 @@ export function toPipelineFilterCondition( const values = value?.arrayValue?.values?.map(val => Constant.of(val) ); - return and(field.exists(), not(field.in(...values!))); + return and(field.exists(), field.notEqAny(...values!)); } } } diff --git a/dev/system-test/pipeline.ts b/dev/system-test/pipeline.ts index 82d380b11..317c4e839 100644 --- a/dev/system-test/pipeline.ts +++ b/dev/system-test/pipeline.ts @@ -13,25 +13,22 @@ // limitations under the License. import { - AggregateQuery, DocumentData, FirestorePipelineConverter, QuerySnapshot, - VectorValue, } from '@google-cloud/firestore'; import {expect} from 'chai'; -import {afterEach, beforeEach, describe, it} from 'mocha'; +import {afterEach, describe, it} from 'mocha'; import { CollectionReference, DocumentReference, - DocumentSnapshot, FieldPath, - FieldValue, - Filter, Firestore, + logicalMinimum, + logicalMaximum, Query, - QueryDocumentSnapshot, + cond, } from '../src'; import { add, @@ -60,6 +57,8 @@ import { Constant, mapGet, lte, + eqAny, + notEqAny, } from '../src/expression'; import {PipelineResult} from '../src/pipeline'; import {verifyInstance} from '../test/util/helpers'; @@ -258,7 +257,7 @@ describe.only('Pipeline class', () => { .aggregate( countAll().as('count'), avg('rating').as('avg_rating'), - Field.of('rating').max().as('max_rating') + Field.of('rating').maximum().as('max_rating') ) .execute(); expectResults(result, {count: 2, avg_rating: 4.4, max_rating: 4.6}); @@ -277,7 +276,8 @@ describe.only('Pipeline class', () => { ).to.be.rejected; }); - it('returns distinct values as expected', async () => { + // toLower not impelemented + it.skip('returns distinct values as expected', async () => { const results = await randomCol .pipeline() .where(lt('published', 1900)) @@ -295,26 +295,27 @@ describe.only('Pipeline class', () => { .pipeline() .where(lt(Field.of('published'), 1984)) .aggregate({ - accumulators: [avg('rating').as('avg_rating')], + accumulators: [avg('rating').as('avgRating')], groups: ['genre'], }) - .where(gt('avg_rating', 4.3)) + .where(gt('avgRating', 4.3)) + .sort(Field.of('avgRating').descending()) .execute(); expectResults( results, - {avg_rating: 4.7, genre: 'Fantasy'}, - {avg_rating: 4.5, genre: 'Romance'}, - {avg_rating: 4.4, genre: 'Science Fiction'} + {avgRating: 4.7, genre: 'Fantasy'}, + {avgRating: 4.5, genre: 'Romance'}, + {avgRating: 4.4, genre: 'Science Fiction'} ); }); - it('returns min and max accumulations', async () => { + it('returns minimum and maximum accumulations', async () => { const results = await randomCol .pipeline() .aggregate( countAll().as('count'), - Field.of('rating').max().as('max_rating'), - Field.of('published').min().as('min_published') + Field.of('rating').maximum().as('max_rating'), + Field.of('published').minimum().as('min_published') ) .execute(); expectResults(results, { @@ -443,6 +444,95 @@ describe.only('Pipeline class', () => { ); }); + it('logical min works', async () => { + const results = await randomCol + .pipeline() + .select( + 'title', + logicalMinimum(Constant.of(1960), Field.of('published')).as( + 'published-safe' + ) + ) + .sort(Field.of('title').ascending()) + .limit(3) + .execute(); + expectResults( + results, + {title: '1984', 'published-safe': 1949}, + {title: 'Crime and Punishment', 'published-safe': 1866}, + {title: 'Dune', 'published-safe': 1960} + ); + }); + + it('logical max works', async () => { + const results = await randomCol + .pipeline() + .select( + 'title', + logicalMaximum(Constant.of(1960), Field.of('published')).as( + 'published-safe' + ) + ) + .sort(Field.of('title').ascending()) + .limit(3) + .execute(); + expectResults( + results, + {title: '1984', 'published-safe': 1960}, + {title: 'Crime and Punishment', 'published-safe': 1960}, + {title: 'Dune', 'published-safe': 1965} + ); + }); + + it('cond works', async () => { + const results = await randomCol + .pipeline() + .select( + 'title', + cond( + lt(Field.of('published'), 1960), + Constant.of(1960), + Field.of('published') + ).as('published-safe') + ) + .sort(Field.of('title').ascending()) + .limit(3) + .execute(); + expectResults( + results, + {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 + .pipeline() + .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'} + ); + }); + + it('notEqAny works', async () => { + const results = await randomCol + .pipeline() + .where( + notEqAny( + 'published', + [1965, 1925, 1949, 1960, 1866, 1985, 1954, 1967, 1979] + ) + ) + .select('title') + .execute(); + expectResults(results, {title: 'Pride and Prejudice'}); + }); + it('arrayContains works', async () => { const results = await randomCol .pipeline() @@ -483,7 +573,8 @@ describe.only('Pipeline class', () => { expect(results.length).to.equal(10); }); - it('arrayConcat works', async () => { + // array_concat not implemented + it.skip('arrayConcat works', async () => { const results = await randomCol .pipeline() .select( @@ -547,18 +638,33 @@ describe.only('Pipeline class', () => { Field.of('title') ) .where(gt('titleLength', 20)) + .sort(Field.of('title').ascending()) .execute(); + expectResults( results, - {titleLength: 32, title: "The Hitchhiker's Guide to the Galaxy"}, + { - titleLength: 27, + titleLength: 29, title: 'One Hundred Years of Solitude', + }, + { + titleLength: 36, + title: "The Hitchhiker's Guide to the Galaxy", + }, + { + titleLength: 21, + title: 'The Lord of the Rings', + }, + { + titleLength: 21, + title: 'To Kill a Mockingbird', } ); }); - it('testToLowercase', async () => { + // to_lower not implemented + it.skip('testToLowercase', async () => { const results = await randomCol .pipeline() .select(Field.of('title').toLower().as('lowercaseTitle')) @@ -569,7 +675,8 @@ describe.only('Pipeline class', () => { }); }); - it('testToUppercase', async () => { + // to_upper not implemented + it.skip('testToUppercase', async () => { const results = await randomCol .pipeline() .select(Field.of('author').toUpper().as('uppercaseAuthor')) @@ -578,7 +685,8 @@ describe.only('Pipeline class', () => { expectResults(results, {uppercaseAuthor: 'DOUGLAS ADAMS'}); }); - it('testTrim', async () => { + // trim not implemented + it.skip('testTrim', async () => { const results = await randomCol .pipeline() .addFields(strConcat(' ', Field.of('title'), ' ').as('spacedTitle')) diff --git a/types/firestore.d.ts b/types/firestore.d.ts index fce440cd2..152f35996 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -3875,7 +3875,7 @@ declare namespace FirebaseFirestore { * @param others The values or expressions to check against. * @return A new `Expr` representing the 'IN' comparison. */ - in(...others: Expr[]): In; + eqAny(...others: Expr[]): EqAny; /** * Creates an expression that checks if this expression is equal to any of the provided values or @@ -3889,7 +3889,7 @@ declare namespace FirebaseFirestore { * @param others The values or expressions to check against. * @return A new `Expr` representing the 'IN' comparison. */ - in(...others: any[]): In; + eqAny(...others: any[]): EqAny; /** * Creates an expression that checks if this expression evaluates to 'NaN' (Not a Number). @@ -4274,76 +4274,76 @@ declare namespace FirebaseFirestore { * * ```typescript * // Find the lowest price of all products - * Field.of("price").min().as("lowestPrice"); + * Field.of("price").minimum().as("lowestPrice"); * ``` * - * @return A new `Accumulator` representing the 'min' aggregation. + * @return A new `Accumulator` representing the 'minimum' aggregation. */ - min(): Min; + minimum(): Minimum; /** * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. * * ```typescript * // Find the highest score in a leaderboard - * Field.of("score").max().as("highestScore"); + * Field.of("score").maximum().as("highestScore"); * ``` * - * @return A new `Accumulator` representing the 'max' aggregation. + * @return A new `Accumulator` representing the 'maximum' aggregation. */ - max(): Max; + maximum(): Maximim; /** * Creates an expression that returns the larger value between this expression and another expression, based on Firestore's value type ordering. * * ```typescript * // Returns the larger value between the 'timestamp' field and the current timestamp. - * Field.of("timestamp").logicalMax(Function.currentTimestamp()); + * Field.of("timestamp").logicalMaximum(Function.currentTimestamp()); * ``` * * @param other The expression to compare with. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ - logicalMax(other: Expr): LogicalMax; + 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").logicalMax(10); + * Field.of("value").logicalMaximum(10); * ``` * * @param other The constant value to compare with. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ - logicalMax(other: any): LogicalMax; + logicalMaximum(other: any): LogicalMaximum; /** * Creates an expression that returns the smaller value between this expression and another expression, based on Firestore's value type ordering. * * ```typescript * // Returns the smaller value between the 'timestamp' field and the current timestamp. - * Field.of("timestamp").logicalMin(Function.currentTimestamp()); + * Field.of("timestamp").logicalMinimum(Function.currentTimestamp()); * ``` * * @param other The expression to compare with. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ - logicalMin(other: Expr): LogicalMin; + 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").logicalMin(10); + * Field.of("value").logicalMinimum(10); * ``` * * @param other The constant value to compare with. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ - logicalMin(other: any): LogicalMin; + logicalMinimum(other: any): LogicalMinimum; /** * Creates an expression that calculates the length (number of dimensions) of this Firestore Vector expression. @@ -5030,7 +5030,7 @@ declare namespace FirebaseFirestore { /** * @beta */ - export class In extends Function implements FilterCondition { + export class EqAny extends Function implements FilterCondition { filterable: true; } @@ -5086,12 +5086,12 @@ declare namespace FirebaseFirestore { /** * @beta */ - export class LogicalMax extends Function {} + export class LogicalMaximum extends Function {} /** * @beta */ - export class LogicalMin extends Function {} + export class LogicalMinimum extends Function {} /** * @beta @@ -5209,14 +5209,14 @@ declare namespace FirebaseFirestore { /** * @beta */ - export class Min extends Function implements Accumulator { + export class Minimum extends Function implements Accumulator { accumulator: true; } /** * @beta */ - export class Max extends Function implements Accumulator { + export class Maximim extends Function implements Accumulator { accumulator: true; } @@ -6648,14 +6648,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); + * eqAny(Field.of("category"), [Constant.of("Electronics"), Field.of("primaryType")]); * ``` * * @param element The expression to compare. * @param others The values to check against. * @return A new {@code Expr} representing the 'IN' comparison. */ - export function inAny(element: Expr, others: Expr[]): In; + export function eqAny(element: Expr, others: Expr[]): EqAny; /** * @beta @@ -6665,14 +6665,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); + * eqAny(Field.of("category"), ["Electronics", Field.of("primaryType")]); * ``` * * @param element The expression to compare. * @param others The values to check against. * @return A new {@code Expr} representing the 'IN' comparison. */ - export function inAny(element: Expr, others: any[]): In; + export function eqAny(element: Expr, others: any[]): EqAny; /** * @beta @@ -6682,14 +6682,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); + * eqAny("category", [Constant.of("Electronics"), Field.of("primaryType")]); * ``` * * @param element The field to compare. * @param others The values to check against. * @return A new {@code Expr} representing the 'IN' comparison. */ - export function inAny(element: string, others: Expr[]): In; + export function eqAny(element: string, others: Expr[]): EqAny; /** * @beta @@ -6699,14 +6699,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Check if the 'category' field is either "Electronics" or value of field 'primaryType' - * inAny("category", ["Electronics", Field.of("primaryType")]); + * eqAny("category", ["Electronics", Field.of("primaryType")]); * ``` * * @param element The field to compare. * @param others The values to check against. * @return A new {@code Expr} representing the 'IN' comparison. */ - export function inAny(element: string, others: any[]): In; + export function eqAny(element: string, others: any[]): EqAny; /** * @beta @@ -6716,14 +6716,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); + * notEqAny(Field.of("status"), [Constant.of("pending"), Field.of("rejectedStatus")]); * ``` * * @param element The expression to compare. * @param others The values to check against. * @return A new {@code Expr} representing the 'NOT IN' comparison. */ - export function notInAny(element: Expr, others: Expr[]): Not; + export function notEqAny(element: Expr, others: Expr[]): NotEqAny; /** * @beta @@ -6733,14 +6733,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); + * notEqAny(Field.of("status"), ["pending", Field.of("rejectedStatus")]); * ``` * * @param element The expression to compare. * @param others The values to check against. * @return A new {@code Expr} representing the 'NOT IN' comparison. */ - export function notInAny(element: Expr, others: any[]): Not; + export function notEqAny(element: Expr, others: any[]): NotEqAny; /** * @beta @@ -6750,14 +6750,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny("status", [Constant.of("pending"), Field.of("rejectedStatus")]); + * notEqAny("status", [Constant.of("pending"), Field.of("rejectedStatus")]); * ``` * * @param element The field name to compare. * @param others The values to check against. * @return A new {@code Expr} representing the 'NOT IN' comparison. */ - export function notInAny(element: string, others: Expr[]): Not; + export function notEqAny(element: string, others: Expr[]): NotEqAny; /** * @beta @@ -6767,14 +6767,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Check if the 'status' field is neither "pending" nor the value of 'rejectedStatus' - * notInAny("status", ["pending", Field.of("rejectedStatus")]); + * notEqAny("status", ["pending", Field.of("rejectedStatus")]); * ``` * * @param element The field name to compare. * @param others The values to check against. * @return A new {@code Expr} representing the 'NOT IN' comparison. */ - export function notInAny(element: string, others: any[]): Not; + export function notEqAny(element: string, others: any[]): NotEqAny; /** * @beta @@ -6839,7 +6839,7 @@ declare namespace FirebaseFirestore { * * ```typescript * // If 'age' is greater than 18, return "Adult"; otherwise, return "Minor". - * ifFunction( + * cond( * gt("age", 18), Constant.of("Adult"), Constant.of("Minor")); * ``` * @@ -6876,14 +6876,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Returns the larger value between the 'timestamp' field and the current timestamp. - * logicalMax(Field.of("timestamp"), Function.currentTimestamp()); + * logicalMaximum(Field.of("timestamp"), Function.currentTimestamp()); * ``` * * @param left The left operand expression. * @param right The right operand expression. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ - export function logicalMax(left: Expr, right: Expr): LogicalMax; + export function logicalMaximum(left: Expr, right: Expr): LogicalMaximum; /** * @beta @@ -6892,14 +6892,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Returns the larger value between the 'value' field and 10. - * logicalMax(Field.of("value"), 10); + * logicalMaximum(Field.of("value"), 10); * ``` * * @param left The left operand expression. * @param right The right operand constant. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ - export function logicalMax(left: Expr, right: any): LogicalMax; + export function logicalMaximum(left: Expr, right: any): LogicalMaximum; /** * @beta @@ -6908,14 +6908,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Returns the larger value between the 'timestamp' field and the current timestamp. - * logicalMax("timestamp", Function.currentTimestamp()); + * logicalMaximum("timestamp", Function.currentTimestamp()); * ``` * * @param left The left operand field name. * @param right The right operand expression. - * @return A new {@code Expr} representing the logical max operation. + * @return A new {@code Expr} representing the logical maximum operation. */ - export function logicalMax(left: string, right: Expr): LogicalMax; + export function logicalMaximum(left: string, right: Expr): LogicalMaximum; /** * @beta @@ -6924,14 +6924,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Returns the larger value between the 'value' field and 10. - * logicalMax("value", 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. + * @return A new {@code Expr} representing the logical maximum operation. */ - export function logicalMax(left: string, right: any): LogicalMax; + export function logicalMaximum(left: string, right: any): LogicalMaximum; /** * @beta @@ -6940,14 +6940,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Returns the smaller value between the 'timestamp' field and the current timestamp. - * logicalMin(Field.of("timestamp"), Function.currentTimestamp()); + * logicalMinimum(Field.of("timestamp"), Function.currentTimestamp()); * ``` * * @param left The left operand expression. * @param right The right operand expression. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ - export function logicalMin(left: Expr, right: Expr): LogicalMin; + export function logicalMinimum(left: Expr, right: Expr): LogicalMinimum; /** * @beta @@ -6956,14 +6956,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Returns the smaller value between the 'value' field and 10. - * logicalMin(Field.of("value"), 10); + * logicalMinimum(Field.of("value"), 10); * ``` * * @param left The left operand expression. * @param right The right operand constant. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ - export function logicalMin(left: Expr, right: any): LogicalMin; + export function logicalMinimum(left: Expr, right: any): LogicalMinimum; /** * @beta @@ -6972,14 +6972,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Returns the smaller value between the 'timestamp' field and the current timestamp. - * logicalMin("timestamp", Function.currentTimestamp()); + * logicalMinimum("timestamp", Function.currentTimestamp()); * ``` * * @param left The left operand field name. * @param right The right operand expression. - * @return A new {@code Expr} representing the logical min operation. + * @return A new {@code Expr} representing the logical minimum operation. */ - export function logicalMin(left: string, right: Expr): LogicalMin; + export function logicalMinimum(left: string, right: Expr): LogicalMinimum; /** * @beta @@ -6988,14 +6988,14 @@ declare namespace FirebaseFirestore { * * ```typescript * // Returns the smaller value between the 'value' field and 10. - * logicalMin("value", 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. + * @return A new {@code Expr} representing the logical minimum operation. */ - export function logicalMin(left: string, right: any): LogicalMin; + export function logicalMinimum(left: string, right: any): LogicalMinimum; /** * @beta @@ -7986,13 +7986,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Find the lowest price of all products - * min(Field.of("price")).as("lowestPrice"); + * minimum(Field.of("price")).as("lowestPrice"); * ``` * * @param value The expression to find the minimum value of. - * @return A new {@code Accumulator} representing the 'min' aggregation. + * @return A new {@code Accumulator} representing the 'minimum' aggregation. */ - export function min(value: Expr): Min; + export function minimum(value: Expr): Minimum; /** * @beta @@ -8001,13 +8001,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Find the lowest price of all products - * min("price").as("lowestPrice"); + * 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. + * @return A new {@code Accumulator} representing the 'minimum' aggregation. */ - export function min(value: string): Min; + export function minimum(value: string): Minimum; /** * @beta @@ -8017,13 +8017,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Find the highest score in a leaderboard - * max(Field.of("score")).as("highestScore"); + * maximum(Field.of("score")).as("highestScore"); * ``` * * @param value The expression to find the maximum value of. - * @return A new {@code Accumulator} representing the 'max' aggregation. + * @return A new {@code Accumulator} representing the 'maximum' aggregation. */ - export function max(value: Expr): Max; + export function maximum(value: Expr): Maximim; /** * @beta @@ -8032,13 +8032,13 @@ declare namespace FirebaseFirestore { * * ```typescript * // Find the highest score in a leaderboard - * max("score").as("highestScore"); + * 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. + * @return A new {@code Accumulator} representing the 'maximum' aggregation. */ - export function max(value: string): Max; + export function maximum(value: string): Maximim; /** * @beta