This is an automated email from the ASF dual-hosted git repository.

curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new b0e13cc41d GH-44271: [C#] Add support for Decimal32 and Decimal64 
(#44272)
b0e13cc41d is described below

commit b0e13cc41d27ffb734bc163b165fc0989adaa9c9
Author: Curt Hagenlocher <[email protected]>
AuthorDate: Mon Sep 30 20:34:37 2024 -0700

    GH-44271: [C#] Add support for Decimal32 and Decimal64 (#44272)
    
    ### What changes are included in this PR?
    
    Implementation of decimal32 and decimal64 for C#.
    Tests for the implementation, including enablement of Archery tests.
    
    ### Are these changes tested?
    
    Yes.
    
    ### Are there any user-facing changes?
    
    New types Decimal32Array and Decimal64Array.
    
    Closes #44271
    * GitHub Issue: #44271
    
    Authored-by: Curt Hagenlocher <[email protected]>
    Signed-off-by: Curt Hagenlocher <[email protected]>
---
 .../Arrays/ArrowArrayBuilderFactory.cs             |   4 +
 .../src/Apache.Arrow/Arrays/ArrowArrayFactory.cs   |   4 +
 csharp/src/Apache.Arrow/Arrays/Decimal32Array.cs   | 182 +++++++++++
 csharp/src/Apache.Arrow/Arrays/Decimal64Array.cs   | 182 +++++++++++
 csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs  |   4 +
 csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs  |  18 +-
 csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs   |   6 +
 .../Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs |  16 +
 csharp/src/Apache.Arrow/Ipc/MessageSerializer.cs   |   4 +
 csharp/src/Apache.Arrow/RecordBatch.Builder.cs     |   6 +
 csharp/src/Apache.Arrow/Types/Decimal32Type.cs     |  35 +++
 csharp/src/Apache.Arrow/Types/Decimal64Type.cs     |  35 +++
 csharp/src/Apache.Arrow/Types/IArrowType.cs        |   2 +
 .../test/Apache.Arrow.IntegrationTest/JsonFile.cs  |  18 +-
 .../ArrowArrayConcatenatorTests.cs                 |   9 +-
 .../test/Apache.Arrow.Tests/ArrowReaderVerifier.cs |   4 +
 .../CDataInterfacePythonTests.cs                   |   4 +-
 .../test/Apache.Arrow.Tests/Decimal32ArrayTests.cs | 337 +++++++++++++++++++++
 .../test/Apache.Arrow.Tests/Decimal64ArrayTests.cs | 337 +++++++++++++++++++++
 csharp/test/Apache.Arrow.Tests/TableTests.cs       |   4 +-
 csharp/test/Apache.Arrow.Tests/TestData.cs         |  29 ++
 dev/archery/archery/integration/datagen.py         |   2 -
 docs/source/status.rst                             |   4 +-
 23 files changed, 1225 insertions(+), 21 deletions(-)

diff --git a/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs 
b/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs
index cb7164aa14..3fb77d2c3b 100644
--- a/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs
+++ b/csharp/src/Apache.Arrow/Arrays/ArrowArrayBuilderFactory.cs
@@ -78,6 +78,10 @@ namespace Apache.Arrow
                     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 bd06c3a1b8..38092a7a23 100644
--- a/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs
+++ b/csharp/src/Apache.Arrow/Arrays/ArrowArrayFactory.cs
@@ -87,6 +87,10 @@ namespace Apache.Arrow
                     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 0000000000..21dee1108a
--- /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<decimal?>
+    {
+        public class Builder : BuilderBase<Decimal32Array, Builder>
+        {
+            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<byte> bytes = stackalloc byte[DataType.ByteWidth];
+                DecimalUtility.GetBytes(value, DataType.Precision, 
DataType.Scale, DataType.ByteWidth, bytes);
+
+                return Append(bytes);
+            }
+
+            public Builder AppendRange(IEnumerable<decimal> 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<byte> bytes = stackalloc byte[DataType.ByteWidth];
+                    DecimalUtility.GetBytes(value, DataType.Precision, 
DataType.Scale, ByteWidth, bytes);
+                    Append(bytes);
+                }
+
+                return Instance;
+            }
+
+            public Builder AppendRange(IEnumerable<string> 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<byte> 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<decimal?> ToList(bool includeNulls = false)
+        {
+            var list = new List<decimal?>(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<decimal?>.Count => Length;
+        decimal? IReadOnlyList<decimal?>.this[int index] => GetDecimal(index);
+
+        IEnumerator<decimal?> IEnumerable<decimal?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetDecimal(index);
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => 
((IEnumerable<decimal>)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 0000000000..b50baef7d9
--- /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<decimal?>
+    {
+        public class Builder : BuilderBase<Decimal64Array, Builder>
+        {
+            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<byte> bytes = stackalloc byte[DataType.ByteWidth];
+                DecimalUtility.GetBytes(value, DataType.Precision, 
DataType.Scale, DataType.ByteWidth, bytes);
+
+                return Append(bytes);
+            }
+
+            public Builder AppendRange(IEnumerable<decimal> 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<byte> bytes = stackalloc byte[DataType.ByteWidth];
+                    DecimalUtility.GetBytes(value, DataType.Precision, 
DataType.Scale, ByteWidth, bytes);
+                    Append(bytes);
+                }
+
+                return Instance;
+            }
+
+            public Builder AppendRange(IEnumerable<string> 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<byte> 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<decimal?> ToList(bool includeNulls = false)
+        {
+            var list = new List<decimal?>(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<decimal?>.Count => Length;
+        decimal? IReadOnlyList<decimal?>.this[int index] => GetDecimal(index);
+
+        IEnumerator<decimal?> IEnumerable<decimal?>.GetEnumerator()
+        {
+            for (int index = 0; index < Length; index++)
+            {
+                yield return GetDecimal(index);
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => 
((IEnumerable<decimal>)this).GetEnumerator();
+    }
+}
diff --git a/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs 
b/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs
index 92d48a2d70..1cf6dc78a7 100644
--- a/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowSchemaExporter.cs
@@ -161,6 +161,10 @@ namespace Apache.Arrow.C
                 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 94177184de..a772b8f4a5 100644
--- a/csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs
+++ b/csharp/src/Apache.Arrow/C/CArrowSchemaImporter.cs
@@ -224,19 +224,17 @@ namespace Apache.Arrow.C
                 // 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 eaa8471fa7..ff0b64c09e 100644
--- a/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
+++ b/csharp/src/Apache.Arrow/Ipc/ArrowStreamWriter.cs
@@ -68,6 +68,8 @@ namespace Apache.Arrow.Ipc
             IArrowArrayVisitor<FixedSizeBinaryArray>,
             IArrowArrayVisitor<StructArray>,
             IArrowArrayVisitor<UnionArray>,
+            IArrowArrayVisitor<Decimal32Array>,
+            IArrowArrayVisitor<Decimal64Array>,
             IArrowArrayVisitor<Decimal128Array>,
             IArrowArrayVisitor<Decimal256Array>,
             IArrowArrayVisitor<DictionaryArray>,
@@ -292,6 +294,10 @@ namespace Apache.Arrow.Ipc
                 _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 adc229a051..d1fb921686 100644
--- a/csharp/src/Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs
+++ b/csharp/src/Apache.Arrow/Ipc/ArrowTypeFlatbufferBuilder.cs
@@ -74,6 +74,8 @@ namespace Apache.Arrow.Ipc
             IArrowTypeVisitor<FixedSizeListType>,
             IArrowTypeVisitor<UnionType>,
             IArrowTypeVisitor<StructType>,
+            IArrowTypeVisitor<Decimal32Type>,
+            IArrowTypeVisitor<Decimal64Type>,
             IArrowTypeVisitor<Decimal128Type>,
             IArrowTypeVisitor<Decimal256Type>,
             IArrowTypeVisitor<DictionaryType>,
@@ -276,6 +278,20 @@ namespace Apache.Arrow.Ipc
                 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 8e15632c51..7c7f7a38da 100644
--- a/csharp/src/Apache.Arrow/Ipc/MessageSerializer.cs
+++ b/csharp/src/Apache.Arrow/Ipc/MessageSerializer.cs
@@ -142,6 +142,10 @@ namespace Apache.Arrow.Ipc
                     Flatbuf.Decimal decMeta = 
field.Type<Flatbuf.Decimal>().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 8e0d17ae06..e9a84a48d8 100644
--- a/csharp/src/Apache.Arrow/RecordBatch.Builder.cs
+++ b/csharp/src/Apache.Arrow/RecordBatch.Builder.cs
@@ -47,6 +47,12 @@ namespace Apache.Arrow
 #endif
             public FloatArray Float(Action<FloatArray.Builder> action) => 
Build<FloatArray, FloatArray.Builder>(new FloatArray.Builder(), action);
             public DoubleArray Double(Action<DoubleArray.Builder> action) => 
Build<DoubleArray, DoubleArray.Builder>(new DoubleArray.Builder(), action);
+            public Decimal32Array Decimal32(Decimal32Type type, 
Action<Decimal32Array.Builder> action) =>
+                Build<Decimal32Array, Decimal32Array.Builder>(
+                    new Decimal32Array.Builder(type), action);
+            public Decimal64Array Decimal64(Decimal64Type type, 
Action<Decimal64Array.Builder> action) =>
+                Build<Decimal64Array, Decimal64Array.Builder>(
+                    new Decimal64Array.Builder(type), action);
             public Decimal128Array Decimal128(Decimal128Type type, 
Action<Decimal128Array.Builder> action) =>
                 Build<Decimal128Array, Decimal128Array.Builder>(
                     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 0000000000..c494b3b013
--- /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 0000000000..30d544b326
--- /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 7a3159a1bb..6020fd41e3 100644
--- a/csharp/src/Apache.Arrow/Types/IArrowType.cs
+++ b/csharp/src/Apache.Arrow/Types/IArrowType.cs
@@ -56,6 +56,8 @@ namespace Apache.Arrow.Types
         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 c9e44b8d2f..a75c4193da 100644
--- a/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs
+++ b/csharp/test/Apache.Arrow.IntegrationTest/JsonFile.cs
@@ -231,7 +231,11 @@ namespace Apache.Arrow.IntegrationTest
             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 @@ namespace Apache.Arrow.IntegrationTest
             IArrowTypeVisitor<UInt64Type>,
             IArrowTypeVisitor<FloatType>,
             IArrowTypeVisitor<DoubleType>,
+            IArrowTypeVisitor<Decimal32Type>,
+            IArrowTypeVisitor<Decimal64Type>,
             IArrowTypeVisitor<Decimal128Type>,
             IArrowTypeVisitor<Decimal256Type>,
             IArrowTypeVisitor<Date32Type>,
@@ -553,6 +559,16 @@ namespace Apache.Arrow.IntegrationTest
                 }
             }
 
+            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 2437d3d94c..a45a50c4a2 100644
--- a/csharp/test/Apache.Arrow.Tests/ArrowArrayConcatenatorTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/ArrowArrayConcatenatorTests.cs
@@ -80,8 +80,10 @@ namespace Apache.Arrow.Tests
                     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<Field>{
@@ -138,6 +140,8 @@ namespace Apache.Arrow.Tests
             IArrowTypeVisitor<BinaryViewType>,
             IArrowTypeVisitor<StringType>,
             IArrowTypeVisitor<StringViewType>,
+            IArrowTypeVisitor<Decimal32Type>,
+            IArrowTypeVisitor<Decimal64Type>,
             IArrowTypeVisitor<Decimal128Type>,
             IArrowTypeVisitor<Decimal256Type>,
             IArrowTypeVisitor<Date32Type>,
@@ -207,8 +211,9 @@ namespace Apache.Arrow.Tests
             public void Visit(Date32Type type) => GenerateTestData<DateTime, 
Date32Array, Date32Array.Builder>(type, x => DateTime.MinValue.AddDays(x));
             public void Visit(Date64Type type) => GenerateTestData<DateTime, 
Date64Array, Date64Array.Builder>(type, x => DateTime.MinValue.AddDays(x));
 
+            public void Visit(Decimal32Type type) => 
GenerateTestData<Decimal32Array, Decimal32Array.Builder>(type, (builder, x) => 
builder.Append(x));
+            public void Visit(Decimal64Type type) => 
GenerateTestData<Decimal64Array, Decimal64Array.Builder>(type, (builder, x) => 
builder.Append(x));
             public void Visit(Decimal128Type type) => 
GenerateTestData<Decimal128Array, Decimal128Array.Builder>(type, (builder, x) 
=> builder.Append(x));
-
             public void Visit(Decimal256Type type) => 
GenerateTestData<Decimal256Array, Decimal256Array.Builder>(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 35b2c4e7f2..5977c3288a 100644
--- a/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs
+++ b/csharp/test/Apache.Arrow.Tests/ArrowReaderVerifier.cs
@@ -106,6 +106,8 @@ namespace Apache.Arrow.Tests
             IArrowArrayVisitor<LargeBinaryArray>,
             IArrowArrayVisitor<StructArray>,
             IArrowArrayVisitor<UnionArray>,
+            IArrowArrayVisitor<Decimal32Array>,
+            IArrowArrayVisitor<Decimal64Array>,
             IArrowArrayVisitor<Decimal128Array>,
             IArrowArrayVisitor<Decimal256Array>,
             IArrowArrayVisitor<DictionaryArray>,
@@ -150,6 +152,8 @@ namespace Apache.Arrow.Tests
             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<StringArray>(array);
diff --git a/csharp/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs 
b/csharp/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs
index 638cbfb272..0ef8eba261 100644
--- a/csharp/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs
@@ -754,7 +754,7 @@ namespace Apache.Arrow.Tests
         public unsafe void RoundTripTestBatch()
         {
             // TODO: Enable these once this the version of pyarrow referenced 
during testing supports them
-            HashSet<ArrowTypeId> unsupported = new HashSet<ArrowTypeId> { 
ArrowTypeId.ListView, ArrowTypeId.BinaryView, ArrowTypeId.StringView };
+            HashSet<ArrowTypeId> unsupported = new HashSet<ArrowTypeId> { 
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 @@ namespace Apache.Arrow.Tests
         public unsafe void RoundTripTestSlicedBatch()
         {
             // TODO: Enable these once this the version of pyarrow referenced 
during testing supports them
-            HashSet<ArrowTypeId> unsupported = new HashSet<ArrowTypeId> { 
ArrowTypeId.ListView, ArrowTypeId.BinaryView, ArrowTypeId.StringView };
+            HashSet<ArrowTypeId> unsupported = new HashSet<ArrowTypeId> { 
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 0000000000..f7b7d409a0
--- /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<OverflowException>(() => 
builder.Append(100));
+                    Assert.Throws<OverflowException>(() => 
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 0000000000..b37a44a859
--- /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<OverflowException>(() => 
builder.Append(100));
+                    Assert.Throws<OverflowException>(() => 
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 35fbe7cba6..a12e7b9ccd 100644
--- a/csharp/test/Apache.Arrow.Tests/TableTests.cs
+++ b/csharp/test/Apache.Arrow.Tests/TableTests.cs
@@ -63,9 +63,9 @@ namespace Apache.Arrow.Tests
             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 36969766ae..08ada33d7e 100644
--- a/csharp/test/Apache.Arrow.Tests/TestData.cs
+++ b/csharp/test/Apache.Arrow.Tests/TestData.cs
@@ -72,6 +72,8 @@ namespace Apache.Arrow.Tests
                 AddField(CreateField(StringType.Default, i));
                 AddField(CreateField(StringViewType.Default, i));
                 AddField(CreateField(new StructType(new List<Field> { 
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 @@ namespace Apache.Arrow.Tests
             IArrowTypeVisitor<FixedSizeListType>,
             IArrowTypeVisitor<StructType>,
             IArrowTypeVisitor<UnionType>,
+            IArrowTypeVisitor<Decimal32Type>,
+            IArrowTypeVisitor<Decimal64Type>,
             IArrowTypeVisitor<Decimal128Type>,
             IArrowTypeVisitor<Decimal256Type>,
             IArrowTypeVisitor<DictionaryType>,
@@ -190,6 +194,31 @@ namespace Apache.Arrow.Tests
             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 d7f88083f4..9f86d172dd 100644
--- a/dev/archery/archery/integration/datagen.py
+++ b/dev/archery/archery/integration/datagen.py
@@ -1906,7 +1906,6 @@ def get_generated_json_files(tempdir=None):
         .skip_tester('JS'),
 
         generate_decimal32_case()
-        .skip_tester('C#')
         .skip_tester('Java')
         .skip_tester('JS')
         .skip_tester('nanoarrow')
@@ -1914,7 +1913,6 @@ def get_generated_json_files(tempdir=None):
         .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 7deb3f512c..c838604fca 100644
--- a/docs/source/status.rst
+++ b/docs/source/status.rst
@@ -44,9 +44,9 @@ Data Types
 
+-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+
 | Float32/64        | ✓     | ✓     | ✓     | ✓  |  ✓    |  ✓    | ✓     | ✓   
  | ✓         |
 
+-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+
-| Decimal32         | ✓     |       | ✓     |    |       |       |       |     
  |           |
+| Decimal32         | ✓     |       | ✓     |    |  ✓    |       |       |     
  |           |
 
+-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+
-| Decimal64         | ✓     |       | ✓     |    |       |       |       |     
  |           |
+| Decimal64         | ✓     |       | ✓     |    |  ✓    |       |       |     
  |           |
 
+-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+
 | Decimal128        | ✓     | ✓     | ✓     | ✓  |  ✓    |  ✓    | ✓     |     
  | ✓         |
 
+-------------------+-------+-------+-------+----+-------+-------+-------+-------+-----------+

Reply via email to