From 48c96eb8f4b0b12e335cec20b0876cbd24069c99 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 6 Jan 2026 17:00:09 -0500 Subject: [PATCH] feat(DbExporter): add counting data reader for accurate row count Wrap IDataReader with CountingDataReader to track rows as they're read during protobuf serialization, fixing the export returning 0 rows. --- Tools/DbExporter/CountingDataReader.cs | 60 ++++++++++++++++++++++++++ Tools/DbExporter/DatabaseExporter.cs | 7 ++- 2 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 Tools/DbExporter/CountingDataReader.cs diff --git a/Tools/DbExporter/CountingDataReader.cs b/Tools/DbExporter/CountingDataReader.cs new file mode 100644 index 0000000..f500d0c --- /dev/null +++ b/Tools/DbExporter/CountingDataReader.cs @@ -0,0 +1,60 @@ +using System.Data; + +namespace DbExporter; + +/// +/// Wraps an IDataReader to count rows as they're read. +/// +internal sealed class CountingDataReader : IDataReader +{ + private readonly IDataReader _inner; + private int _rowCount; + + public CountingDataReader(IDataReader inner) + { + _inner = inner; + } + + public int RowCount => _rowCount; + + public bool Read() + { + var result = _inner.Read(); + if (result) _rowCount++; + return result; + } + + // Delegate all other members to inner reader + public object this[int i] => _inner[i]; + public object this[string name] => _inner[name]; + public int Depth => _inner.Depth; + public bool IsClosed => _inner.IsClosed; + public int RecordsAffected => _inner.RecordsAffected; + public int FieldCount => _inner.FieldCount; + public void Close() => _inner.Close(); + public void Dispose() => _inner.Dispose(); + public bool GetBoolean(int i) => _inner.GetBoolean(i); + public byte GetByte(int i) => _inner.GetByte(i); + public long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) => _inner.GetBytes(i, fieldOffset, buffer, bufferoffset, length); + public char GetChar(int i) => _inner.GetChar(i); + public long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) => _inner.GetChars(i, fieldoffset, buffer, bufferoffset, length); + public IDataReader GetData(int i) => _inner.GetData(i); + public string GetDataTypeName(int i) => _inner.GetDataTypeName(i); + public DateTime GetDateTime(int i) => _inner.GetDateTime(i); + public decimal GetDecimal(int i) => _inner.GetDecimal(i); + public double GetDouble(int i) => _inner.GetDouble(i); + public Type GetFieldType(int i) => _inner.GetFieldType(i); + public float GetFloat(int i) => _inner.GetFloat(i); + public Guid GetGuid(int i) => _inner.GetGuid(i); + public short GetInt16(int i) => _inner.GetInt16(i); + public int GetInt32(int i) => _inner.GetInt32(i); + public long GetInt64(int i) => _inner.GetInt64(i); + public string GetName(int i) => _inner.GetName(i); + public int GetOrdinal(string name) => _inner.GetOrdinal(name); + public DataTable GetSchemaTable() => _inner.GetSchemaTable()!; + public string GetString(int i) => _inner.GetString(i); + public object GetValue(int i) => _inner.GetValue(i); + public int GetValues(object[] values) => _inner.GetValues(values); + public bool IsDBNull(int i) => _inner.IsDBNull(i); + public bool NextResult() => _inner.NextResult(); +} diff --git a/Tools/DbExporter/DatabaseExporter.cs b/Tools/DbExporter/DatabaseExporter.cs index 33b4206..40ef23d 100644 --- a/Tools/DbExporter/DatabaseExporter.cs +++ b/Tools/DbExporter/DatabaseExporter.cs @@ -26,7 +26,8 @@ public sealed class DatabaseExporter command.CommandText = definition.Query; command.CommandTimeout = 0; // No timeout for large exports - await using var reader = await command.ExecuteReaderAsync(cancellationToken); + await using var baseReader = await command.ExecuteReaderAsync(cancellationToken); + var reader = new CountingDataReader(baseReader); long uncompressedSize = 0; @@ -51,9 +52,7 @@ public sealed class DatabaseExporter var compressedSize = new FileInfo(definition.OutputPath).Length; - // Row count requires a separate pass or we estimate from verify - // Return 0 for now, verify will get accurate count - return new ExportResult(0, uncompressedSize, compressedSize, hash); + return new ExportResult(reader.RowCount, uncompressedSize, compressedSize, hash); } private static DbConnection CreateConnection(string providerType, string connectionString)