rustyconover commented on code in PR #928:
URL: https://github.com/apache/arrow-nanoarrow/pull/928#discussion_r4180224254


##########
src/nanoarrow/ipc/writer.c:
##########
@@ -357,57 +430,386 @@ ArrowErrorCode ArrowIpcWriterWriteDictionaryBatch(
                                     &private->buffer),
       error);
 
-  if (private->writing_file) {
-    _NANOARROW_CHECK_RANGE(private->buffer.size_bytes, 0, INT32_MAX);
-    struct ArrowIpcFileBlock block = {
-        .offset = private->bytes_written,
-        .metadata_length = (int32_t) private->buffer.size_bytes,
-        .body_length = private->body_buffer.size_bytes,
-    };
-    NANOARROW_RETURN_NOT_OK_WITH_ERROR(
-        ArrowBufferAppend(&private->footer.dictionary_blocks, &block, 
sizeof(block)),
-        error);
+  return ArrowIpcWriterWriteEncodedDictionaryBatch(writer, error);
+}
+
+static struct ArrowIpcWriterDictionaryCacheEntry* 
ArrowIpcWriterFindDictionaryCacheEntry(
+    struct ArrowIpcWriterPrivate* private, int64_t dictionary_id) {
+  int64_t n_cached_dictionaries =
+      private->dictionary_cache.size_bytes /
+      (int64_t)sizeof(struct ArrowIpcWriterDictionaryCacheEntry);
+  struct ArrowIpcWriterDictionaryCacheEntry* cached_dictionaries =
+      (struct 
ArrowIpcWriterDictionaryCacheEntry*)private->dictionary_cache.data;
+  for (int64_t i = 0; i < n_cached_dictionaries; i++) {
+    if (cached_dictionaries[i].dictionary_id == dictionary_id) {
+      return &cached_dictionaries[i];
+    }
+  }
+
+  return NULL;
+}
+
+static ArrowErrorCode ArrowIpcWriterArrayViewInitLike(struct ArrowArrayView* 
out,
+                                                      const struct 
ArrowArrayView* src) {
+  ArrowArrayViewInitFromType(out, src->storage_type);
+  out->layout = src->layout;
+
+  ArrowErrorCode result = ArrowArrayViewAllocateChildren(out, src->n_children);
+  if (result != NANOARROW_OK) {
+    ArrowArrayViewReset(out);
+    return result;
+  }
+
+  for (int64_t i = 0; i < src->n_children; i++) {
+    result = ArrowIpcWriterArrayViewInitLike(out->children[i], 
src->children[i]);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
+  }
+
+  if (src->dictionary != NULL) {
+    result = ArrowArrayViewAllocateDictionary(out);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
+
+    result = ArrowIpcWriterArrayViewInitLike(out->dictionary, src->dictionary);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
   }
-  private->bytes_written += private->buffer.size_bytes;
-  private->bytes_written += private->body_buffer.size_bytes;
 
-  NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite(
-      &private->output_stream, ArrowBufferToBufferView(&private->buffer), 
error));
-  NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite(
-      &private->output_stream, ArrowBufferToBufferView(&private->body_buffer), 
error));
   return NANOARROW_OK;
 }
 
