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
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user