diff --git a/NEW/src/JdeScoping.DataSync/Etl/Transformers/ColumnDropTransformer.cs b/NEW/src/JdeScoping.DataSync/Etl/Transformers/ColumnDropTransformer.cs new file mode 100644 index 0000000..7d49b72 --- /dev/null +++ b/NEW/src/JdeScoping.DataSync/Etl/Transformers/ColumnDropTransformer.cs @@ -0,0 +1,66 @@ +using System.Data; + +namespace JdeScoping.DataSync.Etl.Transformers; + +/// +/// A data transformer that removes specified columns from the data stream. +/// Columns are matched by name (case-insensitive). +/// +public class ColumnDropTransformer : DataTransformerBase +{ + private readonly HashSet _columnsToDrop; + private int[]? _ordinalMap; + private Dictionary? _nameToOrdinal; + + /// + public override string TransformerName => $"DropColumns:{string.Join(",", _columnsToDrop)}"; + + /// + /// Creates a new ColumnDropTransformer that removes the specified columns. + /// + /// The names of columns to drop (case-insensitive). + public ColumnDropTransformer(params string[] columnsToDrop) + { + ArgumentNullException.ThrowIfNull(columnsToDrop); + _columnsToDrop = new HashSet(columnsToDrop, StringComparer.OrdinalIgnoreCase); + } + + /// + protected override void OnInitialize(IDataReader source) + { + var ordinalList = new List(); + _nameToOrdinal = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < source.FieldCount; i++) + { + var name = source.GetName(i); + if (!_columnsToDrop.Contains(name)) + { + _nameToOrdinal[name] = ordinalList.Count; + ordinalList.Add(i); + } + } + _ordinalMap = ordinalList.ToArray(); + } + + /// + public override int GetFieldCount(IDataReader source) => _ordinalMap!.Length; + + /// + public override string GetName(int ordinal, IDataReader source) => source.GetName(_ordinalMap![ordinal]); + + /// + public override Type GetFieldType(int ordinal, IDataReader source) => source.GetFieldType(_ordinalMap![ordinal]); + + /// + public override object GetValue(int ordinal, IDataReader source) => source.GetValue(_ordinalMap![ordinal]); + + /// + public override int GetOrdinal(string name, IDataReader source) + { + if (_nameToOrdinal!.TryGetValue(name, out var ordinal)) return ordinal; + throw new IndexOutOfRangeException($"Column '{name}' not found or was dropped."); + } + + /// + public override bool IsDBNull(int ordinal, IDataReader source) => source.IsDBNull(_ordinalMap![ordinal]); +} diff --git a/NEW/tests/JdeScoping.DataSync.Tests/Etl/Transformers/ColumnDropTransformerTests.cs b/NEW/tests/JdeScoping.DataSync.Tests/Etl/Transformers/ColumnDropTransformerTests.cs new file mode 100644 index 0000000..b7301d2 --- /dev/null +++ b/NEW/tests/JdeScoping.DataSync.Tests/Etl/Transformers/ColumnDropTransformerTests.cs @@ -0,0 +1,87 @@ +using System.Data; +using JdeScoping.DataSync.Etl.Transformers; +using NSubstitute; + +namespace JdeScoping.DataSync.Tests.Etl.Transformers; + +public class ColumnDropTransformerTests +{ + [Fact] + public void FieldCount_ExcludesDroppedColumns() + { + var source = CreateMockReader(new[] { "Id", "Name", "DropMe", "Value" }); + var transformer = new ColumnDropTransformer("DropMe"); + var reader = transformer.Transform(source); + Assert.Equal(3, reader.FieldCount); + } + + [Fact] + public void GetName_SkipsDroppedColumns() + { + var source = CreateMockReader(new[] { "Id", "Name", "DropMe", "Value" }); + var transformer = new ColumnDropTransformer("DropMe"); + var reader = transformer.Transform(source); + Assert.Equal("Id", reader.GetName(0)); + Assert.Equal("Name", reader.GetName(1)); + Assert.Equal("Value", reader.GetName(2)); + } + + [Fact] + public void GetOrdinal_ReturnsRemappedOrdinal() + { + var source = CreateMockReader(new[] { "Id", "Name", "DropMe", "Value" }); + var transformer = new ColumnDropTransformer("DropMe"); + var reader = transformer.Transform(source); + Assert.Equal(0, reader.GetOrdinal("Id")); + Assert.Equal(1, reader.GetOrdinal("Name")); + Assert.Equal(2, reader.GetOrdinal("Value")); + } + + [Fact] + public void GetOrdinal_DroppedColumn_ThrowsIndexOutOfRange() + { + var source = CreateMockReader(new[] { "Id", "Name", "DropMe", "Value" }); + var transformer = new ColumnDropTransformer("DropMe"); + var reader = transformer.Transform(source); + Assert.Throws(() => reader.GetOrdinal("DropMe")); + } + + [Fact] + public void GetValue_ReturnsCorrectValues() + { + var source = CreateMockReader(new[] { "Id", "Name", "DropMe", "Value" }, new object[] { 1, "Test", "Dropped", 42 }); + source.Read().Returns(true); + var transformer = new ColumnDropTransformer("DropMe"); + var reader = transformer.Transform(source); + reader.Read(); + Assert.Equal(1, reader.GetValue(0)); + Assert.Equal("Test", reader.GetValue(1)); + Assert.Equal(42, reader.GetValue(2)); + } + + [Fact] + public void MultipleDroppedColumns_AllExcluded() + { + var source = CreateMockReader(new[] { "Id", "Drop1", "Name", "Drop2", "Value" }); + var transformer = new ColumnDropTransformer("Drop1", "Drop2"); + var reader = transformer.Transform(source); + Assert.Equal(3, reader.FieldCount); + Assert.Equal("Id", reader.GetName(0)); + Assert.Equal("Name", reader.GetName(1)); + Assert.Equal("Value", reader.GetName(2)); + } + + private static IDataReader CreateMockReader(string[] columns, object[]? values = null) + { + var reader = Substitute.For(); + reader.FieldCount.Returns(columns.Length); + for (int i = 0; i < columns.Length; i++) + { + var index = i; + reader.GetName(index).Returns(columns[index]); + reader.GetOrdinal(columns[index]).Returns(index); + if (values != null) reader.GetValue(index).Returns(values[index]); + } + return reader; + } +}