checkSync method

CheckReport checkSync({
  1. bool verifyBlocks = true,
})

Verifies the manifest, every referenced segment and the file set.

This is a read-only diagnostic: it does not repair anything. With verifyBlocks (default) every block's CRC is checked and every document is parsed, which costs time proportional to the data size.

Implementation

CheckReport checkSync({bool verifyBlocks = true}) {
  _checkOpen();
  final issues = <CheckIssue>[];
  final stats = <String, int>{};
  if (_recoveredFromWal) {
    issues.add(
      const CheckIssue(
        CheckSeverity.warning,
        'wal-only-recovery',
        'no manifest slot decoded on open; state was rebuilt from the WAL. '
            'Orphan segment files, if any, were kept for inspection',
      ),
    );
  }

  final segmentNames = <String>[
    for (final name in _directory.listSync())
      if (name.startsWith(segmentPrefix) && name.endsWith(segmentSuffix))
        name,
  ];
  stats['segmentFiles'] = segmentNames.length;

  Manifest? manifest;
  try {
    manifest = ManifestStore(_directory).loadSync();
  } on Object catch (error) {
    issues.add(
      CheckIssue(CheckSeverity.error, 'manifest', 'read failed: $error'),
    );
  }

  if (manifest == null) {
    // A recovered database has no manifest until its first flush; that is
    // reported by the wal-only-recovery warning above, not as an error.
    if (segmentNames.isNotEmpty && !_recoveredFromWal) {
      issues.add(
        const CheckIssue(
          CheckSeverity.error,
          'no-manifest',
          'segment files exist but no manifest decodes',
        ),
      );
    }
  } else {
    stats['manifestGeneration'] = manifest.generation;
    stats['segments'] = manifest.segments.length;
    final referenced = <String>{};
    var entries = 0;
    var bytes = 0;
    var documents = 0;
    for (final info in manifest.segments) {
      referenced.add(info.name);
      entries += info.entryCount;
      bytes += info.byteLength;
      if (!_directory.existsSync(info.name)) {
        issues.add(
          CheckIssue(CheckSeverity.error, 'missing-segment', info.name),
        );
        continue;
      }
      SyncStorageFile? file;
      try {
        file = _directory.openSync(info.name, create: false);
        final data = file.readAllSync();
        if (data.length != info.byteLength) {
          issues.add(
            CheckIssue(
              CheckSeverity.warning,
              'segment-size',
              '${info.name}: expected ${info.byteLength}, found ${data.length}',
            ),
          );
        }
        final reader = SegmentReader(data);
        if (verifyBlocks) {
          reader.forEach((_) => documents++);
        }
      } on Object catch (error) {
        issues.add(
          CheckIssue(
            CheckSeverity.error,
            'segment-read',
            '${info.name}: $error',
          ),
        );
      } finally {
        file?.closeSync();
      }
    }
    stats['entries'] = entries;
    stats['bytes'] = bytes;
    if (verifyBlocks) stats['documents'] = documents;
    for (final name in segmentNames) {
      if (!referenced.contains(name)) {
        issues.add(CheckIssue(CheckSeverity.warning, 'orphan-segment', name));
      }
    }
  }

  // Use the engine's own WAL handle: opening a second handle and closing it
  // would close the shared handle in in-memory directories.
  stats['walBytes'] = _walFile.lengthSync;
  return CheckReport(issues, stats);
}