Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4402339
rough changes
dlarocque Mar 18, 2026
8f08e78
validate user data on all stages
dlarocque Mar 18, 2026
74e87e3
cleanup
dlarocque Mar 18, 2026
3fa2d9e
fix lint errors
dlarocque Mar 18, 2026
a92c296
Merge branch 'main' into dl/defer-validation
meltsufin Mar 18, 2026
704f491
Merge branch 'main' into dl/defer-validation
dlarocque Mar 23, 2026
6150c34
remove _validateUserData from types
dlarocque Mar 23, 2026
9edc6af
test(firestore): Update `RESOURCE_EXHAUSTED` retry count to 1
dlarocque Mar 23, 2026
351bd95
Revert "test(firestore): Update `RESOURCE_EXHAUSTED` retry count to 1"
dlarocque Mar 23, 2026
db5bca0
Merge branch 'main' into dl/defer-validation
dlarocque Mar 23, 2026
7868bbf
remove name from validateUserData method
dlarocque Mar 23, 2026
f791132
feat(firestore): add support for subqueries
dlarocque Mar 23, 2026
b8ef888
Merge branch 'main' into dl/subq
dlarocque Mar 24, 2026
b0e829a
fix docs
dlarocque Mar 24, 2026
2088e22
remove beta annotations
dlarocque Mar 26, 2026
0e0c069
remove more beta annotations
dlarocque Mar 26, 2026
817d42a
Merge branch 'main' into dl/subq
dlarocque Apr 1, 2026
c2438ea
getfield docs fix and test guards
dlarocque Apr 2, 2026
66b2de2
Merge branch 'main' into dl/subq
dlarocque Apr 2, 2026
e2f54c2
add test for union with subquery
dlarocque Apr 2, 2026
aeedc71
Merge branch 'main' into dl/subq
dlarocque Apr 7, 2026
5254d56
review fixes
dlarocque Apr 7, 2026
6509cbd
docs wip
dlarocque Apr 8, 2026
3a88fd2
final docs update
dlarocque Apr 8, 2026
99b1618
Work around regression in sinonjs/fake-timers
MarkDuckworth Apr 3, 2026
4444673
Merge branch 'main' into dl/subq
dlarocque Apr 8, 2026
553981e
revert get_field test guards
dlarocque Apr 8, 2026
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
195 changes: 195 additions & 0 deletions handwritten/firestore/dev/src/pipelines/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from './pipeline-util';
import {HasUserData, Serializer, validateUserInput} from '../serializer';
import {cast} from '../util';
import {Pipeline} from './pipelines';

/**
* Represents an expression that can be evaluated to a value within the execution of a `Pipeline`.
Expand Down Expand Up @@ -3072,6 +3073,22 @@ export abstract class Expression
]).asBoolean();
}

/**
* Creates an expression that returns the value of a field from the document that results from the evaluation of this expression.
*
* @example
* ```typescript
* // Get the value of the "city" field in the "address" document.
* field("address").getField("city")
* ```
*
* @param key The field to access in the document.
* @returns A new `Expression` representing the value of the field in the document.
*/
getField(key: string | Expression): Expression {
return new FunctionExpression('get_field', [this, valueToDefaultExpr(key)]);
}

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

/**
Expand Down Expand Up @@ -10238,6 +10255,184 @@ export function isType(
return fieldOrExpression(fieldNameOrExpression).isType(type);
}

/**
* Creates an expression that gets a field from this map (object).
*
* @example
* ```typescript
* // Get the value of the "city" field in the "address" document.
* getField(field("address"), "city")
* ```
*
* @param expression The expression evaluating to the map from which the field will be extracted.
* @param key The field to access in the document.
* @returns A new `Expression` representing the value of the field in the document.
*/
export function getField(expression: Expression, key: string): Expression;
/**
* Creates an expression that gets a field from this map (object).
*
* @example
* ```typescript
* // Get the value of the "city" field in the "address" document.
* getField(field("address"), "city")
* ```
*
* @param expression The expression evaluating to the map from which the field will be extracted.
* @param keyExpr The expression representing the key to access in the document.
* @returns A new `Expression` representing the value of the field in the document.
*/
export function getField(
expression: Expression,
keyExpr: Expression,
): Expression;
/**
* Creates an expression that returns the value of a field from the document with the given field name.
*
* @example
* ```typescript
* // Get the value of the "city" field in the "address" document.
* getField("address", "city")
* ```
*
* @param fieldName The name of the field containing the map/document.
* @param key The key to access.
* @returns A new `Expression` representing the value of the field in the document.
*/
export function getField(fieldName: string, key: string): Expression;
/**
* Creates an expression that returns the value of a field from the document with the given field name.
*
* @example
* ```typescript
* // Get the value of the "city" field in the "address" document.
* getField("address", variable("addressField"))
* ```
*
* @param fieldName The name of the field containing the map/document.
* @param keyExpr The key expression to access.
* @returns A new `Expression` representing the value of the field in the document.
*/
export function getField(fieldName: string, keyExpr: Expression): Expression;
export function getField(
fieldOrExpr: string | Expression,
keyOrExpr: string | Expression,
): Expression {
return fieldOrExpression(fieldOrExpr).getField(keyOrExpr);
}

