indexRange method

Stream<Map<String, Object?>> indexRange(
  1. String indexName, {
  2. List<Object?> equalPrefix = const [],
  3. Object? lower,
  4. bool lowerInclusive = true,
  5. Object? upper,
  6. bool upperInclusive = false,
})

Range-scan a secondary index. equalPrefix (optional) pins leading columns by equality; lower / upper then apply to the next column after that prefix. For a single-column index, pass an empty equalPrefix and bounds on the only column. The encoded value-keys are byte-order-preserving, so SQL semantics carry through.

Returns rows in index order: ascending by indexed-column tuple, ties broken by encoded primary key. Yields nothing when the index does not exist or any prefix value is null.

Implementation

Stream<Map<String, Object?>> indexRange(
  String indexName, {
  List<Object?> equalPrefix = const [],
  Object? lower,
  bool lowerInclusive = true,
  Object? upper,
  bool upperInclusive = false,
}) async* {
  final si = _secondary[indexName];
  if (si == null) return;
  if (equalPrefix.length >= si.columns.length &&
      lower == null &&
      upper == null) {
    // Pure prefix-equality = lookup.
    yield* indexLookup(indexName, equalPrefix);
    return;
  }
  // Build the fixed prefix from equalPrefix values.
  final bb = BytesBuilder(copy: false);
  for (var i = 0; i < equalPrefix.length; i++) {
    final v = equalPrefix[i];
    if (v == null) return;
    bb.add(_encodeIndexValue(v, si.columnTypes[i]));
  }
  final fixed = bb.toBytes();
  final rangeColIdx = equalPrefix.length;
  Uint8List? lo;
  Uint8List? hi;
  if (rangeColIdx >= si.columns.length) {
    // No range column available — bounds must be null. Fall back to
    // prefix-only range scan.
    lo = fixed;
    hi = _bumpPrefix(fixed);
  } else {
    final t = si.columnTypes[rangeColIdx];
    if (lower != null) {
      final l = _encodeIndexValue(lower, t);
      final combined = Uint8List(fixed.length + l.length)
        ..setRange(0, fixed.length, fixed)
        ..setRange(fixed.length, fixed.length + l.length, l);
      lo = lowerInclusive ? combined : _bumpPrefix(combined);
    } else if (fixed.isNotEmpty) {
      lo = fixed;
    }
    if (upper != null) {
      final u = _encodeIndexValue(upper, t);
      final combined = Uint8List(fixed.length + u.length)
        ..setRange(0, fixed.length, fixed)
        ..setRange(fixed.length, fixed.length + u.length, u);
      hi = upperInclusive ? _bumpPrefix(combined) : combined;
    } else if (fixed.isNotEmpty) {
      hi = _bumpPrefix(fixed);
    }
  }
  if (lo == null && hi == null) {
    await for (final entry in si.btree.scan()) {
      final bytes = await _heap.get(entry.value);
      if (bytes == null) continue;
      yield _decodeRow(bytes);
    }
    return;
  }
  await for (final entry in si.btree.range(
    lower: lo,
    lowerInclusive: true,
    upper: hi,
    upperInclusive: false,
  )) {
    final bytes = await _heap.get(entry.value);
    if (bytes == null) continue;
    yield _decodeRow(bytes);
  }
}