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

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


The following commit(s) were added to refs/heads/main by this push:
     new f1a66ffe feat: Use reference counted arrays when decoding batches that 
contain dictionaries (#895)
f1a66ffe is described below

commit f1a66ffe8f60957b064e4796772bad7d49db75e8
Author: Dewey Dunnington <[email protected]>
AuthorDate: Wed Jul 29 21:18:09 2026 -0400

    feat: Use reference counted arrays when decoding batches that contain 
dictionaries (#895)
    
    This PR updates the dictionary decoder to use the reference counted
    array clones in its dictionary decoding process. This is already covered
    by existing test code (dictionary decoding already worked, the
    dictionary values just weren't shared).
---
 src/nanoarrow/common/array.c      | 14 ++++++++++--
 src/nanoarrow/common/utils.c      |  3 ++-
 src/nanoarrow/ipc/decoder.c       | 48 +++++++++++++++++++++++++++++++++------
 src/nanoarrow/ipc/decoder_test.cc |  4 ++--
 src/nanoarrow/nanoarrow.h         |  5 +++-
 5 files changed, 61 insertions(+), 13 deletions(-)

diff --git a/src/nanoarrow/common/array.c b/src/nanoarrow/common/array.c
index 7184c827..68c42d0d 100644
--- a/src/nanoarrow/common/array.c
+++ b/src/nanoarrow/common/array.c
@@ -701,8 +701,18 @@ ArrowErrorCode ArrowArrayMoveShared(struct ArrowArray* 
array, struct ArrowArray*
   struct ArrowArray tmp;
   tmp.release = NULL;
   ArrowErrorCode result = ArrowArrayMoveSharedInternal(array, shared);
-  if (result != NANOARROW_OK && tmp.release != NULL) {
-    ArrowArrayRelease(&tmp);
+  if (result != NANOARROW_OK) {
+    // On failure, release the temporary output
+    if (tmp.release != NULL) {
+      ArrowArrayRelease(&tmp);
+    }
+
+    // Because this operation may have partially moved the input array at this 
point
+    // we also have to release it on failure to be predictable. These failures 
are
+    // usually failed heap allocations and are difficult to trigger in 
practice.
+    if (array->release != NULL) {
+      ArrowArrayRelease(array);
+    }
   }
 
   return result;
diff --git a/src/nanoarrow/common/utils.c b/src/nanoarrow/common/utils.c
index 976a509b..c86d00b3 100644
--- a/src/nanoarrow/common/utils.c
+++ b/src/nanoarrow/common/utils.c
@@ -434,7 +434,8 @@ int ArrowIsSharedBuffer(struct ArrowBuffer* buffer) {
 
 ArrowErrorCode ArrowSharedBufferClone(struct ArrowBuffer* shared,
                                       struct ArrowBuffer* shared_out) {
-  if (shared->size_bytes == 0) {
+  // If the buffer has no data, initialize an empty buffer
+  if (shared->data == NULL) {
     ArrowBufferInit(shared_out);
     return NANOARROW_OK;
   }
diff --git a/src/nanoarrow/ipc/decoder.c b/src/nanoarrow/ipc/decoder.c
index 42ba2457..c9687f0c 100644
--- a/src/nanoarrow/ipc/decoder.c
+++ b/src/nanoarrow/ipc/decoder.c
@@ -325,7 +325,11 @@ static ArrowErrorCode ArrowIpcDictionaryReplace(struct 
ArrowIpcDictionary* dicti
     ArrowArrayRelease(&dictionary->current_value);
   }
 
-  ArrowArrayMove(value, &dictionary->current_value);
+  // Convert to a shared array so that clones can reference the same data
+  // without copying
+  struct ArrowArray shared;
+  NANOARROW_RETURN_NOT_OK(ArrowArrayMoveShared(value, &shared));
+  ArrowArrayMove(&shared, &dictionary->current_value);
   return NANOARROW_OK;
 }
 
@@ -417,15 +421,26 @@ static ArrowErrorCode 
ArrowIpcDictionariesInitDictionaries(
     // Set the initial array value to a valid array with zero length. This is
     // needed because empty and/or all null columns may not have a dictionary
     // message emitted before a record batch arrives.
-    result = ArrowArrayInitFromSchema(&dictionary->current_value,
-                                      encoding->schema->dictionary, error);
+    struct ArrowArray initial_value;
+    result =
+        ArrowArrayInitFromSchema(&initial_value, encoding->schema->dictionary, 
error);
     if (result != NANOARROW_OK) {
       *num_initialized_decoders_out = i + 1;
       return result;
     }
 
-    result = ArrowArrayFinishBuildingDefault(&dictionary->current_value, 
error);
+    result = ArrowArrayFinishBuildingDefault(&initial_value, error);
     if (result != NANOARROW_OK) {
+      ArrowArrayRelease(&initial_value);
+      *num_initialized_decoders_out = i + 1;
+      return result;
+    }
+
+    // Convert to a shared array so that clones can reference the same data
+    // without copying
+    result = ArrowArrayMoveShared(&initial_value, &dictionary->current_value);
+    if (result != NANOARROW_OK) {
+      ArrowArrayRelease(&initial_value);
       *num_initialized_decoders_out = i + 1;
       return result;
     }
@@ -2319,9 +2334,12 @@ static int ArrowIpcDecoderWalkGetArray(struct 
ArrowArrayView* array_view,
   }
 
   if (array_view->dictionary != NULL) {
-    // TODO: this currently copies the array for every output.
-    NANOARROW_RETURN_NOT_OK(ArrowIpcDecoderWalkGetArray(
-        array_view->dictionary, array->dictionary, out->dictionary, error));
+    // Release the dictionary that was pre-initialized by 
ArrowArrayInitFromArrayView
+    if (out->dictionary->release != NULL) {
+      ArrowArrayRelease(out->dictionary);
+    }
+    // Move the pre-cloned shared dictionary to output (avoids copying)
+    ArrowArrayMove(array->dictionary, out->dictionary);
   }
 
   return NANOARROW_OK;
@@ -2360,6 +2378,22 @@ static int ArrowIpcDecoderWalkSetArrayView(struct 
ArrowIpcDecoder* decoder,
     // decode.
     NANOARROW_RETURN_NOT_OK(
         ArrowArrayViewSetArray(array_view->dictionary, dictionary, error));
+
+    // Clone the shared dictionary for output (avoids copying dictionary data)
+    if (array->dictionary != NULL) {
+      if (array->dictionary->release != NULL) {
+        ArrowArrayRelease(array->dictionary);
+      }
+      int clone_result =
+          ArrowArrayCloneShared((struct ArrowArray*)dictionary, 
array->dictionary);
+      if (clone_result != NANOARROW_OK) {
+        ArrowErrorSet(error,
+                      "Failed to clone shared dictionary with ID %" PRId64
+                      " (dictionary may not be shared)",
+                      ipc_field->dictionary_id);
+        return clone_result;
+      }
+    }
   }
 
   ns(FieldNode_struct_t) field =
diff --git a/src/nanoarrow/ipc/decoder_test.cc 
b/src/nanoarrow/ipc/decoder_test.cc
index 3c366017..900bc32f 100644
--- a/src/nanoarrow/ipc/decoder_test.cc
+++ b/src/nanoarrow/ipc/decoder_test.cc
@@ -1312,7 +1312,7 @@ TEST_P(ArrowTypeParameterizedTestFixture, 
NanoarrowIpcArrowArrayRoundtrip) {
             NANOARROW_OK);
 
   auto maybe_batch = arrow::ImportRecordBatch(&array, dummy_schema);
-  ASSERT_TRUE(maybe_batch.ok());
+  ASSERT_TRUE(maybe_batch.ok()) << maybe_batch.status().ToString();
   EXPECT_EQ(maybe_batch.ValueUnsafe()->ToString(), empty->ToString());
 
   // Arrow C++ MakeEmpty() loses the ordered=1 flag and unsigned index types 
for
@@ -1348,7 +1348,7 @@ TEST_P(ArrowTypeParameterizedTestFixture, 
NanoarrowIpcArrowArrayRoundtrip) {
             NANOARROW_OK);
 
   maybe_batch = arrow::ImportRecordBatch(&array, dummy_schema);
-  ASSERT_TRUE(maybe_batch.ok());
+  ASSERT_TRUE(maybe_batch.ok()) << maybe_batch.status().ToString();
   EXPECT_EQ(maybe_batch.ValueUnsafe()->ToString(), nulls->ToString());
   EXPECT_TRUE(maybe_batch.ValueUnsafe()->Equals(*nulls));
 
diff --git a/src/nanoarrow/nanoarrow.h b/src/nanoarrow/nanoarrow.h
index 1ec13876..564db40a 100644
--- a/src/nanoarrow/nanoarrow.h
+++ b/src/nanoarrow/nanoarrow.h
@@ -1154,7 +1154,10 @@ NANOARROW_DLL ArrowErrorCode ArrowArrayFinishBuilding(
 /// reference-counted. On success, shared is a new ArrowArray whose buffers
 /// are backed by ArrowSharedArray references, and array is consumed
 /// (release set to NULL). The resulting shared array can be safely moved
-/// or have its buffers cloned via ArrowSharedBufferClone().
+/// or have its buffers cloned via ArrowSharedBufferClone(). On error,
+/// (e.g., failure to allocate a copy of a buffer), the input array is
+/// released as it may have been partially moved at the point the error
+/// occurs.
 NANOARROW_DLL ArrowErrorCode ArrowArrayMoveShared(struct ArrowArray* array,
                                                   struct ArrowArray* shared);
 

Reply via email to