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 2b2e5c17 chore(r): Add tests for nested dictionary conversion (#918)
2b2e5c17 is described below

commit 2b2e5c17133749e95139dc941ec0673cfbd3295c
Author: Dewey Dunnington <[email protected]>
AuthorDate: Thu Jul 30 21:58:55 2026 -0400

    chore(r): Add tests for nested dictionary conversion (#918)
    
    Adds some tests. They all pass, but I thought i should check my release
    post claim of "supportes nested dictionaries" before publishing. I am
    also not sure this case is tested in the integration testing files.
---
 r/tests/testthat/test-convert-array-stream.R | 96 ++++++++++++++++++++++++++++
 r/tests/testthat/test-convert-array.R        | 75 ++++++++++++++++++++++
 r/tests/testthat/test-ipc.R                  | 89 ++++++++++++++++++++++++++
 3 files changed, 260 insertions(+)

diff --git a/r/tests/testthat/test-convert-array-stream.R 
b/r/tests/testthat/test-convert-array-stream.R
index 068e3958..da81c08e 100644
--- a/r/tests/testthat/test-convert-array-stream.R
+++ b/r/tests/testthat/test-convert-array-stream.R
@@ -345,3 +345,99 @@ test_that("fixed-size stream conversion errors when the 
output has insufficient
     "Expected to materialize 100 values in batch 1 but materialized 2"
   )
 })
+
+test_that("convert array stream works for dictionaries of structs of 
dictionaries", {
+  skip_if_not_installed("arrow")
+  skip_if_not_installed("tibble")
+
+  # Create a record batch with a dictionary inside a struct. Use tibble
+  # for slightly easier nested data frame construction.
+  df <- tibble::tibble(
+    idx = 1:26,
+    letter = letters,
+    nested = data.frame(idx2 = 1:26, LETTER = LETTERS)
+  )
+
+  schema <- arrow::schema(
+    idx = arrow::int32(),
+    letter = arrow::utf8(),
+    nested = arrow::struct(
+      idx2 = arrow::int32(),
+      LETTER = arrow::dictionary(
+        index_type = arrow::int32(),
+        value_type = arrow::large_utf8()
+      )
+    )
+  )
+
+  batch <- arrow::record_batch(df)$cast(schema)
+
+  # Modify the batch so that the inner struct is the values member of a dict
+  na_batch <- as_nanoarrow_array(batch)
+  na_batch$children$nested <- nanoarrow_array_modify(
+    nanoarrow_array_init(
+      as_nanoarrow_schema(
+        arrow::dictionary(
+          index_type = arrow::int32(),
+          value_type = arrow::struct(
+            idx2 = arrow::int32(),
+            LETTER = arrow::dictionary(
+              index_type = arrow::int8(),
+              value_type = arrow::large_utf8()
+            )
+          )
+        )
+      )
+    ),
+    list(
+      length = 26,
+      null_count = 0,
+      buffers = list(
+        NULL,
+        0:25
+      ),
+      dictionary = na_batch$children$nested
+    ),
+  )
+
+  # Split into multiple batches (first 13 rows and last 13 rows)
+  batch_with_nested_dictionary <- arrow::as_record_batch(na_batch)
+  batch1 <- batch_with_nested_dictionary$Take(0:12)
+  batch2 <- batch_with_nested_dictionary$Take(13:25)
+
+  # Create a stream with multiple batches
+  stream <- basic_array_stream(
+    list(
+      as_nanoarrow_array(batch1),
+      as_nanoarrow_array(batch2)
+    )
+  )
+
+  # Ensure the stream converts back to the original
+  expect_identical(
+    convert_array_stream(stream),
+    as.data.frame(df)
+  )
+
+  # Also test with reversed batches
+  reversed_batch1 <- batch_with_nested_dictionary$Take(12:0)
+  reversed_batch2 <- batch_with_nested_dictionary$Take(25:13)
+
+  reversed_stream <- basic_array_stream(
+    list(
+      as_nanoarrow_array(reversed_batch1),
+      as_nanoarrow_array(reversed_batch2)
+    )
+  )
+
+  expected_reversed <- rbind(
+    as.data.frame(df[13:1, ]),
+    as.data.frame(df[26:14, ])
+  )
+  row.names(expected_reversed) <- 1:26
+
+  expect_identical(
+    convert_array_stream(reversed_stream),
+    expected_reversed
+  )
+})
diff --git a/r/tests/testthat/test-convert-array.R 
b/r/tests/testthat/test-convert-array.R
index 7b7be069..8ceca779 100644
--- a/r/tests/testthat/test-convert-array.R
+++ b/r/tests/testthat/test-convert-array.R
@@ -1332,3 +1332,78 @@ test_that("convert to vector works for lists nested in 
data frames", {
     df_in_list_in_df
   )
 })
