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-dotnet.git


The following commit(s) were added to refs/heads/main by this push:
     new 3d0c7a2  Add bool8 extension type support (#274)
3d0c7a2 is described below

commit 3d0c7a24644666794c7ebcefcdd5849782a35709
Author: Curt Hagenlocher <[email protected]>
AuthorDate: Tue Mar 3 05:02:44 2026 -0800

    Add bool8 extension type support (#274)
    
    ## What's Changed
    
    This PR adds support for the bool8 extension type. The support is off by
    default.
---
 src/Apache.Arrow/Arrays/Bool8Array.cs              | 151 +++++++++++++++++++++
 .../CDataInterfacePythonTests.cs                   | 106 +++++++++++++++
 2 files changed, 257 insertions(+)

diff --git a/src/Apache.Arrow/Arrays/Bool8Array.cs 
b/src/Apache.Arrow/Arrays/Bool8Array.cs
new file mode 100644
index 0000000..c250e4e
--- /dev/null
+++ b/src/Apache.Arrow/Arrays/Bool8Array.cs
@@ -0,0 +1,151 @@
+// 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 Apache.Arrow.Arrays;
+using Apache.Arrow.Types;
+
+namespace Apache.Arrow
+{
+    /// <summary>
+    /// Extension definition for the "arrow.bool8" extension type,
+    /// backed by the Int8 storage type.
+    /// </summary>
+    public class Bool8ExtensionDefinition : ExtensionDefinition
+    {
+        public static Bool8ExtensionDefinition Instance = new 
Bool8ExtensionDefinition();
+
+        public override string ExtensionName => "arrow.bool8";
+
+        private Bool8ExtensionDefinition() { }
+
+        public override bool TryCreateType(IArrowType storageType, string 
metadata, out ExtensionType type)
+        {
+            if (storageType is Int8Type i8Type)
+            {
+                type = new Bool8Type(i8Type);
+                return true;
+            }
+            type = null;
+            return false;
+        }
+    }
+
+    /// <summary>
+    /// Extension type representing 1-byte boolean values
+    /// </summary>
+    public class Bool8Type : ExtensionType
+    {
+        public static Bool8Type Default = new Bool8Type();
+
+        public override string Name => "arrow.bool8";
+        public override string ExtensionMetadata => "";
+
+        public Bool8Type() : base(Int8Type.Default) { }
+
+        internal Bool8Type(Int8Type storageType) : base(storageType) { }
+
+        public override ExtensionArray CreateArray(IArrowArray storageArray)
+        {
+            return new Bool8Array(this, storageArray);
+        }
+    }
+
+    /// <summary>
+    /// Extension array for 1-byte boolean values, backed by an Int8Array.
+    /// </summary>
+    public class Bool8Array : ExtensionArray, IReadOnlyList<bool?>
+    {
+        public Int8Array StorageArray => (Int8Array)Storage;
+
+        public Bool8Array(Bool8Type bool8Type, IArrowArray storage) : 
base(bool8Type, storage) { }
+
+        public Bool8Array(IArrowArray storage) : base(Bool8Type.Default, 
storage) { }
+
+        public class Builder : PrimitiveArrayBuilder<sbyte, Bool8Array, 
Builder>
+        {
+            protected override Bool8Array Build(
+                ArrowBuffer valueBuffer, ArrowBuffer nullBitmapBuffer,
+                int length, int nullCount, int offset) =>
+                new Bool8Array(new Int8Array(valueBuffer, nullBitmapBuffer, 
length, nullCount, offset));
+
+            public Builder Append(bool value)
+            {
+                return Append(value ? (sbyte)1 : (sbyte)0);
+            }
+
+            public Builder Append(bool? value)
+            {
+                if (value == null)
+                {
+                    return AppendNull();
+                }
+                return Append(value.Value);
+            }
+
+            public Builder AppendRange(IEnumerable<bool> values)
+            {
+                if (values == null)
+                {
+                    throw new ArgumentNullException(nameof(values));
+                }
+
+                foreach (bool value in values)
+                {
+                    Append(value);
+                }
+
+                return Instance;
+            }
+
+            public Builder AppendRange(IEnumerable<bool?> values)
+            {
+                if (values == null)
+                {
+                    throw new ArgumentNullException(nameof(values));
+                }
+
+                foreach (bool? value in values)
+                {
+                    Append(value);
+                }
+
+                return Instance;
+            }
+
+            public Builder Set(int index, bool value)
+            {
+                return Set(index, value ? (sbyte)1 : (sbyte)0);
+            }
+        }
+
+        public int Count => Length;
+        public bool? this[int index] => GetValue(index);
+
+        public bool? GetValue(int index) => IsValid(index) ? 
StorageArray.GetValue(index).Value != 0 : null;
+
+        public IEnumerator<bool?> GetEnumerator()
+        {
+            for (int i = 0; i < Length; i++)
+            {
+                yield return GetValue(i);
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+    }
+}
diff --git a/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs 
b/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs
index 4276061..6a10255 100644
--- a/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs
+++ b/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs
@@ -1201,6 +1201,112 @@ namespace Apache.Arrow.Tests
             CArrowArray.Free(cArray);
         }
 
+        [SkippableFact]
+        public unsafe void ExportBool8Array()
+        {
+            // Export a C# Bool8Array via the C Data Interface and verify
+            // that Python/pyarrow sees it as an arrow.bool8 extension array
+            // with the correct values.
+
+            var builder = new Bool8Array.Builder();
+            builder.Append(true);
+            builder.Append(false);
+            builder.Append(null);
+            builder.Append(false);
+            builder.Append(true);
+            var bool8Array = builder.Build();
+
+            var field = new Field("bools", Bool8Type.Default, true);
+            var schema = new Schema(new[] { field }, null);
+            var batch = new RecordBatch(schema, new[] { bool8Array }, 
bool8Array.Length);
+
+            CArrowArray* cArray = CArrowArray.Create();
+            CArrowArrayExporter.ExportRecordBatch(batch, cArray);
+
+            CArrowSchema* cSchema = CArrowSchema.Create();
+            CArrowSchemaExporter.ExportSchema(batch.Schema, cSchema);
+
+            try
+            {
+                long arrayPtr = ((IntPtr)cArray).ToInt64();
+                long schemaPtr = ((IntPtr)cSchema).ToInt64();
+
+                using (Py.GIL())
+                {
+                    dynamic pa = Py.Import("pyarrow");
+
+                    dynamic pyBatch = pa.RecordBatch._import_from_c(arrayPtr, 
schemaPtr);
+                    dynamic pyArray = pyBatch.column(0);
+
+                    // Build the expected bool8 array in Python
+                    dynamic expectedArray = pa.array(
+                        new PyList(new PyObject[]
+                        {
+                        PyObject.FromManagedObject(true),
+                        PyObject.FromManagedObject(false),
+                        PyObject.None,
+                        PyObject.FromManagedObject(false),
+                        PyObject.FromManagedObject(true),
+                        }),
+                        type: pa.bool8());
+
+                    Assert.True((bool)pyArray.equals(expectedArray));
+                }
+            }
+            finally
+            {
+                CArrowArray.Free(cArray);
+                CArrowSchema.Free(cSchema);
+            }
+        }
+
+        [SkippableFact]
+        public unsafe void ImportBool8Array()
+        {
+            // Create a bool8 array in Python, export it via the C Data 
Interface,
+            // and verify that C# resolves it as a Bool8Array with correct 
values.
+
+            CArrowArray* cArray = CArrowArray.Create();
+            CArrowSchema* cSchema = CArrowSchema.Create();
+
+            using (Py.GIL())
+            {
+                dynamic pa = Py.Import("pyarrow");
+
+                dynamic pyArray = pa.array(
+                    new PyList(new PyObject[]
+                    {
+                        PyObject.None,
+                        PyObject.FromManagedObject(false),
+                        PyObject.FromManagedObject(true),
+                        PyObject.FromManagedObject(true),
+                    }),
+                    type: pa.bool8());
+
+                long arrayPtr = ((IntPtr)cArray).ToInt64();
+                long schemaPtr = ((IntPtr)cSchema).ToInt64();
+                pyArray._export_to_c(arrayPtr, schemaPtr);
+            }
+
+            var registry = new ExtensionTypeRegistry();
+            registry.Register(Bool8ExtensionDefinition.Instance);
+
+            Field field = CArrowSchemaImporter.ImportField(cSchema, registry);
+            Assert.IsType<Bool8Type>(field.DataType);
+
+            IArrowArray importedArray = 
CArrowArrayImporter.ImportArray(cArray, field.DataType);
+            Assert.IsType<Bool8Array>(importedArray);
+
+            var bool8Array = (Bool8Array)importedArray;
+            Assert.Equal(4, bool8Array.Length);
+            Assert.Null(bool8Array.GetValue(0));
+            Assert.Equal(false, bool8Array.GetValue(1));
+            Assert.Equal(true, bool8Array.GetValue(2));
+            Assert.Equal(true, bool8Array.GetValue(3));
+
+            CArrowArray.Free(cArray);
+        }
+
         private static PyObject List(params int?[] values)
         {
             return new PyList(values.Select(i => i == null ? PyObject.None : 
new PyInt(i.Value)).ToArray());

Reply via email to