From 87a02d5368eac3cb76d77c3f4a1182d39cb5caa3 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 6 Jan 2026 16:57:58 -0500 Subject: [PATCH] feat(DbExporter): implement CLI entry point --- Tools/DbExporter/Program.cs | 134 ++++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 6 deletions(-) diff --git a/Tools/DbExporter/Program.cs b/Tools/DbExporter/Program.cs index 364216b..55720ad 100644 --- a/Tools/DbExporter/Program.cs +++ b/Tools/DbExporter/Program.cs @@ -1,9 +1,131 @@ -namespace DbExporter; +using System.Text.Json; +using DbExporter; -class Program +if (args.Length < 1 || args.Contains("--help") || args.Contains("-h")) { - static void Main(string[] args) - { - Console.WriteLine("DbExporter - Database Export Tool"); - } + PrintUsage(); + return args.Contains("--help") || args.Contains("-h") ? 0 : 1; +} + +var definitionPath = args[0]; +var verify = args.Contains("--verify"); +var verifyFull = args.Contains("--verify-full"); + +if (!File.Exists(definitionPath)) +{ + Console.WriteLine($"Error: Definition file not found: {definitionPath}"); + return 1; +} + +try +{ + var json = await File.ReadAllTextAsync(definitionPath); + var definition = JsonSerializer.Deserialize(json); + + if (definition is null) + { + Console.WriteLine("Error: Failed to parse definition file."); + return 1; + } + + // Validate required fields + if (string.IsNullOrWhiteSpace(definition.ProviderType)) + { + Console.WriteLine("Error: providerType is required."); + return 1; + } + if (string.IsNullOrWhiteSpace(definition.ConnectionString)) + { + Console.WriteLine("Error: connectionString is required."); + return 1; + } + if (string.IsNullOrWhiteSpace(definition.Query)) + { + Console.WriteLine("Error: query is required."); + return 1; + } + if (string.IsNullOrWhiteSpace(definition.OutputPath)) + { + Console.WriteLine("Error: outputPath is required."); + return 1; + } + + var exporter = new DatabaseExporter(); + var verifier = new Verifier(); + + Console.WriteLine($"Exporting from {definition.ProviderType}..."); + Console.WriteLine($"Query: {Truncate(definition.Query, 80)}"); + + var result = await exporter.ExportAsync(definition); + + // Always do a quick verify to get row count + var quickVerify = verifier.Verify(definition.OutputPath, computeHash: false); + + var ratio = result.CompressedSize > 0 && quickVerify.RowCount > 0 + ? $" ({(double)result.CompressedSize / result.UncompressedSize * 100:F1}%)" + : ""; + + Console.WriteLine($"✓ Exported: {quickVerify.RowCount:N0} rows, {result.UncompressedSize:N0} → {result.CompressedSize:N0} bytes{ratio}"); + + if (verify || verifyFull) + { + Console.WriteLine(); + Console.WriteLine("Verifying..."); + + var verifyResult = verifier.Verify(definition.OutputPath, computeHash: verifyFull); + + Console.WriteLine($"✓ Verified: {verifyResult.RowCount:N0} rows"); + Console.WriteLine($"Schema: {verifier.FormatSchema(verifyResult.Schema)}"); + + if (verifyFull && verifyResult.HashMatch.HasValue) + { + if (verifyResult.HashMatch.Value) + { + Console.WriteLine($"✓ Checksum: SHA256 match ({verifyResult.ComputedHash})"); + } + else + { + Console.WriteLine($"✗ Checksum: SHA256 MISMATCH"); + Console.WriteLine($" Expected: {verifyResult.ExpectedHash}"); + Console.WriteLine($" Computed: {verifyResult.ComputedHash}"); + return 1; + } + } + } + + return 0; +} +catch (Exception ex) +{ + Console.WriteLine($"Error: {ex.Message}"); + return 1; +} + +static void PrintUsage() +{ + Console.WriteLine("Usage: DbExporter [options]"); + Console.WriteLine(); + Console.WriteLine("Arguments:"); + Console.WriteLine(" definition-file Path to JSON definition file"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" --verify Verify output (row count + schema)"); + Console.WriteLine(" --verify-full Verify output with SHA256 checksum"); + Console.WriteLine(" --help Show this help"); + Console.WriteLine(); + Console.WriteLine("Definition file format:"); + Console.WriteLine(" {"); + Console.WriteLine(" \"providerType\": \"SqlServer\","); + Console.WriteLine(" \"connectionString\": \"Server=...;Database=...;\","); + Console.WriteLine(" \"query\": \"SELECT * FROM MyTable\","); + Console.WriteLine(" \"outputPath\": \"./output/mytable.pb.zstd\","); + Console.WriteLine(" \"compressionLevel\": 10"); + Console.WriteLine(" }"); +} + +static string Truncate(string value, int maxLength) +{ + if (string.IsNullOrEmpty(value)) return value; + var singleLine = value.Replace("\r", "").Replace("\n", " "); + return singleLine.Length <= maxLength ? singleLine : singleLine[..(maxLength - 3)] + "..."; }