+
+test_that("convert to vector works for dictionaries of structs of 
dictionaries", {
+  skip_if_not_installed("arrow")
+  skip_if_not_installed("tibble")
+
+  # Create a record batch with a dictionary inside a struct. Use tibble
+  # for slightly easier nested data frame construction.
+  df <- tibble::tibble(
+    idx = 1:26,
+    letter = letters,
+    nested = data.frame(idx2 = 1:26, LETTER = LETTERS)
+  )
+
+  schema <- arrow::schema(
+    idx = arrow::int32(),
+    letter = arrow::utf8(),
+    nested = arrow::struct(
+      idx2 = arrow::int32(),
+      LETTER = arrow::dictionary(
+        index_type = arrow::int32(),
+        value_type = arrow::large_utf8()
+      )
+    )
+  )
+
+  batch <- arrow::record_batch(df)$cast(schema)
+
+  # Modify the batch so that the inner struct is the values member of a dict
+  na_batch <- as_nanoarrow_array(batch)
+  na_batch$children$nested <- nanoarrow_array_modify(
+    nanoarrow_array_init(
+      as_nanoarrow_schema(
+        arrow::dictionary(
+          index_type = arrow::int32(),
+          value_type = arrow::struct(
+            idx2 = arrow::int32(),
+            LETTER = arrow::dictionary(
+              index_type = arrow::int8(),
+              value_type = arrow::large_utf8()
+            )
+          )
+        )
+      )
+    ),
+    list(
+      length = 26,
+      null_count = 0,
+      buffers = list(
+        NULL,
+        0:25
+      ),
+      dictionary = na_batch$children$nested
+    ),
+  )
+
+  # Ensure the batch converts back to the original
+  expect_identical(
+    convert_array(na_batch),
+    as.data.frame(df)
+  )
+
+  # Ensure a reversed batch converts back to the original
+  batch_with_nested_dictionary <- arrow::as_record_batch(na_batch)
+  reversed_batch_with_nested_dictionary <- batch_with_nested_dictionary$Take(
+    (batch$num_rows - 1):0
+  )
+
+  converted_reversed <- convert_array(
+    as_nanoarrow_array(reversed_batch_with_nested_dictionary)
+  )
+  expect_identical(
+    converted_reversed,
+    as.data.frame(df[rev(seq_len(nrow(df))), ])
+  )
+})
diff --git a/r/tests/testthat/test-ipc.R b/r/tests/testthat/test-ipc.R
index 69f96aa2..1daab86b 100644
--- a/r/tests/testthat/test-ipc.R
+++ b/r/tests/testthat/test-ipc.R
@@ -433,3 +433,92 @@ test_that("read_nanoarrow() works for dictionary arrays 
produced by arrow", {
   df$col <- as.character(df$col)
   expect_identical(result, do.call(rbind, rep(list(df), 10)))
 })
+
+test_that("read_nanoarrow() works for dictionaries of structs of 
dictionaries", {
+  skip_if_not_installed("arrow")
+  skip_if_not_installed("tibble")
+
+  # Create a record batch with a dictionary inside a struct. Use tibble
+  # for slightly easier nested data frame construction.
+  df <- tibble::tibble(
+    idx = 1:26,
+    letter = letters,
+    nested = data.frame(idx2 = 1:26, LETTER = LETTERS)
+  )
+
+  schema <- arrow::schema(
+    idx = arrow::int32(),
+    letter = arrow::utf8(),
+    nested = arrow::struct(
+      idx2 = arrow::int32(),
+      LETTER = arrow::dictionary(
+        index_type = arrow::int32(),
+        value_type = arrow::large_utf8()
+      )
+    )
+  )
+
+  batch <- arrow::record_batch(df)$cast(schema)
+
+  # Modify the batch so that the inner struct is the values member of a dict
+  na_batch <- as_nanoarrow_array(batch)
+  na_batch$children$nested <- nanoarrow_array_modify(
+    nanoarrow_array_init(
+      as_nanoarrow_schema(
+        arrow::dictionary(
+          index_type = arrow::int32(),
+          value_type = arrow::struct(
+            idx2 = arrow::int32(),
+            LETTER = arrow::dictionary(
+              index_type = arrow::int8(),
+              value_type = arrow::large_utf8()
+            )
+          )
+        )
+      )
+    ),
+    list(
+      length = 26,
+      null_count = 0,
+      buffers = list(
+        NULL,
+        0:25
+      ),
+      dictionary = na_batch$children$nested
+    ),
+  )
+
+  # Create a reversed batch as well (same dictionary, different indices)
+  batch_with_nested_dictionary <- arrow::as_record_batch(na_batch)
+  reversed_batch_with_nested_dictionary <- batch_with_nested_dictionary$Take(
+    (batch$num_rows - 1):0
+  )
+
+  # Test IPC roundtrip with multiple batches
+  tf <- tempfile()
+  on.exit(unlink(tf))
+
+  # Write multiple batches using RecordBatchStreamWriter
+  sink <- arrow::FileOutputStream$create(tf)
+  writer <- arrow::RecordBatchStreamWriter$create(
+    sink,
+    batch_with_nested_dictionary$schema
+  )
+
+  # Write the forward batch and the reversed batch
+  writer$write_batch(batch_with_nested_dictionary)
+  writer$write_batch(reversed_batch_with_nested_dictionary)
+  writer$close()
+  sink$close()
+
+  # Read back via nanoarrow and verify result
+  result <- convert_array_stream(read_nanoarrow(tf))
+
+  expected <- rbind(
+    as.data.frame(df),
+    as.data.frame(df[rev(seq_len(nrow(df))), ])
+  )
+  row.names(expected) <- seq_len(nrow(expected))
+
+  expect_identical(result, expected)
+})

Reply via email to