diff --git a/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs b/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs index cb7164aa146c6..3fb77d2c3bd0f 100644 --- a/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs +++ b/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs @@ -78,6 +78,10 @@ internal static IArrowArrayBuilder> return new ListViewArray.Builder(dataType as ListViewType); case ArrowTypeId.FixedSizeList: return new FixedSizeListArray.Builder(dataType as FixedSizeListType); + case ArrowTypeId.Decimal32: + return new Decimal32Array.Builder(dataType as Decimal32Type); + case ArrowTypeId.Decimal64: + return new Decimal64Array.Builder(dataType as Decimal64Type); case ArrowTypeId.Decimal128: return new Decimal128Array.Builder(dataType as Decimal128Type); case ArrowTypeId.Decimal256: diff --git a/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs b/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs index bd06c3a1b8b14..38092a7a23478 100644 --- a/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs +++ b/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs @@ -87,6 +87,10 @@ public static IArrowArray BuildArray(ArrayData data) return new Time64Array(data); case ArrowTypeId.Duration: return new DurationArray(data); + case ArrowTypeId.Decimal32: + return new Decimal32Array(data); + case ArrowTypeId.Decimal64: + return new Decimal64Array(data); case ArrowTypeId.Decimal128: return new Decimal128Array(data); case ArrowTypeId.Decimal256: diff --git a/csharp/src/Apache.Arrow/Arrays/Decimal32Array.cs b/csharp/src/Apache.Arrow/Arrays/Decimal32Array.cs new file mode 100644 index 0000000000000..21dee1108a993 --- /dev/null +++ b/csharp/src/Apache.Arrow/Arrays/Decimal32Array.cs @@ -0,0 +1,182 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using Apache.Arrow.Arrays; +using Apache.Arrow.Types; + +namespace Apache.Arrow +{ + public class Decimal32Array : FixedSizeBinaryArray, IReadOnlyList + { + public class Builder : BuilderBase + { + public Builder(Decimal32Type type) : base(type, 4) + { + DataType = type; + } + + protected new Decimal32Type DataType { get; } + + protected override Decimal32Array Build(ArrayData data) + { + return new Decimal32Array(data); + } + + public Builder Append(decimal value) + { + Span bytes = stackalloc byte[DataType.ByteWidth]; + DecimalUtility.GetBytes(value, DataType.Precision, DataType.Scale, DataType.ByteWidth, bytes); + + return Append(bytes); + } + + public Builder AppendRange(IEnumerable values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + foreach (decimal d in values) + { + Append(d); + } + + return Instance; + } + + public Builder Append(string value) + { + if (value == null) + { + AppendNull(); + } + else + { + Span bytes = stackalloc byte[DataType.ByteWidth]; + DecimalUtility.GetBytes(value, DataType.Precision, DataType.Scale, ByteWidth, bytes); + Append(bytes); + } + + return Instance; + } + + public Builder AppendRange(IEnumerable values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + foreach (string s in values) + { + Append(s); + } + + return Instance; + } + + public Builder Set(int index, decimal value) + { + Span bytes = stackalloc byte[DataType.ByteWidth]; + DecimalUtility.GetBytes(value, DataType.Precision, DataType.Scale, DataType.ByteWidth, bytes); + + return Set(index, bytes); + } + } + + public Decimal32Array(ArrayData data) + : base(ArrowTypeId.Decimal32, data) + { + data.EnsureDataType(ArrowTypeId.Decimal32); + data.EnsureBufferCount(2); + Debug.Assert(Data.DataType is Decimal32Type); + } + public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor); + + public int Scale => ((Decimal32Type)Data.DataType).Scale; + public int Precision => ((Decimal32Type)Data.DataType).Precision; + public int ByteWidth => ((Decimal32Type)Data.DataType).ByteWidth; + + public decimal? GetValue(int index) + { + if (IsNull(index)) + { + return null; + } + return DecimalUtility.GetDecimal(ValueBuffer, Offset + index, Scale, ByteWidth); + } + + public IList ToList(bool includeNulls = false) + { + var list = new List(Length); + + for (int i = 0; i < Length; i++) + { + decimal? value = GetValue(i); + + if (value.HasValue) + { + list.Add(value.Value); + } + else + { + if (includeNulls) + { + list.Add(null); + } + } + } + + return list; + } + + public string GetString(int index) + { + if (IsNull(index)) + { + return null; + } + return DecimalUtility.GetString(ValueBuffer, Offset + index, Precision, Scale, ByteWidth); + } + + public decimal? GetDecimal(int index) + { + if (IsNull(index)) + { + return null; + } + + return DecimalUtility.GetDecimal(ValueBuffer, Offset + index, Scale, ByteWidth); + } + + int IReadOnlyCollection.Count => Length; + decimal? IReadOnlyList.this[int index] => GetDecimal(index); + + IEnumerator IEnumerable.GetEnumerator() + { + for (int index = 0; index < Length; index++) + { + yield return GetDecimal(index); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + } +} diff --git a/csharp/src/Apache.Arrow/Arrays/Decimal64Array.cs b/csharp/src/Apache.Arrow/Arrays/Decimal64Array.cs new file mode 100644 index 0000000000000..b50baef7d9bb7 --- /dev/null +++ b/csharp/src/Apache.Arrow/Arrays/Decimal64Array.cs @@ -0,0 +1,182 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using Apache.Arrow.Arrays; +using Apache.Arrow.Types; + +namespace Apache.Arrow +{ + public class Decimal64Array : FixedSizeBinaryArray, IReadOnlyList + { + public class Builder : BuilderBase + { + public Builder(Decimal64Type type) : base(type, 8) + { + DataType = type; + } + + protected new Decimal64Type DataType { get; } + + protected override Decimal64Array Build(ArrayData data) + { + return new Decimal64Array(data); + } + + public Builder Append(decimal value) + { + Span bytes = stackalloc byte[DataType.ByteWidth]; + DecimalUtility.GetBytes(value, DataType.Precision, DataType.Scale, DataType.ByteWidth, bytes); + + return Append(bytes); + } + + public Builder AppendRange(IEnumerable values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + foreach (decimal d in values) + { + Append(d); + } + + return Instance; + } + + public Builder Append(string value) + { + if (value == null) + { + AppendNull(); + } + else + { + Span bytes = stackalloc byte[DataType.ByteWidth]; + DecimalUtility.GetBytes(value, DataType.Precision, DataType.Scale, ByteWidth, bytes); + Append(bytes); + } + + return Instance; + } + + public Builder AppendRange(IEnumerable values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + foreach (string s in values) + { + Append(s); + } + + return Instance; + } + + public Builder Set(int index, decimal value) + { + Span bytes = stackalloc byte[DataType.ByteWidth]; + DecimalUtility.GetBytes(value, DataType.Precision, DataType.Scale, DataType.ByteWidth, bytes); + + return Set(index, bytes); + } + } + + public Decimal64Array(ArrayData data) + : base(ArrowTypeId.Decimal64, data) + { + data.EnsureDataType(ArrowTypeId.Decimal64); + data.EnsureBufferCount(2); + Debug.Assert(Data.DataType is Decimal64Type); + } + public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor); + + public int Scale => ((Decimal64Type)Data.DataType).Scale; + public int Precision => ((Decimal64Type)Data.DataType).Precision; + public int ByteWidth => ((Decimal64Type)Data.DataType).ByteWidth; + + public decimal? GetValue(int index) + { + if (IsNull(index)) + { + return null; + } + return DecimalUtility.GetDecimal(ValueBuffer, Offset + index, Scale, ByteWidth); + } + + public IList ToList(bool includeNulls = false) + { + var list = new List(Length); + + for (int i = 0; i < Length; i++) + { + decimal? value = GetValue(i); + + if (value.HasValue) + { + list.Add(value.Value); + } + else + { + if (includeNulls) + { + list.Add(null); + } + } + } + + return list; + } + + public string GetString(int index) + { + if (IsNull(index)) + { + return null; + } + return DecimalUtility.GetString(ValueBuffer, Offset + index, Precision, Scale, ByteWidth); + } + + public decimal? GetDecimal(int index) + { + if (IsNull(index)) + { + return null; + } + + return DecimalUtility.GetDecimal(ValueBuffer, Offset + index, Scale, ByteWidth); + } + + int IReadOnlyCollection.Count => Length; + decimal? IReadOnlyList.this[int index] => GetDecimal(index); + + IEnumerator IEnumerable.GetEnumerator() + { + for (int index = 0; index < Length; index++) + { + yield return GetDecimal(index); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + } +} diff --git a/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs b/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs index 92d48a2d70880..1cf6dc78a789c 100644 --- a/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs +++ b/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs @@ -161,6 +161,10 @@ private static string GetFormat(IArrowType datatype) case FloatType _: return "f"; case DoubleType _: return "g"; // Decimal + case Decimal32Type decimalType: + return $"d:{decimalType.Precision},{decimalType.Scale},32"; + case Decimal64Type decimalType: + return $"d:{decimalType.Precision},{decimalType.Scale},64"; case Decimal128Type decimalType: return $"d:{decimalType.Precision},{decimalType.Scale}"; case Decimal256Type decimalType: diff --git a/csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs b/csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs index 94177184dea00..a772b8f4a5e46 100644 --- a/csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs +++ b/csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs @@ -224,19 +224,17 @@ public ArrowType GetAsType() // Decimals if (format.StartsWith("d:")) { - bool is256 = format.EndsWith(",256"); - string parameters_part = format.Remove(0, 2); - if (is256) parameters_part.Substring(0, parameters_part.Length - 5); - string[] parameters = parameters_part.Split(','); + string[] parameters = format.Substring(2).Split(','); int precision = Int32.Parse(parameters[0]); int scale = Int32.Parse(parameters[1]); - if (is256) + int bitWidth = parameters.Length == 2 ? 128 : Int32.Parse(parameters[2]); + switch (bitWidth) { - return new Decimal256Type(precision, scale); - } - else - { - return new Decimal128Type(precision, scale); + case 32: return new Decimal32Type(precision, scale); + case 64: return new Decimal64Type(precision, scale); + case 128: return new Decimal128Type(precision, scale); + case 256: return new Decimal256Type(precision, scale); + default: throw new InvalidDataException($"Unexpected bit width {bitWidth}"); } } diff --git a/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs b/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs index eaa8471fa7bd3..ff0b64c09eeb6 100644 --- a/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs +++ b/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs @@ -68,6 +68,8 @@ private class ArrowRecordBatchFlatBufferBuilder : IArrowArrayVisitor, IArrowArrayVisitor, IArrowArrayVisitor, + IArrowArrayVisitor, + IArrowArrayVisitor, IArrowArrayVisitor, IArrowArrayVisitor, IArrowArrayVisitor, @@ -292,6 +294,10 @@ public void Visit(FixedSizeBinaryArray array) _buffers.Add(CreateSlicedBuffer(array.ValueBuffer, itemSize, array.Offset, array.Length)); } + public void Visit(Decimal32Array array) => Visit(array as FixedSizeBinaryArray); + + public void Visit(Decimal64Array array) => Visit(array as FixedSizeBinaryArray); + public void Visit(Decimal128Array array) => Visit(array as FixedSizeBinaryArray); public void Visit(Decimal256Array array) => Visit(array as FixedSizeBinaryArray); diff --git a/csharp/src/Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs b/csharp/src/Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs index adc229a051227..d1fb921686dc8 100644 --- a/csharp/src/Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs +++ b/csharp/src/Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs @@ -74,6 +74,8 @@ class TypeVisitor : IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, + IArrowTypeVisitor, + IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, @@ -276,6 +278,20 @@ public void Visit(StructType type) Result = FieldType.Build(Flatbuf.Type.Struct_, Flatbuf.Struct_.EndStruct_(Builder)); } + public void Visit(Decimal32Type type) + { + Result = FieldType.Build( + Flatbuf.Type.Decimal, + Flatbuf.Decimal.CreateDecimal(Builder, type.Precision, type.Scale, type.BitWidth)); + } + + public void Visit(Decimal64Type type) + { + Result = FieldType.Build( + Flatbuf.Type.Decimal, + Flatbuf.Decimal.CreateDecimal(Builder, type.Precision, type.Scale, type.BitWidth)); + } + public void Visit(Decimal128Type type) { Result = FieldType.Build( diff --git a/csharp/src/Apache.Arrow/Ipc/MessageSerializer.cs b/csharp/src/Apache.Arrow/Ipc/MessageSerializer.cs index 8e15632c517e1..7c7f7a38daddd 100644 --- a/csharp/src/Apache.Arrow/Ipc/MessageSerializer.cs +++ b/csharp/src/Apache.Arrow/Ipc/MessageSerializer.cs @@ -142,6 +142,10 @@ private static Types.IArrowType GetFieldArrowType(Flatbuf.Field field, Field[] c Flatbuf.Decimal decMeta = field.Type().Value; switch (decMeta.BitWidth) { + case 32: + return new Types.Decimal32Type(decMeta.Precision, decMeta.Scale); + case 64: + return new Types.Decimal64Type(decMeta.Precision, decMeta.Scale); case 128: return new Types.Decimal128Type(decMeta.Precision, decMeta.Scale); case 256: diff --git a/csharp/src/Apache.Arrow/RecordBatch.Builder.cs b/csharp/src/Apache.Arrow/RecordBatch.Builder.cs index 8e0d17ae06f49..e9a84a48d8e3c 100644 --- a/csharp/src/Apache.Arrow/RecordBatch.Builder.cs +++ b/csharp/src/Apache.Arrow/RecordBatch.Builder.cs @@ -47,6 +47,12 @@ internal ArrayBuilder(MemoryAllocator allocator) #endif public FloatArray Float(Action action) => Build(new FloatArray.Builder(), action); public DoubleArray Double(Action action) => Build(new DoubleArray.Builder(), action); + public Decimal32Array Decimal32(Decimal32Type type, Action action) => + Build( + new Decimal32Array.Builder(type), action); + public Decimal64Array Decimal64(Decimal64Type type, Action action) => + Build( + new Decimal64Array.Builder(type), action); public Decimal128Array Decimal128(Decimal128Type type, Action action) => Build( new Decimal128Array.Builder(type), action); diff --git a/csharp/src/Apache.Arrow/Types/Decimal32Type.cs b/csharp/src/Apache.Arrow/Types/Decimal32Type.cs new file mode 100644 index 0000000000000..c494b3b013fac --- /dev/null +++ b/csharp/src/Apache.Arrow/Types/Decimal32Type.cs @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Apache.Arrow.Types +{ + public sealed class Decimal32Type : FixedSizeBinaryType + { + public override ArrowTypeId TypeId => ArrowTypeId.Decimal32; + public override string Name => "decimal32"; + + public int Precision { get; } + public int Scale { get; } + + public Decimal32Type(int precision, int scale) + : base(4) + { + Precision = precision; + Scale = scale; + } + + public override void Accept(IArrowTypeVisitor visitor) => Accept(this, visitor); + } +} diff --git a/csharp/src/Apache.Arrow/Types/Decimal64Type.cs b/csharp/src/Apache.Arrow/Types/Decimal64Type.cs new file mode 100644 index 0000000000000..30d544b32659d --- /dev/null +++ b/csharp/src/Apache.Arrow/Types/Decimal64Type.cs @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Apache.Arrow.Types +{ + public sealed class Decimal64Type : FixedSizeBinaryType + { + public override ArrowTypeId TypeId => ArrowTypeId.Decimal64; + public override string Name => "decimal64"; + + public int Precision { get; } + public int Scale { get; } + + public Decimal64Type(int precision, int scale) + : base(8) + { + Precision = precision; + Scale = scale; + } + + public override void Accept(IArrowTypeVisitor visitor) => Accept(this, visitor); + } +} diff --git a/csharp/src/Apache.Arrow/Types/IArrowType.cs b/csharp/src/Apache.Arrow/Types/IArrowType.cs index 7a3159a1bbccd..6020fd41e38bb 100644 --- a/csharp/src/Apache.Arrow/Types/IArrowType.cs +++ b/csharp/src/Apache.Arrow/Types/IArrowType.cs @@ -56,6 +56,8 @@ public enum ArrowTypeId LargeList, LargeBinary, LargeString, + Decimal32, + Decimal64, } public interface IArrowType diff --git a/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs b/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs index c9e44b8d2f491..a75c4193da831 100644 --- a/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs +++ b/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs @@ -231,7 +231,11 @@ private static IArrowType ToDecimalArrowType(JsonArrowType type) return type.BitWidth switch { 256 => new Decimal256Type(type.DecimalPrecision, type.Scale), - _ => new Decimal128Type(type.DecimalPrecision, type.Scale), + 128 => new Decimal128Type(type.DecimalPrecision, type.Scale), + 64 => new Decimal64Type(type.DecimalPrecision, type.Scale), + 32 => new Decimal32Type(type.DecimalPrecision, type.Scale), + 0 => new Decimal128Type(type.DecimalPrecision, type.Scale), + _ => throw new NotSupportedException($"Decimal type not supported. BitWidth: {type.BitWidth}"), }; } @@ -458,6 +462,8 @@ private class ArrayCreator : IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, + IArrowTypeVisitor, + IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, @@ -553,6 +559,16 @@ public void Visit(IntervalType type) } } + public void Visit(Decimal32Type type) + { + Array = new Decimal32Array(GetDecimalArrayData(type)); + } + + public void Visit(Decimal64Type type) + { + Array = new Decimal64Array(GetDecimalArrayData(type)); + } + public void Visit(Decimal128Type type) { Array = new Decimal128Array(GetDecimalArrayData(type)); diff --git a/csharp/test/Apache.Arrow.Tests/ArrowArrayConcatenatorTests.cs b/csharp/test/Apache.Arrow.Tests/ArrowArrayConcatenatorTests.cs index 2437d3d94c446..a45a50c4a278b 100644 --- a/csharp/test/Apache.Arrow.Tests/ArrowArrayConcatenatorTests.cs +++ b/csharp/test/Apache.Arrow.Tests/ArrowArrayConcatenatorTests.cs @@ -80,8 +80,10 @@ private static IEnumerable, IArrowArray>> GenerateTestDa Date32Type.Default, Date64Type.Default, TimestampType.Default, + new Decimal32Type(7, 3), + new Decimal64Type(14, 4), new Decimal128Type(14, 10), - new Decimal256Type(14,10), + new Decimal256Type(14, 10), new ListType(Int64Type.Default), new ListViewType(Int64Type.Default), new StructType(new List{ @@ -138,6 +140,8 @@ private class TestDataGenerator : IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, + IArrowTypeVisitor, + IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, @@ -207,8 +211,9 @@ public TestDataGenerator(bool slicedArrays) public void Visit(Date32Type type) => GenerateTestData(type, x => DateTime.MinValue.AddDays(x)); public void Visit(Date64Type type) => GenerateTestData(type, x => DateTime.MinValue.AddDays(x)); + public void Visit(Decimal32Type type) => GenerateTestData(type, (builder, x) => builder.Append(x)); + public void Visit(Decimal64Type type) => GenerateTestData(type, (builder, x) => builder.Append(x)); public void Visit(Decimal128Type type) => GenerateTestData(type, (builder, x) => builder.Append(x)); - public void Visit(Decimal256Type type) => GenerateTestData(type, (builder, x) => builder.Append(x)); public void Visit(TimestampType type) diff --git a/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs b/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs index 35b2c4e7f2ad3..5977c3288a4ec 100644 --- a/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs +++ b/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs @@ -106,6 +106,8 @@ private class ArrayComparer : IArrowArrayVisitor, IArrowArrayVisitor, IArrowArrayVisitor, + IArrowArrayVisitor, + IArrowArrayVisitor, IArrowArrayVisitor, IArrowArrayVisitor, IArrowArrayVisitor, @@ -150,6 +152,8 @@ public ArrayComparer(IArrowArray expectedArray, bool strictCompare) public void Visit(LargeListArray array) => CompareArrays(array); public void Visit(FixedSizeListArray array) => CompareArrays(array); public void Visit(FixedSizeBinaryArray array) => CompareArrays(array); + public void Visit(Decimal32Array array) => CompareArrays(array); + public void Visit(Decimal64Array array) => CompareArrays(array); public void Visit(Decimal128Array array) => CompareArrays(array); public void Visit(Decimal256Array array) => CompareArrays(array); public void Visit(StringArray array) => CompareBinaryArrays(array); diff --git a/csharp/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs b/csharp/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs index 638cbfb272de4..0ef8eba26129e 100644 --- a/csharp/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs +++ b/csharp/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs @@ -754,7 +754,7 @@ public unsafe void ExportBatch() public unsafe void RoundTripTestBatch() { // TODO: Enable these once this the version of pyarrow referenced during testing supports them - HashSet unsupported = new HashSet { ArrowTypeId.ListView, ArrowTypeId.BinaryView, ArrowTypeId.StringView }; + HashSet unsupported = new HashSet { ArrowTypeId.ListView, ArrowTypeId.BinaryView, ArrowTypeId.StringView, ArrowTypeId.Decimal32, ArrowTypeId.Decimal64 }; RecordBatch batch1 = TestData.CreateSampleRecordBatch(4, excludedTypes: unsupported); RecordBatch batch2 = batch1.Clone(); @@ -796,7 +796,7 @@ public unsafe void RoundTripTestBatch() public unsafe void RoundTripTestSlicedBatch() { // TODO: Enable these once this the version of pyarrow referenced during testing supports them - HashSet unsupported = new HashSet { ArrowTypeId.ListView, ArrowTypeId.BinaryView, ArrowTypeId.StringView }; + HashSet unsupported = new HashSet { ArrowTypeId.ListView, ArrowTypeId.BinaryView, ArrowTypeId.StringView, ArrowTypeId.Decimal32, ArrowTypeId.Decimal64 }; RecordBatch batch1 = TestData.CreateSampleRecordBatch(4, excludedTypes: unsupported); RecordBatch batch1slice = batch1.Slice(1, 2); RecordBatch batch2 = batch1slice.Clone(); diff --git a/csharp/test/Apache.Arrow.Tests/Decimal32ArrayTests.cs b/csharp/test/Apache.Arrow.Tests/Decimal32ArrayTests.cs new file mode 100644 index 0000000000000..f7b7d409a08c7 --- /dev/null +++ b/csharp/test/Apache.Arrow.Tests/Decimal32ArrayTests.cs @@ -0,0 +1,337 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Linq; +using Apache.Arrow.Types; +using Xunit; + +namespace Apache.Arrow.Tests +{ + public class Decimal32ArrayTests + { + public class Builder + { + public class AppendNull + { + [Fact] + public void AppendThenGetGivesNull() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)); + + // Act + + builder = builder.AppendNull(); + builder = builder.AppendNull(); + builder = builder.AppendNull(); + // Assert + var array = builder.Build(); + + Assert.Equal(3, array.Length); + Assert.Equal(array.Data.Buffers[1].Length, array.ByteWidth * 3); + Assert.Null(array.GetValue(0)); + Assert.Null(array.GetValue(1)); + Assert.Null(array.GetValue(2)); + } + } + + public class Append + { + [Theory] + [InlineData(200)] + public void AppendDecimal(int count) + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)); + + // Act + decimal?[] testData = new decimal?[count]; + for (int i = 0; i < count; i++) + { + if (i == count - 2) + { + builder.AppendNull(); + testData[i] = null; + continue; + } + decimal rnd = i * (decimal)Math.Round(new Random().NextDouble(), 2); + testData[i] = rnd; + builder.Append(rnd); + } + + // Assert + var array = builder.Build(); + Assert.Equal(count, array.Length); + for (int i = 0; i < count; i++) + { + Assert.Equal(testData[i], array.GetValue(i)); + } + } + + [Fact] + public void AppendLargeDecimal() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)); + decimal large = 9999.999M; + // Act + builder.Append(large); + builder.Append(-large); + + // Assert + var array = builder.Build(); + Assert.Equal(large, array.GetValue(0)); + Assert.Equal(-large, array.GetValue(1)); + } + + [Fact] + public void AppendFractionalDecimal() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(9, 9)); + decimal fraction = 0.999999999M; + // Act + builder.Append(fraction); + builder.Append(-fraction); + + // Assert + var array = builder.Build(); + Assert.Equal(fraction, array.GetValue(0)); + Assert.Equal(-fraction, array.GetValue(1)); + } + + [Fact] + public void AppendRangeDecimal() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)); + var range = new decimal[] { 2.123M, 1.598M, -0.001M, 7987.123M }; + + // Act + builder.AppendRange(range); + builder.AppendNull(); + + // Assert + var array = builder.Build(); + for (int i = 0; i < range.Length; i++) + { + Assert.Equal(range[i], array.GetValue(i)); + } + + Assert.Null(array.GetValue(range.Length)); + } + + [Fact] + public void AppendClearAppendDecimal() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)); + + // Act + builder.Append(1); + builder.Clear(); + builder.Append(10); + + // Assert + var array = builder.Build(); + Assert.Equal(10, array.GetValue(0)); + } + + [Fact] + public void AppendInvalidPrecisionAndScaleDecimal() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(2, 1)); + + // Assert + Assert.Throws(() => builder.Append(100)); + Assert.Throws(() => builder.Append(0.01M)); + builder.Append(-9.9M); + builder.Append(0); + builder.Append(9.9M); + } + } + + public class Set + { + [Fact] + public void SetDecimal() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)) + .Resize(1); + + // Act + builder.Set(0, 50.123M); + builder.Set(0, 1.01M); + + // Assert + var array = builder.Build(); + Assert.Equal(1.01M, array.GetValue(0)); + } + + [Fact] + public void SetNull() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)) + .Resize(1); + + // Act + builder.Set(0, 50.123M); + builder.SetNull(0); + + // Assert + var array = builder.Build(); + Assert.Null(array.GetValue(0)); + } + } + + public class Swap + { + [Fact] + public void SetDecimal() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)); + + // Act + builder.Append(123.45M); + builder.Append(678.9M); + builder.Swap(0, 1); + + // Assert + var array = builder.Build(); + Assert.Equal(678.9M, array.GetValue(0)); + Assert.Equal(123.45M, array.GetValue(1)); + } + + [Fact] + public void SwapNull() + { + // Arrange + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)); + + // Act + builder.Append(123.456M); + builder.AppendNull(); + builder.Swap(0, 1); + + // Assert + var array = builder.Build(); + Assert.Null(array.GetValue(0)); + Assert.Equal(123.456M, array.GetValue(1)); + } + } + + public class Strings + { + [Theory] + [InlineData(200)] + public void AppendString(int count) + { + // Arrange + const int precision = 4; + var builder = new Decimal32Array.Builder(new Decimal32Type(9, precision)); + + // Act + string[] testData = new string[count]; + for (int i = 0; i < count; i++) + { + if (i == count - 2) + { + builder.AppendNull(); + testData[i] = null; + continue; + } + decimal rnd = i * (decimal)Math.Round(new Random().NextDouble(), precision - 2); + builder.Append(rnd); + testData[i] = decimal.Round(rnd, precision - 1).ToString(); + } + + // Assert + var array = builder.Build(); + Assert.Equal(count, array.Length); + for (int i = 0; i < count; i++) + { + if (testData[i] == null) + { + Assert.Null(array.GetString(i)); + Assert.Null(array.GetDecimal(i)); + } + else + { + Assert.Equal(NormalizeNumber(testData[i]), NormalizeNumber(array.GetString(i))); + Assert.Equal(Decimal.Parse(testData[i]), array.GetDecimal(i)); + } + } + } + + static string NormalizeNumber(string number) + { + if (number.IndexOf('.') > 0) + { + number = number.TrimEnd('0'); + number = number.TrimEnd('.'); + } + return number; + } + } + } + + [Fact] + public void SliceDecimal32Array() + { + // Arrange + const int originalLength = 50; + const int offset = 3; + const int sliceLength = 32; + + var builder = new Decimal32Array.Builder(new Decimal32Type(7, 3)); + var random = new Random(); + + for (int i = 0; i < originalLength; i++) + { + if (random.NextDouble() < 0.2) + { + builder.AppendNull(); + } + else + { + builder.Append(i * (decimal)Math.Round(random.NextDouble(), 2)); + } + } + + var array = builder.Build(); + + // Act + var slice = (Decimal32Array)array.Slice(offset, sliceLength); + + // Assert + Assert.NotNull(slice); + Assert.Equal(sliceLength, slice.Length); + for (int i = 0; i < sliceLength; ++i) + { + Assert.Equal(array.GetValue(offset + i), slice.GetValue(i)); + Assert.Equal(array.GetString(offset + i), slice.GetString(i)); + } + + Assert.Equal( + array.ToList(includeNulls: true).Skip(offset).Take(sliceLength).ToList(), + slice.ToList(includeNulls: true)); + } + } +} diff --git a/csharp/test/Apache.Arrow.Tests/Decimal64ArrayTests.cs b/csharp/test/Apache.Arrow.Tests/Decimal64ArrayTests.cs new file mode 100644 index 0000000000000..b37a44a8598fc --- /dev/null +++ b/csharp/test/Apache.Arrow.Tests/Decimal64ArrayTests.cs @@ -0,0 +1,337 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Linq; +using Apache.Arrow.Types; +using Xunit; + +namespace Apache.Arrow.Tests +{ + public class Decimal64ArrayTests + { + public class Builder + { + public class AppendNull + { + [Fact] + public void AppendThenGetGivesNull() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(7, 3)); + + // Act + + builder = builder.AppendNull(); + builder = builder.AppendNull(); + builder = builder.AppendNull(); + // Assert + var array = builder.Build(); + + Assert.Equal(3, array.Length); + Assert.Equal(array.Data.Buffers[1].Length, array.ByteWidth * 3); + Assert.Null(array.GetValue(0)); + Assert.Null(array.GetValue(1)); + Assert.Null(array.GetValue(2)); + } + } + + public class Append + { + [Theory] + [InlineData(200)] + public void AppendDecimal(int count) + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(7, 3)); + + // Act + decimal?[] testData = new decimal?[count]; + for (int i = 0; i < count; i++) + { + if (i == count - 2) + { + builder.AppendNull(); + testData[i] = null; + continue; + } + decimal rnd = i * (decimal)Math.Round(new Random().NextDouble(), 2); + testData[i] = rnd; + builder.Append(rnd); + } + + // Assert + var array = builder.Build(); + Assert.Equal(count, array.Length); + for (int i = 0; i < count; i++) + { + Assert.Equal(testData[i], array.GetValue(i)); + } + } + + [Fact] + public void AppendLargeDecimal() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(19, 6)); + decimal large = 9199999999999.999999M; + // Act + builder.Append(large); + builder.Append(-large); + + // Assert + var array = builder.Build(); + Assert.Equal(large, array.GetValue(0)); + Assert.Equal(-large, array.GetValue(1)); + } + + [Fact] + public void AppendFractionalDecimal() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(9, 9)); + decimal fraction = 0.999999999M; + // Act + builder.Append(fraction); + builder.Append(-fraction); + + // Assert + var array = builder.Build(); + Assert.Equal(fraction, array.GetValue(0)); + Assert.Equal(-fraction, array.GetValue(1)); + } + + [Fact] + public void AppendRangeDecimal() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(17, 4)); + var range = new decimal[] { 2.123M, 12345678.598M, -0.001M, 7987.1237M }; + + // Act + builder.AppendRange(range); + builder.AppendNull(); + + // Assert + var array = builder.Build(); + for (int i = 0; i < range.Length; i++) + { + Assert.Equal(range[i], array.GetValue(i)); + } + + Assert.Null(array.GetValue(range.Length)); + } + + [Fact] + public void AppendClearAppendDecimal() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(7, 3)); + + // Act + builder.Append(1); + builder.Clear(); + builder.Append(10); + + // Assert + var array = builder.Build(); + Assert.Equal(10, array.GetValue(0)); + } + + [Fact] + public void AppendInvalidPrecisionAndScaleDecimal() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(2, 1)); + + // Assert + Assert.Throws(() => builder.Append(100)); + Assert.Throws(() => builder.Append(0.01M)); + builder.Append(-9.9M); + builder.Append(0); + builder.Append(9.9M); + } + } + + public class Set + { + [Fact] + public void SetDecimal() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(7, 3)) + .Resize(1); + + // Act + builder.Set(0, 50.123M); + builder.Set(0, 1.01M); + + // Assert + var array = builder.Build(); + Assert.Equal(1.01M, array.GetValue(0)); + } + + [Fact] + public void SetNull() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(7, 3)) + .Resize(1); + + // Act + builder.Set(0, 50.123M); + builder.SetNull(0); + + // Assert + var array = builder.Build(); + Assert.Null(array.GetValue(0)); + } + } + + public class Swap + { + [Fact] + public void SetDecimal() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(7, 3)); + + // Act + builder.Append(123.45M); + builder.Append(678.9M); + builder.Swap(0, 1); + + // Assert + var array = builder.Build(); + Assert.Equal(678.9M, array.GetValue(0)); + Assert.Equal(123.45M, array.GetValue(1)); + } + + [Fact] + public void SwapNull() + { + // Arrange + var builder = new Decimal64Array.Builder(new Decimal64Type(7, 3)); + + // Act + builder.Append(123.456M); + builder.AppendNull(); + builder.Swap(0, 1); + + // Assert + var array = builder.Build(); + Assert.Null(array.GetValue(0)); + Assert.Equal(123.456M, array.GetValue(1)); + } + } + + public class Strings + { + [Theory] + [InlineData(200)] + public void AppendString(int count) + { + // Arrange + const int precision = 4; + var builder = new Decimal64Array.Builder(new Decimal64Type(9, precision)); + + // Act + string[] testData = new string[count]; + for (int i = 0; i < count; i++) + { + if (i == count - 2) + { + builder.AppendNull(); + testData[i] = null; + continue; + } + decimal rnd = i * (decimal)Math.Round(new Random().NextDouble(), precision - 2); + builder.Append(rnd); + testData[i] = decimal.Round(rnd, precision - 1).ToString(); + } + + // Assert + var array = builder.Build(); + Assert.Equal(count, array.Length); + for (int i = 0; i < count; i++) + { + if (testData[i] == null) + { + Assert.Null(array.GetString(i)); + Assert.Null(array.GetDecimal(i)); + } + else + { + Assert.Equal(NormalizeNumber(testData[i]), NormalizeNumber(array.GetString(i))); + Assert.Equal(Decimal.Parse(testData[i]), array.GetDecimal(i)); + } + } + } + + static string NormalizeNumber(string number) + { + if (number.IndexOf('.') > 0) + { + number = number.TrimEnd('0'); + number = number.TrimEnd('.'); + } + return number; + } + } + } + + [Fact] + public void SliceDecimal64Array() + { + // Arrange + const int originalLength = 50; + const int offset = 3; + const int sliceLength = 32; + + var builder = new Decimal64Array.Builder(new Decimal64Type(7, 3)); + var random = new Random(); + + for (int i = 0; i < originalLength; i++) + { + if (random.NextDouble() < 0.2) + { + builder.AppendNull(); + } + else + { + builder.Append(i * (decimal)Math.Round(random.NextDouble(), 2)); + } + } + + var array = builder.Build(); + + // Act + var slice = (Decimal64Array)array.Slice(offset, sliceLength); + + // Assert + Assert.NotNull(slice); + Assert.Equal(sliceLength, slice.Length); + for (int i = 0; i < sliceLength; ++i) + { + Assert.Equal(array.GetValue(offset + i), slice.GetValue(i)); + Assert.Equal(array.GetString(offset + i), slice.GetString(i)); + } + + Assert.Equal( + array.ToList(includeNulls: true).Skip(offset).Take(sliceLength).ToList(), + slice.ToList(includeNulls: true)); + } + } +} diff --git a/csharp/test/Apache.Arrow.Tests/TableTests.cs b/csharp/test/Apache.Arrow.Tests/TableTests.cs index 35fbe7cba68f1..a12e7b9ccdfa2 100644 --- a/csharp/test/Apache.Arrow.Tests/TableTests.cs +++ b/csharp/test/Apache.Arrow.Tests/TableTests.cs @@ -63,9 +63,9 @@ public void TestTableFromRecordBatches() Table table1 = Table.TableFromRecordBatches(recordBatch1.Schema, recordBatches); Assert.Equal(20, table1.RowCount); #if NET5_0_OR_GREATER - Assert.Equal(38, table1.ColumnCount); + Assert.Equal(40, table1.ColumnCount); #else - Assert.Equal(37, table1.ColumnCount); + Assert.Equal(39, table1.ColumnCount); #endif Assert.Equal("ChunkedArray: Length=20, DataType=list", table1.Column(0).Data.ToString()); diff --git a/csharp/test/Apache.Arrow.Tests/TestData.cs b/csharp/test/Apache.Arrow.Tests/TestData.cs index 36969766aeae0..08ada33d7e822 100644 --- a/csharp/test/Apache.Arrow.Tests/TestData.cs +++ b/csharp/test/Apache.Arrow.Tests/TestData.cs @@ -72,6 +72,8 @@ void AddField(Field field) AddField(CreateField(StringType.Default, i)); AddField(CreateField(StringViewType.Default, i)); AddField(CreateField(new StructType(new List { CreateField(StringType.Default, i), CreateField(Int32Type.Default, i) }), i)); + AddField(CreateField(new Decimal32Type(9, 2), i)); + AddField(CreateField(new Decimal64Type(10, 4), i)); AddField(CreateField(new Decimal128Type(10, 6), i)); AddField(CreateField(new Decimal256Type(16, 8), i)); AddField(CreateField(new MapType(StringType.Default, Int32Type.Default), i)); @@ -154,6 +156,8 @@ private class ArrayCreator : IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, + IArrowTypeVisitor, + IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, IArrowTypeVisitor, @@ -190,6 +194,31 @@ public ArrayCreator(int length) public void Visit(HalfFloatType type) => GenerateArray(new HalfFloatArray.Builder(), x => ((Half)x / (Half)Length)); #endif public void Visit(DoubleType type) => GenerateArray(new DoubleArray.Builder(), x => ((double)x / Length)); + + public void Visit(Decimal32Type type) + { + var builder = new Decimal32Array.Builder(type).Reserve(Length); + + for (var i = 0; i < Length; i++) + { + builder.Append((decimal)i / Length); + } + + Array = builder.Build(); + } + + public void Visit(Decimal64Type type) + { + var builder = new Decimal64Array.Builder(type).Reserve(Length); + + for (var i = 0; i < Length; i++) + { + builder.Append((decimal)i / Length); + } + + Array = builder.Build(); + } + public void Visit(Decimal128Type type) { var builder = new Decimal128Array.Builder(type).Reserve(Length); diff --git a/dev/archery/archery/integration/datagen.py b/dev/archery/archery/integration/datagen.py index d7f88083f4a4e..9f86d172ddbcf 100644 --- a/dev/archery/archery/integration/datagen.py +++ b/dev/archery/archery/integration/datagen.py @@ -1906,7 +1906,6 @@ def _temp_path(): .skip_tester('JS'), generate_decimal32_case() - .skip_tester('C#') .skip_tester('Java') .skip_tester('JS') .skip_tester('nanoarrow') @@ -1914,7 +1913,6 @@ def _temp_path(): .skip_tester('Go'), generate_decimal64_case() - .skip_tester('C#') .skip_tester('Java') .skip_tester('JS') .skip_tester('nanoarrow') diff --git a/docs/source/status.rst b/docs/source/status.rst index 7deb3f512c68b..c838604fcaef6 100644 --- a/docs/source/status.rst +++ b/docs/source/status.rst @@ -44,9 +44,9 @@ Data Types +-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+ | Float32/64 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+ -| Decimal32 | ✓ | | ✓ | | | | | | | +| Decimal32 | ✓ | | ✓ | | ✓ | | | | | +-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+ -| Decimal64 | ✓ | | ✓ | | | | | | | +| Decimal64 | ✓ | | ✓ | | ✓ | | | | | +-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+ | Decimal128 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | +-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+