From 431c4ea4d9facb23c612631317a2e1f862087ba7 Mon Sep 17 00:00:00 2001 From: Curt Hagenlocher Date: Thu, 14 Dec 2023 12:08:55 -0800 Subject: [PATCH] GH-39223: [C#] Support IReadOnlyList on remaining scalar types (#39224) ### What changes are included in this PR? Decimal128Array implements IReadOnlyList and IReadOnlyList. Decimal256Array implements IReadOnlyList, IReadOnlyList and IReadOnlyList. FixedLengthBinaryArray implements IReadOnlyList. DurationArray implements IReadOnlyList. Also removes #ifs which are no longer relevant now that netstandard13 isn't being built any more. ### Are these changes tested? Yes. * Closes: #39223 Authored-by: Curt Hagenlocher Signed-off-by: Curt Hagenlocher --- .../Apache.Arrow/Arrays/Decimal128Array.cs | 23 ++++++---- .../Apache.Arrow/Arrays/Decimal256Array.cs | 44 ++++++++++++++++--- .../src/Apache.Arrow/Arrays/DurationArray.cs | 17 ++++++- .../Arrays/FixedSizeBinaryArray.cs | 17 ++++++- csharp/src/Apache.Arrow/DecimalUtility.cs | 6 --- .../Decimal128ArrayTests.cs | 25 +++-------- .../Decimal256ArrayTests.cs | 36 +++++++-------- .../Apache.Arrow.Tests/DecimalUtilityTests.cs | 5 --- .../Apache.Arrow.Tests/DurationArrayTests.cs | 4 ++ 9 files changed, 112 insertions(+), 65 deletions(-) diff --git a/csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs b/csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs index 0e3ec56740449..5a51175b7c4da 100644 --- a/csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs +++ b/csharp/src/Apache.Arrow/Arrays/Decimal128Array.cs @@ -14,18 +14,16 @@ // limitations under the License. using System; +using System.Collections; using System.Collections.Generic; -#if !NETSTANDARD1_3 using System.Data.SqlTypes; -#endif using System.Diagnostics; -using System.Numerics; using Apache.Arrow.Arrays; using Apache.Arrow.Types; namespace Apache.Arrow { - public class Decimal128Array : FixedSizeBinaryArray + public class Decimal128Array : FixedSizeBinaryArray, IReadOnlyList { public class Builder : BuilderBase { @@ -95,7 +93,6 @@ public Builder AppendRange(IEnumerable values) return Instance; } -#if !NETSTANDARD1_3 public Builder Append(SqlDecimal value) { Span bytes = stackalloc byte[DataType.ByteWidth]; @@ -118,7 +115,6 @@ public Builder AppendRange(IEnumerable values) return Instance; } -#endif public Builder Set(int index, decimal value) { @@ -184,7 +180,6 @@ public string GetString(int index) return DecimalUtility.GetString(ValueBuffer, index, Precision, Scale, ByteWidth); } -#if !NETSTANDARD1_3 public SqlDecimal? GetSqlDecimal(int index) { if (IsNull(index)) @@ -194,6 +189,18 @@ public string GetString(int index) return DecimalUtility.GetSqlDecimal128(ValueBuffer, index, Precision, Scale); } -#endif + + int IReadOnlyCollection.Count => Length; + SqlDecimal? IReadOnlyList.this[int index] => GetSqlDecimal(index); + + IEnumerator IEnumerable.GetEnumerator() + { + for (int index = 0; index < Length; index++) + { + yield return GetSqlDecimal(index); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); } } diff --git a/csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs b/csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs index 94a47f258280e..eca2611b6f3bb 100644 --- a/csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs +++ b/csharp/src/Apache.Arrow/Arrays/Decimal256Array.cs @@ -14,17 +14,16 @@ // limitations under the License. using System; +using System.Collections; using System.Collections.Generic; -#if !NETSTANDARD1_3 using System.Data.SqlTypes; -#endif using System.Diagnostics; using Apache.Arrow.Arrays; using Apache.Arrow.Types; namespace Apache.Arrow { - public class Decimal256Array : FixedSizeBinaryArray + public class Decimal256Array : FixedSizeBinaryArray, IReadOnlyList, IReadOnlyList { public class Builder : BuilderBase { @@ -94,7 +93,6 @@ public Builder AppendRange(IEnumerable values) return Instance; } -#if !NETSTANDARD1_3 public Builder Append(SqlDecimal value) { Span bytes = stackalloc byte[DataType.ByteWidth]; @@ -123,7 +121,6 @@ public Builder AppendRange(IEnumerable values) return Instance; } -#endif public Builder Set(int index, decimal value) { @@ -190,7 +187,6 @@ public string GetString(int index) return DecimalUtility.GetString(ValueBuffer, index, Precision, Scale, ByteWidth); } -#if !NETSTANDARD1_3 public bool TryGetSqlDecimal(int index, out SqlDecimal? value) { if (IsNull(index)) @@ -211,6 +207,40 @@ public bool TryGetSqlDecimal(int index, out SqlDecimal? value) value = null; return false; } -#endif + + private SqlDecimal? GetSqlDecimal(int index) + { + SqlDecimal? value; + if (TryGetSqlDecimal(index, out value)) + { + return value; + } + + throw new OverflowException("decimal256 value out of range of SqlDecimal"); + } + + int IReadOnlyCollection.Count => Length; + SqlDecimal? IReadOnlyList.this[int index] => GetSqlDecimal(index); + + IEnumerator IEnumerable.GetEnumerator() + { + for (int index = 0; index < Length; index++) + { + yield return GetSqlDecimal(index); + } + } + + int IReadOnlyCollection.Count => Length; + string? IReadOnlyList.this[int index] => GetString(index); + + IEnumerator IEnumerable.GetEnumerator() + { + for (int index = 0; index < Length; index++) + { + yield return GetString(index); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); } } diff --git a/csharp/src/Apache.Arrow/Arrays/DurationArray.cs b/csharp/src/Apache.Arrow/Arrays/DurationArray.cs index 3649dda50cd97..f725a71e377ab 100644 --- a/csharp/src/Apache.Arrow/Arrays/DurationArray.cs +++ b/csharp/src/Apache.Arrow/Arrays/DurationArray.cs @@ -14,11 +14,13 @@ // limitations under the License. using System; +using System.Collections; +using System.Collections.Generic; using Apache.Arrow.Types; namespace Apache.Arrow { - public class DurationArray : PrimitiveArray + public class DurationArray : PrimitiveArray, IReadOnlyList { public class Builder : PrimitiveArrayBuilder { @@ -80,5 +82,18 @@ public DurationArray(ArrayData data) } public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor); + + int IReadOnlyCollection.Count => Length; + TimeSpan? IReadOnlyList.this[int index] => GetTimeSpan(index); + + IEnumerator IEnumerable.GetEnumerator() + { + for (int index = 0; index < Length; index++) + { + yield return GetTimeSpan(index); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); } } diff --git a/csharp/src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs b/csharp/src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs index 866a674bc9df8..0fa7954724f38 100644 --- a/csharp/src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs +++ b/csharp/src/Apache.Arrow/Arrays/FixedSizeBinaryArray.cs @@ -14,13 +14,14 @@ // limitations under the License. using System; +using System.Collections; using System.Collections.Generic; using Apache.Arrow.Memory; using Apache.Arrow.Types; namespace Apache.Arrow.Arrays { - public class FixedSizeBinaryArray : Array + public class FixedSizeBinaryArray : Array, IReadOnlyList { public FixedSizeBinaryArray(ArrayData data) : base(data) @@ -70,6 +71,19 @@ public ReadOnlySpan GetBytes(int index) return ValueBuffer.Span.Slice(index * size, size); } + int IReadOnlyCollection.Count => Length; + byte[] IReadOnlyList.this[int index] => GetBytes(index).ToArray(); + + IEnumerator IEnumerable.GetEnumerator() + { + for (int index = 0; index < Length; index++) + { + yield return GetBytes(index).ToArray(); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + public abstract class BuilderBase : IArrowArrayBuilder where TArray : IArrowArray where TBuilder : class, IArrowArrayBuilder @@ -220,7 +234,6 @@ public TBuilder SetNull(int index) ValidityBuffer.Set(index, false); return Instance; } - } } } diff --git a/csharp/src/Apache.Arrow/DecimalUtility.cs b/csharp/src/Apache.Arrow/DecimalUtility.cs index bb3f0834fcec3..e2ab18d479edb 100644 --- a/csharp/src/Apache.Arrow/DecimalUtility.cs +++ b/csharp/src/Apache.Arrow/DecimalUtility.cs @@ -14,9 +14,7 @@ // limitations under the License. using System; -#if !NETSTANDARD1_3 using System.Data.SqlTypes; -#endif using System.Numerics; namespace Apache.Arrow @@ -183,7 +181,6 @@ internal unsafe static string GetString(in ArrowBuffer valueBuffer, int index, i } #endif -#if !NETSTANDARD1_3 internal static SqlDecimal GetSqlDecimal128(in ArrowBuffer valueBuffer, int index, int precision, int scale) { const int byteWidth = 16; @@ -207,7 +204,6 @@ internal static SqlDecimal GetSqlDecimal128(in ArrowBuffer valueBuffer, int inde return new SqlDecimal((byte)precision, (byte)scale, false, (int)(data1 & 0xffffffff), (int)(data1 >> 32), (int)(data2 & 0xffffffff), (int)(data2 >> 32)); } } -#endif private static decimal DivideByScale(BigInteger integerValue, int scale) { @@ -428,7 +424,6 @@ internal static void GetBytes(string value, int precision, int scale, int byteWi } } -#if !NETSTANDARD1_3 internal static void GetBytes(SqlDecimal value, int precision, int scale, Span bytes) { if (value.Precision != precision || value.Scale != scale) @@ -446,6 +441,5 @@ internal static void GetBytes(SqlDecimal value, int precision, int scale, Span asList = array; + for (int i = 0; i < asList.Count; i++) + { + Assert.Equal(testData[i], asList[i]); + } } [Fact] @@ -467,7 +457,6 @@ public void AppendRangeSqlDecimal() Assert.Null(array.GetValue(range.Length)); } } -#endif } } } diff --git a/csharp/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs b/csharp/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs index 3924c73a4e2f7..baeb7ee5419b9 100644 --- a/csharp/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs +++ b/csharp/test/Apache.Arrow.Tests/Decimal256ArrayTests.cs @@ -14,10 +14,9 @@ // limitations under the License. using System; -#if !NETSTANDARD1_3 +using System.Collections.Generic; using System.Data.SqlTypes; using System.Linq; -#endif using Apache.Arrow.Types; using Xunit; @@ -25,7 +24,6 @@ namespace Apache.Arrow.Tests { public class Decimal256ArrayTests { -#if !NETSTANDARD1_3 static SqlDecimal? GetSqlDecimal(Decimal256Array array, int index) { SqlDecimal? result; @@ -42,7 +40,11 @@ public class Decimal256ArrayTests { return value == null ? null : value.Value.Value; } -#endif + + static decimal? Convert(string value) + { + return value == null ? null : decimal.Parse(value); + } public class Builder { @@ -68,11 +70,9 @@ public void AppendThenGetGivesNull() Assert.Null(array.GetValue(1)); Assert.Null(array.GetValue(2)); -#if !NETSTANDARD1_3 Assert.Null(GetSqlDecimal(array, 0)); Assert.Null(GetSqlDecimal(array, 1)); Assert.Null(GetSqlDecimal(array, 2)); -#endif } } @@ -106,9 +106,7 @@ public void AppendDecimal(int count) for (int i = 0; i < count; i++) { Assert.Equal(testData[i], array.GetValue(i)); -#if !NETSTANDARD1_3 Assert.Equal(Convert(testData[i]), GetSqlDecimal(array, i)); -#endif } } @@ -127,10 +125,8 @@ public void AppendLargeDecimal() Assert.Equal(large, array.GetValue(0)); Assert.Equal(-large, array.GetValue(1)); -#if !NETSTANDARD1_3 Assert.Equal(Convert(large), GetSqlDecimal(array, 0)); Assert.Equal(Convert(-large), GetSqlDecimal(array, 1)); -#endif } [Fact] @@ -152,12 +148,10 @@ public void AppendMaxAndMinDecimal() Assert.Equal(Decimal.MaxValue - 10, array.GetValue(2)); Assert.Equal(Decimal.MinValue + 10, array.GetValue(3)); -#if !NETSTANDARD1_3 Assert.Equal(Convert(Decimal.MaxValue), GetSqlDecimal(array, 0)); Assert.Equal(Convert(Decimal.MinValue), GetSqlDecimal(array, 1)); Assert.Equal(Convert(Decimal.MaxValue) - 10, GetSqlDecimal(array, 2)); Assert.Equal(Convert(Decimal.MinValue) + 10, GetSqlDecimal(array, 3)); -#endif } [Fact] @@ -175,10 +169,8 @@ public void AppendFractionalDecimal() Assert.Equal(fraction, array.GetValue(0)); Assert.Equal(-fraction, array.GetValue(1)); -#if !NETSTANDARD1_3 Assert.Equal(Convert(fraction), GetSqlDecimal(array, 0)); Assert.Equal(Convert(-fraction), GetSqlDecimal(array, 1)); -#endif } [Fact] @@ -197,9 +189,7 @@ public void AppendRangeDecimal() for(int i = 0; i < range.Length; i ++) { Assert.Equal(range[i], array.GetValue(i)); -#if !NETSTANDARD1_3 Assert.Equal(Convert(range[i]), GetSqlDecimal(array, i)); -#endif } Assert.Null( array.GetValue(range.Length)); @@ -308,7 +298,6 @@ public void SwapNull() } } -#if !NETSTANDARD1_3 public class SqlDecimals { [Theory] @@ -342,6 +331,18 @@ public void AppendSqlDecimal(int count) Assert.Equal(testData[i], GetSqlDecimal(array, i)); Assert.Equal(Convert(testData[i]), array.GetValue(i)); } + + IReadOnlyList asDecimalList = array; + for (int i = 0; i < asDecimalList.Count; i++) + { + Assert.Equal(testData[i], asDecimalList[i]); + } + + IReadOnlyList asStringList = array; + for (int i = 0; i < asStringList.Count; i++) + { + Assert.Equal(Convert(testData[i]?.ToString()), Convert(asStringList[i])); + } } [Fact] @@ -474,7 +475,6 @@ public void AppendRangeSqlDecimal() Assert.Null(array.GetValue(range.Length)); } } -#endif } } } diff --git a/csharp/test/Apache.Arrow.Tests/DecimalUtilityTests.cs b/csharp/test/Apache.Arrow.Tests/DecimalUtilityTests.cs index 677e9b6cadfcf..1156ecb452c94 100644 --- a/csharp/test/Apache.Arrow.Tests/DecimalUtilityTests.cs +++ b/csharp/test/Apache.Arrow.Tests/DecimalUtilityTests.cs @@ -14,9 +14,7 @@ // limitations under the License. using System; -#if !NETSTANDARD1_3 using System.Data.SqlTypes; -#endif using Apache.Arrow.Types; using Xunit; @@ -72,8 +70,6 @@ public void Decimal256HasExpectedResultOrThrows(decimal d, int precision, int sc public class SqlDecimals { - -#if !NETSTANDARD1_3 [Fact] public void NegativeSqlDecimal() { @@ -119,7 +115,6 @@ public void LargeScale() Assert.Equal(negative, sqlNegative); Assert.Equal(digits, sqlNegative.ToString()); } -#endif } public class Strings diff --git a/csharp/test/Apache.Arrow.Tests/DurationArrayTests.cs b/csharp/test/Apache.Arrow.Tests/DurationArrayTests.cs index 3395ca7bc9ad7..59080d739b10b 100644 --- a/csharp/test/Apache.Arrow.Tests/DurationArrayTests.cs +++ b/csharp/test/Apache.Arrow.Tests/DurationArrayTests.cs @@ -113,6 +113,10 @@ public void AppendTimeSpanGivesSameTimeSpan(TimeSpan? timeSpan, DurationType typ var array = builder.Build(); Assert.Equal(1, array.Length); Assert.Equal(timeSpan, array.GetTimeSpan(0)); + + IReadOnlyList asList = array; + Assert.Equal(1, asList.Count); + Assert.Equal(timeSpan, asList[0]); } }