ba11407be4
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
80 lines
2.4 KiB
C#
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();
|
|
}
|
|
}
|