queryCollection function

Future<List<QueryDocument>> queryCollection(
  1. String collectionPath, {
  2. List<Where> where = const [],
  3. List<OrderBy> orderBy = const [],
  4. int? limit,
  5. int? limitToLast,
  6. List<Object?>? startAt,
  7. List<Object?>? startAfter,
  8. List<Object?>? endAt,
  9. List<Object?>? endBefore,
  10. bool collectionGroup = false,
})

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;
}