Files
Joseph Doherty ba11407be4 feat(DbExporter): implement verify and verify-full
Add Verifier class that reads exported .pb.zstd files and:
- Deserializes protobuf data through zstd decompression
- Extracts schema information (column names and types)
- Counts rows for verification
- Optionally computes SHA256 hash and compares against sidecar file
2026-01-06 16:55:18 -05:00

80 lines
2.4 KiB
C#

using System.Data;
using System.Security.Cryptography;
using System.Text;
using ProtoBuf.Data;
using ZstdSharp;
namespace DbExporter;
public sealed class Verifier
{
public record VerifyResult(int RowCount, List<ColumnInfo> Schema, string? ComputedHash, string? ExpectedHash, bool? HashMatch);
public record ColumnInfo(string Name, Type Type);
public VerifyResult Verify(string filePath, bool computeHash = false)
{
using var inputFile = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 256 * 1024);
using var decompressStream = new DecompressionStream(inputFile);
Stream readStream = decompressStream;
SHA256? sha256 = null;
CryptoStream? hashStream = null;
if (computeHash)
{
sha256 = SHA256.Create();
hashStream = new CryptoStream(decompressStream, sha256, CryptoStreamMode.Read);
readStream = hashStream;
}
using var reader = DataSerializer.Deserialize(readStream);
// Extract schema
var schema = new List<ColumnInfo>();
for (int i = 0; i < reader.FieldCount; i++)
{
schema.Add(new ColumnInfo(reader.GetName(i), reader.GetFieldType(i)));
}
// Count rows
int rowCount = 0;
while (reader.Read())
{
rowCount++;
}
string? computedHashStr = null;
string? expectedHash = null;
bool? hashMatch = null;
if (computeHash && sha256 != null)
{
hashStream?.Dispose();
computedHashStr = Convert.ToHexString(sha256.Hash!).ToLowerInvariant();
// Read expected hash from sidecar
var hashFilePath = filePath + ".sha256";
if (File.Exists(hashFilePath))
{
expectedHash = File.ReadAllText(hashFilePath).Trim().ToLowerInvariant();
hashMatch = computedHashStr == expectedHash;
}
sha256.Dispose();
}
return new VerifyResult(rowCount, schema, computedHashStr, expectedHash, hashMatch);
}
public string FormatSchema(List<ColumnInfo> schema)
{
var sb = new StringBuilder();
foreach (var col in schema)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append($"{col.Name} ({col.Type.Name})");
}
return sb.ToString();
}
}