+static void ArrowIpcWriterCanonicalizeBitmapPadding(
+    struct ArrowArray* array, const struct ArrowArrayView* array_view) {
+  int64_t remainder = array->length % 8;
+  if (remainder != 0) {
+    uint8_t mask = (uint8_t)((1U << remainder) - 1U);
+    for (int i = 0; i < NANOARROW_MAX_FIXED_BUFFERS; i++) {
+      if (array_view->layout.element_size_bits[i] == 1) {
+        struct ArrowBuffer* buffer = ArrowArrayBuffer(array, i);
+        if (buffer->size_bytes > 0) {
+          buffer->data[buffer->size_bytes - 1] &= mask;
+        }
+      }
+    }
+  }
+
+  for (int64_t i = 0; i < array->n_children; i++) {
+    ArrowIpcWriterCanonicalizeBitmapPadding(array->children[i], 
array_view->children[i]);
+  }
+
+  if (array->dictionary != NULL) {
+    ArrowIpcWriterCanonicalizeBitmapPadding(array->dictionary, 
array_view->dictionary);
+  }
+}
+
+static ArrowErrorCode ArrowIpcWriterMaterializeArrayView(const struct 
ArrowArrayView* src,
+                                                         int64_t offset, 
int64_t length,
+                                                         struct ArrowArray* 
out,
+                                                         struct ArrowError* 
error) {
+  out->release = NULL;
+  if (offset < 0 || length < 0 || offset > src->length || length > src->length 
- offset) {
+    ArrowErrorSet(error,
+                  "Invalid dictionary slice [%" PRId64 ", %" PRId64
+                  ") for array of length %" PRId64,
+                  offset, offset + length, src->length);
+    return EINVAL;
+  }
+
+  struct ArrowArrayView slice = *src;
+  slice.offset += offset;
+  slice.length = length;
+  slice.null_count = -1;
+
+  ArrowErrorCode result = ArrowArrayInitFromArrayView(out, src, error);
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayStartAppending(out);
+  }

Review Comment:
   Gone along with the materialized copies; there are no more `result` chains 
in the writer.



##########
src/nanoarrow/ipc/writer.c:
##########
@@ -357,57 +430,386 @@ ArrowErrorCode ArrowIpcWriterWriteDictionaryBatch(
                                     &private->buffer),
       error);
 
-  if (private->writing_file) {
-    _NANOARROW_CHECK_RANGE(private->buffer.size_bytes, 0, INT32_MAX);
-    struct ArrowIpcFileBlock block = {
-        .offset = private->bytes_written,
-        .metadata_length = (int32_t) private->buffer.size_bytes,
-        .body_length = private->body_buffer.size_bytes,
-    };
-    NANOARROW_RETURN_NOT_OK_WITH_ERROR(
-        ArrowBufferAppend(&private->footer.dictionary_blocks, &block, 
sizeof(block)),
-        error);
+  return ArrowIpcWriterWriteEncodedDictionaryBatch(writer, error);
+}
+
+static struct ArrowIpcWriterDictionaryCacheEntry* 
ArrowIpcWriterFindDictionaryCacheEntry(
+    struct ArrowIpcWriterPrivate* private, int64_t dictionary_id) {
+  int64_t n_cached_dictionaries =
+      private->dictionary_cache.size_bytes /
+      (int64_t)sizeof(struct ArrowIpcWriterDictionaryCacheEntry);
+  struct ArrowIpcWriterDictionaryCacheEntry* cached_dictionaries =
+      (struct 
ArrowIpcWriterDictionaryCacheEntry*)private->dictionary_cache.data;
+  for (int64_t i = 0; i < n_cached_dictionaries; i++) {
+    if (cached_dictionaries[i].dictionary_id == dictionary_id) {
+      return &cached_dictionaries[i];
+    }
+  }
+
+  return NULL;
+}
+
+static ArrowErrorCode ArrowIpcWriterArrayViewInitLike(struct ArrowArrayView* 
out,
+                                                      const struct 
ArrowArrayView* src) {
+  ArrowArrayViewInitFromType(out, src->storage_type);
+  out->layout = src->layout;
+
+  ArrowErrorCode result = ArrowArrayViewAllocateChildren(out, src->n_children);
+  if (result != NANOARROW_OK) {
+    ArrowArrayViewReset(out);
+    return result;
+  }
+
+  for (int64_t i = 0; i < src->n_children; i++) {
+    result = ArrowIpcWriterArrayViewInitLike(out->children[i], 
src->children[i]);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
+  }
+
+  if (src->dictionary != NULL) {
+    result = ArrowArrayViewAllocateDictionary(out);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
+
+    result = ArrowIpcWriterArrayViewInitLike(out->dictionary, src->dictionary);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
   }
-  private->bytes_written += private->buffer.size_bytes;
-  private->bytes_written += private->body_buffer.size_bytes;
 
-  NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite(
-      &private->output_stream, ArrowBufferToBufferView(&private->buffer), 
error));
-  NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite(
-      &private->output_stream, ArrowBufferToBufferView(&private->body_buffer), 
error));
   return NANOARROW_OK;
 }
 
