Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 351 additions & 0 deletions handwritten/firestore/dev/src/pipelines/expression.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add @returns tags and usage examples to expression docs where those are missing

Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ import {
fieldOrExpression,
isFirestoreValue,
isString,
toField,
valueToDefaultExpr,
vectorToExpr,
} from './pipeline-util';
import {HasUserData, Serializer, validateUserInput} from '../serializer';
import {cast} from '../util';
import {GeoPoint} from '../geo-point';
import {OptionsUtil} from './options-util';

/**
* @beta
Expand Down Expand Up @@ -3100,6 +3103,85 @@ export abstract class Expression
]).asBoolean();
}

/**
* Evaluates if the result of this `expression` is between
* the `lowerBound` (inclusive) and `upperBound` (inclusive).
*
* @example
* ```typescript
* // Evaluate if the 'tireWidth' is between 2.2 and 2.4
* field('tireWidth').between(constant(2.2), constant(2.4))
*
* // This is functionally equivalent to
* and(field('tireWidth').greaterThanOrEqual(contant(2.2)), field('tireWidth').lessThanOrEqual(constant(2.4)))
* ```
*
* @param lowerBound - An `Expression` that evaluates to the lower bound (inclusive) of the range.
* @param upperBound - An `Expression` that evaluates to the upper bound (inclusive) of the range.
*/
between(lowerBound: Expression, upperBound: Expression): BooleanExpression;

/**
* Evaluates if the result of this `expression` is between
* the `lowerBound` (inclusive) and `upperBound` (inclusive).
*
* @example
* ```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applies to all other docs like this

Suggested change
* ```
* ```typescript

* // Evaluate if the 'tireWidth' is between 2.2 and 2.4
* field('tireWidth').between(2.2, 2.4)
*
* // This is functionally equivalent to
* and(field('tireWidth').greaterThanOrEqual(2.2), field('tireWidth').lessThanOrEqual(2.4))
* ```
*
* @param lowerBound - Lower bound (inclusive) of the range.
* @param upperBound - Upper bound (inclusive) of the range.
*/
between(lowerBound: unknown, upperBound: unknown): BooleanExpression;

between(lowerBound: unknown, upperBound: unknown): BooleanExpression {
Comment on lines +3140 to +3142
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make these number types? What other types can evaluate to a lower or upper bound?

return new FunctionExpression('between', [
this,
valueToDefaultExpr(lowerBound),
valueToDefaultExpr(upperBound),
]).asBoolean();
}

/**
* Evaluates to an HTML-formatted text snippet that renders terms matching
* the search query in `<b>bold</b>`.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param rquery Define the search query using the search DTS (TODO(search) link).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to say 'search DSL'?

*/
snippet(rquery: string): Expression;

/**
* Evaluates to an HTML-formatted text snippet that renders terms matching
* the search query in `<b>bold</b>`.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param options Define how snippeting behaves.
*/
snippet(options: firestore.Pipelines.SnippetOptions): Expression;

snippet(
queryOrOptions: string | firestore.Pipelines.SnippetOptions,
): Expression {
const options: firestore.Pipelines.SnippetOptions = isString(queryOrOptions)
? {rquery: queryOrOptions}
: queryOrOptions;
const rquery = options.rquery;
const internalOptions = {
maxSnippetWidth: options.maxSnippetWidth,
maxSnippets: options.maxSnippets,
separator: options.separator,
};
return new SnippetExpression([this, constant(rquery)], internalOptions);
}

// TODO(new-expression): Add new expression method definitions above this line

/**
Expand Down Expand Up @@ -3351,6 +3433,35 @@ export class Field
readonly expressionType: firestore.Pipelines.ExpressionType = 'Field';
selectable = true as const;

/**
* Perform a full-text search on this field.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param rquery Define the search query using the rquery DTS.
*/
matches(rquery: string | Expression): BooleanExpression {
return new FunctionExpression('matches', [
this,
valueToDefaultExpr(rquery),
]).asBoolean();
}

/**
* Evaluates to the distance in meters between the location specified
* by this field and the query location.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param location - Compute distance to this GeoPoint.
*/
geoDistance(location: GeoPoint | Expression): Expression {
return new FunctionExpression('geo_distance', [
this,
valueToDefaultExpr(location),
]).asBoolean();
}

/**
* @beta
* @internal
Expand Down Expand Up @@ -3686,6 +3797,51 @@ export class FunctionExpression extends Expression {
}
}

/**
* SnippetExpression extends from FunctionExpression because it
* supports options and requires the options util.
*/
export class SnippetExpression extends FunctionExpression {
/**
* @private
* @internal
*/
get _optionsUtil(): OptionsUtil {
return new OptionsUtil({
maxSnippetWidth: {
serverName: 'max_snippet_width',
},
maxSnippets: {
serverName: 'max_snippets',
},
separator: {
serverName: 'separator',
},
});
}

/**
* @hideconstructor
*/
constructor(
params: Expression[],
private _options?: {},
) {
super('snippet', params);
}

_toProto(serializer: Serializer): api.IValue {
const proto = super._toProto(serializer);
proto.functionValue!.options = this._optionsUtil.getOptionsProto(
serializer,
this._options ?? {},
{},
);

return proto;
}
}

/**
* @beta
* This class defines the base class for Firestore `Pipeline` functions, which can be evaluated within pipeline
Expand Down Expand Up @@ -10258,6 +10414,201 @@ export function isType(
return fieldOrExpression(fieldNameOrExpression).isType(type);
}

/**
* Perform a full-text search on the specified field.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param searchField Search the specified field.
* @param rquery Define the search query using the search DTS.
*/
export function matches(
searchField: string | Field,
rquery: string | Expression,
): BooleanExpression {
return toField(searchField).matches(rquery);
}

