Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
90bd9b5
feat(firestore): add support for Pipeline APIs
SelaseKay Feb 6, 2026
1c69d01
feat(firestore): implement pipeline execution and stages with options
SelaseKay Feb 11, 2026
1094fd0
refactor: change PipelineSerializable to mixin and update Constant cl…
SelaseKay Feb 12, 2026
89eb198
chore: add `Expression` functions
SelaseKay Feb 12, 2026
d92a2b0
chore: add support for Pigeon
SelaseKay Feb 13, 2026
b4089d8
chore: implement pipeline execution and expression parsing utilities …
SelaseKay Feb 17, 2026
7d90353
chore: enhance PigeonPipelineResult to support nullable fields and ad…
SelaseKay Feb 18, 2026
6fbd3e4
chore: enhance pipeline stage handling
SelaseKay Feb 20, 2026
3e557c6
chore: refactor sorting functionality in pipeline stages to support m…
SelaseKay Feb 24, 2026
d3c2eeb
chore: implement iOS support for Firestore pipeline execution
SelaseKay Feb 24, 2026
b4f8466
Merge branch 'feat/firestore-pipelines' into feat-firestore-pipelines
SelaseKay Feb 24, 2026
d7bc27c
refactor: move aggregate classes to pipeline_aggregate.dart
SelaseKay Feb 25, 2026
85d87e6
feat: enhance expression parsing in FLTPipelineParser to support addi…
SelaseKay Feb 25, 2026
6699324
fix: update args type in _UnnestStage to use a more specific map type
SelaseKay Feb 25, 2026
59c0b88
feat: add filter expression parsing to FLTPipelineParser for enhanced…
SelaseKay Feb 25, 2026
642c0de
fix: handle null values in parseConstantValue
SelaseKay Feb 27, 2026
0a06aea
feat: introduce keyForExpressionMap method in FLTPipelineParser for i…
SelaseKay Feb 27, 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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import com.google.firebase.firestore.MemoryCacheSettings;
import com.google.firebase.firestore.PersistentCacheIndexManager;
import com.google.firebase.firestore.PersistentCacheSettings;
import com.google.firebase.firestore.Pipeline;
import com.google.firebase.firestore.PipelineResult;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.firestore.SetOptions;
Expand All @@ -52,6 +54,7 @@
import io.flutter.plugins.firebase.firestore.streamhandler.TransactionStreamHandler;
import io.flutter.plugins.firebase.firestore.utils.ExceptionConverter;
import io.flutter.plugins.firebase.firestore.utils.PigeonParser;
import io.flutter.plugins.firebase.firestore.utils.PipelineParser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -983,4 +986,73 @@ public void documentReferenceSnapshot(
parameters.getServerTimestampBehavior()),
PigeonParser.parseListenSource(source))));
}

