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); } } 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..8479e7779 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: "/v1/{database=projects/*/databases/*}/documents: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..35e6ad599 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": "/v1/{database=projects/*/databases/*}/documents:executePipeline", + "(google.api.http).body": "*" + }, + "parsedOptions": [ + { + "(google.api.http)": { + "post": "/v1/{database=projects/*/databases/*}/documents: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": { diff --git a/dev/src/expression.ts b/dev/src/expression.ts new file mode 100644 index 000000000..399433096 --- /dev/null +++ b/dev/src/expression.ts @@ -0,0 +1,6792 @@ +// 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; + +import * as firestore from '@google-cloud/firestore'; + +import {VectorValue} from './field-value'; +import {FieldPath} from './path'; +import {Pipeline} from './pipeline'; +import {isFirestoreValue} from './pipeline-util'; +import {Serializer} from './serializer'; + +/** + * @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 implements firestore.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: firestore.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) { + return new Add(this, other); + } + 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: firestore.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) { + return new Subtract(this, other); + } + 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: firestore.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) { + return new Multiply(this, other); + } + 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: firestore.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) { + return new Divide(this, other); + } + 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. + * + * ```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: firestore.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) { + return new Eq(this, other); + } + 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: firestore.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) { + return new Neq(this, other); + } + 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: firestore.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) { + return new Lt(this, other); + } + 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: firestore.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) { + return new Lte(this, other); + } + 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: firestore.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) { + return new Gt(this, other); + } + 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: firestore.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) { + return new Gte(this, other); + } + 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 arrays The array expressions to concatenate. + * @return A new `Expr` representing the concatenated array. + */ + arrayConcat(arrays: firestore.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; + arrayConcat(arrays: any[]): ArrayConcat { + const exprValues = arrays.map(value => + value instanceof Expr ? value : Constant.of(value) + ); + 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: firestore.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) { + return new ArrayContains(this, element); + } + 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: firestore.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 => + value instanceof Expr ? value : Constant.of(value) + ); + 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: firestore.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 => + value instanceof Expr ? value : Constant.of(value) + ); + return new ArrayContainsAny(this, exprValues); + } + + /** + * 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 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").eqAny("Electronics", Field.of("primaryType")); + * ``` + * + * @param others The values or expressions to check against. + * @return A new `Expr` representing the 'EqAny' comparison. + */ + eqAny(...others: firestore.Expr[]): EqAny; + + /** + * 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").eqAny("Electronics", Field.of("primaryType")); + * ``` + * + * @param others The values or expressions to check against. + * @return A new `Expr` representing the 'EqAny' comparison. + */ + eqAny(...others: any[]): EqAny; + eqAny(...others: any[]): EqAny { + const exprOthers = others.map(other => + other instanceof Expr ? other : Constant.of(other) + ); + return new EqAny(this, exprOthers); + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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 character length of a string in UTF-8. + * + * ```typescript + * // 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. + */ + charLength(): CharLength { + return new CharLength(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: firestore.Expr): Like; + like(stringOrExpr: string | firestore.Expr): Like { + if (typeof stringOrExpr === 'string') { + return new Like(this, Constant.of(stringOrExpr)); + } + return new Like(this, stringOrExpr as Expr); + } + + /** + * 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: firestore.Expr): RegexContains; + regexContains(stringOrExpr: string | firestore.Expr): RegexContains { + if (typeof stringOrExpr === 'string') { + return new RegexContains(this, Constant.of(stringOrExpr)); + } + return new RegexContains(this, stringOrExpr as Expr); + } + + /** + * 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: firestore.Expr): RegexMatch; + regexMatch(stringOrExpr: string | firestore.Expr): RegexMatch { + if (typeof stringOrExpr === 'string') { + return new RegexMatch(this, Constant.of(stringOrExpr)); + } + 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. + * + * ```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: firestore.Expr): StartsWith; + startsWith(stringOrExpr: string | firestore.Expr): StartsWith { + if (typeof stringOrExpr === 'string') { + return new StartsWith(this, Constant.of(stringOrExpr)); + } + return new StartsWith(this, stringOrExpr as Expr); + } + + /** + * 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: firestore.Expr): EndsWith; + endsWith(stringOrExpr: string | firestore.Expr): EndsWith { + if (typeof stringOrExpr === 'string') { + return new EndsWith(this, Constant.of(stringOrExpr)); + } + return new EndsWith(this, stringOrExpr as Expr); + } + + /** + * Creates an expression that converts a string to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * Field.of("name").toLower(); + * ``` + * + * @return A new `Expr` representing the lowercase string. + */ + toLower(): ToLower { + return new ToLower(this); + } + + /** + * Creates an expression that converts a string to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * Field.of("title").toUpper(); + * ``` + * + * @return A new `Expr` representing the uppercase string. + */ + toUpper(): ToUpper { + return new ToUpper(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 | 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. + * + * ```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").minimum().as("lowestPrice"); + * ``` + * + * @return A new `Accumulator` representing the 'minimum' aggregation. + */ + minimum(): Minimum { + return new Minimum(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").maximum().as("highestScore"); + * ``` + * + * @return A new `Accumulator` representing the 'maximum' aggregation. + */ + maximum(): Maximum { + return new Maximum(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").logicalMaximum(Function.currentTimestamp()); + * ``` + * + * @param other The expression to compare with. + * @return A new {@code Expr} representing the logical maximum operation. + */ + 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").logicalMaximum(10); + * ``` + * + * @param other The constant value to compare with. + * @return A new {@code Expr} representing the logical maximum operation. + */ + logicalMaximum(other: any): firestore.LogicalMaximum; + logicalMaximum(other: any): firestore.LogicalMaximum { + if (other instanceof Expr) { + return new LogicalMaximum(this, other as Expr); + } + return new LogicalMaximum(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").logicalMinimum(Function.currentTimestamp()); + * ``` + * + * @param other The expression to compare with. + * @return A new {@code Expr} representing the logical minimum operation. + */ + 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").logicalMinimum(10); + * ``` + * + * @param other The constant value to compare with. + * @return A new {@code Expr} representing the logical minimum operation. + */ + logicalMinimum(other: any): firestore.LogicalMinimum; + logicalMinimum(other: any): firestore.LogicalMinimum { + if (other instanceof Expr) { + return new LogicalMinimum(this, other as Expr); + } + return new LogicalMinimum(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. + * + * ```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: firestore.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: firestore.Expr | firestore.VectorValue | number[] + ): CosineDistance { + if (other instanceof Expr) { + return new CosineDistance(this, other as Expr); + } else { + return new CosineDistance( + this, + Constant.vector(other as VectorValue | number[]) + ); + } + } + + /** + * 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: firestore.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; + dotProduct( + other: firestore.Expr | firestore.VectorValue | number[] + ): DotProduct { + if (other instanceof Expr) { + return new DotProduct(this, other as Expr); + } else { + return new DotProduct( + this, + Constant.vector(other as VectorValue | number[]) + ); + } + } + + /** + * 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: firestore.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: firestore.Expr | firestore.VectorValue | number[] + ): EuclideanDistance { + if (other instanceof Expr) { + return new EuclideanDistance(this, other as Expr); + } else { + return new EuclideanDistance( + this, + Constant.vector(other as VectorValue | number[]) + ); + } + } + + /** + * 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(): firestore.UnixMicrosToTimestamp { + return new UnixMicrosToTimestamp(this); + } + + /** + * 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(): firestore.TimestampToUnixMicros { + return new TimestampToUnixMicros(this); + } + + /** + * 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(): firestore.UnixMillisToTimestamp { + return new UnixMillisToTimestamp(this); + } + + /** + * 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); + } + + /** + * 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; + + constructor( + public expr: T, + public alias: string + ) { + super(); + } + + _toProto(serializer: Serializer): api.IValue { + throw new Error('ExprWithAlias should not be serialized directly.'); + } +} + +/** + * @internal + */ +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)!), + }, + }; + } +} + +/** + * @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 = 'Field'; + selectable = true as const; + + private constructor( + private fieldPath: firestore.FieldPath, + private pipeline: Pipeline | null = null + ) { + super(); + } + + /** + * Creates a {@code Field} instance representing the field at the given path. + * + * The path can be a simple field name (e.g., "name") or a dot-separated path to a nested field + * (e.g., "address.city"). + * + * ```typescript + * // Create a Field instance for the 'title' field + * const titleField = Field.of("title"); + * + * // Create a Field instance for a nested field 'author.firstName' + * const authorFirstNameField = Field.of("author.firstName"); + * ``` + * + * @param name The path to the field. + * @return A new {@code Field} instance representing the specified field. + */ + static of(name: string): Field; + static of(path: firestore.FieldPath): Field; + static of(nameOrPath: string | firestore.FieldPath): Field; + static of(pipeline: firestore.Pipeline, name: string): Field; + static of( + pipelineOrName: firestore.Pipeline | string | firestore.FieldPath, + name?: string + ): Field { + if (typeof pipelineOrName === 'string') { + if (FieldPath.documentId().formattedName === pipelineOrName) { + return new Field(FieldPath.documentId()); + } + + return new Field(FieldPath.fromArgument(pipelineOrName)); + } else if (pipelineOrName instanceof FieldPath) { + if (FieldPath.documentId().isEqual(pipelineOrName)) { + return new Field(FieldPath.documentId()); + } + return new Field(pipelineOrName); + } else { + return new Field( + FieldPath.fromArgument(name!), + pipelineOrName as Pipeline + ); + } + } + + fieldName(): string { + return (this.fieldPath as FieldPath).formattedName; + } + + _toProto(serializer: Serializer): api.IValue { + return { + fieldReferenceValue: (this.fieldPath as FieldPath).formattedName, + }; + } +} + +/** + * @beta + */ +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)), + }, + }; + } +} + +/** + * @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 = 'Constant'; + + private constructor(private value: any) { + super(); + } + + /** + * Creates a `Constant` instance for a number value. + * + * @param value The number value. + * @return A new `Constant` instance. + */ + static of(value: number): Constant; + + /** + * Creates a `Constant` instance for a string value. + * + * @param value The string value. + * @return A new `Constant` instance. + */ + static of(value: string): Constant; + + /** + * Creates a `Constant` instance for a boolean value. + * + * @param value The boolean value. + * @return A new `Constant` instance. + */ + static of(value: boolean): Constant; + + /** + * Creates a `Constant` instance for a null value. + * + * @param value The null value. + * @return A new `Constant` instance. + */ + static of(value: null): Constant; + + /** + * Creates a `Constant` instance for an undefined value. + * + * @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: firestore.GeoPoint): Constant; + + /** + * Creates a `Constant` instance for a Timestamp value. + * + * @param value The Timestamp value. + * @return A new `Constant` instance. + */ + static of(value: firestore.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: firestore.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: firestore.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 vector(value: Array | firestore.VectorValue): Constant { + if (value instanceof VectorValue) { + return new Constant(value); + } else { + return new Constant(new VectorValue(value as Array)); + } + } + + _toProto(serializer: Serializer): api.IValue { + if (isFirestoreValue(this.value)) { + return this.value; + } + + return serializer.encodeValue(this.value)!; + } +} + +/** + * @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 = '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)!), + }, + }; + } +} + +/** + * @beta + */ +export class Add extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('add', [left, right]); + } +} + +/** + * @beta + */ +export class Subtract extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('subtract', [left, right]); + } +} + +/** + * @beta + */ +export class Multiply extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('multiply', [left, right]); + } +} + +/** + * @beta + */ +export class Divide extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('divide', [left, right]); + } +} + +/** + * @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 + */ +export class Eq extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('eq', [left, right]); + } + filterable = true as const; +} + +/** + * @beta + */ +class Neq extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('neq', [left, right]); + } + filterable = true as const; +} + +/** + * @beta + */ +class Lt extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('lt', [left, right]); + } + filterable = true as const; +} + +/** + * @beta + */ +class Lte extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('lte', [left, right]); + } + filterable = true as const; +} + +/** + * @beta + */ +class Gt extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('gt', [left, right]); + } + filterable = true as const; +} + +/** + * @beta + */ +class Gte extends Function implements FilterCondition { + constructor( + private left: Expr, + private right: Expr + ) { + super('gte', [left, right]); + } + filterable = true as const; +} + +/** + * @beta + */ +class ArrayConcat extends Function { + constructor( + private array: Expr, + private elements: Expr[] + ) { + super('array_concat', [array, ...elements]); + } +} + +/** + * @beta + */ +class ArrayReverse extends Function { + constructor(private array: Expr) { + super('array_reverse', [array]); + } +} + +/** + * @beta + */ +class ArrayContains extends Function implements FilterCondition { + constructor( + private array: Expr, + private element: Expr + ) { + super('array_contains', [array, element]); + } + filterable = true as const; +} + +/** + * @beta + */ +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; +} + +/** + * @beta + */ +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; +} + +/** + * @beta + */ +class ArrayLength extends Function { + constructor(private array: Expr) { + super('array_length', [array]); + } +} + +/** + * @beta + */ +class ArrayElement extends Function { + constructor() { + super('array_element', []); + } +} + +/** + * @beta + */ +class EqAny extends Function implements FilterCondition { + constructor( + private left: Expr, + private others: Expr[] + ) { + 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; +} + +/** + * @beta + */ +class IsNan extends Function implements FilterCondition { + constructor(private expr: Expr) { + super('is_nan', [expr]); + } + filterable = true as const; +} + +/** + * @beta + */ +class Exists extends Function implements FilterCondition { + constructor(private expr: Expr) { + super('exists', [expr]); + } + filterable = true as const; +} + +/** + * @beta + */ +class Not extends Function implements FilterCondition { + constructor(private expr: Expr) { + super('not', [expr]); + } + filterable = true as const; +} + +/** + * @beta + */ +class And extends Function implements FilterCondition { + constructor(private conditions: FilterExpr[]) { + super('and', conditions); + } + + filterable = true as const; +} + +/** + * @beta + */ +class Or extends Function implements FilterCondition { + constructor(private conditions: FilterExpr[]) { + super('or', conditions); + } + filterable = true as const; +} + +/** + * @beta + */ +class Xor extends Function implements FilterCondition { + constructor(private conditions: FilterExpr[]) { + super('xor', conditions); + } + filterable = true as const; +} + +/** + * @beta + */ +class Cond extends Function implements FilterCondition { + constructor( + private condition: FilterExpr, + private thenExpr: Expr, + private elseExpr: Expr + ) { + super('cond', [condition, thenExpr, elseExpr]); + } + filterable = true as const; +} + +/** + * @beta + */ +class LogicalMaximum extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('logical_maximum', [left, right]); + } +} + +/** + * @beta + */ +class LogicalMinimum extends Function { + constructor( + private left: Expr, + private right: Expr + ) { + super('logical_minimum', [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]); + } +} + +/** + * @beta + */ +class Like extends Function implements FilterCondition { + constructor( + private expr: Expr, + private pattern: Expr + ) { + super('like', [expr, pattern]); + } + filterable = true as const; +} + +/** + * @beta + */ +class RegexContains extends Function implements FilterCondition { + constructor( + private expr: Expr, + private pattern: Expr + ) { + super('regex_contains', [expr, pattern]); + } + filterable = true as const; +} + +/** + * @beta + */ +class RegexMatch extends Function implements FilterCondition { + constructor( + private expr: Expr, + private pattern: Expr + ) { + super('regex_match', [expr, pattern]); + } + 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 + */ +class StartsWith extends Function implements FilterCondition { + constructor( + private expr: Expr, + private prefix: Expr + ) { + super('starts_with', [expr, prefix]); + } + filterable = true as const; +} + +/** + * @beta + */ +class EndsWith extends Function implements FilterCondition { + constructor( + private expr: Expr, + private suffix: Expr + ) { + super('ends_with', [expr, suffix]); + } + filterable = true as const; +} + +/** + * @beta + */ +class ToLower extends Function { + constructor(private expr: Expr) { + super('to_lower', [expr]); + } +} + +/** + * @beta + */ +class ToUpper extends Function { + constructor(private expr: Expr) { + super('to_upper', [expr]); + } +} + +/** + * @beta + */ +class Trim extends Function { + constructor(private expr: Expr) { + super('trim', [expr]); + } +} + +/** + * @beta + */ +class StrConcat extends Function { + constructor( + private first: Expr, + private rest: Expr[] + ) { + super('str_concat', [first, ...rest]); + } +} + +/** + * @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( + private value: Expr | undefined, + private distinct: boolean + ) { + super('count', value === undefined ? [] : [value]); + } +} + +/** + * @beta + */ +class Sum extends Function implements Accumulator { + accumulator = true as const; + constructor( + private value: Expr, + private distinct: boolean + ) { + super('sum', [value]); + } +} + +/** + * @beta + */ +class Avg extends Function implements Accumulator { + accumulator = true as const; + constructor( + private value: Expr, + private distinct: boolean + ) { + super('avg', [value]); + } +} + +/** + * @beta + */ +class Minimum extends Function implements Accumulator { + accumulator = true as const; + constructor( + private value: Expr, + private distinct: boolean + ) { + super('minimum', [value]); + } +} + +/** + * @beta + */ +class Maximum extends Function implements Accumulator { + accumulator = true as const; + constructor( + private value: Expr, + private distinct: boolean + ) { + super('maximum', [value]); + } +} + +/** + * @beta + */ +class CosineDistance extends Function { + constructor( + private vector1: Expr, + private vector2: Expr + ) { + super('cosine_distance', [vector1, vector2]); + } +} + +/** + * @beta + */ +class DotProduct extends Function { + constructor( + private vector1: Expr, + private vector2: Expr + ) { + super('dot_product', [vector1, vector2]); + } +} + +/** + * @beta + */ +class EuclideanDistance extends Function { + constructor( + private vector1: Expr, + private vector2: Expr + ) { + super('euclidean_distance', [vector1, vector2]); + } +} + +/** + * @beta + */ +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; + +/** + * @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; +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); +} + +/** + * @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; +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); +} + +/** + * @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; +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); +} + +/** + * @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; +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); +} + +/** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing two expressions. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod(Field.of("field1"), Field.of("field2")); + * ``` + * + * @param left The dividend expression. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. + */ +export function mod(left: Expr, right: Expr): Mod; + +/** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing an expression by a constant. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 5. + * mod(Field.of("field1"), 5); + * ``` + * + * @param left The dividend expression. + * @param right The divisor constant. + * @return A new {@code Expr} representing the modulo operation. + */ +export function mod(left: Expr, right: any): Mod; + +/** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by an expression. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod("field1", Field.of("field2")); + * ``` + * + * @param left The dividend field name. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. + */ +export function mod(left: string, right: Expr): Mod; + +/** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by a constant. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 5. + * mod("field1", 5); + * ``` + * + * @param left The dividend field name. + * @param right The divisor constant. + * @return A new {@code Expr} representing the modulo operation. + */ +export function mod(left: string, right: any): Mod; +export function mod(left: Expr | string, right: Expr | any): Mod { + const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; + const normalizedRight = right instanceof Expr ? right : Constant.of(right); + return new Mod(normalizedLeft, normalizedRight); +} + +// /** +// * @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 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; + +/** + * @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; +export function gte(left: Expr | string, right: any): Gte { + const leftExpr = left instanceof Expr ? left : Field.of(left); + const rightExpr = right instanceof Expr ? right : Constant.of(right); + return new Gte(leftExpr, rightExpr); +} + +/** + * @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; +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); +} + +/** + * @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; +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); +} + +/** + * @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; +export function arrayContainsAny( + array: Expr | string, + values: any[] +): ArrayContainsAny { + const arrayExpr = array instanceof Expr ? array : Field.of(array); + const exprValues = values.map(value => + value instanceof Expr ? value : Constant.of(value) + ); + return new ArrayContainsAny(arrayExpr, exprValues); +} + +/** + * @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; +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 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 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' + * 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 'eqAny' comparison. + */ +export function eqAny(element: Expr, others: Expr[]): EqAny; + +/** + * @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' + * 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 'eqAny' comparison. + */ +export function eqAny(element: Expr, others: any[]): EqAny; + +/** + * @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' + * 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 'eqAny' comparison. + */ +export function eqAny(element: string, others: Expr[]): EqAny; + +/** + * @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' + * 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 'eqAny' comparison. + */ +export function eqAny(element: string, others: any[]): EqAny; +export function eqAny(element: Expr | string, others: any[]): EqAny { + const elementExpr = element instanceof Expr ? element : Field.of(element); + const exprOthers = others.map(other => + other instanceof Expr ? other : Constant.of(other) + ); + return new EqAny(elementExpr, exprOthers); +} + +/** + * @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' + * 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 'NotEqAny' comparison. + */ +export function notEqAny(element: Expr, others: Expr[]): NotEqAny; + +/** + * @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' + * 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 'NotEqAny' comparison. + */ +export function notEqAny(element: Expr, others: any[]): NotEqAny; + +/** + * @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' + * 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 'NotEqAny' comparison. + */ +export function notEqAny(element: string, others: Expr[]): NotEqAny; + +/** + * @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' + * 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 'NotEqAny' comparison. + */ +export function notEqAny(element: string, others: any[]): NotEqAny; +export function notEqAny(element: Expr | string, others: any[]): NotEqAny { + const elementExpr = element instanceof Expr ? element : Field.of(element); + const exprOthers = others.map(other => + other instanceof Expr ? other : Constant.of(other) + ); + return new NotEqAny(elementExpr, exprOthers); +} + +/** + * @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 { + return new And([left, ...right]); +} + +/** + * @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 { + return new Or([left, ...right]); +} + +/** + * @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 { + 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". + * cond( + * 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 cond( + condition: FilterExpr, + thenExpr: Expr, + elseExpr: Expr +): Cond { + return new Cond(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. + * 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 maximum operation. + */ +export function logicalMaximum(left: Expr, right: Expr): LogicalMaximum; + +/** + * @beta + * + * Creates an expression that returns the larger value between an expression and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'value' field and 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 maximum operation. + */ +export function logicalMaximum(left: Expr, right: any): LogicalMaximum; + +/** + * @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. + * 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 maximum operation. + */ +export function logicalMaximum(left: string, right: Expr): LogicalMaximum; + +/** + * @beta + * + * Creates an expression that returns the larger value between a field and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'value' field and 10. + * logicalMaximum("value", 10); + * ``` + * + * @param left The left operand field name. + * @param right The right operand constant. + * @return A new {@code Expr} representing the logical maximum operation. + */ +export function logicalMaximum(left: string, right: any): LogicalMaximum; +export function logicalMaximum( + left: Expr | string, + right: Expr | any +): LogicalMaximum { + const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; + const normalizedRight = right instanceof Expr ? right : Constant.of(right); + return new LogicalMaximum(normalizedLeft, normalizedRight); +} + +/** + * @beta + * + * Creates an expression that returns the smaller value between two expressions, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'field1' field and the 'field2' field. + * logicalMinimum(Field.of("field1"), Field.of("field2")); + * ``` + * + * @param left The left operand expression. + * @param right The right operand expression. + * @return A new {@code Expr} representing the logical minimum operation. + */ +export function logicalMinimum(left: Expr, right: Expr): LogicalMinimum; + +/** + * @beta + * + * Creates an expression that returns the smaller value between an expression and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'value' field and 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 minimum operation. + */ +export function logicalMinimum(left: Expr, right: any): LogicalMinimum; + +/** + * @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. + * 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 minimum operation. + */ +export function logicalMinimum(left: string, right: Expr): LogicalMinimum; + +/** + * @beta + * + * Creates an expression that returns the smaller value between a field and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'value' field and 10. + * logicalMinimum("value", 10); + * ``` + * + * @param left The left operand field name. + * @param right The right operand constant. + * @return A new {@code Expr} representing the logical minimum operation. + */ +export function logicalMinimum(left: string, right: any): LogicalMinimum; +export function logicalMinimum( + left: Expr | string, + right: Expr | any +): LogicalMinimum { + const normalizedLeft = typeof left === 'string' ? Field.of(left) : left; + const normalizedRight = right instanceof Expr ? right : Constant.of(right); + return new LogicalMinimum(normalizedLeft, normalizedRight); +} + +/** + * @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 replaceFirst( + value: Expr, + find: Expr, + replace: Expr +): ReplaceFirst; + +/** + * @beta + * + * Creates an expression that replaces the first occurrence of a substring within a string represented by a field with another substring. + * + * ```typescript + * // Replace the first occurrence of "hello" with "hi" in the 'message' field. + * replaceFirst("message", "hello", "hi"); + * ``` + * + * @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 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 replaces all occurrences of a substring within a string with another substring. + * + * ```typescript + * // Replace all occurrences of "hello" with "hi" in the 'message' field. + * replaceAll(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 all occurrences of 'find' with. + * @return A new {@code Expr} representing the string with all occurrences replaced. + */ +export function replaceAll( + value: Expr, + find: string, + replace: string +): ReplaceAll; + +/** + * @beta + * + * 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 + * // 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 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 replaceAll(value: Expr, find: Expr, replace: Expr): ReplaceAll; + +/** + * @beta + * + * Creates an expression that replaces all occurrences of a substring within a string represented by a field with another substring. + * + * ```typescript + * // Replace all occurrences of "hello" with "hi" in the 'message' field. + * replaceAll("message", "hello", "hi"); + * ``` + * + * @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 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 calculates the byte length of a string in UTF-8, or just the length of a Blob. + * + * ```typescript + * // Calculate the length of the 'myString' field in bytes. + * byteLength(Field.of("myString")); + * ``` + * + * @param expr The expression representing the string. + * @return A new {@code Expr} representing the length of the string in bytes. + */ +export function byteLength(expr: Expr): ByteLength; + +/** + * @beta + * + * 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 + * // Calculate the length of the 'myString' field in bytes. + * byteLength("myString"); + * ``` + * + * @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 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 character length of a string field in UTF8. + * + * ```typescript + * // 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 charLength(field: string): CharLength; + +/** + * @beta + * + * Creates an expression that calculates the character length of a string expression in UTF-8. + * + * ```typescript + * // 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 charLength(expr: Expr): CharLength; +export function charLength(value: Expr | string): CharLength { + const valueExpr = value instanceof Expr ? value : Field.of(value); + return new CharLength(valueExpr); +} + +/** + * @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; +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); +} + +/** + * @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; +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); +} + +/** + * @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; +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); +} + +/** + * @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 + * + * 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; +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); +} + +/** + * @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; +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); +} + +/** + * @beta + * + * Creates an expression that converts a string field to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * toLower("name"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the lowercase string. + */ +export function toLower(expr: string): ToLower; + +/** + * @beta + * + * Creates an expression that converts a string expression to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * 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 toLower(expr: Expr): ToLower; +export function toLower(expr: Expr | string): ToLower { + return new ToLower(expr instanceof Expr ? expr : Field.of(expr)); +} + +/** + * @beta + * + * Creates an expression that converts a string field to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * toUpper("title"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the uppercase string. + */ +export function toUpper(expr: string): ToUpper; + +/** + * @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 toUpper(expr: Expr): ToUpper; +export function toUpper(expr: Expr | string): ToUpper { + return new ToUpper(expr instanceof Expr ? expr : Field.of(expr)); +} + +/** + * @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; +export function trim(expr: Expr | string): Trim { + return new Trim(expr instanceof Expr ? expr : Field.of(expr)); +} + +/** + * @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; +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); +} + +/** + * @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; +export function mapGet(fieldOrExpr: string | Expr, subField: string): MapGet { + return new MapGet( + typeof fieldOrExpr === 'string' ? Field.of(fieldOrExpr) : fieldOrExpr, + subField + ); +} + +/** + * @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 { + return new Count(undefined, false); +} + +/** + * @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; +export function count(value: Expr | string): Count { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Count(exprValue, false); +} + +/** + * @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; +export function sum(value: Expr | string): Sum { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Sum(exprValue, false); +} + +/** + * @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; +export function avg(value: Expr | string): Avg { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Avg(exprValue, false); +} + +/** + * @beta + * + * Creates an aggregation that finds the minimum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the lowest price of all products + * minimum(Field.of("price")).as("lowestPrice"); + * ``` + * + * @param value The expression to find the minimum value of. + * @return A new {@code Accumulator} representing the 'minimum' aggregation. + */ +export function minimum(value: Expr): Minimum; + +/** + * @beta + * + * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the lowest price of all products + * minimum("price").as("lowestPrice"); + * ``` + * + * @param value The name of the field to find the minimum value of. + * @return A new {@code Accumulator} representing the 'minimum' aggregation. + */ +export function minimum(value: string): Minimum; +export function minimum(value: Expr | string): Minimum { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Minimum(exprValue, false); +} + +/** + * @beta + * + * Creates an aggregation that finds the maximum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * maximum(Field.of("score")).as("highestScore"); + * ``` + * + * @param value The expression to find the maximum value of. + * @return A new {@code Accumulator} representing the 'maximum' aggregation. + */ +export function maximum(value: Expr): Maximum; + +/** + * @beta + * + * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * maximum("score").as("highestScore"); + * ``` + * + * @param value The name of the field to find the maximum value of. + * @return A new {@code Accumulator} representing the 'maximum' aggregation. + */ +export function maximum(value: string): Maximum; +export function maximum(value: Expr | string): Maximum { + const exprValue = value instanceof Expr ? value : Field.of(value); + return new Maximum(exprValue, false); +} + +/** + * @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; +export function cosineDistance( + expr: Expr | string, + other: Expr | number[] | VectorValue +): CosineDistance { + const expr1 = expr instanceof Expr ? expr : Field.of(expr); + const expr2 = other instanceof Expr ? other : Constant.vector(other); + return new CosineDistance(expr1, expr2); +} + +/** + * @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; +export function dotProduct( + expr: Expr | string, + other: Expr | number[] | VectorValue +): DotProduct { + const expr1 = expr instanceof Expr ? expr : Field.of(expr); + const expr2 = other instanceof Expr ? other : Constant.vector(other); + return new DotProduct(expr1, expr2); +} + +/** + * @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; +export function euclideanDistance( + expr: Expr | string, + other: Expr | number[] | VectorValue +): EuclideanDistance { + const expr1 = expr instanceof Expr ? expr : Field.of(expr); + const expr2 = other instanceof Expr ? other : Constant.vector(other); + return new EuclideanDistance(expr1, expr2); +} + +/** + * @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 + * + * 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); +} + +/** + * @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 { + return new Ordering(expr, 'ascending'); +} + +/** + * @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 { + return new Ordering(expr, 'descending'); +} + +/** + * @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 { + constructor( + private expr: Expr, + private direction: 'ascending' | 'descending' + ) {} + + _toProto(serializer: Serializer): api.IValue { + return { + mapValue: { + fields: { + direction: serializer.encodeValue(this.direction)!, + expression: serializer.encodeValue(this.expr)!, + }, + }, + }; + } +} diff --git a/dev/src/index.ts b/dev/src/index.ts index 8d470c29e..5b83d596c 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'; @@ -122,6 +123,95 @@ 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, + arrayLength, + eqAny, + notEqAny, + and, + or, + xor, + cond, + not, + exists, + isNan, + charLength, + like, + regexContains, + regexMatch, + strContains, + startsWith, + endsWith, + toLower, + toUpper, + trim, + strConcat, + mapGet, + countAll, + count, + sum, + avg, + minimum, + maximum, + cosineDistance, + dotProduct, + euclideanDistance, + genericFunction, + ascending, + descending, + // bitLeftShift, + // bitOr, + // bitRightShift, + // bitXor, + // bitAnd, + // bitNot, + timestampAdd, + timestampSub, + timestampToUnixMicros, + timestampToUnixMillis, + timestampToUnixSeconds, + unixMicrosToTimestamp, + unixMillisToTimestamp, + unixSecondsToTimestamp, + logicalMaximum, + logicalMinimum, + vectorLength, + byteLength, + reverse, + replaceAll, + replaceFirst, + mod, +} from './expression'; const libVersion = require('../../package.json').version; setLibVersion(libVersion); @@ -898,6 +988,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 new file mode 100644 index 000000000..1bb77b482 --- /dev/null +++ b/dev/src/pipeline-util.ts @@ -0,0 +1,473 @@ +// 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'; +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, + AccumulatorTarget, + ExprWithAlias, + Accumulator, +} 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 { + PipelineResponse, + 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 { + constructor( + /** @private */ + readonly _firestore: Firestore, + /** @private */ + readonly _serializer: Serializer, + readonly _converter: firestore.FirestorePipelineConverter + ) {} + + _getResponse( + pipeline: Pipeline, + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + explainOptions?: firestore.ExplainOptions + ): Promise> { + // Capture the error stack to preserve stack tracing across async calls. + const stack = Error().stack!; + + return new Promise((resolve, reject) => { + const result: Array> = []; + const output: PipelineResponse = {}; + + this._stream(pipeline, transactionOrReadTime, explainOptions) + .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) { + result.push(element.result); + } + } + }) + .on('end', () => { + output.result = result; + resolve(output); + }); + }); + } + + // 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 + ) => { + console.log(`Pipeline response: ${JSON.stringify(proto, null, 2)}`); + if (proto === NOOP_MESSAGE) { + callback(undefined); + return; + } + + 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 || undefined, + Timestamp.fromProto(proto.executionTime!), + result.createTime + ? Timestamp.fromProto(result.createTime!) + : undefined, + result.updateTime + ? Timestamp.fromProto(result.updateTime!) + : undefined, + this._converter + ); + return 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( + transactionOrReadTime, + explainOptions + ); + + console.log( + `Executing pipeline: \n ${JSON.stringify(request, null, 2)}` + ); + + 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( + 'PipelineUtil._stream', + tag, + 'Pipeline 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 => { + logger( + 'PipelineUtil._stream', + tag, + 'Pipeline failed with stream error:', + 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 true; + } + + 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') + ) { + return true; + } + + 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 true; + } + + 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 true; + } + + 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)) + ) { + return true; + } + + 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 true; + } + + return false; +} + +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 === 'NULL_VALUE')) || + ('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 true; + } + + return false; +} + +export function toPipelineFilterCondition( + f: FilterInternal, + serializer: Serializer +): FilterCondition & Expr { + if (f instanceof FieldFilterInternal) { + const field = Field.of(f.field); + if (f.isNanChecking()) { + if (f.nanOp() === 'IS_NAN') { + return and(field.exists(), field.isNaN()); + } else { + return and(field.exists(), not(field.isNaN())); + } + } else if (f.isNullChecking()) { + if (f.nullOp() === 'IS_NULL') { + return and(field.exists(), field.eq(null)); + } else { + return and(field.exists(), not(field.eq(null))); + } + } else { + // Comparison filters + const value = isFirestoreValue(f.value) + ? f.value + : serializer.encodeValue(f.value); + switch (f.op) { + case 'LESS_THAN': + return and(field.exists(), field.lt(value)); + case 'LESS_THAN_OR_EQUAL': + return and(field.exists(), field.lte(value)); + case 'GREATER_THAN': + return and(field.exists(), field.gt(value)); + case 'GREATER_THAN_OR_EQUAL': + return and(field.exists(), field.gte(value)); + case 'EQUAL': + return and(field.exists(), field.eq(value)); + case 'NOT_EQUAL': + return and(field.exists(), field.neq(value)); + case 'ARRAY_CONTAINS': + return and(field.exists(), field.arrayContains(value)); + case 'IN': { + const values = value?.arrayValue?.values?.map(val => + Constant.of(val) + ); + return and(field.exists(), field.eqAny(...values!)); + } + case 'ARRAY_CONTAINS_ANY': { + const values = value?.arrayValue?.values?.map(val => + Constant.of(val) + ); + return and(field.exists(), field.arrayContainsAny(values!)); + } + case 'NOT_IN': { + const values = value?.arrayValue?.values?.map(val => + Constant.of(val) + ); + return and(field.exists(), field.notEqAny(...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..4fc32d402 --- /dev/null +++ b/dev/src/pipeline.ts @@ -0,0 +1,966 @@ +// 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 * as deepEqual from 'fast-deep-equal'; +import {google} from '../protos/firestore_v1_proto_api'; +import { + Accumulator, + AccumulatorTarget, + Expr, + ExprWithAlias, + Field, + Fields, + FilterCondition, + Function, + Ordering, + Selectable, +} from './expression'; +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, + Aggregate, + CollectionSource, + CollectionGroupSource, + DatabaseSource, + DocumentsSource, + Where, + FindNearest, + FindNearestOptions, + GenericStage, + Limit, + Offset, + Select, + Sort, + Stage, + Distinct, + RemoveFields, +} from './stage'; +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 {isOptionalEqual} from './util'; + +/** + * Represents the source of a Firestore {@link Pipeline}. + * @beta + */ +export class PipelineSource implements firestore.PipelineSource { + constructor(private db: Firestore) {} + + collection(collectionPath: string): Pipeline { + return new Pipeline(this.db, [new CollectionSource(collectionPath)]); + } + + collectionGroup(collectionId: string): Pipeline { + return new Pipeline(this.db, [new CollectionGroupSource(collectionId)]); + } + + database(): Pipeline { + return new Pipeline(this.db, [new DatabaseSource()]); + } + + documents(docs: DocumentReference[]): Pipeline { + return new Pipeline(this.db, [DocumentsSource.of(docs)]); + } +} + +/** + * @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 + implements firestore.Pipeline +{ + constructor( + private db: Firestore, + private stages: Stage[], + private converter: firestore.FirestorePipelineConverter = defaultPipelineConverter() + ) {} + + /** + * 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: 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); + } + + /** + * 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. + * + *

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( + ...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); + } + + 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 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; + } + + /** + * 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 & firestore.Expr): Pipeline { + const copy = this.stages.map(s => s); + copy.push(new Where(condition)); + return new Pipeline(this.db, copy, this.converter); + } + + /** + * 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, this.converter); + } + + /** + * 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, this.converter); + } + + /** + * 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 | 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); + } + + /** + * 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: firestore.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: firestore.AccumulatorTarget[]; + groups?: (string | Selectable)[]; + }): Pipeline; + aggregate( + optionsOrTarget: + | 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: firestore.AccumulatorTarget) => [ + (target as unknown as AccumulatorTarget).alias, + (target as unknown as AccumulatorTarget).expr, + ] + ) + ), + this.selectablesToMap(optionsOrTarget.groups || []) + ) + ); + } else { + copy.push( + new Aggregate( + new Map( + [optionsOrTarget, ...rest].map(target => [ + (target as unknown as AccumulatorTarget).alias, + (target as unknown as AccumulatorTarget).expr, + ]) + ), + new Map() + ) + ); + } + return new Pipeline(this.db, copy, this.converter); + } + + 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); + } + + /** + * 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( + * Field.of("rating").descending(), + * Field.of("title").ascending() + * ); + * ``` + * + * @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; + sort( + optionsOrOrderings: + | Ordering + | { + orderings: Ordering[]; + }, + ...rest: Ordering[] + ): Pipeline { + const copy = this.stages.map(s => s); + // Option object + if ('orderings' in optionsOrOrderings) { + copy.push(new Sort(optionsOrOrderings.orderings)); + } else { + // Ordering object + copy.push(new Sort([optionsOrOrderings, ...rest])); + } + + return new Pipeline(this.db, copy, this.converter); + } + + /** + * 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 GenericStage(name, params)); + return new Pipeline(this.db, copy, this.converter); + } + + withConverter(converter: null): Pipeline; + withConverter( + 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( + 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. + * + *

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>> { + 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, transactionOrReadTime, explainOptions) + .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, + this.db._serializer!, + this.converter + ); + return util.stream(this); + } + + _toProto( + transactionOrReadTime?: Uint8Array | Timestamp | api.ITransactionOptions, + explainOptions?: FirebaseFirestore.ExplainOptions + ): api.IExecutePipelineRequest { + const stages: IStage[] = this.stages.map(stage => + stage._toProto(this.db._serializer!) + ); + const structuredPipeline: IStructuredPipeline = {pipeline: {stages}}; + 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; + } +} + +/** + * @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 + implements firestore.PipelineResult +{ + private readonly _ref: DocumentReference | undefined; + private _serializer: Serializer; + public readonly _executionTime: Timestamp | undefined; + public readonly _createTime: Timestamp | undefined; + public readonly _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 result was read (or undefined if + * the document exists only locally). + * @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, + ref?: DocumentReference, + /** + * @internal + * @private + **/ + readonly _fieldsProto?: ApiMapValue, + readTime?: Timestamp, + createTime?: Timestamp, + updateTime?: Timestamp, + readonly converter: firestore.FirestorePipelineConverter = defaultPipelineConverter() + ) { + this._ref = ref; + this._serializer = serializer; + 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 PipelineResult contains data, if it is a document; otherwise `undefined`. + * + * @type {string} + * @readonly + * + */ + get id(): string | undefined { + return this._ref?.id; + } + + /** + * The time the document was created. Undefined if this result is not a document. + * + * @type {Timestamp|undefined} + * @readonly + */ + get createTime(): Timestamp | undefined { + return this._createTime; + } + + /** + * The time the document was last updated (at the time the snapshot was + * generated). Undefined if this result is not a document. + * + * @type {Timestamp|undefined} + * @readonly + */ + get updateTime(): Timestamp | undefined { + return this._updateTime; + } + + /** + * The time at which the pipeline producing this result is executed. + * + * @type {Timestamp} + * @readonly + * + */ + get executionTime(): Timestamp { + if (this._executionTime === undefined) { + throw new Error( + "'executionTime' is expected to exist, but it is undefined" + ); + } + return this._executionTime; + } + + /** + * Retrieves all fields in the result as an object. Returns 'undefined' if + * the document doesn't exist. + * + * @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 { + 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.converter && this.converter !== defaultPipelineConverter()) { + return this.converter.fromFirestore( + new PipelineResult( + this._serializer, + this.ref, + this._fieldsProto, + this._executionTime, + this.createTime, + this.updateTime, + defaultPipelineConverter() + ) + ); + } 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 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 { + 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 `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 { + 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 6171c34b4..fa88567a2 100644 --- a/dev/src/reference/aggregate-query.ts +++ b/dev/src/reference/aggregate-query.ts @@ -22,6 +22,15 @@ import * as deepEqual from 'fast-deep-equal'; import * as firestore from '@google-cloud/firestore'; import {Aggregate, AggregateSpec} from '../aggregate'; +import { + AccumulatorTarget, + 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 +340,27 @@ export class AggregateQuery< return runQueryRequest; } + pipeline(): Pipeline { + const aggregates = mapToArray( + this._aggregates, + (aggregate, clientAlias) => { + if (aggregate.aggregateType === 'count') { + if (aggregate._field === undefined) { + return countAll().as(clientAlias); + } + return count(Field.of(aggregate._field)).as(clientAlias); + } else if (aggregate.aggregateType === 'avg') { + return avg(Field.of(aggregate._field!)).as(clientAlias); + } else if (aggregate.aggregateType === 'sum') { + return sum(Field.of(aggregate._field!)).as(clientAlias); + } else { + throw new Error(`Unknown aggregate type ${aggregate.aggregateType}`); + } + } + ); + return this._query.pipeline().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..e6f1e8d4a 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 {and, 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,83 @@ export class Query< ); } + pipeline(): Pipeline { + let pipeline; + if (this._queryOptions.allDescendants) { + pipeline = this.firestore + .pipeline() + .collectionGroup(this._queryOptions.collectionId); + } else { + pipeline = this.firestore + .pipeline() + .collection( + this._queryOptions.parentPath.append(this._queryOptions.collectionId) + .relativeName + ); + } + + // filters + for (const f of this._queryOptions.filters) { + pipeline = pipeline.where(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 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: 'ascending' | 'descending' | undefined = undefined; + switch (fieldOrder.direction) { + case 'ASCENDING': { + dir = 'ascending'; + break; + } + case 'DESCENDING': { + dir = 'descending'; + break; + } + } + return new Ordering(Field.of(fieldOrder.field), dir || 'ascending'); + }); + if (orderings.length > 0) { + pipeline = pipeline.sort({orderings: orderings}); + } + + // Cursors, Limit and Offset + if ( + !!this._queryOptions.startAt || + !!this._queryOptions.endAt || + this._queryOptions.limitType === LimitType.Last + ) { + 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); + } + if (this._queryOptions.limit) { + pipeline = pipeline.limit(this._queryOptions.limit); + } + } + return pipeline; + } + /** * Returns true if this `Query` is equal to the provided value. * @@ -706,7 +787,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 +800,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 +999,7 @@ export class Query< 1 ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot ); const startAt = this.createCursor( @@ -958,7 +1043,7 @@ export class Query< 1 ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot ); const startAt = this.createCursor( @@ -1001,7 +1086,7 @@ export class Query< 1 ); - const fieldOrders = this.createImplicitOrderBy( + const fieldOrders = this.createImplicitOrderByForCursor( fieldValuesOrDocumentSnapshot ); const endAt = this.createCursor( @@ -1044,7 +1129,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..d282b507f 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,23 @@ export enum LimitType { Last, } +export interface PipelineStreamElement< + AppModelType = firestore.DocumentData, + DbModelType extends firestore.DocumentData = firestore.DocumentData, +> { + transaction?: Uint8Array; + executionTime?: Timestamp; + explainMetrics?: ExplainMetrics; + result?: PipelineResult; +} + +export interface PipelineResponse { + transaction?: Uint8Array; + executionTime?: Timestamp; + explainMetrics?: ExplainMetrics; + result?: Array>; +} + /** * onSnapshot() callback that receives a QuerySnapshot. * diff --git a/dev/src/reference/vector-query.ts b/dev/src/reference/vector-query.ts index 4df93e60f..c1d783ea8 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,22 @@ export class VectorQuery< return result; } + toPipeline(): Pipeline { + const options: FindNearestOptions = { + field: Field.of(this.vectorField), + vectorValue: this.queryVector, + limit: this.options.limit, + distanceMeasure: this.options.distanceMeasure.toLowerCase() as + | 'cosine' + | 'euclidean' + | 'dot_product', + }; + return this.query + .pipeline() + .where(Field.of(this.vectorField).exists()) + .findNearest(options); + } + _getResponse( explainOptions?: firestore.ExplainOptions ): Promise>> { diff --git a/dev/src/serializer.ts b/dev/src/serializer.ts index ace53ce4c..a66bba1b4 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: {}, @@ -202,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 new file mode 100644 index 000000000..5be9be513 --- /dev/null +++ b/dev/src/stage.ts @@ -0,0 +1,314 @@ +// 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 * 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'; + +/** + * @beta + */ +export interface Stage { + name: string; + _toProto(serializer: Serializer): api.Pipeline.IStage; +} + +/** + * @beta + */ +export class AddFields implements Stage { + name = 'add_fields'; + + constructor(private fields: Map) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [serializer.encodeValue(this.fields)!], + }; + } +} + +/** + * @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 + */ +export class Aggregate implements Stage { + name = 'aggregate'; + + constructor( + private accumulators: Map, + private groups: Map + ) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [ + serializer.encodeValue(this.accumulators)!, + serializer.encodeValue(this.groups)!, + ], + }; + } +} + +/** + * @beta + */ +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)!], + }; + } +} + +/** + * @beta + */ +export class CollectionSource implements Stage { + name = 'collection'; + + constructor(private collectionPath: string) { + if (!this.collectionPath.startsWith('/')) { + this.collectionPath = '/' + this.collectionPath; + } + } + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [{referenceValue: this.collectionPath}], + }; + } +} + +/** + * @beta + */ +export class CollectionGroupSource 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)!], + }; + } +} + +/** + * @beta + */ +export class DatabaseSource implements Stage { + name = 'database'; + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + }; + } +} + +/** + * @beta + */ +export class DocumentsSource implements Stage { + name = 'documents'; + + constructor(private docPaths: string[]) {} + + static of(refs: DocumentReference[]): DocumentsSource { + return new DocumentsSource(refs.map(ref => '/' + ref.path)); + } + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.docPaths.map(p => { + return {referenceValue: p}; + }), + }; + } +} + +/** + * @beta + */ +export class Where implements Stage { + name = 'where'; + + constructor(private condition: firestore.FilterCondition & firestore.Expr) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: [(this.condition as unknown as Expr)._toProto(serializer)], + }; + } +} + +/** + * @beta + */ +export interface FindNearestOptions { + field: firestore.Field; + vectorValue: firestore.VectorValue | number[]; + distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + limit?: number; + distanceField?: string; +} + +/** + * @beta + */ +export class FindNearest implements Stage { + name = 'find_nearest'; + + constructor(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._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[]), + serializer.encodeValue(this._options.distanceMeasure)!, + ], + options, + }; + } +} + +/** + * @beta + */ +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)!], + }; + } +} + +/** + * @beta + */ +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)!], + }; + } +} + +/** + * @beta + */ +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)!], + }; + } +} + +/** + * @beta + */ +export class Sort implements Stage { + name = 'sort'; + + constructor(private orders: Ordering[]) {} + + _toProto(serializer: Serializer): api.Pipeline.IStage { + return { + name: this.name, + args: this.orders.map(o => o._toProto(serializer)), + }; + } +} + +/** + * @beta + */ +export class GenericStage implements Stage { + constructor( + public name: string, + params: any[] + ) {} + + _toProto(serializer: Serializer): api.Pipeline.Stage { + return new api.Pipeline.Stage(); + } +} diff --git a/dev/src/transaction.ts b/dev/src/transaction.ts index f29fccbe5..abc101a13 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,54 @@ 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>> { + 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 +786,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/src/types.ts b/dev/src/types.ts index ac7a62d22..e52756272 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; @@ -64,6 +66,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 +102,7 @@ export type FirestoreUnaryMethod = /** Streaming methods used in the Firestore SDK. */ export type FirestoreStreamingMethod = + | 'executePipeline' | 'listen' | 'partitionQueryStream' | 'runQuery' @@ -146,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 defaultPipelineConverterObj as FirestorePipelineConverter; +} + /** * Update data that has been resolved to a mapping of FieldPaths to values. */ 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/src/v1/firestore_client.ts b/dev/src/v1/firestore_client.ts index e1f4b242d..09be77e4f 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: request.database ? `${request.database}/documents` : '', + }); + 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..317c4e839 --- /dev/null +++ b/dev/system-test/pipeline.ts @@ -0,0 +1,975 @@ +// 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, + FirestorePipelineConverter, + QuerySnapshot, +} from '@google-cloud/firestore'; + +import {expect} from 'chai'; +import {afterEach, describe, it} from 'mocha'; +import { + CollectionReference, + DocumentReference, + FieldPath, + Firestore, + logicalMinimum, + logicalMaximum, + Query, + cond, +} from '../src'; +import { + add, + and, + arrayContains, + arrayContainsAny, + avg, + countAll, + endsWith, + eq, + Field, + gt, + like, + lt, + neq, + not, + or, + regexContains, + regexMatch, + startsWith, + strConcat, + subtract, + cosineDistance, + dotProduct, + euclideanDistance, + Constant, + mapGet, + lte, + eqAny, + notEqAny, +} from '../src/expression'; +import {PipelineResult} from '../src/pipeline'; +import {verifyInstance} from '../test/util/helpers'; +import {DeferredPromise, getTestRoot} from './firestore'; + +describe.only('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 expectResults( + result: PipelineResult[], + ...docs: string[] + ): void; + function expectResults( + result: PipelineResult[], + ...data: DocumentData[] + ): void; + + function expectResults( + result: PipelineResult[], + ...data: DocumentData[] | string[] + ): void { + expect(result.length).to.equal(data.length); + + if (data.length > 0) { + if (typeof data[0] === 'string') { + const actualIds = result.map(result => result.ref?.id); + expect(actualIds).to.deep.equal(data); + } else { + result.forEach(r => { + expect(r.data()).to.deep.equal(data.shift()); + }); + } + } + } + + async function compareQueryAndPipeline(query: Query): Promise { + const queryResults = await query.get(); + const pipeline = query.pipeline(); + const pipelineResults = await pipeline.execute(); + + expect(queryResults.docs.map(s => s._fieldsProto)).to.deep.equal( + pipelineResults.map(r => r._fieldsProto) + ); + return queryResults; + } + + async function setupBookDocs(): Promise> { + const bookDocs: {[id: string]: DocumentData} = { + book1: { + title: "The Hitchhiker's Guide to the Galaxy", + author: 'Douglas Adams', + genre: 'Science Fiction', + published: 1979, + rating: 4.2, + tags: ['comedy', 'space', 'adventure'], + awards: {hugo: true, nebula: false, others: {unknown: {year: 1980}}}, + nestedField: {'level.1': {'level.2': true}}, + }, + book2: { + title: 'Pride and Prejudice', + author: 'Jane Austen', + genre: 'Romance', + published: 1813, + rating: 4.5, + tags: ['classic', 'social commentary', 'love'], + awards: {none: true}, + }, + book3: { + title: 'One Hundred Years of Solitude', + author: 'Gabriel García Márquez', + genre: 'Magical Realism', + published: 1967, + rating: 4.3, + tags: ['family', 'history', 'fantasy'], + awards: {nobel: true, nebula: false}, + }, + book4: { + title: 'The Lord of the Rings', + author: 'J.R.R. Tolkien', + genre: 'Fantasy', + published: 1954, + rating: 4.7, + tags: ['adventure', 'magic', 'epic'], + awards: {hugo: false, nebula: false}, + }, + book5: { + title: "The Handmaid's Tale", + author: 'Margaret Atwood', + genre: 'Dystopian', + published: 1985, + rating: 4.1, + tags: ['feminism', 'totalitarianism', 'resistance'], + awards: {'arthur c. clarke': true, 'booker prize': false}, + }, + book6: { + title: 'Crime and Punishment', + author: 'Fyodor Dostoevsky', + genre: 'Psychological Thriller', + published: 1866, + rating: 4.3, + tags: ['philosophy', 'crime', 'redemption'], + awards: {none: true}, + }, + book7: { + title: 'To Kill a Mockingbird', + author: 'Harper Lee', + genre: 'Southern Gothic', + published: 1960, + rating: 4.2, + tags: ['racism', 'injustice', 'coming-of-age'], + awards: {pulitzer: true}, + }, + book8: { + title: '1984', + author: 'George Orwell', + genre: 'Dystopian', + published: 1949, + rating: 4.2, + tags: ['surveillance', 'totalitarianism', 'propaganda'], + awards: {prometheus: true}, + }, + book9: { + title: 'The Great Gatsby', + author: 'F. Scott Fitzgerald', + genre: 'Modernist', + published: 1925, + rating: 4.0, + tags: ['wealth', 'american dream', 'love'], + awards: {none: true}, + }, + 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('empty results as expected', async () => { + const result = await firestore + .pipeline() + .collection(randomCol.path) + .limit(0) + .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').maximum().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; + }); + + // toLower not impelemented + it.skip('returns distinct values as expected', async () => { + const results = await randomCol + .pipeline() + .where(lt('published', 1900)) + .distinct(Field.of('genre').toLower().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(Field.of('published'), 1984)) + .aggregate({ + accumulators: [avg('rating').as('avgRating')], + groups: ['genre'], + }) + .where(gt('avgRating', 4.3)) + .sort(Field.of('avgRating').descending()) + .execute(); + expectResults( + results, + {avgRating: 4.7, genre: 'Fantasy'}, + {avgRating: 4.5, genre: 'Romance'}, + {avgRating: 4.4, genre: 'Science Fiction'} + ); + }); + + it('returns minimum and maximum accumulations', async () => { + const results = await randomCol + .pipeline() + .aggregate( + countAll().as('count'), + Field.of('rating').maximum().as('max_rating'), + Field.of('published').minimum().as('min_published') + ) + .execute(); + expectResults(results, { + count: 10, + max_rating: 4.7, + min_published: 1813, + }); + }); + + 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() + .collection(randomCol.path) + .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 firestore + .pipeline() + .collection(randomCol.path) + .sort(Field.of('author').ascending()) + .offset(5) + .limit(3) + .select('title', 'author') + .execute(); + expectResults( + results, + {title: '1984', author: 'George Orwell'}, + {title: 'To Kill a Mockingbird', author: 'Harper Lee'}, + {title: 'The Lord of the Rings', author: 'J.R.R. Tolkien'} + ); + }); + + 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() + .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); + }); + + // array_concat not implemented + it.skip('arrayConcat works', async () => { + const results = await randomCol + .pipeline() + .select( + Field.of('tags').arrayConcat(['newTag1', 'newTag2']).as('modifiedTags') + ) + .limit(1) + .execute(); + expectResults(results, { + modifiedTags: ['comedy', 'space', 'adventure', 'newTag1', 'newTag2'], + }); + }); + + it('testStrConcat', async () => { + const results = await randomCol + .pipeline() + .select( + Field.of('author').strConcat(' - ', Field.of('title')).as('bookInfo') + ) + .limit(1) + .execute(); + expectResults(results, { + bookInfo: "Douglas Adams - The Hitchhiker's Guide to the Galaxy", + }); + }); + + it('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').charLength().as('titleLength'), + Field.of('title') + ) + .where(gt('titleLength', 20)) + .sort(Field.of('title').ascending()) + .execute(); + + expectResults( + results, + + { + 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', + } + ); + }); + + // to_lower not implemented + it.skip('testToLowercase', async () => { + const results = await randomCol + .pipeline() + .select(Field.of('title').toLower().as('lowercaseTitle')) + .limit(1) + .execute(); + expectResults(results, { + lowercaseTitle: "the hitchhiker's guide to the galaxy", + }); + }); + + // to_upper not implemented + it.skip('testToUppercase', async () => { + const results = await randomCol + .pipeline() + .select(Field.of('author').toUpper().as('uppercaseAuthor')) + .limit(1) + .execute(); + expectResults(results, {uppercaseAuthor: 'DOUGLAS ADAMS'}); + }); + + // trim not implemented + it.skip('testTrim', async () => { + const results = await randomCol + .pipeline() + .addFields(strConcat(' ', Field.of('title'), ' ').as('spacedTitle')) + .select( + Field.of('spacedTitle').trim().as('trimmedTitle'), + Field.of('spacedTitle') + ) + .limit(1) + .execute(); + expectResults(results, { + spacedTitle: " The Hitchhiker's Guide to the Galaxy ", + trimmedTitle: "The Hitchhiker's Guide to the Galaxy", + }); + }); + + it('testLike', async () => { + const results = await randomCol + .pipeline() + .where(like('title', '%Guide%')) + .select('title') + .execute(); + expectResults(results, {title: "The Hitchhiker's Guide to the Galaxy"}); + }); + + 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('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() + .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), + lte(Field.of('rating'), 4.5), + neq('genre', 'Science Fiction') + ) + ) + .select('rating', 'title') + .sort(Field.of('title').ascending()) + .execute(); + expectResults( + results, + {rating: 4.3, title: 'Crime and Punishment'}, + { + rating: 4.3, + title: 'One Hundred Years of Solitude', + }, + {rating: 4.5, title: 'Pride and Prejudice'} + ); + }); + + it('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( + Field.of('rating').eq(null).as('ratingIsNull'), + not(Field.of('rating').isNaN()).as('ratingIsNotNaN') + ) + .limit(1) + .execute(); + expectResults(results, {ratingIsNull: false, ratingIsNotNaN: true}); + }); + + it('testMapGet', async () => { + const results = await randomCol + .pipeline() + .select( + Field.of('awards').mapGet('hugo').as('hugoAward'), + Field.of('awards').mapGet('others').as('others'), + Field.of('title') + ) + .where(eq('hugoAward', true)) + .execute(); + expectResults( + results, + { + hugoAward: true, + title: "The Hitchhiker's Guide to the Galaxy", + others: {unknown: {year: 1980}}, + }, + {hugoAward: true, title: 'Dune', others: null} + ); + }); + + // it('testParent', async () => { + // const results = await randomCol + // .pipeline() + // .select( + // parent(randomCol.doc('chile').collection('subCollection').path).as( + // 'parent' + // ) + // ) + // .limit(1) + // .execute(); + // expect(results[0].data().parent.endsWith('/books')).to.be.true; + // }); + // + // it('testCollectionId', async () => { + // const results = await randomCol + // .pipeline() + // .select(collectionId(randomCol.doc('chile')).as('collectionId')) + // .limit(1) + // .execute(); + // expectResults(results, {collectionId: 'books'}); + // }); + + it('testDistanceFunctions', async () => { + const sourceVector = [0.1, 0.1]; + const targetVector = [0.5, 0.8]; + const results = await randomCol + .pipeline() + .select( + cosineDistance(Constant.vector(sourceVector), targetVector).as( + 'cosineDistance' + ), + dotProduct(Constant.vector(sourceVector), targetVector).as( + 'dotProductDistance' + ), + euclideanDistance(Constant.vector(sourceVector), targetVector).as( + 'euclideanDistance' + ) + ) + .limit(1) + .execute(); + + expectResults(results, { + cosineDistance: 0.02560880430538015, + dotProductDistance: 0.13, + euclideanDistance: 0.806225774829855, + }); + }); + + it('testNestedFields', async () => { + const results = await randomCol + .pipeline() + .where(eq('awards.hugo', true)) + .select('title', 'awards.hugo') + .execute(); + expectResults( + results, + {title: "The Hitchhiker's Guide to the Galaxy", 'awards.hugo': true}, + {title: 'Dune', 'awards.hugo': true} + ); + }); + + it('test mapGet with field name including . notation', async () => { + const results = await randomCol + .pipeline() + .where(eq('awards.hugo', true)) + .select( + 'title', + Field.of('nestedField.level.1'), + mapGet('nestedField', 'level.1').mapGet('level.2').as('nested') + ) + .execute(); + 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 = { + 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', + }); + }); + + 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/dev/system-test/query.ts b/dev/system-test/query.ts new file mode 100644 index 000000000..84853f79b --- /dev/null +++ b/dev/system-test/query.ts @@ -0,0 +1,2251 @@ +// 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, + 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('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.pipeline(); + 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('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('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('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('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('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('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('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('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('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('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('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('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('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('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('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('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 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 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('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('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 : 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 : 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 : 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 + : 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'); + }); + }); +}); diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 702e5ee84..152f35996 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; @@ -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. */ @@ -545,6 +549,8 @@ declare namespace FirebaseFirestore { */ collectionGroup(collectionId: string): CollectionGroup; + pipeline(): PipelineSource; + /** * Retrieves multiple documents from Firestore. * @@ -802,6 +808,44 @@ 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 + ): Promise>>; + /** * Create the document referred to by the provided `DocumentReference`. * The operation will fail the transaction if a document exists at the @@ -3192,6 +3236,6169 @@ 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 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. + * + * ```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 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 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. + */ + eqAny(...others: Expr[]): EqAny; + + /** + * 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. + */ + eqAny(...others: any[]): EqAny; + + /** + * 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 character length of a string in UTF-8. + * + * ```typescript + * // 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. + */ + charLength(): CharLength; + + /** + * 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 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. + * + * ```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").toLower(); + * ``` + * + * @return A new `Expr` representing the lowercase string. + */ + toLower(): ToLower; + + /** + * Creates an expression that converts a string to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * Field.of("title").toUpper(); + * ``` + * + * @return A new `Expr` representing the uppercase string. + */ + toUpper(): ToUpper; + + /** + * 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; + + /** + * 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. + * + * ```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").minimum().as("lowestPrice"); + * ``` + * + * @return A new `Accumulator` representing the 'minimum' aggregation. + */ + 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").maximum().as("highestScore"); + * ``` + * + * @return A new `Accumulator` representing the 'maximum' aggregation. + */ + 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").logicalMaximum(Function.currentTimestamp()); + * ``` + * + * @param other The expression to compare with. + * @return A new {@code Expr} representing the logical maximum operation. + */ + logicalMaximum(other: Expr): LogicalMaximum; + + /** + * Creates an expression that returns the larger value between this expression and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'value' field and 10. + * Field.of("value").logicalMaximum(10); + * ``` + * + * @param other The constant value to compare with. + * @return A new {@code Expr} representing the logical maximum operation. + */ + 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").logicalMinimum(Function.currentTimestamp()); + * ``` + * + * @param other The expression to compare with. + * @return A new {@code Expr} representing the logical minimum operation. + */ + logicalMinimum(other: Expr): LogicalMinimum; + + /** + * Creates an expression that returns the smaller value between this expression and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'value' field and 10. + * Field.of("value").logicalMinimum(10); + * ``` + * + * @param other The constant value to compare with. + * @return A new {@code Expr} representing the logical minimum operation. + */ + logicalMinimum(other: any): LogicalMinimum; + + /** + * 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. + * + * ```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 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. + * + * ```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 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; + } + + /** + * @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 ArrayLength extends Function {} + + /** + * @beta + */ + export class EqAny 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 LogicalMaximum extends Function {} + + /** + * @beta + */ + export class LogicalMinimum extends Function {} + + /** + * @beta + */ + export class CharLength 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 StrContains 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 ToLower extends Function {} + + /** + * @beta + */ + export class ToUpper extends Function {} + + /** + * @beta + */ + export class Trim extends Function {} + + /** + * @beta + */ + 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 + */ + 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 Minimum extends Function implements Accumulator { + accumulator: true; + } + + /** + * @beta + */ + export class Maximim 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 + */ + 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 + * + * 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 calculates the modulo (remainder) of dividing two expressions. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod(Field.of("field1"), Field.of("field2")); + * ``` + * + * @param left The dividend expression. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. + */ + export function mod(left: Expr, right: Expr): Mod; + + /** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing an expression by a constant. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 5. + * mod(Field.of("field1"), 5); + * ``` + * + * @param left The dividend expression. + * @param right The divisor constant. + * @return A new {@code Expr} representing the modulo operation. + */ + export function mod(left: Expr, right: any): Mod; + + /** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by an expression. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 'field2'. + * mod("field1", Field.of("field2")); + * ``` + * + * @param left The dividend field name. + * @param right The divisor expression. + * @return A new {@code Expr} representing the modulo operation. + */ + export function mod(left: string, right: Expr): Mod; + + /** + * @beta + * + * Creates an expression that calculates the modulo (remainder) of dividing a field's value by a constant. + * + * ```typescript + * // Calculate the remainder of dividing 'field1' by 5. + * mod("field1", 5); + * ``` + * + * @param left The dividend field name. + * @param right The divisor constant. + * @return A new {@code Expr} representing the modulo operation. + */ + 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 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 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' + * 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 eqAny(element: Expr, others: Expr[]): EqAny; + + /** + * @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' + * 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 eqAny(element: Expr, others: any[]): EqAny; + + /** + * @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' + * 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 eqAny(element: string, others: Expr[]): EqAny; + + /** + * @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' + * 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 eqAny(element: string, others: any[]): EqAny; + + /** + * @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' + * 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 notEqAny(element: Expr, others: Expr[]): NotEqAny; + + /** + * @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' + * 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 notEqAny(element: Expr, others: any[]): NotEqAny; + + /** + * @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' + * 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 notEqAny(element: string, others: Expr[]): NotEqAny; + + /** + * @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' + * 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 notEqAny(element: string, others: any[]): NotEqAny; + + /** + * @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". + * cond( + * 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 returns the larger value between two expressions, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'timestamp' field and the current timestamp. + * 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 maximum operation. + */ + export function logicalMaximum(left: Expr, right: Expr): LogicalMaximum; + + /** + * @beta + * + * Creates an expression that returns the larger value between an expression and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'value' field and 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 maximum operation. + */ + export function logicalMaximum(left: Expr, right: any): LogicalMaximum; + + /** + * @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 'timestamp' field and the current timestamp. + * 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 maximum operation. + */ + export function logicalMaximum(left: string, right: Expr): LogicalMaximum; + + /** + * @beta + * + * Creates an expression that returns the larger value between a field and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the larger value between the 'value' field and 10. + * logicalMaximum("value", 10); + * ``` + * + * @param left The left operand field name. + * @param right The right operand constant. + * @return A new {@code Expr} representing the logical maximum operation. + */ + export function logicalMaximum(left: string, right: any): LogicalMaximum; + + /** + * @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 'timestamp' field and the current timestamp. + * 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 minimum operation. + */ + export function logicalMinimum(left: Expr, right: Expr): LogicalMinimum; + + /** + * @beta + * + * Creates an expression that returns the smaller value between an expression and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'value' field and 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 minimum operation. + */ + export function logicalMinimum(left: Expr, right: any): LogicalMinimum; + + /** + * @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 'timestamp' field and the current timestamp. + * 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 minimum operation. + */ + export function logicalMinimum(left: string, right: Expr): LogicalMinimum; + + /** + * @beta + * + * Creates an expression that returns the smaller value between a field and a constant value, based on Firestore's value type ordering. + * + * ```typescript + * // Returns the smaller value between the 'value' field and 10. + * logicalMinimum("value", 10); + * ``` + * + * @param left The left operand field name. + * @param right The right operand constant. + * @return A new {@code Expr} representing the logical minimum operation. + */ + export function logicalMinimum(left: string, right: any): LogicalMinimum; + + /** + * @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 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; + + /** + * @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 replaceFirst( + value: Expr, + find: Expr, + replace: Expr + ): ReplaceFirst; + + /** + * @beta + * + * Creates an expression that replaces the first occurrence of a substring within a string represented by a field with another substring. + * + * ```typescript + * // Replace the first occurrence of "hello" with "hi" in the 'message' field. + * replaceFirst("message", "hello", "hi"); + * ``` + * + * @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 replaceFirst( + field: string, + find: string, + replace: string + ): ReplaceFirst; + + /** + * @beta + * + * 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 + * // 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 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 replaceFirst( + field: string, + find: Expr, + replace: Expr + ): ReplaceFirst; + + /** + * @beta + * + * Creates an expression that replaces all occurrences of a substring within a string with another substring. + * + * ```typescript + * // Replace all occurrences of "hello" with "hi" in the 'message' field. + * replaceAll(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 all occurrences of 'find' with. + * @return A new {@code Expr} representing the string with all occurrences replaced. + */ + export function replaceAll( + value: Expr, + find: string, + replace: string + ): ReplaceAll; + + /** + * @beta + * + * 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 + * // 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 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 replaceAll( + value: Expr, + find: Expr, + replace: Expr + ): ReplaceAll; + + /** + * @beta + * + * Creates an expression that replaces all occurrences of a substring within a string represented by a field with another substring. + * + * ```typescript + * // Replace all occurrences of "hello" with "hi" in the 'message' field. + * replaceAll("message", "hello", "hi"); + * ``` + * + * @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 replaceAll( + field: string, + find: string, + replace: string + ): ReplaceAll; + + /** + * @beta + * + * 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 + * // 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 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 replaceAll( + field: string, + find: Expr, + replace: Expr + ): ReplaceAll; + + /** + * @beta + * + * Creates an expression that calculates the length of a string in bytes. + * + * ```typescript + * // Calculate the length of the 'myString' field in bytes. + * byteLength(Field.of("myString")); + * ``` + * + * @param expr The expression representing the string. + * @return A new {@code Expr} representing the length of the string in bytes. + */ + export function byteLength(expr: Expr): ByteLength; + + /** + * @beta + * + * Creates an expression that calculates the length of a string represented by a field in bytes. + * + * ```typescript + * // Calculate the length of the 'myString' field in bytes. + * byteLength("myString"); + * ``` + * + * @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 byteLength(field: string): ByteLength; + + /** + * @beta + * + * Creates an expression that calculates the character length of a string field in UTF-8. + * + * ```typescript + * // 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 charLength(field: string): CharLength; + + /** + * @beta + * + * Creates an expression that calculates the character length of a string expression in UTF-8. + * + * ```typescript + * // 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 charLength(expr: Expr): CharLength; + + /** + * @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 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 + * + * 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 + * toLower("name"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the lowercase string. + */ + export function toLower(expr: string): ToLower; + + /** + * @beta + * + * Creates an expression that converts a string expression to lowercase. + * + * ```typescript + * // Convert the 'name' field to lowercase + * 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 toLower(expr: Expr): ToLower; + + /** + * @beta + * + * Creates an expression that converts a string field to uppercase. + * + * ```typescript + * // Convert the 'title' field to uppercase + * toUpper("title"); + * ``` + * + * @param expr The name of the field containing the string. + * @return A new {@code Expr} representing the uppercase string. + */ + export function toUpper(expr: string): ToUpper; + + /** + * @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 toUpper(expr: Expr): ToUpper; + + /** + * @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 + * minimum(Field.of("price")).as("lowestPrice"); + * ``` + * + * @param value The expression to find the minimum value of. + * @return A new {@code Accumulator} representing the 'minimum' aggregation. + */ + export function minimum(value: Expr): Minimum; + + /** + * @beta + * + * Creates an aggregation that finds the minimum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the lowest price of all products + * minimum("price").as("lowestPrice"); + * ``` + * + * @param value The name of the field to find the minimum value of. + * @return A new {@code Accumulator} representing the 'minimum' aggregation. + */ + export function minimum(value: string): Minimum; + + /** + * @beta + * + * Creates an aggregation that finds the maximum value of an expression across multiple stage + * inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * maximum(Field.of("score")).as("highestScore"); + * ``` + * + * @param value The expression to find the maximum value of. + * @return A new {@code Accumulator} representing the 'maximum' aggregation. + */ + export function maximum(value: Expr): Maximim; + + /** + * @beta + * + * Creates an aggregation that finds the maximum value of a field across multiple stage inputs. + * + * ```typescript + * // Find the highest score in a leaderboard + * maximum("score").as("highestScore"); + * ``` + * + * @param value The name of the field to find the maximum value of. + * @return A new {@code Accumulator} representing the 'maximum' aggregation. + */ + export function maximum(value: string): Maximim; + + /** + * @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 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 + * + * 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 RemoveFields 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 { + field: Field; + vectorValue: VectorValue | number[]; + distanceMeasure: 'euclidean' | 'cosine' | 'dot_product'; + limit?: number; + 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; + + /** + * 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. + * + *

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(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( + * Field.of("rating").descending(), + * Field.of("title").ascending() + * ); + * ``` + * + * @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' {