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.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
using System.Data;
|
||||
|
||||
namespace DbExporter;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps an IDataReader to count rows as they're read.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user