diff --git a/src/Microsoft.Data.Analysis/DataFrame.Join.cs b/src/Microsoft.Data.Analysis/DataFrame.Join.cs index fe4b44e0d5..0f26ec3144 100644 --- a/src/Microsoft.Data.Analysis/DataFrame.Join.cs +++ b/src/Microsoft.Data.Analysis/DataFrame.Join.cs @@ -26,6 +26,15 @@ public partial class DataFrame private void SetSuffixForDuplicatedColumnNames(DataFrame dataFrame, DataFrameColumn column, string leftSuffix, string rightSuffix) { int index = dataFrame._columnCollection.IndexOf(column.Name); + + // The loop below appends leftSuffix to the existing column and rightSuffix to the new one until the two + // names differ. If both suffixes are the same the names stay equal however many times they are appended, + // so there is no name that ends the loop. + if (index != -1 && leftSuffix == rightSuffix) + { + throw new ArgumentException(string.Format(Strings.SuffixesMustBeDifferent, nameof(leftSuffix), nameof(rightSuffix), column.Name), nameof(rightSuffix)); + } + while (index != -1) { // Pre-existing column. Change name diff --git a/src/Microsoft.Data.Analysis/Strings.Designer.cs b/src/Microsoft.Data.Analysis/Strings.Designer.cs index 69f0daf9bc..9aeb46ff38 100644 --- a/src/Microsoft.Data.Analysis/Strings.Designer.cs +++ b/src/Microsoft.Data.Analysis/Strings.Designer.cs @@ -492,6 +492,15 @@ internal static string StreamDoesntSupportReading { } } + /// + /// Looks up a localized string similar to {0} and {1} must be different when both DataFrames contain a column called '{2}'. + /// + internal static string SuffixesMustBeDifferent { + get { + return ResourceManager.GetString("SuffixesMustBeDifferent", resourceCulture); + } + } + /// /// Looks up a localized string similar to Value name '{0}' matches an existing column name. /// diff --git a/src/Microsoft.Data.Analysis/Strings.resx b/src/Microsoft.Data.Analysis/Strings.resx index bc58112290..faaa77037a 100644 --- a/src/Microsoft.Data.Analysis/Strings.resx +++ b/src/Microsoft.Data.Analysis/Strings.resx @@ -261,6 +261,9 @@ Stream doesn't support reading + + {0} and {1} must be different when both DataFrames contain a column called '{2}' + Value name '{0}' matches an existing column name diff --git a/test/Microsoft.Data.Analysis.Tests/DataFrameTests.Join.cs b/test/Microsoft.Data.Analysis.Tests/DataFrameTests.Join.cs index e782296dd9..62624bcc9c 100644 --- a/test/Microsoft.Data.Analysis.Tests/DataFrameTests.Join.cs +++ b/test/Microsoft.Data.Analysis.Tests/DataFrameTests.Join.cs @@ -82,6 +82,40 @@ public void TestJoin() VerifyJoin(join, left, right, JoinAlgorithm.Inner); } + [Theory] + [InlineData(JoinAlgorithm.Left)] + [InlineData(JoinAlgorithm.Right)] + [InlineData(JoinAlgorithm.FullOuter)] + [InlineData(JoinAlgorithm.Inner)] + public void TestJoin_ColumnNamesCollide_ThrowsOnSameSuffixOnBothSides(JoinAlgorithm joinAlgorithm) + { + DataFrame left = MakeDataFrameWithNumericColumns(3, false); + DataFrame right = MakeDataFrameWithNumericColumns(4, false); + + // Both frames have a column called "Int", and one suffix cannot make the two names different, + // so the join used to keep renaming for ever instead of coming back + Assert.Throws(() => left.Join(right, "_same", "_same", joinAlgorithm)); + Assert.Throws(() => left.Join(right, "", "", joinAlgorithm)); + + // different suffixes still work + DataFrame join = left.Join(right, "_left", "_right", joinAlgorithm); + Assert.Equal(left.Columns.Count + right.Columns.Count, join.Columns.Count); + } + + [Fact] + public void TestJoin_SameSuffixWithNoSharedColumnNames() + { + DataFrame left = new DataFrame(new Int32DataFrameColumn("Left", new int?[] { 0, 1, 2 })); + DataFrame right = new DataFrame(new Int32DataFrameColumn("Right", new int?[] { 0, 1, 2 })); + + // no name is shared, so nothing is renamed and the suffixes are never used + DataFrame join = left.Join(right, "_same", "_same"); + + Assert.Equal(2, join.Columns.Count); + Assert.Equal("Left", join.Columns[0].Name); + Assert.Equal("Right", join.Columns[1].Name); + } + private void VerifyJoin(DataFrame join, DataFrame left, DataFrame right, JoinAlgorithm joinAlgorithm) { Int64DataFrameColumn mapIndices = new Int64DataFrameColumn("map", join.Rows.Count); diff --git a/test/Microsoft.Data.Analysis.Tests/DataFrameTests.Merge.cs b/test/Microsoft.Data.Analysis.Tests/DataFrameTests.Merge.cs index dbd689ff3d..9a563ee525 100644 --- a/test/Microsoft.Data.Analysis.Tests/DataFrameTests.Merge.cs +++ b/test/Microsoft.Data.Analysis.Tests/DataFrameTests.Merge.cs @@ -1013,6 +1013,18 @@ public void TestMerge_CorrectColumnTypes() Assert.NotNull(merge.Columns.GetDateTimeColumn("DateTime_right")); } + [Fact] + public void TestMerge_ColumnNamesCollide_ThrowsOnSameSuffixOnBothSides() + { + DataFrame left = MakeDataFrameWithNumericColumns(3, false); + DataFrame right = MakeDataFrameWithNumericColumns(4, false); + + Assert.Throws(() => left.Merge(right, "Int", "Int", "_same", "_same")); + + DataFrame merge = left.Merge(right, "Int", "Int"); + Assert.Equal(left.Columns.Count + right.Columns.Count, merge.Columns.Count); + } + private void VerifyMerge(DataFrame merge, DataFrame left, DataFrame right, JoinAlgorithm joinAlgorithm) { if (joinAlgorithm == JoinAlgorithm.Left || joinAlgorithm == JoinAlgorithm.Inner)