Skip to content

Commit fb317f8

Browse files
authored
feat(firestore): Add support for subqueries (#7839)
1 parent 2f0884d commit fb317f8

7 files changed

Lines changed: 1669 additions & 9 deletions

File tree

handwritten/firestore/dev/src/pipelines/expression.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from './pipeline-util';
2929
import {HasUserData, Serializer, validateUserInput} from '../serializer';
3030
import {cast} from '../util';
31+
import {Pipeline} from './pipelines';
3132

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

3076+
/**
3077+
* Creates an expression that returns the value of a field from the document that results from the evaluation of this expression.
3078+
*
3079+
* @example
3080+
* ```typescript
3081+
* // Get the value of the "city" field in the "address" document.
3082+
* field("address").getField("city")
3083+
* ```
3084+
*
3085+
* @param key The field to access in the document.
3086+
* @returns A new `Expression` representing the value of the field in the document.
3087+
*/
3088+
getField(key: string | Expression): Expression {
3089+
return new FunctionExpression('get_field', [this, valueToDefaultExpr(key)]);
3090+
}
3091+
30753092
// TODO(new-expression): Add new expression method definitions above this line
30763093

30773094
/**
@@ -10238,6 +10255,184 @@ export function isType(
1023810255
return fieldOrExpression(fieldNameOrExpression).isType(type);
1023910256
}
1024010257

10258+
/**
10259+
* Creates an expression that gets a field from this map (object).
10260+
*
10261+
* @example
10262+
* ```typescript
10263+
* // Get the value of the "city" field in the "address" document.
10264+
* getField(field("address"), "city")
10265+
* ```
10266+
*
10267+
* @param expression The expression evaluating to the map from which the field will be extracted.
10268+
* @param key The field to access in the document.
10269+
* @returns A new `Expression` representing the value of the field in the document.
10270+
*/
10271+
export function getField(expression: Expression, key: string): Expression;
10272+
/**
10273+
* Creates an expression that gets a field from this map (object).
10274+
*
10275+
* @example
10276+
* ```typescript
10277+
* // Get the value of the "city" field in the "address" document.
10278+
* getField(field("address"), "city")
10279+
* ```
10280+
*
10281+
* @param expression The expression evaluating to the map from which the field will be extracted.
10282+
* @param keyExpr The expression representing the key to access in the document.
10283+
* @returns A new `Expression` representing the value of the field in the document.
10284+
*/
10285+
export function getField(
10286+
expression: Expression,
10287+
keyExpr: Expression,
10288+
): Expression;
10289+
/**
10290+
* Creates an expression that returns the value of a field from the document with the given field name.
10291+
*
10292+
* @example
10293+
* ```typescript
10294+
* // Get the value of the "city" field in the "address" document.
10295+
* getField("address", "city")
10296+
* ```
10297+
*
10298+
* @param fieldName The name of the field containing the map/document.
10299+
* @param key The key to access.
10300+
* @returns A new `Expression` representing the value of the field in the document.
10301+
*/
10302+
export function getField(fieldName: string, key: string): Expression;
10303+
/**
10304+
* Creates an expression that returns the value of a field from the document with the given field name.
10305+
*
10306+
* @example
10307+
* ```typescript
10308+
* // Get the value of the "city" field in the "address" document.
10309+
* getField("address", variable("addressField"))
10310+
* ```
10311+
*
10312+
* @param fieldName The name of the field containing the map/document.
10313+
* @param keyExpr The key expression to access.
10314+
* @returns A new `Expression` representing the value of the field in the document.
10315+
*/
10316+
export function getField(fieldName: string, keyExpr: Expression): Expression;
10317+
export function getField(
10318+
fieldOrExpr: string | Expression,
10319+
keyOrExpr: string | Expression,
10320+
): Expression {
10321+
return fieldOrExpression(fieldOrExpr).getField(keyOrExpr);
10322+
}
10323+
10324+
/**
10325+
* @internal
10326+
* Expression representing a variable reference. This evaluates to the value of a variable
10327+
* defined in a pipeline.
10328+
*/
10329+
export class VariableExpression extends Expression {
10330+
expressionType: firestore.Pipelines.ExpressionType = 'Variable';
10331+
10332+
/**
10333+
* @hideconstructor
10334+
*/
10335+
constructor(private readonly name: string) {
10336+
super();
10337+
}
10338+
10339+
/**
10340+
* @internal
10341+
*/
10342+
_toProto(_serializer: Serializer): api.IValue {
10343+
return {
10344+
variableReferenceValue: this.name,
10345+
};
10346+
}
10347+
10348+
/**
10349+
* @internal
10350+
*/
10351+
_validateUserData(_ignoreUndefinedProperties: boolean): void {}
10352+
}
10353+
10354+
/**
10355+
* Creates an expression that retrieves the value of a variable bound via `define()`.
10356+
*
10357+
* @example
10358+
* ```typescript
10359+
* db.pipeline().collection("products")
10360+
* .define(
10361+
* field("price").multiply(0.9).as("discountedPrice"),
10362+
* field("stock").add(10).as("newStock")
10363+
* )
10364+
* .where(variable("discountedPrice").lessThan(100))
10365+
* .select(field("name"), variable("newStock"));
10366+
* ```
10367+
*
10368+
* @param name - The name of the variable to retrieve.
10369+
* @returns An `Expression` representing the variable's value.
10370+
*/
10371+
export function variable(name: string): Expression {
10372+
return new VariableExpression(name);
10373+
}
10374+
10375+
/**
10376+
* Creates an expression that represents the current document being processed.
10377+
*
10378+
* @example
10379+
* ```typescript
10380+
* // Define the current document as a variable "doc"
10381+
* firestore.pipeline().collection("books")
10382+
* .define(currentDocument().as("doc"))
10383+
* // Access a field from the defined document variable
10384+
* .select(variable("doc").getField("title"));
10385+
* ```
10386+
*
10387+
* @returns An `Expression` representing the current document.
10388+
*/
10389+
export function currentDocument(): Expression {
10390+
return new FunctionExpression('current_document', []);
10391+
}
10392+
10393+
/**
10394+
* @internal
10395+
*/
10396+
class PipelineValueExpression extends Expression {
10397+
expressionType: firestore.Pipelines.ExpressionType = 'PipelineValue';
10398+
10399+
/**
10400+
* @hideconstructor
10401+
*/
10402+
constructor(private readonly pipeline: firestore.Pipelines.Pipeline) {
10403+
super();
10404+
}
10405+
10406+
/**
10407+
* @internal
10408+
*/
10409+
_toProto(serializer: Serializer): api.IValue {
10410+
return {
10411+
// Casting to bypass type checking becuase _validateUserData does not exist in the public types
10412+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10413+
pipelineValue: (this.pipeline as Pipeline)._toProto(serializer),
10414+
};
10415+
}
10416+
10417+
/**
10418+
* @internal
10419+
*/
10420+
_validateUserData(_ignoreUndefinedProperties: boolean): void {
10421+
// Casting to bypass type checking becuase _validateUserData does not exist in the public types
10422+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10423+
(this.pipeline as any)._validateUserData('PipelineValueExpression');
10424+
}
10425+
}
10426+
10427+
/**
10428+
* @internal
10429+
*/
10430+
export function pipelineValue(
10431+
pipeline: firestore.Pipelines.Pipeline,
10432+
): Expression {
10433+
return new PipelineValueExpression(pipeline);
10434+
}
10435+
1024110436
// TODO(new-expression): Add new top-level expression function definitions above this line
1024210437

1024310438
/**

handwritten/firestore/dev/src/pipelines/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export {
1717
PipelineResult,
1818
PipelineSnapshot,
1919
PipelineSource,
20+
subcollection,
2021
} from './pipelines';
2122

2223
export {
@@ -159,6 +160,9 @@ export {
159160
stringReplaceOne,
160161
nor,
161162
switchOn,
163+
getField,
164+
variable,
165+
currentDocument,
162166
ifNull,
163167
coalesce,
164168
// TODO(new-expression): Add new expression exports above this line

handwritten/firestore/dev/src/pipelines/pipeline-util.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ import {
6565
lessThan,
6666
Field,
6767
AggregateFunction,
68+
pipelineValue,
69+
AliasedExpression,
6870
} from './expression';
6971
import {Pipeline, PipelineResult, ExplainStats} from './pipelines';
7072
import {StructuredPipeline} from './structured-pipeline';
@@ -605,6 +607,12 @@ export function isBooleanExpr(
605607
return val instanceof BooleanExpression;
606608
}
607609

610+
export function isAliasedExpr(
611+
val: unknown,
612+
): val is firestore.Pipelines.AliasedExpression {
613+
return val instanceof AliasedExpression;
614+
}
615+
608616
export function isField(val: unknown): val is firestore.Pipelines.Field {
609617
return val instanceof Field;
610618
}
@@ -632,6 +640,9 @@ export function valueToDefaultExpr(value: unknown): Expression {
632640
if (isFirestoreValue(value)) {
633641
return constant(value);
634642
}
643+
if (isPipeline(value)) {
644+
return pipelineValue(value);
645+
}
635646
if (value instanceof Expression) {
636647
return value;
637648
} else if (isPlainObject(value)) {
@@ -642,7 +653,6 @@ export function valueToDefaultExpr(value: unknown): Expression {
642653
result = constant(value);
643654
}
644655

645-
// TODO(pipeline) is this still used?
646656
result._createdFromLiteral = true;
647657
return result;
648658
}

0 commit comments

Comments
 (0)