feat(etl): add collision detection to ColumnRenameTransformer
This commit is contained in:
@@ -30,10 +30,23 @@ public class ColumnRenameTransformer : DataTransformerBase
|
|||||||
{
|
{
|
||||||
_outputNames = new string[source.FieldCount];
|
_outputNames = new string[source.FieldCount];
|
||||||
_nameToOrdinal = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
_nameToOrdinal = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
for (int i = 0; i < source.FieldCount; i++)
|
for (int i = 0; i < source.FieldCount; i++)
|
||||||
{
|
{
|
||||||
var originalName = source.GetName(i);
|
var originalName = source.GetName(i);
|
||||||
var outputName = _renames.TryGetValue(originalName, out var newName) ? newName : originalName;
|
var outputName = _renames.TryGetValue(originalName, out var newName)
|
||||||
|
? newName
|
||||||
|
: originalName;
|
||||||
|
|
||||||
|
if (_nameToOrdinal.TryGetValue(outputName, out var existingOrdinal))
|
||||||
|
{
|
||||||
|
var existingOriginal = source.GetName(existingOrdinal);
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Column name collision: '{originalName}' → '{outputName}' conflicts with " +
|
||||||
|
$"'{existingOriginal}' (already at ordinal {existingOrdinal}). " +
|
||||||
|
$"Each output column name must be unique.");
|
||||||
|
}
|
||||||
|
|
||||||
_outputNames[i] = outputName;
|
_outputNames[i] = outputName;
|
||||||
_nameToOrdinal[outputName] = i;
|
_nameToOrdinal[outputName] = i;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,33 @@ public class ColumnRenameTransformerTests
|
|||||||
Assert.Equal("Charlie", reader.GetName(2));
|
Assert.Equal("Charlie", reader.GetName(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnInitialize_RenameCollision_ThrowsInvalidOperationException()
|
||||||
|
{
|
||||||
|
// Arrange - renaming A to B when B already exists
|
||||||
|
var transformer = new ColumnRenameTransformer(("A", "B"));
|
||||||
|
var source = CreateMockReader(new[] { "A", "B", "C" });
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
transformer.Transform(source));
|
||||||
|
Assert.Contains("A", ex.Message);
|
||||||
|
Assert.Contains("B", ex.Message);
|
||||||
|
Assert.Contains("collision", ex.Message.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnInitialize_PreExistingDuplicates_ThrowsInvalidOperationException()
|
||||||
|
{
|
||||||
|
// Arrange - source has duplicate column names (case-insensitive)
|
||||||
|
var transformer = new ColumnRenameTransformer();
|
||||||
|
var source = CreateMockReaderWithDuplicates(new[] { "Name", "name" });
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
transformer.Transform(source));
|
||||||
|
}
|
||||||
|
|
||||||
private static IDataReader CreateMockReader(string[] columns)
|
private static IDataReader CreateMockReader(string[] columns)
|
||||||
{
|
{
|
||||||
var reader = Substitute.For<IDataReader>();
|
var reader = Substitute.For<IDataReader>();
|
||||||
@@ -66,4 +93,16 @@ public class ColumnRenameTransformerTests
|
|||||||
}
|
}
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IDataReader CreateMockReaderWithDuplicates(string[] columns)
|
||||||
|
{
|
||||||
|
var reader = Substitute.For<IDataReader>();
|
||||||
|
reader.FieldCount.Returns(columns.Length);
|
||||||
|
for (int i = 0; i < columns.Length; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
reader.GetName(index).Returns(columns[index]);
|
||||||
|
}
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user