diff --git a/NEW/src/JdeScoping.DataSync/Etl/Transformers/ColumnRenameTransformer.cs b/NEW/src/JdeScoping.DataSync/Etl/Transformers/ColumnRenameTransformer.cs
new file mode 100644
index 0000000..dba8c3d
--- /dev/null
+++ b/NEW/src/JdeScoping.DataSync/Etl/Transformers/ColumnRenameTransformer.cs
@@ -0,0 +1,51 @@
+using System.Data;
+
+namespace JdeScoping.DataSync.Etl.Transformers;
+
+///
+/// A data transformer that renames specified columns in the data stream.
+/// Columns are matched by name (case-insensitive).
+///
+public class ColumnRenameTransformer : DataTransformerBase
+{
+ private readonly Dictionary _renames;
+ private string[]? _outputNames;
+ private Dictionary? _nameToOrdinal;
+
+ ///
+ public override string TransformerName => $"RenameColumns:{_renames.Count}";
+
+ ///
+ /// Creates a new ColumnRenameTransformer that renames the specified columns.
+ ///
+ /// Tuples of (OldName, NewName) for columns to rename (case-insensitive matching).
+ public ColumnRenameTransformer(params (string OldName, string NewName)[] renames)
+ {
+ ArgumentNullException.ThrowIfNull(renames);
+ _renames = renames.ToDictionary(r => r.OldName, r => r.NewName, StringComparer.OrdinalIgnoreCase);
+ }
+
+ ///
+ protected override void OnInitialize(IDataReader source)
+ {
+ _outputNames = new string[source.FieldCount];
+ _nameToOrdinal = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ for (int i = 0; i < source.FieldCount; i++)
+ {
+ var originalName = source.GetName(i);
+ var outputName = _renames.TryGetValue(originalName, out var newName) ? newName : originalName;
+ _outputNames[i] = outputName;
+ _nameToOrdinal[outputName] = i;
+ }
+ }
+
+ ///
+ public override string GetName(int ordinal, IDataReader source) => _outputNames![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.");
+ }
+}
diff --git a/NEW/tests/JdeScoping.DataSync.Tests/Etl/Transformers/ColumnRenameTransformerTests.cs b/NEW/tests/JdeScoping.DataSync.Tests/Etl/Transformers/ColumnRenameTransformerTests.cs
new file mode 100644
index 0000000..1cee1f6
--- /dev/null
+++ b/NEW/tests/JdeScoping.DataSync.Tests/Etl/Transformers/ColumnRenameTransformerTests.cs
@@ -0,0 +1,69 @@
+using System.Data;
+using JdeScoping.DataSync.Etl.Transformers;
+using NSubstitute;
+
+namespace JdeScoping.DataSync.Tests.Etl.Transformers;
+
+public class ColumnRenameTransformerTests
+{
+ [Fact]
+ public void GetName_ReturnsRenamedColumn()
+ {
+ var source = CreateMockReader(new[] { "OldName", "Other" });
+ var transformer = new ColumnRenameTransformer(("OldName", "NewName"));
+ var reader = transformer.Transform(source);
+ Assert.Equal("NewName", reader.GetName(0));
+ Assert.Equal("Other", reader.GetName(1));
+ }
+
+ [Fact]
+ public void GetOrdinal_FindsByNewName()
+ {
+ var source = CreateMockReader(new[] { "OldName", "Other" });
+ var transformer = new ColumnRenameTransformer(("OldName", "NewName"));
+ var reader = transformer.Transform(source);
+ Assert.Equal(0, reader.GetOrdinal("NewName"));
+ }
+
+ [Fact]
+ public void GetOrdinal_OldName_ThrowsIndexOutOfRange()
+ {
+ var source = CreateMockReader(new[] { "OldName", "Other" });
+ var transformer = new ColumnRenameTransformer(("OldName", "NewName"));
+ var reader = transformer.Transform(source);
+ Assert.Throws(() => reader.GetOrdinal("OldName"));
+ }
+
+ [Fact]
+ public void FieldCount_Unchanged()
+ {
+ var source = CreateMockReader(new[] { "OldName", "Other" });
+ var transformer = new ColumnRenameTransformer(("OldName", "NewName"));
+ var reader = transformer.Transform(source);
+ Assert.Equal(2, reader.FieldCount);
+ }
+
+ [Fact]
+ public void MultipleRenames_AllApplied()
+ {
+ var source = CreateMockReader(new[] { "A", "B", "C" });
+ var transformer = new ColumnRenameTransformer(("A", "Alpha"), ("C", "Charlie"));
+ var reader = transformer.Transform(source);
+ Assert.Equal("Alpha", reader.GetName(0));
+ Assert.Equal("B", reader.GetName(1));
+ Assert.Equal("Charlie", reader.GetName(2));
+ }
+
+ private static IDataReader CreateMockReader(string[] columns)
+ {
+ 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);
+ }
+ return reader;
+ }
+}