+static void ArrowIpcWriterCanonicalizeBitmapPadding(
+    struct ArrowArray* array, const struct ArrowArrayView* array_view) {
+  int64_t remainder = array->length % 8;
+  if (remainder != 0) {
+    uint8_t mask = (uint8_t)((1U << remainder) - 1U);
+    for (int i = 0; i < NANOARROW_MAX_FIXED_BUFFERS; i++) {
+      if (array_view->layout.element_size_bits[i] == 1) {
+        struct ArrowBuffer* buffer = ArrowArrayBuffer(array, i);
+        if (buffer->size_bytes > 0) {
+          buffer->data[buffer->size_bytes - 1] &= mask;
+        }
+      }
+    }
+  }
+
+  for (int64_t i = 0; i < array->n_children; i++) {
+    ArrowIpcWriterCanonicalizeBitmapPadding(array->children[i], 
array_view->children[i]);
+  }
+
+  if (array->dictionary != NULL) {
+    ArrowIpcWriterCanonicalizeBitmapPadding(array->dictionary, 
array_view->dictionary);
+  }
+}
+
+static ArrowErrorCode ArrowIpcWriterMaterializeArrayView(const struct 
ArrowArrayView* src,
+                                                         int64_t offset, 
int64_t length,
+                                                         struct ArrowArray* 
out,
+                                                         struct ArrowError* 
error) {
+  out->release = NULL;
+  if (offset < 0 || length < 0 || offset > src->length || length > src->length 
- offset) {
+    ArrowErrorSet(error,
+                  "Invalid dictionary slice [%" PRId64 ", %" PRId64
+                  ") for array of length %" PRId64,
+                  offset, offset + length, src->length);
+    return EINVAL;
+  }
+
+  struct ArrowArrayView slice = *src;
+  slice.offset += offset;
+  slice.length = length;
+  slice.null_count = -1;
+
+  ArrowErrorCode result = ArrowArrayInitFromArrayView(out, src, error);
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayStartAppending(out);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayReserve(out, length);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayAppendStorageFromArrayView(out, &slice, error);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayFinishBuildingDefault(out, error);
+  }
+  if (result == NANOARROW_OK) {
+    // Arrow bitmaps do not require producers to initialize padding bits. 
Clear them so
+    // physical comparisons of two otherwise identical materialized arrays 
never read
+    // indeterminate data and do not treat padding as part of dictionary 
identity.
+    ArrowIpcWriterCanonicalizeBitmapPadding(out, src);
+  }
+
+  if (result != NANOARROW_OK && out->release != NULL) {
+    ArrowArrayRelease(out);
+  }
+  return result;
+}
+
+static ArrowErrorCode ArrowIpcWriterCompareMaterializedArrays(
+    const struct ArrowArray* lhs, const struct ArrowArray* rhs,
+    const struct ArrowArrayView* shape, int* out, struct ArrowError* error) {

Review Comment:
   Done: dictionaries are now compared by shape and buffer addresses, 
recursively. To keep that safe, `WriteArrayStream()` holds the previous batch 
until the next one is written, so a released buffer's address can't be reused 
for different values mid-comparison. There's a test with a producer that 
recycles memory this way. `WriteDictionaryBatch()` only sees a borrowed view 
and always writes.



##########
src/nanoarrow/ipc/writer.c:
##########
@@ -357,57 +430,386 @@ ArrowErrorCode ArrowIpcWriterWriteDictionaryBatch(
                                     &private->buffer),
       error);
 
