Skip to content

Commit dd1a205

Browse files
committed
feat(firestore): add support for subqueries
1 parent 7868bbf commit dd1a205

7 files changed

Lines changed: 1620 additions & 9 deletions

File tree

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

Lines changed: 198 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
* @beta
@@ -3071,6 +3072,23 @@ export abstract class Expression
30713072
]).asBoolean();
30723073
}
30733074

3075+
/**
3076+
* @beta
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('field', [this, valueToDefaultExpr(key)]);
3090+
}
3091+
30743092
// TODO(new-expression): Add new expression method definitions above this line
30753093

30763094
/**
@@ -10237,6 +10255,186 @@ export function isType(
1023710255
return fieldOrExpression(fieldNameOrExpression).isType(type);
1023810256
}
1023910257

10258+
/**
10259+
* @beta
10260+
* Creates an expression that returns the value of a field from a document that results from the evaluation of the expression.
10261+
*
10262+
* @example
10263+
* ```typescript
10264+
* // Get the value of the "city" field in the "address" document.
10265+
* getField(field("address"), "city")
10266+
* ```
10267+
*
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+
* @beta
10274+
* Creates an expression that returns the value of a field from a document that results from the evaluation of the expression.
10275+
*
10276+
* @example
10277+
* ```typescript
10278+
* // Get the value of the key resulting from the "addressField" variable in the "address" document.
10279+
* getField(field("address", variable("addressField")),
10280+
* ```
10281+
*
10282+
* @param key 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+
* @beta
10291+
* Creates an expression that returns the value of a field from the document with the given field name.
10292+
*
10293+
* @example
10294+
* ```typescript
10295+
* // Get the value of the "city" field in the "address" document.
10296+
* getField("address", "city")
10297+
* ```
10298+
*
10299+
* @param key The field to access in the document.
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+
* @beta
10305+
* Creates an expression that returns the value of a field from the document with the given field name.
10306+
*
10307+
* @example
10308+
* ```typescript
10309+
* // Get the value of the "city" field in the "address" document.
10310+
* getField("address", variable("addressField"))
10311+
* ```
10312+
*
10313+
* @param key The field to access in the document.
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+
* @beta
10356+
* Creates an expression that retrieves the value of a variable bound via `define()`.
10357+
*
10358+
* @example
10359+
* ```typescript
10360+
* db.pipeline().collection("products")
10361+
* .define(
10362+
* field("price").multiply(0.9).as("discountedPrice"),
10363+
* field("stock").add(10).as("newStock")
10364+
* )
10365+
* .where(variable("discountedPrice").lessThan(100))
10366+
* .select(field("name"), variable("newStock"));
10367+
* ```
10368+
*
10369+
* @param name - The name of the variable to retrieve.
10370+
* @returns An `Expression` representing the variable's value.
10371+
*/
10372+
export function variable(name: string): Expression {
10373+
return new VariableExpression(name);
10374+
}
10375+
10376+
/**
10377+
* @beta
10378+
* Creates an expression that represents the current document being processed.
10379+
*
10380+
* @example
10381+
* ```typescript
10382+
* // Define the current document as a variable "doc"
10383+
* firestore.pipeline().collection("books")
10384+
* .define(currentDocument().as("doc"))
10385+
* // Access a field from the defined document variable
10386+
* .select(variable("doc").mapGet("title"));
10387+
* ```
10388+
*
10389+
* @returns An `Expression` representing the current document.
10390+
*/
10391+
export function currentDocument(): Expression {
10392+
return new FunctionExpression('current_document', []);
10393+
}
10394+
10395+
/**
10396+
* @internal
10397+
*/
10398+
class PipelineValueExpression extends Expression {
10399+
expressionType: firestore.Pipelines.ExpressionType = 'PipelineValue';
10400+
10401+
/**
10402+
* @hideconstructor
10403+
*/
10404+
constructor(private readonly pipeline: firestore.Pipelines.Pipeline) {
10405+
super();
10406+
}
10407+
10408+
/**
10409+
* @internal
10410+
*/
10411+
_toProto(serializer: Serializer): api.IValue {
10412+
return {
10413+
// Casting to bypass type checking becuase _validateUserData does not exist in the public types
10414+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10415+
pipelineValue: (this.pipeline as Pipeline)._toProto(serializer),
10416+
};
10417+
}
10418+
10419+
/**
10420+
* @internal
10421+
*/
10422+
_validateUserData(_ignoreUndefinedProperties: boolean): void {
10423+
// Casting to bypass type checking becuase _validateUserData does not exist in the public types
10424+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10425+
(this.pipeline as any)._validateUserData('PipelineValueExpression');
10426+
}
10427+
}
10428+
10429+
/**
10430+
* @internal
10431+
*/
10432+
export function pipelineValue(
10433+
pipeline: firestore.Pipelines.Pipeline,
10434+
): Expression {
10435+
return new PipelineValueExpression(pipeline);
10436+
}
10437+
1024010438
// TODO(new-expression): Add new top-level expression function definitions above this line
1024110439

1024210440
/**

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 {
@@ -156,5 +157,8 @@ export {
156157
stringReplaceOne,
157158
nor,
158159
switchOn,
160+
getField,
161+
variable,
162+
currentDocument,
159163
// TODO(new-expression): Add new expression exports above this line
160164
} from './expression';

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';
@@ -603,6 +605,12 @@ export function isBooleanExpr(
603605
return val instanceof BooleanExpression;
604606
}
605607

608+
export function isAliasedExpr(
609+
val: unknown,
610+
): val is firestore.Pipelines.AliasedExpression {
611+
return val instanceof AliasedExpression;
612+
}
613+
606614
export function isField(val: unknown): val is firestore.Pipelines.Field {
607615
return val instanceof Field;
608616
}
@@ -630,6 +638,9 @@ export function valueToDefaultExpr(value: unknown): Expression {
630638
if (isFirestoreValue(value)) {
631639
return constant(value);
632640
}
641+
if (isPipeline(value)) {
642+
return pipelineValue(value);
643+
}
633644
if (value instanceof Expression) {
634645
return value;
635646
} else if (isPlainObject(value)) {
@@ -640,7 +651,6 @@ export function valueToDefaultExpr(value: unknown): Expression {
640651
result = constant(value);
641652
}
642653

643-
// TODO(pipeline) is this still used?
644654
result._createdFromLiteral = true;
645655
return result;
646656
}

0 commit comments

Comments
 (0)