/**
* Perform a full-text search on the document.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param rquery Define the search query using the rquery DTS.
*/
export function documentMatches(
rquery: string | Expression,
): BooleanExpression {
return new FunctionExpression('document_matches', [
valueToDefaultExpr(rquery),
]).asBoolean();
}

/**
* Evaluates to the search score that reflects the topicality of the document
* to all of the text predicates (`queryMatch`)
* in the search query. If `SearchOptions.query` is not set or does not contain
* any text predicates, then this topicality score will always be `0`.
*
* @remarks This Expression can only be used within a `Search` stage.
*/
export function score(): Expression {
return new FunctionExpression('score', []).asBoolean();
}

/**
* Evaluates to an HTML-formatted text snippet that highlights terms matching
* the search query in `<b>bold</b>`.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param searchField Search the specified field for matching terms.
* @param rquery Define the search query using the search DTS (TODO(search) link).
*/
export function snippet(
searchField: string | Field,
rquery: string,
): Expression;

/**
* Evaluates to an HTML-formatted text snippet that highlights terms matching
* the search query in `<b>bold</b>`.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param searchField Search the specified field for matching terms.
* @param options Define the search query using the search DTS (TODO(search) link).
*/
export function snippet(
searchField: string | Field,
options: firestore.Pipelines.SnippetOptions,
): Expression;
export function snippet(
field: string | Field,
queryOrOptions: string | firestore.Pipelines.SnippetOptions,
): Expression {
return toField(field).snippet(
isString(queryOrOptions) ? {rquery: queryOrOptions} : queryOrOptions,
);
}

/**
* Evaluates to the distance in meters between the location in the specified
* field and the query location.
*
* @remarks This Expression can only be used within a `Search` stage.
*
* @param fieldName - Specifies the field in the document which contains
* the first GeoPoint for distance computation.
* @param location - Compute distance to this GeoPoint.
*/
export function geoDistance(
fieldName: string | Field,
location: GeoPoint | Expression,
): Expression {
return toField(fieldName).geoDistance(location);
}

/**
* Evaluates if the value in the field specified by `fieldName` is between
* the evaluated values for `lowerBound` (inclusive) and `upperBound` (inclusive).
*
* @example
* ```
* // Evaluate if the 'tireWidth' is between 2.2 and 2.4
* between('tireWidth', constant(2.2), constant(2.4))
*
* // This is functionally equivalent to
* and(greaterThanOrEqual('tireWidth', constant(2.2)), lessThanOrEqual('tireWidth', constant(2.4)))
* ```
*
* @param fieldName - Evaluate if the value stored in this field is between the lower and upper bounds.
* @param lowerBound - Lower bound (inclusive) of the range.
* @param upperBound - Upper bound (inclusive) of the range.
*/
export function between(
fieldName: string,
lowerBound: Expression,
upperBound: Expression,
): BooleanExpression;

/**
* Evaluates if the value in the field specified by `fieldName` is between
* the values for `lowerBound` (inclusive) and `upperBound` (inclusive).
*
* @example
* ```
* // Evaluate if the 'tireWidth' is between 2.2 and 2.4
* between('tireWidth', 2.2, 2.4)
*
* // This is functionally equivalent to
* and(greaterThanOrEqual('tireWidth', 2.2), lessThanOrEqual('tireWidth', 2.4))
* ```
*
* @param fieldName - Evaluate if the value stored in this field is between the lower and upper bounds.
* @param lowerBound - Lower bound (inclusive) of the range.
* @param upperBound - Upper bound (inclusive) of the range.
*/
export function between(
fieldName: string,
lowerBound: unknown,
upperBound: unknown,
): BooleanExpression;

/**
* Evaluates if the result of the specified `expression` is between
* the results of `lowerBound` (inclusive) and `upperBound` (inclusive).
*
* @example
* ```
* // Evaluate if the 'tireWidth' is between 2.2 and 2.4
* between(field('tireWidth'), constant(2.2), constant(2.4))
*
* // This is functionally equivalent to
* and(greaterThanOrEqual(field('tireWidth'), constant(2.2)), lessThanOrEqual(field('tireWidth'), constant(2.4)))
* ```
*
* @param expression - Evaluate if the result of this expression is between the lower and upper bounds.
* @param lowerBound - Lower bound (inclusive) of the range.
* @param upperBound - Upper bound (inclusive) of the range.
*/
export function between(
expression: Expression,
lowerBound: Expression,
upperBound: Expression,
): BooleanExpression;

/**
* Evaluates if the result of the specified `expression` is between
* the `lowerBound` (inclusive) and `upperBound` (inclusive).
*
* @example
* ```
* // Evaluate if the 'tireWidth' is between 2.2 and 2.4
* between(field('tireWidth'), 2.2, 2.4)
*
* // This is functionally equivalent to
* and(greaterThanOrEqual(field('tireWidth'), 2.2), lessThanOrEqual(field('tireWidth'), 2.4))
* ```
*
* @param expression - Evaluate if the result of this expression is between the lower and upper bounds.
* @param lowerBound - Lower bound (inclusive) of the range.
* @param upperBound - Upper bound (inclusive) of the range.
*/
export function between(
expression: Expression,
lowerBound: unknown,
upperBound: unknown,
): BooleanExpression;

export function between(
expression: Expression | string,
lowerBound: unknown,
upperBound: unknown,
): BooleanExpression {
return fieldOrExpression(expression).between(lowerBound, upperBound);
}

// TODO(new-expression): Add new top-level expression function definitions above this line

/**
Expand Down
Loading
Loading