-  if (private->writing_file) {
-    _NANOARROW_CHECK_RANGE(private->buffer.size_bytes, 0, INT32_MAX);
-    struct ArrowIpcFileBlock block = {
-        .offset = private->bytes_written,
-        .metadata_length = (int32_t) private->buffer.size_bytes,
-        .body_length = private->body_buffer.size_bytes,
-    };
-    NANOARROW_RETURN_NOT_OK_WITH_ERROR(
-        ArrowBufferAppend(&private->footer.dictionary_blocks, &block, 
sizeof(block)),
-        error);
+  return ArrowIpcWriterWriteEncodedDictionaryBatch(writer, error);
+}
+
+static struct ArrowIpcWriterDictionaryCacheEntry* 
ArrowIpcWriterFindDictionaryCacheEntry(
+    struct ArrowIpcWriterPrivate* private, int64_t dictionary_id) {
+  int64_t n_cached_dictionaries =
+      private->dictionary_cache.size_bytes /
+      (int64_t)sizeof(struct ArrowIpcWriterDictionaryCacheEntry);
+  struct ArrowIpcWriterDictionaryCacheEntry* cached_dictionaries =
+      (struct 
ArrowIpcWriterDictionaryCacheEntry*)private->dictionary_cache.data;
+  for (int64_t i = 0; i < n_cached_dictionaries; i++) {
+    if (cached_dictionaries[i].dictionary_id == dictionary_id) {
+      return &cached_dictionaries[i];
+    }
+  }
+
+  return NULL;
+}
+
+static ArrowErrorCode ArrowIpcWriterArrayViewInitLike(struct ArrowArrayView* 
out,
+                                                      const struct 
ArrowArrayView* src) {
+  ArrowArrayViewInitFromType(out, src->storage_type);
+  out->layout = src->layout;
+
+  ArrowErrorCode result = ArrowArrayViewAllocateChildren(out, src->n_children);
+  if (result != NANOARROW_OK) {
+    ArrowArrayViewReset(out);
+    return result;
+  }
+
+  for (int64_t i = 0; i < src->n_children; i++) {
+    result = ArrowIpcWriterArrayViewInitLike(out->children[i], 
src->children[i]);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
+  }
+
+  if (src->dictionary != NULL) {
+    result = ArrowArrayViewAllocateDictionary(out);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
+
+    result = ArrowIpcWriterArrayViewInitLike(out->dictionary, src->dictionary);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
   }
-  private->bytes_written += private->buffer.size_bytes;
-  private->bytes_written += private->body_buffer.size_bytes;
 
-  NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite(
-      &private->output_stream, ArrowBufferToBufferView(&private->buffer), 
error));
-  NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite(
-      &private->output_stream, ArrowBufferToBufferView(&private->body_buffer), 
error));
   return NANOARROW_OK;
 }
 
