Skip to content

Commit afbe8b9

Browse files
feat: Add subquery support in pipeline (#7736)
## Summary This PR implements the Query Pipeline Subquery API in the Android SDK, enabling complex data retrieval patterns including joins, aggregations on related collections, and value binding. ## New API ### Pipeline - **`define(aliasedExpression: AliasedExpression, ...)`**: Defines one or more variables in the pipeline's scope for internal reuse. - **`define(stage: DefineStage)`**: Overload for defining variables using a `DefineStage` object. - **`toScalarExpression()`**: Converts the pipeline into an expression that evaluates to a single scalar result. - **`toArrayExpression()`**: Converts the pipeline into an expression that evaluates to an array of results. - **[subcollection(path: String)](cci:1://file:///Users/cheryllin/Desktop/firebase-sdk/firebase-android-sdk/firebase-firestore/src/main/java/com/google/firebase/firestore/Pipeline.kt:1132:4-1151:5)**: Initializes a pipeline scoped to a subcollection for use in subqueries. - **[subcollection(source: SubcollectionSource)](cci:1://file:///Users/cheryllin/Desktop/firebase-sdk/firebase-android-sdk/firebase-firestore/src/main/java/com/google/firebase/firestore/Pipeline.kt:1132:4-1151:5)**: Overload for creating subcollection pipelines with a specific source. ### Expression - **[variable(name: String)](cci:1://file:///Users/cheryllin/Desktop/firebase-sdk/firebase-android-sdk/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt:5332:4-5346:70)**: Creates an expression that refers to a variable defined in the pipeline scope. - **[currentDocument()](cci:1://file:///Users/cheryllin/Desktop/firebase-sdk/firebase-android-sdk/firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt:5348:4-5363:94)**: Creates an expression that refers to the current document being processed. ## Implementation Details - `toScalarExpression` and `toArrayExpression` handle the execution of sub-pipelines and format the results based on the output structure (unwrapping single fields or returning maps for multiple fields). - Validations are added to ensure relative subcollection pipelines are used correctly within a parent context. --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 4b76cbc commit afbe8b9

9 files changed

Lines changed: 1366 additions & 15 deletions

File tree

firebase-firestore/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Unreleased
22

33
- [feature] Pipeline operations are GA now.
4+
- [feature] Added support for Pipeline Subqueries, including `define` and `variable` expressions.
5+
[#7736](https://github.com/firebase/firebase-android-sdk/pull/7736)
46
- [feature] Added support for Pipeline expressions `nor` and `switchOn`.
57
[#7903](https://github.com/firebase/firebase-android-sdk/pull/7903)
68
- [feature] Added support for `first`, `last`, `arrayAgg`, and `arrayAggDistinct` Pipeline expressions.

firebase-firestore/api.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ package com.google.firebase.firestore {
424424
method public com.google.firebase.firestore.Pipeline aggregate(com.google.firebase.firestore.pipeline.AggregateStage aggregateStage);
425425
method public com.google.firebase.firestore.Pipeline aggregate(com.google.firebase.firestore.pipeline.AggregateStage aggregateStage, com.google.firebase.firestore.pipeline.AggregateOptions options);
426426
method public com.google.firebase.firestore.Pipeline aggregate(com.google.firebase.firestore.pipeline.AliasedAggregate accumulator, com.google.firebase.firestore.pipeline.AliasedAggregate... additionalAccumulators);
427+
method public com.google.firebase.firestore.Pipeline define(com.google.firebase.firestore.pipeline.AliasedExpression aliasedExpression, com.google.firebase.firestore.pipeline.AliasedExpression... additionalExpressions);
427428
method public com.google.firebase.firestore.Pipeline distinct(com.google.firebase.firestore.pipeline.Selectable group, java.lang.Object... additionalGroups);
428429
method public com.google.firebase.firestore.Pipeline distinct(String groupField, java.lang.Object... additionalGroups);
429430
method public com.google.android.gms.tasks.Task<com.google.firebase.firestore.Pipeline.Snapshot> execute();
@@ -443,6 +444,8 @@ package com.google.firebase.firestore {
443444
method public com.google.firebase.firestore.Pipeline select(com.google.firebase.firestore.pipeline.Selectable selection, java.lang.Object... additionalSelections);
444445
method public com.google.firebase.firestore.Pipeline select(String fieldName, java.lang.Object... additionalSelections);
445446
method public com.google.firebase.firestore.Pipeline sort(com.google.firebase.firestore.pipeline.Ordering order, com.google.firebase.firestore.pipeline.Ordering... additionalOrders);
447+
method public com.google.firebase.firestore.pipeline.Expression toArrayExpression();
448+
method public com.google.firebase.firestore.pipeline.Expression toScalarExpression();
446449
method public com.google.firebase.firestore.Pipeline union(com.google.firebase.firestore.Pipeline other);
447450
method public com.google.firebase.firestore.Pipeline unnest(com.google.firebase.firestore.pipeline.Selectable arrayWithAlias);
448451
method public com.google.firebase.firestore.Pipeline unnest(com.google.firebase.firestore.pipeline.Selectable arrayWithAlias, com.google.firebase.firestore.pipeline.UnnestOptions options);
@@ -496,6 +499,14 @@ package com.google.firebase.firestore {
496499
method public com.google.firebase.firestore.Pipeline database();
497500
method public com.google.firebase.firestore.Pipeline documents(com.google.firebase.firestore.DocumentReference... documents);
498501
method public com.google.firebase.firestore.Pipeline documents(java.lang.String... documents);
502+
method public static com.google.firebase.firestore.Pipeline subcollection(com.google.firebase.firestore.pipeline.SubcollectionSource source);
503+
method public static com.google.firebase.firestore.Pipeline subcollection(String path);
504+
field public static final com.google.firebase.firestore.PipelineSource.Companion Companion;
505+
}
506+
507+
public static final class PipelineSource.Companion {
508+
method public com.google.firebase.firestore.Pipeline subcollection(com.google.firebase.firestore.pipeline.SubcollectionSource source);
509+
method public com.google.firebase.firestore.Pipeline subcollection(String path);
499510
}
500511

501512
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) public @interface PropertyName {
@@ -973,6 +984,7 @@ package com.google.firebase.firestore.pipeline {
973984
method public static final com.google.firebase.firestore.pipeline.Expression cosineDistance(String vectorFieldName, double[] vector);
974985
method public final com.google.firebase.firestore.pipeline.AggregateFunction count();
975986
method public final com.google.firebase.firestore.pipeline.AggregateFunction countDistinct();
987+
method public static final com.google.firebase.firestore.pipeline.Expression currentDocument();
976988
method public static final com.google.firebase.firestore.pipeline.Expression currentTimestamp();
977989
method public final com.google.firebase.firestore.pipeline.Ordering descending();
978990
method public final com.google.firebase.firestore.pipeline.Expression divide(com.google.firebase.firestore.pipeline.Expression divisor);
@@ -1033,6 +1045,12 @@ package com.google.firebase.firestore.pipeline {
10331045
method public final com.google.firebase.firestore.pipeline.Expression floor();
10341046
method public static final com.google.firebase.firestore.pipeline.Expression floor(com.google.firebase.firestore.pipeline.Expression numericExpr);
10351047
method public static final com.google.firebase.firestore.pipeline.Expression floor(String numericField);
1048+
method public final com.google.firebase.firestore.pipeline.Expression getField(com.google.firebase.firestore.pipeline.Expression keyExpression);
1049+
method public static final com.google.firebase.firestore.pipeline.Expression getField(com.google.firebase.firestore.pipeline.Expression expression, com.google.firebase.firestore.pipeline.Expression keyExpression);
1050+
method public static final com.google.firebase.firestore.pipeline.Expression getField(com.google.firebase.firestore.pipeline.Expression expression, String key);
1051+
method public final com.google.firebase.firestore.pipeline.Expression getField(String key);
1052+
method public static final com.google.firebase.firestore.pipeline.Expression getField(String fieldName, com.google.firebase.firestore.pipeline.Expression keyExpression);
1053+
method public static final com.google.firebase.firestore.pipeline.Expression getField(String fieldName, String key);
10361054
method public final com.google.firebase.firestore.pipeline.BooleanExpression greaterThan(com.google.firebase.firestore.pipeline.Expression other);
10371055
method public static final com.google.firebase.firestore.pipeline.BooleanExpression greaterThan(com.google.firebase.firestore.pipeline.Expression left, com.google.firebase.firestore.pipeline.Expression right);
10381056
method public static final com.google.firebase.firestore.pipeline.BooleanExpression greaterThan(com.google.firebase.firestore.pipeline.Expression left, Object right);
@@ -1304,6 +1322,7 @@ package com.google.firebase.firestore.pipeline {
13041322
method public final com.google.firebase.firestore.pipeline.Expression unixSecondsToTimestamp();
13051323
method public static final com.google.firebase.firestore.pipeline.Expression unixSecondsToTimestamp(com.google.firebase.firestore.pipeline.Expression expr);
13061324
method public static final com.google.firebase.firestore.pipeline.Expression unixSecondsToTimestamp(String fieldName);
1325+
method public static final com.google.firebase.firestore.pipeline.Expression variable(String name);
13071326
method public static final com.google.firebase.firestore.pipeline.Expression vector(com.google.firebase.firestore.VectorValue vector);
13081327
method public static final com.google.firebase.firestore.pipeline.Expression vector(double[] vector);
13091328
method public final com.google.firebase.firestore.pipeline.Expression vectorLength();
@@ -1431,6 +1450,7 @@ package com.google.firebase.firestore.pipeline {
14311450
method public com.google.firebase.firestore.pipeline.Expression cosineDistance(String vectorFieldName, com.google.firebase.firestore.pipeline.Expression vector);
14321451
method public com.google.firebase.firestore.pipeline.Expression cosineDistance(String vectorFieldName, com.google.firebase.firestore.VectorValue vector);
14331452
method public com.google.firebase.firestore.pipeline.Expression cosineDistance(String vectorFieldName, double[] vector);
1453+
method public com.google.firebase.firestore.pipeline.Expression currentDocument();
14341454
method public com.google.firebase.firestore.pipeline.Expression currentTimestamp();
14351455
method public com.google.firebase.firestore.pipeline.Expression divide(com.google.firebase.firestore.pipeline.Expression dividend, com.google.firebase.firestore.pipeline.Expression divisor);
14361456
method public com.google.firebase.firestore.pipeline.Expression divide(com.google.firebase.firestore.pipeline.Expression dividend, Number divisor);
@@ -1471,6 +1491,10 @@ package com.google.firebase.firestore.pipeline {
14711491
method public com.google.firebase.firestore.pipeline.Field field(String name);
14721492
method public com.google.firebase.firestore.pipeline.Expression floor(com.google.firebase.firestore.pipeline.Expression numericExpr);
14731493
method public com.google.firebase.firestore.pipeline.Expression floor(String numericField);
1494+
method public com.google.firebase.firestore.pipeline.Expression getField(com.google.firebase.firestore.pipeline.Expression expression, com.google.firebase.firestore.pipeline.Expression keyExpression);
1495+
method public com.google.firebase.firestore.pipeline.Expression getField(com.google.firebase.firestore.pipeline.Expression expression, String key);
1496+
method public com.google.firebase.firestore.pipeline.Expression getField(String fieldName, com.google.firebase.firestore.pipeline.Expression keyExpression);
1497+
method public com.google.firebase.firestore.pipeline.Expression getField(String fieldName, String key);
14741498
method public com.google.firebase.firestore.pipeline.BooleanExpression greaterThan(com.google.firebase.firestore.pipeline.Expression left, com.google.firebase.firestore.pipeline.Expression right);
14751499
method public com.google.firebase.firestore.pipeline.BooleanExpression greaterThan(com.google.firebase.firestore.pipeline.Expression left, Object right);
14761500
method public com.google.firebase.firestore.pipeline.BooleanExpression greaterThan(String fieldName, com.google.firebase.firestore.pipeline.Expression expression);
@@ -1653,6 +1677,7 @@ package com.google.firebase.firestore.pipeline {
16531677
method public com.google.firebase.firestore.pipeline.Expression unixMillisToTimestamp(String fieldName);
16541678
method public com.google.firebase.firestore.pipeline.Expression unixSecondsToTimestamp(com.google.firebase.firestore.pipeline.Expression expr);
16551679
method public com.google.firebase.firestore.pipeline.Expression unixSecondsToTimestamp(String fieldName);
1680+
method public com.google.firebase.firestore.pipeline.Expression variable(String name);
16561681
method public com.google.firebase.firestore.pipeline.Expression vector(com.google.firebase.firestore.VectorValue vector);
16571682
method public com.google.firebase.firestore.pipeline.Expression vector(double[] vector);
16581683
method public com.google.firebase.firestore.pipeline.Expression vectorLength(com.google.firebase.firestore.pipeline.Expression vectorExpression);
@@ -1773,6 +1798,15 @@ package com.google.firebase.firestore.pipeline {
17731798
method public final T withOption(String key, long value);
17741799
}
17751800

1801+
public final class SubcollectionSource extends com.google.firebase.firestore.pipeline.Stage<com.google.firebase.firestore.pipeline.SubcollectionSource> {
1802+
method public static com.google.firebase.firestore.pipeline.SubcollectionSource of(String path);
1803+
field public static final com.google.firebase.firestore.pipeline.SubcollectionSource.Companion Companion;
1804+
}
1805+
1806+
public static final class SubcollectionSource.Companion {
1807+
method public com.google.firebase.firestore.pipeline.SubcollectionSource of(String path);
1808+
}
1809+
17761810
public final class UnnestOptions extends com.google.firebase.firestore.pipeline.AbstractOptions<com.google.firebase.firestore.pipeline.UnnestOptions> {
17771811
ctor public UnnestOptions();
17781812
method public com.google.firebase.firestore.pipeline.UnnestOptions withIndexField(String indexField);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
version=26.1.3
1+
version=26.2.3
22
latestReleasedVersion=26.1.2

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/PipelineTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public void tearDown() {
144144
IntegrationTestUtil.tearDown();
145145
}
146146

147-
private final Map<String, Map<String, Object>> bookDocs =
147+
static final Map<String, Map<String, Object>> bookDocs =
148148
mapOfEntries(
149149
entry(
150150
"book1",

0 commit comments

Comments
 (0)