queryCollection function
Runs a query over collectionPath.
The whole query travels as one CBOR spec rather than as a chain of calls, so the native side holds no per-query state and there is no handle to leak if a caller goes away mid-build.
A spec the native side cannot apply is refused rather than run without the offending clause: a dropped filter returns more documents and no error, which reads as data.
Implementation
Future<List<QueryDocument>> queryCollection(
String collectionPath, {
List<Where> where = const [],
List<OrderBy> orderBy = const [],
int? limit,
int? limitToLast,
List<Object?>? startAt,
List<Object?>? startAfter,
List<Object?>? endAt,
List<Object?>? endBefore,
/// Searches every collection with this id, at any depth, rather than one
/// collection at a path.
bool collectionGroup = false,
}) {
final spec = _querySpec(
where: where,
orderBy: orderBy,
limit: limit,
limitToLast: limitToLast,
startAt: startAt,
startAfter: startAfter,
endAt: endAt,
endBefore: endBefore,
collectionGroup: collectionGroup,
);
final completer = Completer<List<QueryDocument>>();
final receive = RawReceivePort();
receive.handler = (Object? message) {
receive.close();
final bytes = message! as Uint8List;
final header = ByteData.sublistView(bytes);
final seq = header.getInt64(8, Endian.host);
if (seq < 0) {
completer.completeError(
FirestoreException(
seq.toInt(),
seq == -2 ? 'the result could not be encoded' : 'query failed',
'query $collectionPath',
),
);
return;
}
completer.complete(_decodeQueryResult(bytes));
};
final p = collectionPath.toNativeUtf8();
final encoded = _encodeSpec(spec);
final buf = calloc<Uint8>(encoded.isEmpty ? 1 : encoded.length);
if (encoded.isNotEmpty) {
buf.asTypedList(encoded.length).setAll(0, encoded);
}
try {
final rc = fdbFsQuery(
p.cast(),
buf,
encoded.length,
receive.sendPort.nativePort,
);
if (rc != 0) {
receive.close();
return Future.error(switch (rc) {
-3 => ArgumentError('this query cannot be expressed through the ABI'),
// The SDK refused something the spec expressed: a field path with a
// '/' or '[' in it, for instance. Distinct from -3 so a caller can
// tell "I cannot say that" from "Firestore will not accept it".
-4 => ArgumentError('Firestore refused this query; see stderr'),
_ => StateError('query $collectionPath failed to start ($rc)'),
});
}
} finally {
calloc
..free(p)
..free(buf);
}
return completer.future;
}