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 ea215b00 fix(ipc): handle missing flatbuffer tables and dictionary 
flags (#960)
ea215b00 is described below

commit ea215b000788ec2e94fc88bad5249290c94b01cd
Author: Dewey Dunnington <[email protected]>
AuthorDate: Mon Oct 12 21:12:04 2026 -0500

    fix(ipc): handle missing flatbuffer tables and dictionary flags (#960)
    
    Reject IPC messages with missing Field.type, Message.header, or
    DictionaryBatch.data tables. Validate nested dictionary record-batch
    node and buffer counts before accessing their vectors, returning EINVAL
    for missing or mismatched vectors. Preserve outer-field nullability
    while keeping dictionary values nullable.
    
    Add regressions for malformed tables, missing/empty/incorrect vector
    counts through both dictionary decoding APIs, recovery after rejected
    batches, default int32 dictionary indices, and non-nullable dictionary
    round trips.
    
    Closes #958
    Closes #959
    
    Tests: 102 IPC-related CTests passed with ZSTD and LZ4 enabled;
    pre-commit checks passed. The new vector-count regression reproduces the
    assertion failure before the fix.
---
 src/nanoarrow/ipc/decoder.c       |  66 +++++++----
 src/nanoarrow/ipc/decoder_test.cc | 235 ++++++++++++++++++++++++++++++++++----
 src/nanoarrow/ipc/writer_test.cc  |   3 +
 3 files changed, 263 insertions(+), 41 deletions(-)

diff --git a/src/nanoarrow/ipc/decoder.c b/src/nanoarrow/ipc/decoder.c
index 8eb91fe5..8953cb29 100644
--- a/src/nanoarrow/ipc/decoder.c
+++ b/src/nanoarrow/ipc/decoder.c
@@ -1139,26 +1139,30 @@ static int ArrowIpcDecoderSetTypeUnion(struct 
ArrowSchema* schema,
 
 static int ArrowIpcDecoderSetType(struct ArrowSchema* schema, 
ns(Field_table_t) field,
                                   int64_t n_children, struct ArrowError* 
error) {
-  int type_type = ns(Field_type_type(field));
+  ns(Type_union_type_t) type_type = ns(Field_type_type(field));
+  flatbuffers_generic_t type = ns(Field_type_get(field));
+  if (type == NULL) {
+    ArrowErrorSet(error, "Field type '%s' has no value", 
ns(Type_type_name(type_type)));
+    return EINVAL;
+  }
+
   switch (type_type) {
     case ns(Type_Null):
       return ArrowIpcDecoderSetTypeSimple(schema, NANOARROW_TYPE_NA, error);
     case ns(Type_Bool):
       return ArrowIpcDecoderSetTypeSimple(schema, NANOARROW_TYPE_BOOL, error);
     case ns(Type_Int):
-      return ArrowIpcDecoderSetTypeInt(schema, ns(Field_type_get(field)), 
error);
+      return ArrowIpcDecoderSetTypeInt(schema, type, error);
     case ns(Type_FloatingPoint):
-      return ArrowIpcDecoderSetTypeFloatingPoint(schema, 
ns(Field_type_get(field)),
-                                                 error);
+      return ArrowIpcDecoderSetTypeFloatingPoint(schema, type, error);
     case ns(Type_Decimal):
-      return ArrowIpcDecoderSetTypeDecimal(schema, ns(Field_type_get(field)), 
error);
+      return ArrowIpcDecoderSetTypeDecimal(schema, type, error);
     case ns(Type_Binary):
       return ArrowIpcDecoderSetTypeSimple(schema, NANOARROW_TYPE_BINARY, 
error);
     case ns(Type_LargeBinary):
       return ArrowIpcDecoderSetTypeSimple(schema, NANOARROW_TYPE_LARGE_BINARY, 
error);
     case ns(Type_FixedSizeBinary):
-      return ArrowIpcDecoderSetTypeFixedSizeBinary(schema, 
ns(Field_type_get(field)),
-                                                   error);
+      return ArrowIpcDecoderSetTypeFixedSizeBinary(schema, type, error);
     case ns(Type_BinaryView):
       ArrowErrorSet(error, "BinaryView not yet supported in IPC reader");
       return ENOTSUP;
@@ -1170,15 +1174,15 @@ static int ArrowIpcDecoderSetType(struct ArrowSchema* 
schema, ns(Field_table_t)
       ArrowErrorSet(error, "Utf8View not yet supported in IPC reader");
       return ENOTSUP;
     case ns(Type_Date):
-      return ArrowIpcDecoderSetTypeDate(schema, ns(Field_type_get(field)), 
error);
+      return ArrowIpcDecoderSetTypeDate(schema, type, error);
     case ns(Type_Time):
-      return ArrowIpcDecoderSetTypeTime(schema, ns(Field_type_get(field)), 
error);
+      return ArrowIpcDecoderSetTypeTime(schema, type, error);
     case ns(Type_Timestamp):
-      return ArrowIpcDecoderSetTypeTimestamp(schema, 
ns(Field_type_get(field)), error);
+      return ArrowIpcDecoderSetTypeTimestamp(schema, type, error);
     case ns(Type_Duration):
-      return ArrowIpcDecoderSetTypeDuration(schema, ns(Field_type_get(field)), 
error);
+      return ArrowIpcDecoderSetTypeDuration(schema, type, error);
     case ns(Type_Interval):
-      return ArrowIpcDecoderSetTypeInterval(schema, ns(Field_type_get(field)), 
error);
+      return ArrowIpcDecoderSetTypeInterval(schema, type, error);
     case ns(Type_Struct_):
       return ArrowIpcDecoderSetTypeSimpleNested(schema, "+s", error);
     case ns(Type_List):
@@ -1186,17 +1190,15 @@ static int ArrowIpcDecoderSetType(struct ArrowSchema* 
schema, ns(Field_table_t)
     case ns(Type_LargeList):
       return ArrowIpcDecoderSetTypeSimpleNested(schema, "+L", error);
     case ns(Type_FixedSizeList):
-      return ArrowIpcDecoderSetTypeFixedSizeList(schema, 
ns(Field_type_get(field)),
-                                                 error);
+      return ArrowIpcDecoderSetTypeFixedSizeList(schema, type, error);
     case ns(Type_ListView):
     case ns(Type_LargeListView):
       ArrowErrorSet(error, "ListView/LargeListView not yet supported in IPC 
reader");
       return ENOTSUP;
     case ns(Type_Map):
-      return ArrowIpcDecoderSetTypeMap(schema, ns(Field_type_get(field)), 
error);
+      return ArrowIpcDecoderSetTypeMap(schema, type, error);
     case ns(Type_Union):
-      return ArrowIpcDecoderSetTypeUnion(schema, ns(Field_type_get(field)), 
n_children,
-                                         error);
+      return ArrowIpcDecoderSetTypeUnion(schema, type, n_children, error);
     case ns(Type_RunEndEncoded):
       ArrowErrorSet(error, "RunEndEncoded not yet supported in IPC reader");
       return ENOTSUP;
@@ -1229,6 +1231,9 @@ static int ArrowIpcSetDictionaryEncoding(
       return EINVAL;
   }
 
+  int64_t field_flags = schema->flags;
+  schema->flags |= ARROW_FLAG_NULLABLE;
+
   struct ArrowSchema tmp;
   ArrowSchemaMove(schema, &tmp);
 
@@ -1259,6 +1264,10 @@ static int ArrowIpcSetDictionaryEncoding(
     schema->flags |= ARROW_FLAG_DICTIONARY_ORDERED;
   }
 
+  if (!(field_flags & ARROW_FLAG_NULLABLE)) {
+    schema->flags &= ~ARROW_FLAG_NULLABLE;
+  }
+
   // Sort out field metadata between the schema and the dictionary member
   
NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowIpcMoveDictionaryMetadataBackToField(schema),
                                      error);
@@ -1424,6 +1433,11 @@ static int ArrowIpcDecoderDecodeDictionaryBatchHeader(
       (struct ArrowIpcDecoderPrivate*)decoder->private_data;
 
   ns(DictionaryBatch_table_t) dictionary = 
(ns(DictionaryBatch_table_t))message_header;
+  if (!ns(DictionaryBatch_data_is_present(dictionary))) {
+    ArrowErrorSet(error, "DictionaryBatch has no data");
+    return EINVAL;
+  }
+
   private_data->dictionary.id = ns(DictionaryBatch_id(dictionary));
   private_data->dictionary.is_delta = ns(DictionaryBatch_isDelta(dictionary));
 
@@ -1710,7 +1724,8 @@ ArrowErrorCode ArrowIpcDecoderDecodeHeader(struct 
ArrowIpcDecoder* decoder,
 
   // Read some basic information from the message
   decoder->metadata_version = ns(Message_version(message));
-  decoder->message_type = ns(Message_header_type(message));
+  ns(MessageHeader_union_type_t) message_type = 
ns(Message_header_type(message));
+  decoder->message_type = message_type;
   decoder->body_size_bytes = ns(Message_bodyLength(message));
 
   switch (decoder->metadata_version) {
@@ -1730,7 +1745,13 @@ ArrowErrorCode ArrowIpcDecoderDecodeHeader(struct 
ArrowIpcDecoder* decoder,
   }
 
   flatbuffers_generic_t message_header = ns(Message_header_get(message));
-  switch (decoder->message_type) {
+  if (message_header == NULL) {
+    ArrowErrorSet(error, "Message header '%s' has no value",
+                  ns(MessageHeader_type_name(message_type)));
+    return EINVAL;
+  }
+
+  switch (message_type) {
     case ns(MessageHeader_Schema):
       NANOARROW_RETURN_NOT_OK(
           ArrowIpcDecoderDecodeSchemaHeader(decoder, message_header, error));
@@ -2776,11 +2797,14 @@ static ArrowErrorCode 
ArrowIpcDecoderDecodeDictionaryInternal(
   struct ArrowIpcDecoderPrivate* dictionary_decoder_private_data =
       (struct ArrowIpcDecoderPrivate*)dictionary->decoder.private_data;
   dictionary->decoder.message_type = NANOARROW_IPC_MESSAGE_TYPE_RECORD_BATCH;
-  dictionary_decoder_private_data->last_message = record_batch;
+  dictionary->decoder.metadata_version = decoder->metadata_version;
   // Transfer the endianness and compression settings so that buffers are 
byte-swapped
   // and decompressed if needed (the nested decoder uses a default 
decompressor)
   dictionary_decoder_private_data->endianness = private_data->endianness;
-  dictionary->decoder.codec = decoder->codec;
+  // Validate the nested batch's node and buffer counts before accessing its 
vectors.
+  NANOARROW_RETURN_NOT_OK(
+      ArrowIpcDecoderDecodeRecordBatchHeader(&dictionary->decoder, 
record_batch, error));
+  dictionary_decoder_private_data->last_message = record_batch;
 
   struct ArrowArrayView* array_view;
   NANOARROW_RETURN_NOT_OK(ArrowIpcDecoderDecodeArrayViewInternal(
diff --git a/src/nanoarrow/ipc/decoder_test.cc 
b/src/nanoarrow/ipc/decoder_test.cc
index b2cbaf7d..3fb2bf6e 100644
--- a/src/nanoarrow/ipc/decoder_test.cc
+++ b/src/nanoarrow/ipc/decoder_test.cc
@@ -685,6 +685,7 @@ TEST(NanoarrowIpcTest, 
NanoarrowIpcDecodeDictionarySchemaWithoutIndexType) {
   struct ArrowIpcDecoder decoder;
   struct ArrowError error;
   struct ArrowSchema schema;
+  struct ArrowIpcDictionaryEncodings dictionary_encodings;
 
   struct ArrowBufferView data;
   data.data.as_uint8 = kDictionarySchemaNoIndexType;
@@ -696,15 +697,19 @@ TEST(NanoarrowIpcTest, 
NanoarrowIpcDecodeDictionarySchemaWithoutIndexType) {
       << error.message;
   ASSERT_EQ(decoder.message_type, NANOARROW_IPC_MESSAGE_TYPE_SCHEMA);
 
-  ASSERT_EQ(ArrowIpcDecoderDecodeSchema(&decoder, &schema, &error), 
NANOARROW_OK)
+  ASSERT_EQ(ArrowIpcDecoderDecodeSchemaWithDictionaries(&decoder, &schema,
+                                                        &dictionary_encodings, 
&error),
+            NANOARROW_OK)
       << error.message;
   ASSERT_EQ(schema.n_children, 1);
   EXPECT_STREQ(schema.children[0]->name, "some_col");
   EXPECT_STREQ(schema.children[0]->format, "i");
   ASSERT_NE(schema.children[0]->dictionary, nullptr);
   EXPECT_STREQ(schema.children[0]->dictionary->format, "u");
+  ASSERT_NE(ArrowIpcDictionaryEncodingsFindById(&dictionary_encodings, 0), 
nullptr);
 
   ArrowSchemaRelease(&schema);
+  ArrowIpcDictionaryEncodingsReset(&dictionary_encodings);
   ArrowIpcDecoderReset(&decoder);
 }
 
@@ -1430,6 +1435,30 @@ void AssertArrayViewIdentical(const struct 
ArrowArrayView* actual,
   }
 }
 
+static void FinishEncapsulatedMessage(flatcc_builder_t* builder,
+                                      struct ArrowBuffer* out) {
+  // Encapsulate: continuation, little endian header size, header, padding to 
8 bytes
+  size_t size = flatcc_builder_get_buffer_size(builder);
+  int64_t padded_size = _ArrowRoundUpToMultipleOf8(static_cast<int64_t>(size));
+  ASSERT_EQ(
+      ArrowBufferReserve(out, 2 * static_cast<int64_t>(sizeof(int32_t)) + 
padded_size),
+      NANOARROW_OK);
+  ASSERT_EQ(ArrowBufferAppendInt32(out, -1), NANOARROW_OK);
+#if defined(__BIG_ENDIAN__)
+  ASSERT_EQ(ArrowBufferAppendInt32(
+                out, 
static_cast<int32_t>(bswap32(static_cast<uint32_t>(padded_size)))),
+            NANOARROW_OK);
+#else
+  ASSERT_EQ(ArrowBufferAppendInt32(out, static_cast<int32_t>(padded_size)), 
NANOARROW_OK);
+#endif
+  ASSERT_NE(flatcc_builder_copy_buffer(builder, out->data + out->size_bytes, 
size),
+            nullptr);
+  out->size_bytes += size;
+  while (out->size_bytes % 8 != 0) {
+    out->data[out->size_bytes++] = 0;
+  }
+}
+
 // Builds an encapsulated Schema message whose Message.custom_metadata and
 // Schema.custom_metadata each contain a KeyValue with no key. Both `key` and 
`value`
 // are optional fields of KeyValue in the IPC format, so this passes flatbuffer
@@ -1462,30 +1491,196 @@ static void MakeKeylessMetadataSchemaMessage(struct 
ArrowBuffer* out) {
   ASSERT_EQ(ns(Message_bodyLength_add(&builder, 0)), 0);
   ASSERT_NE(ns(Message_end_as_root(&builder)), 0);
 
-  // Encapsulate: continuation, little endian header size, header, padding to 
8 bytes
-  size_t size = flatcc_builder_get_buffer_size(&builder);
-  int64_t padded_size = _ArrowRoundUpToMultipleOf8(static_cast<int64_t>(size));
-  ASSERT_EQ(
-      ArrowBufferReserve(out, 2 * static_cast<int64_t>(sizeof(int32_t)) + 
padded_size),
-      NANOARROW_OK);
-  ASSERT_EQ(ArrowBufferAppendInt32(out, -1), NANOARROW_OK);
-#if defined(__BIG_ENDIAN__)
-  ASSERT_EQ(ArrowBufferAppendInt32(
-                out, 
static_cast<int32_t>(bswap32(static_cast<uint32_t>(padded_size)))),
-            NANOARROW_OK);
-#else
-  ASSERT_EQ(ArrowBufferAppendInt32(out, static_cast<int32_t>(padded_size)), 
NANOARROW_OK);
-#endif
-  ASSERT_NE(flatcc_builder_copy_buffer(&builder, out->data + out->size_bytes, 
size),
-            nullptr);
-  out->size_bytes += size;
-  while (out->size_bytes % 8 != 0) {
-    out->data[out->size_bytes++] = 0;
+  ASSERT_NO_FATAL_FAILURE(FinishEncapsulatedMessage(&builder, out));
+  flatcc_builder_clear(&builder);
+}
+
+static void MakeSchemaWithMissingFieldType(struct ArrowBuffer* out) {
+  flatcc_builder_t builder;
+  flatcc_builder_init(&builder);
+
+  ASSERT_EQ(ns(Message_start_as_root(&builder)), 0);
+  ASSERT_EQ(ns(Message_version_add(&builder, ns(MetadataVersion_V5))), 0);
+  ASSERT_EQ(ns(Message_header_Schema_start(&builder)), 0);
+  ASSERT_EQ(ns(Schema_endianness_add(&builder, ns(Endianness_Little))), 0);
+  ASSERT_EQ(ns(Schema_fields_start(&builder)), 0);
+  ASSERT_EQ(ns(Schema_fields_push_start(&builder)), 0);
+  ASSERT_EQ(ns(Field_type_add_type(&builder, ns(Type_Int))), 0);
+  ASSERT_NE(ns(Schema_fields_push_end(&builder)), nullptr);
+  ASSERT_EQ(ns(Schema_fields_end(&builder)), 0);
+  ASSERT_EQ(ns(Message_header_Schema_end(&builder)), 0);
+  ASSERT_NE(ns(Message_end_as_root(&builder)), 0);
+
+  ASSERT_NO_FATAL_FAILURE(FinishEncapsulatedMessage(&builder, out));
+  flatcc_builder_clear(&builder);
+}
+
+static void MakeMessageWithMissingHeader(struct ArrowBuffer* out) {
+  flatcc_builder_t builder;
+  flatcc_builder_init(&builder);
+
+  ASSERT_EQ(ns(Message_start_as_root(&builder)), 0);
+  ASSERT_EQ(ns(Message_version_add(&builder, ns(MetadataVersion_V5))), 0);
+  ASSERT_EQ(ns(Message_header_add_type(&builder, ns(MessageHeader_Schema))), 
0);
+  ASSERT_NE(ns(Message_end_as_root(&builder)), 0);
+
+  ASSERT_NO_FATAL_FAILURE(FinishEncapsulatedMessage(&builder, out));
+  flatcc_builder_clear(&builder);
+}
+
+static void MakeDictionaryBatchWithoutData(struct ArrowBuffer* out) {
+  flatcc_builder_t builder;
+  flatcc_builder_init(&builder);
+
+  ASSERT_EQ(ns(Message_start_as_root(&builder)), 0);
+  ASSERT_EQ(ns(Message_version_add(&builder, ns(MetadataVersion_V5))), 0);
+  ASSERT_EQ(ns(Message_header_DictionaryBatch_start(&builder)), 0);
+  ASSERT_EQ(ns(DictionaryBatch_id_add(&builder, 0)), 0);
+  ASSERT_EQ(ns(Message_header_DictionaryBatch_end(&builder)), 0);
+  ASSERT_NE(ns(Message_end_as_root(&builder)), 0);
+
+  ASSERT_NO_FATAL_FAILURE(FinishEncapsulatedMessage(&builder, out));
+  flatcc_builder_clear(&builder);
+}
+
+TEST(NanoarrowIpcTest, NanoarrowIpcDecodeMissingOptionalTables) {
+  struct ArrowError error;
+  nanoarrow::ipc::UniqueDecoder decoder;
+  ASSERT_EQ(ArrowIpcDecoderInit(decoder.get()), NANOARROW_OK);
+
+  nanoarrow::UniqueBuffer missing_field_type;
+  
ASSERT_NO_FATAL_FAILURE(MakeSchemaWithMissingFieldType(missing_field_type.get()));
+  struct ArrowBufferView data;
+  data.data.as_uint8 = missing_field_type->data;
+  data.size_bytes = missing_field_type->size_bytes;
+  ASSERT_EQ(ArrowIpcDecoderVerifyHeader(decoder.get(), data, &error), 
NANOARROW_OK)
+      << error.message;
+  ASSERT_EQ(ArrowIpcDecoderDecodeHeader(decoder.get(), data, &error), 
NANOARROW_OK)
+      << error.message;
+  nanoarrow::UniqueSchema schema;
+  EXPECT_EQ(ArrowIpcDecoderDecodeSchema(decoder.get(), schema.get(), &error), 
EINVAL);
+  EXPECT_STREQ(error.message, "Field type 'Int' has no value");
+
+  nanoarrow::UniqueBuffer missing_header;
+  ASSERT_NO_FATAL_FAILURE(MakeMessageWithMissingHeader(missing_header.get()));
+  data.data.as_uint8 = missing_header->data;
+  data.size_bytes = missing_header->size_bytes;
+  ASSERT_EQ(ArrowIpcDecoderVerifyHeader(decoder.get(), data, &error), 
NANOARROW_OK)
+      << error.message;
+  EXPECT_EQ(ArrowIpcDecoderDecodeHeader(decoder.get(), data, &error), EINVAL);
+  EXPECT_STREQ(error.message, "Message header 'Schema' has no value");
+
+  nanoarrow::UniqueBuffer missing_dictionary_data;
+  
ASSERT_NO_FATAL_FAILURE(MakeDictionaryBatchWithoutData(missing_dictionary_data.get()));
+  data.data.as_uint8 = missing_dictionary_data->data;
+  data.size_bytes = missing_dictionary_data->size_bytes;
+  ASSERT_EQ(ArrowIpcDecoderVerifyHeader(decoder.get(), data, &error), 
NANOARROW_OK)
+      << error.message;
+  EXPECT_EQ(ArrowIpcDecoderDecodeHeader(decoder.get(), data, &error), EINVAL);
+  EXPECT_STREQ(error.message, "DictionaryBatch has no data");
+}
+
+static void MakeDictionaryBatchWithVectorCounts(int64_t n_nodes, int64_t 
n_buffers,
+                                                struct ArrowBuffer* out) {
+  flatcc_builder_t builder;
+  flatcc_builder_init(&builder);
+
+  ASSERT_EQ(ns(Message_start_as_root(&builder)), 0);
+  ASSERT_EQ(ns(Message_version_add(&builder, ns(MetadataVersion_V5))), 0);
+  ASSERT_EQ(ns(Message_header_DictionaryBatch_start(&builder)), 0);
+  ASSERT_EQ(ns(DictionaryBatch_data_start(&builder)), 0);
+  // Negative counts omit the optional vectors entirely.
+  if (n_nodes >= 0) {
+    std::vector<struct ns(FieldNode)> nodes(n_nodes);
+    ASSERT_EQ(ns(RecordBatch_nodes_create(&builder, nodes.data(), 
nodes.size())), 0);
+  }
+  if (n_buffers >= 0) {
+    std::vector<struct ns(Buffer)> buffers(n_buffers);
+    ASSERT_EQ(ns(RecordBatch_buffers_create(&builder, buffers.data(), 
buffers.size())),
+              0);
   }
+  ASSERT_EQ(ns(DictionaryBatch_data_end(&builder)), 0);
+  ASSERT_EQ(ns(Message_header_DictionaryBatch_end(&builder)), 0);
+  ASSERT_NE(ns(Message_end_as_root(&builder)), 0);
 
+  ASSERT_NO_FATAL_FAILURE(FinishEncapsulatedMessage(&builder, out));
   flatcc_builder_clear(&builder);
 }
 
+TEST(NanoarrowIpcTest, NanoarrowIpcDecodeDictionaryBatchInvalidVectorCounts) {
+  struct ArrowError error;
+  nanoarrow::ipc::UniqueDecoder decoder;
+  ASSERT_EQ(ArrowIpcDecoderInit(decoder.get()), NANOARROW_OK);
+  struct ArrowBufferView data;
+  data.data.as_uint8 = kDictionarySchema;
+  data.size_bytes = sizeof(kDictionarySchema);
+  ASSERT_EQ(ArrowIpcDecoderVerifyHeader(decoder.get(), data, &error), 
NANOARROW_OK);
+  ASSERT_EQ(ArrowIpcDecoderDecodeHeader(decoder.get(), data, &error), 
NANOARROW_OK);
+
+  nanoarrow::UniqueSchema schema;
+  nanoarrow::ipc::UniqueDictionaryEncodings encodings;
+  ASSERT_EQ(ArrowIpcDecoderDecodeSchemaWithDictionaries(decoder.get(), 
schema.get(),
+                                                        encodings.get(), 
&error),
+            NANOARROW_OK);
+  nanoarrow::ipc::UniqueDictionaries dictionaries;
+  ASSERT_EQ(ArrowIpcDictionariesInit(dictionaries.get(), encodings.get(), 
&error),
+            NANOARROW_OK);
+  ASSERT_EQ(ArrowIpcDecoderSetEndianness(decoder.get(), 
NANOARROW_IPC_ENDIANNESS_LITTLE),
+            NANOARROW_OK);
+
+  const int64_t counts[][2] = {{-1, 3}, {0, 3}, {2, 3}, {1, -1},
+                               {1, 0},  {1, 1}, {1, 2}, {1, 4}};
+  for (const auto& count : counts) {
+    SCOPED_TRACE(::testing::Message()
+                 << "nodes=" << count[0] << ", buffers=" << count[1]);
+    nanoarrow::UniqueBuffer message;
+    ASSERT_NO_FATAL_FAILURE(
+        MakeDictionaryBatchWithVectorCounts(count[0], count[1], 
message.get()));
+    data.data.as_uint8 = message->data;
+    data.size_bytes = message->size_bytes;
+    ASSERT_EQ(ArrowIpcDecoderVerifyHeader(decoder.get(), data, &error), 
NANOARROW_OK)
+        << error.message;
+    ASSERT_EQ(ArrowIpcDecoderDecodeHeader(decoder.get(), data, &error), 
NANOARROW_OK)
+        << error.message;
+
+    struct ArrowBufferView body = {};
+    std::string expected;
+    if (count[0] != 1) {
+      expected = "Expected 1 field nodes in message but found " +
+                 std::to_string(count[0] < 0 ? 0 : count[0]);
+    } else {
+      expected = "Expected 3 buffers in message but found " +
+                 std::to_string(count[1] < 0 ? 0 : count[1]);
+    }
+    EXPECT_EQ(ArrowIpcDecoderDecodeDictionary(decoder.get(), body,
+                                              NANOARROW_VALIDATION_LEVEL_FULL,
+                                              dictionaries.get(), &error),
+              EINVAL);
+    EXPECT_STREQ(error.message, expected.c_str());
+
+    nanoarrow::UniqueBuffer shared_body;
+    EXPECT_EQ(ArrowIpcDecoderDecodeDictionaryFromShared(decoder.get(), 
shared_body.get(),
+                                                        
NANOARROW_VALIDATION_LEVEL_FULL,
+                                                        dictionaries.get(), 
&error),
+              EINVAL);
+    EXPECT_STREQ(error.message, expected.c_str());
+  }
+
+  // A rejected dictionary batch must not prevent decoding the next valid 
batch.
+  data.data.as_uint8 = kDictionaryBatch;
+  data.size_bytes = sizeof(kDictionaryBatch);
+  ASSERT_EQ(ArrowIpcDecoderVerifyHeader(decoder.get(), data, &error), 
NANOARROW_OK);
+  ASSERT_EQ(ArrowIpcDecoderDecodeHeader(decoder.get(), data, &error), 
NANOARROW_OK);
+  struct ArrowBufferView body;
+  body.data.as_uint8 = data.data.as_uint8 + decoder->header_size_bytes;
+  body.size_bytes = decoder->body_size_bytes;
+  ASSERT_EQ(ArrowIpcDecoderDecodeDictionary(decoder.get(), body,
+                                            NANOARROW_VALIDATION_LEVEL_FULL,
+                                            dictionaries.get(), &error),
+            NANOARROW_OK)
+      << error.message;
+}
+
 TEST(NanoarrowIpcTest, NanoarrowIpcDecodeMetadataWithoutKey) {
   nanoarrow::UniqueBuffer message;
   ASSERT_NO_FATAL_FAILURE(MakeKeylessMetadataSchemaMessage(message.get()));
diff --git a/src/nanoarrow/ipc/writer_test.cc b/src/nanoarrow/ipc/writer_test.cc
index 3b08d0c8..edcc8839 100644
--- a/src/nanoarrow/ipc/writer_test.cc
+++ b/src/nanoarrow/ipc/writer_test.cc
@@ -339,6 +339,7 @@ TEST(NanoarrowIpcWriter, RoundtripDictionaryStream) {
   nanoarrow::UniqueSchema schema;
   nanoarrow::UniqueArray array;
   MakeDictionaryStructArray(array.get(), schema.get());
+  schema->children[0]->flags &= ~ARROW_FLAG_NULLABLE;
 
   nanoarrow::UniqueArrayStream array_stream;
   ASSERT_EQ(ArrowBasicArrayStreamInit(array_stream.get(), schema.get(), 1), 
NANOARROW_OK);
@@ -366,7 +367,9 @@ TEST(NanoarrowIpcWriter, RoundtripDictionaryStream) {
             NANOARROW_OK)
       << error.message;
   ASSERT_EQ(roundtrip_schema->n_children, 1);
+  EXPECT_EQ(roundtrip_schema->children[0]->flags & ARROW_FLAG_NULLABLE, 0);
   ASSERT_NE(roundtrip_schema->children[0]->dictionary, nullptr);
+  EXPECT_NE(roundtrip_schema->children[0]->dictionary->flags & 
ARROW_FLAG_NULLABLE, 0);
   EXPECT_STREQ(roundtrip_schema->children[0]->dictionary->format, "u");
 
   nanoarrow::UniqueArray roundtrip_array;

Reply via email to