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