+static void ArrowIpcWriterCanonicalizeBitmapPadding(
+    struct ArrowArray* array, const struct ArrowArrayView* array_view) {
+  int64_t remainder = array->length % 8;
+  if (remainder != 0) {
+    uint8_t mask = (uint8_t)((1U << remainder) - 1U);
+    for (int i = 0; i < NANOARROW_MAX_FIXED_BUFFERS; i++) {
+      if (array_view->layout.element_size_bits[i] == 1) {
+        struct ArrowBuffer* buffer = ArrowArrayBuffer(array, i);
+        if (buffer->size_bytes > 0) {
+          buffer->data[buffer->size_bytes - 1] &= mask;
+        }
+      }
+    }
+  }
+
+  for (int64_t i = 0; i < array->n_children; i++) {
+    ArrowIpcWriterCanonicalizeBitmapPadding(array->children[i], 
array_view->children[i]);
+  }
+
+  if (array->dictionary != NULL) {
+    ArrowIpcWriterCanonicalizeBitmapPadding(array->dictionary, 
array_view->dictionary);
+  }
+}
+
+static ArrowErrorCode ArrowIpcWriterMaterializeArrayView(const struct 
ArrowArrayView* src,
+                                                         int64_t offset, 
int64_t length,
+                                                         struct ArrowArray* 
out,
+                                                         struct ArrowError* 
error) {
+  out->release = NULL;
+  if (offset < 0 || length < 0 || offset > src->length || length > src->length 
- offset) {
+    ArrowErrorSet(error,
+                  "Invalid dictionary slice [%" PRId64 ", %" PRId64
+                  ") for array of length %" PRId64,
+                  offset, offset + length, src->length);
+    return EINVAL;
+  }
+
+  struct ArrowArrayView slice = *src;
+  slice.offset += offset;
+  slice.length = length;
+  slice.null_count = -1;
+
+  ArrowErrorCode result = ArrowArrayInitFromArrayView(out, src, error);
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayStartAppending(out);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayReserve(out, length);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayAppendStorageFromArrayView(out, &slice, error);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayFinishBuildingDefault(out, error);
+  }
+  if (result == NANOARROW_OK) {
+    // Arrow bitmaps do not require producers to initialize padding bits. 
Clear them so
+    // physical comparisons of two otherwise identical materialized arrays 
never read
+    // indeterminate data and do not treat padding as part of dictionary 
identity.
+    ArrowIpcWriterCanonicalizeBitmapPadding(out, src);
+  }
+
+  if (result != NANOARROW_OK && out->release != NULL) {
+    ArrowArrayRelease(out);
+  }
+  return result;
+}
+
+static ArrowErrorCode ArrowIpcWriterCompareMaterializedArrays(
+    const struct ArrowArray* lhs, const struct ArrowArray* rhs,
+    const struct ArrowArrayView* shape, int* out, struct ArrowError* error) {
+  struct ArrowArrayView lhs_view;
+  struct ArrowArrayView rhs_view;
+  ArrowArrayViewInitFromType(&lhs_view, NANOARROW_TYPE_UNINITIALIZED);
+  ArrowArrayViewInitFromType(&rhs_view, NANOARROW_TYPE_UNINITIALIZED);
+
+  ArrowErrorCode result = ArrowIpcWriterArrayViewInitLike(&lhs_view, shape);
+  if (result == NANOARROW_OK) {
+    result = ArrowIpcWriterArrayViewInitLike(&rhs_view, shape);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayViewSetArray(&lhs_view, lhs, error);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayViewSetArray(&rhs_view, rhs, error);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayViewCompare(&lhs_view, &rhs_view, 
NANOARROW_COMPARE_IDENTICAL, out,
+                                   NULL);
+  }
+
+  ArrowArrayViewReset(&lhs_view);
+  ArrowArrayViewReset(&rhs_view);
+  return result;
+}
+
+static ArrowErrorCode ArrowIpcWriterArrayViewSetMaterialized(
+    struct ArrowArrayView* out, const struct ArrowArrayView* shape,
+    const struct ArrowArray* array, struct ArrowError* error) {
+  ArrowArrayViewInitFromType(out, NANOARROW_TYPE_UNINITIALIZED);
+  NANOARROW_RETURN_NOT_OK(ArrowIpcWriterArrayViewInitLike(out, shape));
+  ArrowErrorCode result = ArrowArrayViewSetArray(out, array, error);
+  if (result != NANOARROW_OK) {
+    ArrowArrayViewReset(out);
+  }
+  return result;
+}
+
+static ArrowErrorCode ArrowIpcWriterWriteDictionaryBatchIfChanged(
+    struct ArrowIpcWriter* writer, int64_t dictionary_id,
+    const struct ArrowArrayView* values_view, int force_emit, int allow_delta,
+    int* emitted, struct ArrowError* error) {
+  struct ArrowIpcWriterPrivate* private =
+      (struct ArrowIpcWriterPrivate*)writer->private_data;
+
+  struct ArrowArray current_values = {.release = NULL};
+  struct ArrowArray prefix_values = {.release = NULL};
+  struct ArrowArray delta_values = {.release = NULL};
+  struct ArrowArrayView encoded_view;
+  ArrowArrayViewInitFromType(&encoded_view, NANOARROW_TYPE_UNINITIALIZED);
+
+  ArrowErrorCode result = ArrowIpcWriterMaterializeArrayView(
+      values_view, 0, values_view->length, &current_values, error);
+  if (result != NANOARROW_OK) {
+    return result;
+  }
+
+  struct ArrowIpcWriterDictionaryCacheEntry* cached =
+      ArrowIpcWriterFindDictionaryCacheEntry(private, dictionary_id);
+  int values_equal = 0;
+  char is_delta = 0;
+  int cached_was_added = 0;
+  const struct ArrowArray* values_to_encode = NULL;
+  if (cached != NULL) {
+    result = ArrowIpcWriterCompareMaterializedArrays(&cached->values, 
&current_values,
+                                                     values_view, 
&values_equal, error);
+    if (result != NANOARROW_OK) {
+      goto cleanup;
+    }
+  }
+
+  if (!force_emit && cached != NULL && values_equal) {
+    *emitted = 0;
+    result = NANOARROW_OK;
+    goto cleanup;
+  }
+
+  if (allow_delta && !force_emit && cached != NULL &&
+      current_values.length > cached->values.length) {
+    result = ArrowIpcWriterMaterializeArrayView(values_view, 0, 
cached->values.length,
+                                                &prefix_values, error);
+    if (result != NANOARROW_OK) {
+      goto cleanup;
+    }
+
+    int prefix_equal = 0;
+    result = ArrowIpcWriterCompareMaterializedArrays(&cached->values, 
&prefix_values,
+                                                     values_view, 
&prefix_equal, error);
+    if (result != NANOARROW_OK) {
+      goto cleanup;
+    }
+
+    if (prefix_equal) {
+      result = ArrowIpcWriterMaterializeArrayView(
+          values_view, cached->values.length,
+          current_values.length - cached->values.length, &delta_values, error);
+      if (result != NANOARROW_OK) {
+        goto cleanup;
+      }
+      is_delta = 1;

Review Comment:
   No more `goto`. The only shared cleanup is in the existing outer 
`WriteArrayStream()` function.



##########
src/nanoarrow/ipc/writer.c:
##########
@@ -357,57 +430,386 @@ ArrowErrorCode ArrowIpcWriterWriteDictionaryBatch(
                                     &private->buffer),
       error);
 
-  if (private->writing_file) {
-    _NANOARROW_CHECK_RANGE(private->buffer.size_bytes, 0, INT32_MAX);
-    struct ArrowIpcFileBlock block = {
-        .offset = private->bytes_written,
-        .metadata_length = (int32_t) private->buffer.size_bytes,
-        .body_length = private->body_buffer.size_bytes,
-    };
-    NANOARROW_RETURN_NOT_OK_WITH_ERROR(
-        ArrowBufferAppend(&private->footer.dictionary_blocks, &block, 
sizeof(block)),
-        error);
+  return ArrowIpcWriterWriteEncodedDictionaryBatch(writer, error);
+}
+
+static struct ArrowIpcWriterDictionaryCacheEntry* 
ArrowIpcWriterFindDictionaryCacheEntry(
+    struct ArrowIpcWriterPrivate* private, int64_t dictionary_id) {
+  int64_t n_cached_dictionaries =
+      private->dictionary_cache.size_bytes /
+      (int64_t)sizeof(struct ArrowIpcWriterDictionaryCacheEntry);
+  struct ArrowIpcWriterDictionaryCacheEntry* cached_dictionaries =
+      (struct 
ArrowIpcWriterDictionaryCacheEntry*)private->dictionary_cache.data;
+  for (int64_t i = 0; i < n_cached_dictionaries; i++) {
+    if (cached_dictionaries[i].dictionary_id == dictionary_id) {
+      return &cached_dictionaries[i];
+    }
+  }
+
+  return NULL;
+}
+
+static ArrowErrorCode ArrowIpcWriterArrayViewInitLike(struct ArrowArrayView* 
out,
+                                                      const struct 
ArrowArrayView* src) {
+  ArrowArrayViewInitFromType(out, src->storage_type);
+  out->layout = src->layout;
+
+  ArrowErrorCode result = ArrowArrayViewAllocateChildren(out, src->n_children);
+  if (result != NANOARROW_OK) {
+    ArrowArrayViewReset(out);
+    return result;
+  }
+
+  for (int64_t i = 0; i < src->n_children; i++) {
+    result = ArrowIpcWriterArrayViewInitLike(out->children[i], 
src->children[i]);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
+  }
+
+  if (src->dictionary != NULL) {
+    result = ArrowArrayViewAllocateDictionary(out);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
+
+    result = ArrowIpcWriterArrayViewInitLike(out->dictionary, src->dictionary);
+    if (result != NANOARROW_OK) {
+      ArrowArrayViewReset(out);
+      return result;
+    }
   }
-  private->bytes_written += private->buffer.size_bytes;
-  private->bytes_written += private->body_buffer.size_bytes;
 
-  NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite(
-      &private->output_stream, ArrowBufferToBufferView(&private->buffer), 
error));
-  NANOARROW_RETURN_NOT_OK(ArrowIpcOutputStreamWrite(
-      &private->output_stream, ArrowBufferToBufferView(&private->body_buffer), 
error));
   return NANOARROW_OK;
 }
 
+static void ArrowIpcWriterCanonicalizeBitmapPadding(
+    struct ArrowArray* array, const struct ArrowArrayView* array_view) {
+  int64_t remainder = array->length % 8;
+  if (remainder != 0) {
+    uint8_t mask = (uint8_t)((1U << remainder) - 1U);
+    for (int i = 0; i < NANOARROW_MAX_FIXED_BUFFERS; i++) {
+      if (array_view->layout.element_size_bits[i] == 1) {
+        struct ArrowBuffer* buffer = ArrowArrayBuffer(array, i);
+        if (buffer->size_bytes > 0) {
+          buffer->data[buffer->size_bytes - 1] &= mask;
+        }
+      }
+    }
+  }
+
+  for (int64_t i = 0; i < array->n_children; i++) {
+    ArrowIpcWriterCanonicalizeBitmapPadding(array->children[i], 
array_view->children[i]);
+  }
+
+  if (array->dictionary != NULL) {
+    ArrowIpcWriterCanonicalizeBitmapPadding(array->dictionary, 
array_view->dictionary);
+  }
+}
+
+static ArrowErrorCode ArrowIpcWriterMaterializeArrayView(const struct 
ArrowArrayView* src,
+                                                         int64_t offset, 
int64_t length,
+                                                         struct ArrowArray* 
out,
+                                                         struct ArrowError* 
error) {
+  out->release = NULL;
+  if (offset < 0 || length < 0 || offset > src->length || length > src->length 
- offset) {
+    ArrowErrorSet(error,
+                  "Invalid dictionary slice [%" PRId64 ", %" PRId64
+                  ") for array of length %" PRId64,
+                  offset, offset + length, src->length);
+    return EINVAL;
+  }
+
+  struct ArrowArrayView slice = *src;
+  slice.offset += offset;
+  slice.length = length;
+  slice.null_count = -1;
+
+  ArrowErrorCode result = ArrowArrayInitFromArrayView(out, src, error);
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayStartAppending(out);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayReserve(out, length);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayAppendStorageFromArrayView(out, &slice, error);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayFinishBuildingDefault(out, error);
+  }
+  if (result == NANOARROW_OK) {
+    // Arrow bitmaps do not require producers to initialize padding bits. 
Clear them so
+    // physical comparisons of two otherwise identical materialized arrays 
never read
+    // indeterminate data and do not treat padding as part of dictionary 
identity.
+    ArrowIpcWriterCanonicalizeBitmapPadding(out, src);
+  }
+
+  if (result != NANOARROW_OK && out->release != NULL) {
+    ArrowArrayRelease(out);
+  }
+  return result;
+}
+
+static ArrowErrorCode ArrowIpcWriterCompareMaterializedArrays(
+    const struct ArrowArray* lhs, const struct ArrowArray* rhs,
+    const struct ArrowArrayView* shape, int* out, struct ArrowError* error) {
+  struct ArrowArrayView lhs_view;
+  struct ArrowArrayView rhs_view;
+  ArrowArrayViewInitFromType(&lhs_view, NANOARROW_TYPE_UNINITIALIZED);
+  ArrowArrayViewInitFromType(&rhs_view, NANOARROW_TYPE_UNINITIALIZED);
+
+  ArrowErrorCode result = ArrowIpcWriterArrayViewInitLike(&lhs_view, shape);
+  if (result == NANOARROW_OK) {
+    result = ArrowIpcWriterArrayViewInitLike(&rhs_view, shape);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayViewSetArray(&lhs_view, lhs, error);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayViewSetArray(&rhs_view, rhs, error);
+  }
+  if (result == NANOARROW_OK) {
+    result = ArrowArrayViewCompare(&lhs_view, &rhs_view, 
NANOARROW_COMPARE_IDENTICAL, out,
+                                   NULL);
+  }
+
+  ArrowArrayViewReset(&lhs_view);
+  ArrowArrayViewReset(&rhs_view);
+  return result;
+}
+
+static ArrowErrorCode ArrowIpcWriterArrayViewSetMaterialized(
+    struct ArrowArrayView* out, const struct ArrowArrayView* shape,
+    const struct ArrowArray* array, struct ArrowError* error) {
+  ArrowArrayViewInitFromType(out, NANOARROW_TYPE_UNINITIALIZED);
+  NANOARROW_RETURN_NOT_OK(ArrowIpcWriterArrayViewInitLike(out, shape));
+  ArrowErrorCode result = ArrowArrayViewSetArray(out, array, error);
+  if (result != NANOARROW_OK) {
+    ArrowArrayViewReset(out);
+  }
+  return result;
+}
+
+static ArrowErrorCode ArrowIpcWriterWriteDictionaryBatchIfChanged(
+    struct ArrowIpcWriter* writer, int64_t dictionary_id,
+    const struct ArrowArrayView* values_view, int force_emit, int allow_delta,
+    int* emitted, struct ArrowError* error) {
+  struct ArrowIpcWriterPrivate* private =
+      (struct ArrowIpcWriterPrivate*)writer->private_data;
+
+  struct ArrowArray current_values = {.release = NULL};
+  struct ArrowArray prefix_values = {.release = NULL};
+  struct ArrowArray delta_values = {.release = NULL};
+  struct ArrowArrayView encoded_view;
+  ArrowArrayViewInitFromType(&encoded_view, NANOARROW_TYPE_UNINITIALIZED);
+
+  ArrowErrorCode result = ArrowIpcWriterMaterializeArrayView(
+      values_view, 0, values_view->length, &current_values, error);
+  if (result != NANOARROW_OK) {
+    return result;
+  }
+
+  struct ArrowIpcWriterDictionaryCacheEntry* cached =
+      ArrowIpcWriterFindDictionaryCacheEntry(private, dictionary_id);
+  int values_equal = 0;
+  char is_delta = 0;
+  int cached_was_added = 0;
+  const struct ArrowArray* values_to_encode = NULL;
+  if (cached != NULL) {
+    result = ArrowIpcWriterCompareMaterializedArrays(&cached->values, 
&current_values,
+                                                     values_view, 
&values_equal, error);
+    if (result != NANOARROW_OK) {
+      goto cleanup;
+    }
+  }
+
+  if (!force_emit && cached != NULL && values_equal) {
+    *emitted = 0;
+    result = NANOARROW_OK;
+    goto cleanup;
+  }
+
+  if (allow_delta && !force_emit && cached != NULL &&
+      current_values.length > cached->values.length) {
+    result = ArrowIpcWriterMaterializeArrayView(values_view, 0, 
cached->values.length,
+                                                &prefix_values, error);
+    if (result != NANOARROW_OK) {
+      goto cleanup;
+    }

Review Comment:
   Agreed. Delta emission is removed from the writer entirely. Applications 
that want deltas can write them with `ArrowIpcWriterWriteDictionaryBatch(..., 
is_delta = 1, ...)`, so I'm not planning a separate PR for it.



-- 
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