/**
* @internal
* Expression representing a variable reference. This evaluates to the value of a variable
* defined in a pipeline.
*/
export class VariableExpression extends Expression {
expressionType: firestore.Pipelines.ExpressionType = 'Variable';

/**
* @hideconstructor
*/
constructor(private readonly name: string) {
super();
}

/**
* @internal
*/
_toProto(_serializer: Serializer): api.IValue {
return {
variableReferenceValue: this.name,
};
}

/**
* @internal
*/
_validateUserData(_ignoreUndefinedProperties: boolean): void {}
}

/**
* Creates an expression that retrieves the value of a variable bound via `define()`.
*
* @example
* ```typescript
* db.pipeline().collection("products")
* .define(
* field("price").multiply(0.9).as("discountedPrice"),
* field("stock").add(10).as("newStock")
* )
* .where(variable("discountedPrice").lessThan(100))
* .select(field("name"), variable("newStock"));
* ```
*
* @param name - The name of the variable to retrieve.
* @returns An `Expression` representing the variable's value.
*/
export function variable(name: string): Expression {
return new VariableExpression(name);
}

/**
* Creates an expression that represents the current document being processed.
*
* @example
* ```typescript
* // Define the current document as a variable "doc"
* firestore.pipeline().collection("books")
* .define(currentDocument().as("doc"))
* // Access a field from the defined document variable
* .select(variable("doc").getField("title"));
* ```
*
* @returns An `Expression` representing the current document.
*/
export function currentDocument(): Expression {
return new FunctionExpression('current_document', []);
}

/**
* @internal
*/
class PipelineValueExpression extends Expression {
expressionType: firestore.Pipelines.ExpressionType = 'PipelineValue';

/**
* @hideconstructor
*/
constructor(private readonly pipeline: firestore.Pipelines.Pipeline) {
super();
}

/**
* @internal
*/
_toProto(serializer: Serializer): api.IValue {
return {
// Casting to bypass type checking becuase _validateUserData does not exist in the public types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
pipelineValue: (this.pipeline as Pipeline)._toProto(serializer),
};
}

/**
* @internal
*/
_validateUserData(_ignoreUndefinedProperties: boolean): void {
// Casting to bypass type checking becuase _validateUserData does not exist in the public types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.pipeline as any)._validateUserData('PipelineValueExpression');
}
}

/**
* @internal
*/
export function pipelineValue(
pipeline: firestore.Pipelines.Pipeline,
): Expression {
return new PipelineValueExpression(pipeline);
}

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

/**
Expand Down
4 changes: 4 additions & 0 deletions handwritten/firestore/dev/src/pipelines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
PipelineResult,
PipelineSnapshot,
PipelineSource,
subcollection,
} from './pipelines';

export {
Expand Down Expand Up @@ -159,6 +160,9 @@ export {
stringReplaceOne,
nor,
switchOn,
getField,
variable,
currentDocument,
ifNull,
coalesce,
// TODO(new-expression): Add new expression exports above this line
Expand Down
12 changes: 11 additions & 1 deletion handwritten/firestore/dev/src/pipelines/pipeline-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ import {
lessThan,
Field,
AggregateFunction,
pipelineValue,
AliasedExpression,
} from './expression';
import {Pipeline, PipelineResult, ExplainStats} from './pipelines';
import {StructuredPipeline} from './structured-pipeline';
Expand Down Expand Up @@ -605,6 +607,12 @@ export function isBooleanExpr(
return val instanceof BooleanExpression;
}

export function isAliasedExpr(
val: unknown,
): val is firestore.Pipelines.AliasedExpression {
return val instanceof AliasedExpression;
}

export function isField(val: unknown): val is firestore.Pipelines.Field {
return val instanceof Field;
}
Expand Down Expand Up @@ -632,6 +640,9 @@ export function valueToDefaultExpr(value: unknown): Expression {
if (isFirestoreValue(value)) {
return constant(value);
}
if (isPipeline(value)) {
return pipelineValue(value);
}
if (value instanceof Expression) {
return value;
} else if (isPlainObject(value)) {
Expand All @@ -642,7 +653,6 @@ export function valueToDefaultExpr(value: unknown): Expression {
result = constant(value);
}

// TODO(pipeline) is this still used?
result._createdFromLiteral = true;
return result;
}
Expand Down
Loading
Loading