@Override
public void executePipeline(
@NonNull GeneratedAndroidFirebaseFirestore.FirestorePigeonFirebaseApp app,
@NonNull List<Map<String, Object>> stages,
@Nullable Map<String, Object> options,
@NonNull
GeneratedAndroidFirebaseFirestore.Result<
GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot>
result) {
cachedThreadPool.execute(
() -> {
try {
FirebaseFirestore firestore = getFirestoreFromPigeon(app);

// Execute pipeline using Android Firestore SDK
Pipeline.Snapshot snapshot = PipelineParser.executePipeline(firestore, stages, options);

// Convert Pipeline.Snapshot to PigeonPipelineSnapshot
List<GeneratedAndroidFirebaseFirestore.PigeonPipelineResult> pipelineResults =
new ArrayList<>();

// Iterate through snapshot results
for (PipelineResult pipelineResult : snapshot.getResults()) {
GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder resultBuilder =
new GeneratedAndroidFirebaseFirestore.PigeonPipelineResult.Builder();
if (pipelineResult.getRef() != null) {
resultBuilder.setDocumentPath(pipelineResult.getRef().getPath());
}

// Convert timestamps (assuming they're in milliseconds)
if (pipelineResult.getCreateTime() != null) {
resultBuilder.setCreateTime(pipelineResult.getCreateTime().toDate().getTime());
} else {
resultBuilder.setCreateTime(0L);
}

if (pipelineResult.getUpdateTime() != null) {
resultBuilder.setUpdateTime(pipelineResult.getUpdateTime().toDate().getTime());
} else {
resultBuilder.setUpdateTime(0L);
}

Map<String, Object> data = pipelineResult.getData();
if (data != null) {
resultBuilder.setData(data);
}

pipelineResults.add(resultBuilder.build());
}

// Build the snapshot
GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot.Builder snapshotBuilder =
new GeneratedAndroidFirebaseFirestore.PigeonPipelineSnapshot.Builder();
snapshotBuilder.setResults(pipelineResults);

// Set execution time (use current time if not available from snapshot)
if (snapshot.getExecutionTime() != null) {
snapshotBuilder.setExecutionTime(snapshot.getExecutionTime().toDate().getTime());
} else {
snapshotBuilder.setExecutionTime(System.currentTimeMillis());
}

result.success(snapshotBuilder.build());
} catch (Exception e) {
ExceptionConverter.sendErrorToFlutter(result, e);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,197 @@ public ArrayList<Object> toList() {
}
}

/** Generated class from Pigeon that represents data sent in messages. */
public static final class PigeonPipelineResult {
private @Nullable String documentPath;

public @Nullable String getDocumentPath() {
return documentPath;
}

public void setDocumentPath(@Nullable String setterArg) {
this.documentPath = setterArg;
}

private @Nullable Long createTime;

public @Nullable Long getCreateTime() {
return createTime;
}

public void setCreateTime(@Nullable Long setterArg) {
this.createTime = setterArg;
}

private @Nullable Long updateTime;

public @Nullable Long getUpdateTime() {
return updateTime;
}

public void setUpdateTime(@Nullable Long setterArg) {
this.updateTime = setterArg;
}

/** All fields in the result (from PipelineResult.data() on Android). */
private @Nullable Map<String, Object> data;

public @Nullable Map<String, Object> getData() {
return data;
}

public void setData(@Nullable Map<String, Object> setterArg) {
this.data = setterArg;
}

public static final class Builder {

private @Nullable String documentPath;

public @NonNull Builder setDocumentPath(@Nullable String setterArg) {
this.documentPath = setterArg;
return this;
}

private @Nullable Long createTime;

public @NonNull Builder setCreateTime(@Nullable Long setterArg) {
this.createTime = setterArg;
return this;
}

private @Nullable Long updateTime;

public @NonNull Builder setUpdateTime(@Nullable Long setterArg) {
this.updateTime = setterArg;
return this;
}

private @Nullable Map<String, Object> data;

public @NonNull Builder setData(@Nullable Map<String, Object> setterArg) {
this.data = setterArg;
return this;
}

public @NonNull PigeonPipelineResult build() {
PigeonPipelineResult pigeonReturn = new PigeonPipelineResult();
pigeonReturn.setDocumentPath(documentPath);
pigeonReturn.setCreateTime(createTime);
pigeonReturn.setUpdateTime(updateTime);
pigeonReturn.setData(data);
return pigeonReturn;
}
}

@NonNull
public ArrayList<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<Object>(4);
toListResult.add(documentPath);
toListResult.add(createTime);
toListResult.add(updateTime);
toListResult.add(data);
return toListResult;
}

static @NonNull PigeonPipelineResult fromList(@NonNull ArrayList<Object> list) {
PigeonPipelineResult pigeonResult = new PigeonPipelineResult();
Object documentPath = list.get(0);
pigeonResult.setDocumentPath((String) documentPath);
Object createTime = list.get(1);
pigeonResult.setCreateTime(
(createTime == null)
? null
: ((createTime instanceof Integer) ? (Integer) createTime : (Long) createTime));
Object updateTime = list.get(2);
pigeonResult.setUpdateTime(
(updateTime == null)
? null
: ((updateTime instanceof Integer) ? (Integer) updateTime : (Long) updateTime));
Object data = list.get(3);
pigeonResult.setData((Map<String, Object>) data);
return pigeonResult;
}
}

/** Generated class from Pigeon that represents data sent in messages. */
public static final class PigeonPipelineSnapshot {
private @NonNull List<PigeonPipelineResult> results;

public @NonNull List<PigeonPipelineResult> getResults() {
return results;
}

public void setResults(@NonNull List<PigeonPipelineResult> setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"results\" is null.");
}
this.results = setterArg;
}

private @NonNull Long executionTime;

public @NonNull Long getExecutionTime() {
return executionTime;
}

public void setExecutionTime(@NonNull Long setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"executionTime\" is null.");
}
this.executionTime = setterArg;
}

/** Constructor is non-public to enforce null safety; use Builder. */
PigeonPipelineSnapshot() {}

