adamreeve commented on code in PR #268:
URL: https://github.com/apache/arrow-dotnet/pull/268#discussion_r2838532019


##########
test/Apache.Arrow.Tests/ExtensionTypeTests.cs:
##########
@@ -0,0 +1,355 @@
+// 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.Generic;
+using System.IO;
+using System.Linq;
+using Apache.Arrow.Arrays;
+using Apache.Arrow.Ipc;
+using Apache.Arrow.Types;
+using Xunit;
+
+namespace Apache.Arrow.Tests
+{
+    public class ExtensionTypeTests
+    {
+        private static RecordBatch BuildGuidRecordBatch(Guid?[] values)
+        {
+            var builder = new GuidArray.Builder();
+
+            foreach (var value in values)
+            {
+                if (value.HasValue)
+                {
+                    builder.Append(value.Value);
+                }
+                else
+                {
+                    builder.AppendNull();
+                }
+            }
+            var guidArray = builder.Build();
+
+            var field = new Field("guids", GuidType.Default, true,
+                new Dictionary<string, string>
+                {
+                    ["ARROW:extension:name"] = "arrow.uuid",
+                    ["ARROW:extension:metadata"] = ""
+                });
+            var schema = new Schema(new[] { field }, null);
+
+            return new RecordBatch(schema, new[] { guidArray }, values.Length);
+        }
+
+        [Fact]
+        public void ExtensionTypeProperties()
+        {
+            var guidType = new GuidType();
+            Assert.Equal(ArrowTypeId.Extension, guidType.TypeId);
+            Assert.Equal("arrow.uuid", guidType.Name);
+            Assert.Equal("", guidType.ExtensionMetadata);
+            Assert.True(guidType.IsFixedWidth);
+            Assert.IsType<FixedSizeBinaryType>(guidType.StorageType);
+            Assert.Equal(16, 
((FixedSizeBinaryType)guidType.StorageType).ByteWidth);
+        }
+
+        [Fact]
+        public void GuidArrayReadValues()
+        {
+            var guids = new Guid?[]
+            {
+                Guid.Parse("01234567-89ab-cdef-0123-456789abcdef"),
+                null,
+                Guid.Parse("fedcba98-7654-3210-fedc-ba9876543210"),
+            };
+
+            var batch = BuildGuidRecordBatch(guids);
+            var array = (GuidArray)batch.Column(0);
+
+            Assert.Equal(3, array.Length);
+            Assert.Equal(guids[0], array.GetGuid(0));
+            Assert.Null(array.GetGuid(1));
+            Assert.True(array.IsNull(1));
+            Assert.Equal(guids[2], array.GetGuid(2));
+        }
+
+        [Fact]
+        public void GuidArraySlice()
+        {
+            var guids = new Guid?[]
+            {
+                Guid.NewGuid(),
+                Guid.NewGuid(),
+                Guid.NewGuid(),
+                Guid.NewGuid(),
+            };
+
+            var batch = BuildGuidRecordBatch(guids);
+            var array = (GuidArray)batch.Column(0);
+
+            var sliced = ArrowArrayFactory.Slice(array, 1, 2);
+            Assert.IsType<GuidArray>(sliced);
+            var slicedGuid = (GuidArray)sliced;
+            Assert.Equal(2, slicedGuid.Length);
+            Assert.Equal(guids[1], slicedGuid.GetGuid(0));
+            Assert.Equal(guids[2], slicedGuid.GetGuid(1));
+        }
+
+        [Fact]
+        public void IpcStreamRoundTrip()
+        {
+            var registry = new ExtensionTypeRegistry();
+            registry.Register(GuidExtensionDefinition.Instance);
+            var context = new ArrowContext(extensionRegistry: registry);
+
+            var guids = new Guid?[]
+            {
+                Guid.Parse("01234567-89ab-cdef-0123-456789abcdef"),
+                null,
+                Guid.Parse("fedcba98-7654-3210-fedc-ba9876543210"),
+            };
+
+            var batch = BuildGuidRecordBatch(guids);
+
+            // Write
+            var stream = new MemoryStream();
+            var writer = new ArrowStreamWriter(stream, batch.Schema);
+            writer.WriteRecordBatch(batch);
+            writer.WriteEnd();
+            stream.Position = 0;
+
+            // Read with extension registry
+            var reader = new ArrowStreamReader(context, stream);
+            var readBatch = reader.ReadNextRecordBatch();
+
+            Assert.NotNull(readBatch);
+            Assert.Equal(3, readBatch.Length);
+
+            var readArray = readBatch.Column(0);
+            Assert.IsType<GuidArray>(readArray);
+
+            var guidArray = (GuidArray)readArray;
+            Assert.Equal(guids[0], guidArray.GetGuid(0));
+            Assert.Null(guidArray.GetGuid(1));
+            Assert.Equal(guids[2], guidArray.GetGuid(2));
+        }
+
+        [Fact]
+        public void IpcStreamRoundTripWithoutRegistration()
+        {
+            // Without registering the extension type, the field should come 
back
+            // as a FixedSizeBinaryArray (backwards compat)
+            var guids = new Guid?[]
+            {
+                Guid.Parse("01234567-89ab-cdef-0123-456789abcdef"),
+                null,
+                Guid.Parse("fedcba98-7654-3210-fedc-ba9876543210"),
+            };
+
+            var batch = BuildGuidRecordBatch(guids);
+
+            // Write
+            var stream = new MemoryStream();
+            var writer = new ArrowStreamWriter(stream, batch.Schema);
+            writer.WriteRecordBatch(batch);
+            writer.WriteEnd();
+            stream.Position = 0;
+
+            // Read without extension registry
+            var context = new ArrowContext(extensionRegistry: new 
ExtensionTypeRegistry());
+            var reader = new ArrowStreamReader(context, stream);
+            var readBatch = reader.ReadNextRecordBatch();
+
+            Assert.NotNull(readBatch);
+            var readArray = readBatch.Column(0);
+            Assert.IsType<FixedSizeBinaryArray>(readArray);
+            Assert.IsNotType<GuidArray>(readArray);
+        }
+
+        [Fact]
+        public void ExtensionMetadataPreservedInRoundTrip()
+        {
+            var guids = new Guid?[] { Guid.NewGuid() };
+            var batch = BuildGuidRecordBatch(guids);
+
+            // Write
+            var stream = new MemoryStream();
+            var writer = new ArrowStreamWriter(stream, batch.Schema);
+            writer.WriteRecordBatch(batch);
+            writer.WriteEnd();
+            stream.Position = 0;
+
+            // Read without extension registry — metadata should still be on 
the field
+            var context = new ArrowContext(extensionRegistry: new 
ExtensionTypeRegistry());
+            var reader = new ArrowStreamReader(context, stream);
+            var readBatch = reader.ReadNextRecordBatch();
+
+            Assert.NotNull(readBatch);
+            var field = readBatch.Schema.GetFieldByIndex(0);
+            Assert.NotNull(field.Metadata);
+            Assert.True(field.Metadata.ContainsKey("ARROW:extension:name"));
+            Assert.Equal("arrow.uuid", field.Metadata["ARROW:extension:name"]);
+        }
+
+        [Fact]
+        public void ContextIsolation()
+        {
+            // Two contexts with different registries should resolve 
differently
+            var registry1 = new ExtensionTypeRegistry();
+            registry1.Register(GuidExtensionDefinition.Instance);
+            var context1 = new ArrowContext(extensionRegistry: registry1);
+
+            var registry2 = new ExtensionTypeRegistry();
+            // registry2 has no Guid definition registered
+            var context2 = new ArrowContext(extensionRegistry: registry2);
+
+            var guids = new Guid?[] { Guid.NewGuid() };
+            var batch = BuildGuidRecordBatch(guids);
+
+            var stream1 = new MemoryStream();
+            var writer = new ArrowStreamWriter(stream1, batch.Schema);
+            writer.WriteRecordBatch(batch);
+            writer.WriteEnd();
+
+            // Read with context1 — should resolve as GuidArray
+            stream1.Position = 0;
+            var reader1 = new ArrowStreamReader(context1, stream1);
+            var readBatch1 = reader1.ReadNextRecordBatch();
+            Assert.IsType<GuidArray>(readBatch1.Column(0));
+
+            // Read with context2 — should resolve as FixedSizeBinaryArray
+            stream1.Position = 0;
+            var reader2 = new ArrowStreamReader(context2, stream1);
+            var readBatch2 = reader2.ReadNextRecordBatch();
+            Assert.IsType<FixedSizeBinaryArray>(readBatch2.Column(0));
+        }
+
+        [Fact]
+        public void ExtensionDefinitionRejectsWrongStorageType()
+        {
+            var def = GuidExtensionDefinition.Instance;
+
+            // Should fail for wrong byte width
+            Assert.False(def.TryCreateType(new FixedSizeBinaryType(8), "", out 
_));
+
+            // Should fail for non-FixedSizeBinary type
+            Assert.False(def.TryCreateType(Int32Type.Default, "", out _));
+
+            // Should succeed for correct type
+            Assert.True(def.TryCreateType(new FixedSizeBinaryType(16), "", out 
var extType));
+            Assert.IsType<GuidType>(extType);
+        }
+
+        [Fact]
+        public void ExtensionTypeRegistryClone()
+        {
+            var registry = new ExtensionTypeRegistry();
+            registry.Register(GuidExtensionDefinition.Instance);
+
+            var clone = registry.Clone();
+
+            Assert.True(clone.TryGetDefinition("arrow.uuid", out _));
+
+            // Mutating clone should not affect original
+            var newRegistry = new ExtensionTypeRegistry();
+            Assert.False(newRegistry.TryGetDefinition("arrow.uuid", out _));

Review Comment:
   This isn't testing what it says it is. I think you want to do something like 
unregister Guid from the clone and check you can still get it from the original?



##########
src/Apache.Arrow/C/CArrowSchemaImporter.cs:
##########
@@ -331,20 +351,61 @@ public ArrowType GetAsType()
             }
 
             public Field GetAsField()
+            {
+                return GetAsField(null);
+            }
+
+            public Field GetAsField(ExtensionTypeRegistry extensionRegistry)
             {
                 string name = StringUtil.PtrToStringUtf8(_cSchema->name);
                 string fieldName = string.IsNullOrEmpty(name) ? "" : name;
 
                 bool nullable = 
_cSchema->GetFlag(CArrowSchema.ArrowFlagNullable);
 
-                return new Field(fieldName, GetAsType(), nullable, 
GetMetadata(_cSchema->metadata));
+                IArrowType type = GetAsType();
+                IReadOnlyDictionary<string, string> metadata = 
GetMetadata(_cSchema->metadata);
+
+                // Resolve extension type from metadata
+                if (metadata != null &&
+                    extensionRegistry != null &&
+                    metadata.TryGetValue("ARROW:extension:name", out string 
extName) &&
+                    extensionRegistry.TryGetDefinition(extName, out 
ExtensionDefinition extDef))
+                {
+                    metadata.TryGetValue("ARROW:extension:metadata", out 
string extMeta);
+                    if (extDef.TryCreateType(type, extMeta, out ExtensionType 
extType))

Review Comment:
   A failure here seems like something that should be logged, but we don't have 
any logging infrastructure in the library so I guess there isn't really 
anything that can be done. I think it's probably right that failure shouldn't 
throw an exception.



##########
src/Apache.Arrow/Arrays/GuidArray.cs:
##########
@@ -0,0 +1,211 @@
+// 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.uuid" extension type,
+    /// backed by FixedSizeBinary(16).
+    /// </summary>
+    public class GuidExtensionDefinition : ExtensionDefinition
+    {
+        public static GuidExtensionDefinition Instance = new 
GuidExtensionDefinition();
+
+        public override string ExtensionName => "arrow.uuid";
+
+        private GuidExtensionDefinition() { }
+
+        public override bool TryCreateType(IArrowType storageType, string 
metadata, out ExtensionType type)
+        {
+            if (storageType is FixedSizeBinaryType fsbType && 
fsbType.ByteWidth == GuidType.ByteWidth)
+            {
+                type = new GuidType(fsbType);
+                return true;
+            }
+            type = null;
+            return false;
+        }
+    }
+
+    /// <summary>
+    /// Extension type representing UUIDs/GUIDs, stored as FixedSizeBinary(16).
+    /// </summary>
+    public class GuidType : ExtensionType
+    {
+        public static GuidType Default = new GuidType();
+
+        internal const int ByteWidth = 16;
+
+        public override string Name => "arrow.uuid";
+        public override string ExtensionMetadata => "";
+
+        public GuidType() : base(new FixedSizeBinaryType(ByteWidth)) { }
+
+        internal GuidType(FixedSizeBinaryType storageType) : base(storageType) 
{ }
+
+        public override ExtensionArray CreateArray(IArrowArray storageArray)
+        {
+            return new GuidArray(this, storageArray);
+        }
+    }
+
+    /// <summary>
+    /// Extension array for UUID/GUID values, backed by a FixedSizeBinaryArray.
+    /// </summary>
+    public class GuidArray : ExtensionArray, IReadOnlyList<Guid?>
+    {
+        // TODO: construct builder

Review Comment:
   What's this TODO referring to? The Builder is implemented below so I'm not 
sure what's missing.



##########
test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs:
##########
@@ -1091,6 +1092,115 @@ public async Task EarlyDisposeOfExportedBatch()
             }
         }
 
+        [SkippableFact]

Review Comment:
   Hmm looks like the Python integration tests aren't running in CI: 
https://github.com/apache/arrow-dotnet/actions/runs/22278848542/job/64445654891?pr=268#step:6:163
   
   I made #270 to follow up on this. Looks like it might be caused by Python 
3.14 being used in CI, which PythonNet doesn't seem to support yet.
   
   Not a blocker for this PR though, I ran the tests on my machine with Python 
3.12.



##########
test/Apache.Arrow.Tests/ExtensionTypeTests.cs:
##########
@@ -0,0 +1,355 @@
+// 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.Generic;
+using System.IO;
+using System.Linq;
+using Apache.Arrow.Arrays;
+using Apache.Arrow.Ipc;
+using Apache.Arrow.Types;
+using Xunit;
+
+namespace Apache.Arrow.Tests
+{
+    public class ExtensionTypeTests
+    {
+        private static RecordBatch BuildGuidRecordBatch(Guid?[] values)
+        {
+            var builder = new GuidArray.Builder();
+
+            foreach (var value in values)
+            {
+                if (value.HasValue)
+                {
+                    builder.Append(value.Value);
+                }
+                else
+                {
+                    builder.AppendNull();
+                }
+            }
+            var guidArray = builder.Build();
+
+            var field = new Field("guids", GuidType.Default, true,
+                new Dictionary<string, string>
+                {
+                    ["ARROW:extension:name"] = "arrow.uuid",
+                    ["ARROW:extension:metadata"] = ""
+                });

Review Comment:
   I think this metadata should be removed here, we want to test that it gets 
automatically added when writing to IPC right?



##########
test/Apache.Arrow.Tests/ExtensionTypeTests.cs:
##########
@@ -0,0 +1,355 @@
+// 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.Generic;
+using System.IO;
+using System.Linq;
+using Apache.Arrow.Arrays;
+using Apache.Arrow.Ipc;
+using Apache.Arrow.Types;
+using Xunit;
+
+namespace Apache.Arrow.Tests
+{
+    public class ExtensionTypeTests
+    {
+        private static RecordBatch BuildGuidRecordBatch(Guid?[] values)
+        {
+            var builder = new GuidArray.Builder();
+
+            foreach (var value in values)
+            {
+                if (value.HasValue)
+                {
+                    builder.Append(value.Value);
+                }
+                else
+                {
+                    builder.AppendNull();
+                }
+            }
+            var guidArray = builder.Build();
+
+            var field = new Field("guids", GuidType.Default, true,
+                new Dictionary<string, string>
+                {
+                    ["ARROW:extension:name"] = "arrow.uuid",
+                    ["ARROW:extension:metadata"] = ""
+                });
+            var schema = new Schema(new[] { field }, null);
+
+            return new RecordBatch(schema, new[] { guidArray }, values.Length);
+        }
+
+        [Fact]
+        public void ExtensionTypeProperties()
+        {
+            var guidType = new GuidType();
+            Assert.Equal(ArrowTypeId.Extension, guidType.TypeId);
+            Assert.Equal("arrow.uuid", guidType.Name);
+            Assert.Equal("", guidType.ExtensionMetadata);
+            Assert.True(guidType.IsFixedWidth);
+            Assert.IsType<FixedSizeBinaryType>(guidType.StorageType);
+            Assert.Equal(16, 
((FixedSizeBinaryType)guidType.StorageType).ByteWidth);
+        }
+
+        [Fact]
+        public void GuidArrayReadValues()
+        {
+            var guids = new Guid?[]
+            {
+                Guid.Parse("01234567-89ab-cdef-0123-456789abcdef"),
+                null,
+                Guid.Parse("fedcba98-7654-3210-fedc-ba9876543210"),
+            };
+
+            var batch = BuildGuidRecordBatch(guids);
+            var array = (GuidArray)batch.Column(0);
+
+            Assert.Equal(3, array.Length);
+            Assert.Equal(guids[0], array.GetGuid(0));
+            Assert.Null(array.GetGuid(1));
+            Assert.True(array.IsNull(1));
+            Assert.Equal(guids[2], array.GetGuid(2));
+        }
+
+        [Fact]
+        public void GuidArraySlice()
+        {
+            var guids = new Guid?[]
+            {
+                Guid.NewGuid(),
+                Guid.NewGuid(),
+                Guid.NewGuid(),
+                Guid.NewGuid(),
+            };
+
+            var batch = BuildGuidRecordBatch(guids);
+            var array = (GuidArray)batch.Column(0);
+
+            var sliced = ArrowArrayFactory.Slice(array, 1, 2);
+            Assert.IsType<GuidArray>(sliced);
+            var slicedGuid = (GuidArray)sliced;
+            Assert.Equal(2, slicedGuid.Length);
+            Assert.Equal(guids[1], slicedGuid.GetGuid(0));
+            Assert.Equal(guids[2], slicedGuid.GetGuid(1));
+        }
+
+        [Fact]
+        public void IpcStreamRoundTrip()
+        {
+            var registry = new ExtensionTypeRegistry();
+            registry.Register(GuidExtensionDefinition.Instance);
+            var context = new ArrowContext(extensionRegistry: registry);
+
+            var guids = new Guid?[]
+            {
+                Guid.Parse("01234567-89ab-cdef-0123-456789abcdef"),
+                null,
+                Guid.Parse("fedcba98-7654-3210-fedc-ba9876543210"),
+            };
+
+            var batch = BuildGuidRecordBatch(guids);
+
+            // Write
+            var stream = new MemoryStream();
+            var writer = new ArrowStreamWriter(stream, batch.Schema);
+            writer.WriteRecordBatch(batch);
+            writer.WriteEnd();
+            stream.Position = 0;
+
+            // Read with extension registry
+            var reader = new ArrowStreamReader(context, stream);
+            var readBatch = reader.ReadNextRecordBatch();
+
+            Assert.NotNull(readBatch);
+            Assert.Equal(3, readBatch.Length);
+
+            var readArray = readBatch.Column(0);
+            Assert.IsType<GuidArray>(readArray);
+
+            var guidArray = (GuidArray)readArray;
+            Assert.Equal(guids[0], guidArray.GetGuid(0));
+            Assert.Null(guidArray.GetGuid(1));
+            Assert.Equal(guids[2], guidArray.GetGuid(2));
+        }
+
+        [Fact]
+        public void IpcStreamRoundTripWithoutRegistration()
+        {
+            // Without registering the extension type, the field should come 
back
+            // as a FixedSizeBinaryArray (backwards compat)
+            var guids = new Guid?[]
+            {
+                Guid.Parse("01234567-89ab-cdef-0123-456789abcdef"),
+                null,
+                Guid.Parse("fedcba98-7654-3210-fedc-ba9876543210"),
+            };
+
+            var batch = BuildGuidRecordBatch(guids);
+
+            // Write
+            var stream = new MemoryStream();
+            var writer = new ArrowStreamWriter(stream, batch.Schema);
+            writer.WriteRecordBatch(batch);
+            writer.WriteEnd();
+            stream.Position = 0;
+
+            // Read without extension registry
+            var context = new ArrowContext(extensionRegistry: new 
ExtensionTypeRegistry());
+            var reader = new ArrowStreamReader(context, stream);
+            var readBatch = reader.ReadNextRecordBatch();
+
+            Assert.NotNull(readBatch);
+            var readArray = readBatch.Column(0);
+            Assert.IsType<FixedSizeBinaryArray>(readArray);
+            Assert.IsNotType<GuidArray>(readArray);
+        }
+
+        [Fact]
+        public void ExtensionMetadataPreservedInRoundTrip()
+        {
+            var guids = new Guid?[] { Guid.NewGuid() };
+            var batch = BuildGuidRecordBatch(guids);
+
+            // Write
+            var stream = new MemoryStream();
+            var writer = new ArrowStreamWriter(stream, batch.Schema);
+            writer.WriteRecordBatch(batch);
+            writer.WriteEnd();
+            stream.Position = 0;
+
+            // Read without extension registry — metadata should still be on 
the field
+            var context = new ArrowContext(extensionRegistry: new 
ExtensionTypeRegistry());
+            var reader = new ArrowStreamReader(context, stream);
+            var readBatch = reader.ReadNextRecordBatch();
+
+            Assert.NotNull(readBatch);
+            var field = readBatch.Schema.GetFieldByIndex(0);
+            Assert.NotNull(field.Metadata);
+            Assert.True(field.Metadata.ContainsKey("ARROW:extension:name"));
+            Assert.Equal("arrow.uuid", field.Metadata["ARROW:extension:name"]);
+        }
+
+        [Fact]
+        public void ContextIsolation()
+        {
+            // Two contexts with different registries should resolve 
differently
+            var registry1 = new ExtensionTypeRegistry();
+            registry1.Register(GuidExtensionDefinition.Instance);
+            var context1 = new ArrowContext(extensionRegistry: registry1);
+
+            var registry2 = new ExtensionTypeRegistry();
+            // registry2 has no Guid definition registered
+            var context2 = new ArrowContext(extensionRegistry: registry2);
+
+            var guids = new Guid?[] { Guid.NewGuid() };
+            var batch = BuildGuidRecordBatch(guids);
+
+            var stream1 = new MemoryStream();
+            var writer = new ArrowStreamWriter(stream1, batch.Schema);
+            writer.WriteRecordBatch(batch);
+            writer.WriteEnd();
+
+            // Read with context1 — should resolve as GuidArray
+            stream1.Position = 0;
+            var reader1 = new ArrowStreamReader(context1, stream1);
+            var readBatch1 = reader1.ReadNextRecordBatch();
+            Assert.IsType<GuidArray>(readBatch1.Column(0));
+
+            // Read with context2 — should resolve as FixedSizeBinaryArray
+            stream1.Position = 0;
+            var reader2 = new ArrowStreamReader(context2, stream1);
+            var readBatch2 = reader2.ReadNextRecordBatch();
+            Assert.IsType<FixedSizeBinaryArray>(readBatch2.Column(0));
+        }
+
+        [Fact]
+        public void ExtensionDefinitionRejectsWrongStorageType()
+        {
+            var def = GuidExtensionDefinition.Instance;
+
+            // Should fail for wrong byte width
+            Assert.False(def.TryCreateType(new FixedSizeBinaryType(8), "", out 
_));
+
+            // Should fail for non-FixedSizeBinary type
+            Assert.False(def.TryCreateType(Int32Type.Default, "", out _));
+
+            // Should succeed for correct type
+            Assert.True(def.TryCreateType(new FixedSizeBinaryType(16), "", out 
var extType));
+            Assert.IsType<GuidType>(extType);
+        }
+
+        [Fact]
+        public void ExtensionTypeRegistryClone()
+        {
+            var registry = new ExtensionTypeRegistry();
+            registry.Register(GuidExtensionDefinition.Instance);
+
+            var clone = registry.Clone();
+
+            Assert.True(clone.TryGetDefinition("arrow.uuid", out _));
+
+            // Mutating clone should not affect original
+            var newRegistry = new ExtensionTypeRegistry();
+            Assert.False(newRegistry.TryGetDefinition("arrow.uuid", out _));
+        }
+
+        [Fact]
+        public void ArrowArrayFactoryBuildExtension()
+        {
+            var guidType = new GuidType();
+            var guid = Guid.NewGuid();
+
+            var buffers = new[]
+            {
+                ArrowBuffer.Empty,
+                new 
ArrowBuffer.Builder<byte>().Append(GuidArray.GuidToBytes(guid)).Build()
+            };
+            var data = new ArrayData(guidType, 1, 0, 0, buffers);
+
+            var array = ArrowArrayFactory.BuildArray(data);
+            Assert.IsType<GuidArray>(array);
+            var guidArray = (GuidArray)array;
+            Assert.Equal(guid, guidArray.GetGuid(0));
+        }
+
+        [Fact]
+        public unsafe void CDataSchemaRoundTrip()
+        {
+            var registry = new ExtensionTypeRegistry();
+            registry.Register(GuidExtensionDefinition.Instance);
+
+            var guidType = new GuidType();
+            var field = new Field("uuid_field", guidType, true,
+                new Dictionary<string, string>
+                {
+                    ["ARROW:extension:name"] = "arrow.uuid",
+                    ["ARROW:extension:metadata"] = ""
+                });

Review Comment:
   We shouldn't be setting the metadata manually here, we want to test it gets 
added by the export process.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to