public static final class Builder {

private @Nullable List<PigeonPipelineResult> results;

public @NonNull Builder setResults(@NonNull List<PigeonPipelineResult> setterArg) {
this.results = setterArg;
return this;
}

private @Nullable Long executionTime;

public @NonNull Builder setExecutionTime(@NonNull Long setterArg) {
this.executionTime = setterArg;
return this;
}

public @NonNull PigeonPipelineSnapshot build() {
PigeonPipelineSnapshot pigeonReturn = new PigeonPipelineSnapshot();
pigeonReturn.setResults(results);
pigeonReturn.setExecutionTime(executionTime);
return pigeonReturn;
}
}

@NonNull
public ArrayList<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<Object>(2);
toListResult.add(results);
toListResult.add(executionTime);
return toListResult;
}

static @NonNull PigeonPipelineSnapshot fromList(@NonNull ArrayList<Object> list) {
PigeonPipelineSnapshot pigeonResult = new PigeonPipelineSnapshot();
Object results = list.get(0);
pigeonResult.setResults((List<PigeonPipelineResult>) results);
Object executionTime = list.get(1);
pigeonResult.setExecutionTime(
(executionTime == null)
? null
: ((executionTime instanceof Integer)
? (Integer) executionTime
: (Long) executionTime));
return pigeonResult;
}
}

/** Generated class from Pigeon that represents data sent in messages. */
public static final class PigeonGetOptions {
private @NonNull Source source;
Expand Down Expand Up @@ -1660,12 +1851,16 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
case (byte) 136:
return PigeonGetOptions.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 137:
return PigeonQueryParameters.fromList((ArrayList<Object>) readValue(buffer));
return PigeonPipelineResult.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 138:
return PigeonQuerySnapshot.fromList((ArrayList<Object>) readValue(buffer));
return PigeonPipelineSnapshot.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 139:
return PigeonSnapshotMetadata.fromList((ArrayList<Object>) readValue(buffer));
return PigeonQueryParameters.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 140:
return PigeonQuerySnapshot.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 141:
return PigeonSnapshotMetadata.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 142:
return PigeonTransactionCommand.fromList((ArrayList<Object>) readValue(buffer));
default:
return super.readValueOfType(type, buffer);
Expand Down Expand Up @@ -1701,17 +1896,23 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
} else if (value instanceof PigeonGetOptions) {
stream.write(136);
writeValue(stream, ((PigeonGetOptions) value).toList());
} else if (value instanceof PigeonQueryParameters) {
} else if (value instanceof PigeonPipelineResult) {
stream.write(137);
writeValue(stream, ((PigeonPipelineResult) value).toList());
} else if (value instanceof PigeonPipelineSnapshot) {
stream.write(138);
writeValue(stream, ((PigeonPipelineSnapshot) value).toList());
} else if (value instanceof PigeonQueryParameters) {
stream.write(139);
writeValue(stream, ((PigeonQueryParameters) value).toList());
} else if (value instanceof PigeonQuerySnapshot) {
stream.write(138);
stream.write(140);
writeValue(stream, ((PigeonQuerySnapshot) value).toList());
} else if (value instanceof PigeonSnapshotMetadata) {
stream.write(139);
stream.write(141);
writeValue(stream, ((PigeonSnapshotMetadata) value).toList());
} else if (value instanceof PigeonTransactionCommand) {
stream.write(140);
stream.write(142);
writeValue(stream, ((PigeonTransactionCommand) value).toList());
} else {
super.writeValue(stream, value);
Expand Down Expand Up @@ -1836,6 +2037,12 @@ void persistenceCacheIndexManagerRequest(
@NonNull PersistenceCacheIndexManagerRequest request,
@NonNull Result<Void> result);

void executePipeline(
@NonNull FirestorePigeonFirebaseApp app,
@NonNull List<Map<String, Object>> stages,
@Nullable Map<String, Object> options,
@NonNull Result<PigeonPipelineSnapshot> result);

/** The codec used by FirebaseFirestoreHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return FirebaseFirestoreHostApiCodec.INSTANCE;
Expand Down Expand Up @@ -2624,6 +2831,39 @@ public void error(Throwable error) {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.cloud_firestore_platform_interface.FirebaseFirestoreHostApi.executePipeline",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
FirestorePigeonFirebaseApp appArg = (FirestorePigeonFirebaseApp) args.get(0);
List<Map<String, Object>> stagesArg = (List<Map<String, Object>>) args.get(1);
Map<String, Object> optionsArg = (Map<String, Object>) args.get(2);
Result<PigeonPipelineSnapshot> resultCallback =
new Result<PigeonPipelineSnapshot>() {
public void success(PigeonPipelineSnapshot result) {
wrapped.add(0, result);
reply.reply(wrapped);
}

public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};

api.executePipeline(appArg, stagesArg, optionsArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
}
Loading
Loading