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

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


The following commit(s) were added to refs/heads/main by this push:
     new 266dfc121 GH-586: Override fixedSizeBinary method for UnionMapWriter 
(#885)
266dfc121 is described below

commit 266dfc12151a49dd49153890eb16e97937c5667c
Author: Aleksei Starikov <[email protected]>
AuthorDate: Mon Nov 17 01:12:50 2025 +0100

    GH-586: Override fixedSizeBinary method for UnionMapWriter (#885)
    
    ## What's Changed
    
    `UnionMapWriter` will null out the entire map struct entry instead of
    setting the value to null in:
    ```
    childWriter.value().fixedSizeBinary().writeNull();
    ```
    
    This PR overrides the `fixedSizeBinary()` method for the
    `UnionMapWriter`, resolving it.
    In addition, it introduces the `fixedSizeBinary(int byteWidth)` which
    needs to be used to initialize `key` and `value` writes in
    `UnionMapWriter` if they have not been initialized.
    
    Closes #586.
---
 .../src/main/codegen/templates/UnionMapWriter.java |  23 +++
 .../org/apache/arrow/vector/TestMapVector.java     | 221 +++++++++++++++++++++
 2 files changed, 244 insertions(+)

diff --git a/vector/src/main/codegen/templates/UnionMapWriter.java 
b/vector/src/main/codegen/templates/UnionMapWriter.java
index 8b2f09121..8bbf6ae0a 100644
--- a/vector/src/main/codegen/templates/UnionMapWriter.java
+++ b/vector/src/main/codegen/templates/UnionMapWriter.java
@@ -243,4 +243,27 @@ public class UnionMapWriter extends UnionListWriter {
         return super.extension(type);
     }
   }
+
+  public FixedSizeBinaryWriter fixedSizeBinary(int byteWidth) {
+    switch (mode) {
+      case KEY:
+        return entryWriter.fixedSizeBinary(MapVector.KEY_NAME, byteWidth);
+      case VALUE:
+        return entryWriter.fixedSizeBinary(MapVector.VALUE_NAME, byteWidth);
+      default:
+        return this;
+    }
+  }
+
+  @Override
+  public FixedSizeBinaryWriter fixedSizeBinary() {
+    switch (mode) {
+      case KEY:
+        return entryWriter.fixedSizeBinary(MapVector.KEY_NAME);
+      case VALUE:
+        return entryWriter.fixedSizeBinary(MapVector.VALUE_NAME);
+      default:
+        return this;
+    }
+  }
 }
diff --git a/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java 
b/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
index 1a1810d0f..8605d250f 100644
--- a/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
+++ b/vector/src/test/java/org/apache/arrow/vector/TestMapVector.java
@@ -16,10 +16,12 @@
  */
 package org.apache.arrow.vector;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.nio.ByteBuffer;
@@ -41,6 +43,7 @@ import 
org.apache.arrow.vector.complex.writer.BaseWriter.ListWriter;
 import org.apache.arrow.vector.complex.writer.BaseWriter.MapWriter;
 import org.apache.arrow.vector.complex.writer.FieldWriter;
 import org.apache.arrow.vector.holder.UuidHolder;
+import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
 import org.apache.arrow.vector.types.Types.MinorType;
 import org.apache.arrow.vector.types.pojo.ArrowType;
 import org.apache.arrow.vector.types.pojo.Field;
@@ -1359,4 +1362,222 @@ public class TestMapVector {
       assertEquals(u2, actualUuid);
     }
   }
+
+  private FixedSizeBinaryHolder getFixedSizeBinaryHolder(byte[] array) {
+    FixedSizeBinaryHolder holder = new FixedSizeBinaryHolder();
+    holder.byteWidth = array.length;
+    holder.buffer = allocator.buffer(array.length);
+    for (int i = 0; i < array.length; i++) {
+      holder.buffer.setByte(i, array[i]);
+    }
+
+    return holder;
+  }
+
+  /**
+   * Regression test for GH-586: UnionMapWriter.fixedSizeBinary() should 
properly delegate to the
+   * entry writer for both key and value paths.
+   */
+  @Test
+  public void testFixedSizeBinaryWriter() {
+    try (MapVector mapVector = MapVector.empty("map_vector", allocator, 
false)) {
+      UnionMapWriter writer = mapVector.getWriter();
+      writer.allocate();
+
+      // populate input vector with the following records
+      // {[11, 22] -> [32, 21]}
+      // {1 -> [11, 22], 2 -> [32, 21]}
+      // null
+      // {[11, 22] -> 1, [32, 21] -> 2}
+      // {[11, 22] -> null}
+      // {null -> [32, 21]} - wrong "for a given entry, the "key" is 
non-nullable" - todo: it
+      // shouldn't work. Should it?
+      FixedSizeBinaryHolder holder1 = getFixedSizeBinaryHolder(new byte[] {11, 
22});
+      FixedSizeBinaryHolder holder2 = getFixedSizeBinaryHolder(new byte[] {32, 
21});
+
+      writer.setPosition(0); // optional
+      writer.startMap();
+      writer.startEntry();
+      writer
+          .key()
+          .fixedSizeBinary(holder1.byteWidth)
+          .write(holder1); // need to initialize with byteWidth - NPE otherwise
+      writer.value().fixedSizeBinary(holder2.byteWidth).write(holder2);
+      writer.endEntry();
+      holder1.buffer.close();
+      holder2.buffer.close();
+      writer.endMap();
+
+      // {1 -> [11, 22], 2 -> [32, 21]}
+      holder1 = getFixedSizeBinaryHolder(new byte[] {11, 22});
+      holder2 = getFixedSizeBinaryHolder(new byte[] {32, 21});
+      writer.setPosition(1);
+      writer.startMap();
+      writer.startEntry();
+      writer.key().bigInt().writeBigInt(1);
+      writer.value().fixedSizeBinary().write(holder1);
+      writer.endEntry();
+      holder1.buffer.close();
+      writer.startEntry();
+      writer.key().bigInt().writeBigInt(2);
+      writer.value().fixedSizeBinary().write(holder2);
+      writer.endEntry();
+      writer.endMap();
+      holder2.buffer.close();
+
+      // {[11, 22] -> 1, [32, 21] -> 2}
+      holder1 = getFixedSizeBinaryHolder(new byte[] {11, 22});
+      holder2 = getFixedSizeBinaryHolder(new byte[] {32, 21});
+      writer.setPosition(3);
+      writer.startMap();
+      writer.startEntry();
+      writer.key().fixedSizeBinary().write(holder1);
+      writer.value().bigInt().writeBigInt(1);
+      writer.endEntry();
+      holder1.buffer.close();
+      writer.startEntry();
+      writer.key().fixedSizeBinary().write(holder2);
+      writer.value().bigInt().writeBigInt(2);
+      writer.endEntry();
+      writer.endMap();
+      holder2.buffer.close();
+
+      // {[11, 22] -> null}
+      holder1 = getFixedSizeBinaryHolder(new byte[] {11, 22});
+      writer.setPosition(4);
+      writer.startMap();
+      writer.startEntry();
+      writer.key().fixedSizeBinary().write(holder1);
+      writer.endEntry();
+      writer.endMap();
+      holder1.buffer.close();
+
+      // {null -> [32, 21]}
+      holder2 = getFixedSizeBinaryHolder(new byte[] {32, 21});
+      writer.setPosition(5);
+      writer.startMap();
+      writer.startEntry();
+      writer.value().fixedSizeBinary().write(holder2);
+      writer.endEntry();
+      writer.endMap();
+      holder2.buffer.close();
+
+      writer.setValueCount(6);
+
+      // assert the output vector is correct
+      FieldReader reader = mapVector.getReader();
+      assertTrue(reader.isSet(), "shouldn't be null");
+      reader.setPosition(1);
+      assertTrue(reader.isSet(), "shouldn't be null");
+      reader.setPosition(2);
+      assertFalse(reader.isSet(), "should be null");
+      reader.setPosition(3);
+      assertTrue(reader.isSet(), "shouldn't be null");
+      reader.setPosition(4);
+      assertTrue(reader.isSet(), "shouldn't be null");
+      reader.setPosition(5);
+      assertTrue(reader.isSet(), "shouldn't be null");
+
+      /* index 0 */
+      Object result = mapVector.getObject(0);
+      ArrayList<?> resultSet = (ArrayList<?>) result;
+      assertEquals(1, resultSet.size());
+      Map<?, ?> resultStruct = (Map<?, ?>) resultSet.get(0);
+      assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+      assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+      assertArrayEquals(new byte[] {11, 22}, (byte[]) 
resultStruct.get(MapVector.KEY_NAME));
+      assertArrayEquals(new byte[] {32, 21}, (byte[]) 
resultStruct.get(MapVector.VALUE_NAME));
+
+      /* index 1 */
+      result = mapVector.getObject(1);
+      resultSet = (ArrayList<?>) result;
+      assertEquals(2, resultSet.size());
+      resultStruct = (Map<?, ?>) resultSet.get(0);
+      assertEquals(1L, getResultKey(resultStruct));
+      assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+      assertArrayEquals(new byte[] {11, 22}, (byte[]) 
resultStruct.get(MapVector.VALUE_NAME));
+      resultStruct = (Map<?, ?>) resultSet.get(1);
+      assertEquals(2L, getResultKey(resultStruct));
+      assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+      assertArrayEquals(new byte[] {32, 21}, (byte[]) 
resultStruct.get(MapVector.VALUE_NAME));
+
+      /* index 2 */
+      result = mapVector.getObject(2);
+      assertNull(result);
+
+      /* index 3 */
+      result = mapVector.getObject(3);
+      resultSet = (ArrayList<?>) result;
+      assertEquals(2, resultSet.size());
+      resultStruct = (Map<?, ?>) resultSet.get(0);
+      assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+      assertArrayEquals(new byte[] {11, 22}, (byte[]) 
resultStruct.get(MapVector.KEY_NAME));
+      assertEquals(1L, getResultValue(resultStruct));
+      resultStruct = (Map<?, ?>) resultSet.get(1);
+      assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+      assertArrayEquals(new byte[] {32, 21}, (byte[]) 
resultStruct.get(MapVector.KEY_NAME));
+      assertEquals(2L, getResultValue(resultStruct));
+
+      /* index 4 */
+      result = mapVector.getObject(4);
+      resultSet = (ArrayList<?>) result;
+      assertEquals(1, resultSet.size());
+      resultStruct = (Map<?, ?>) resultSet.get(0);
+      assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+      assertArrayEquals(new byte[] {11, 22}, (byte[]) 
resultStruct.get(MapVector.KEY_NAME));
+      assertFalse(resultStruct.containsKey(MapVector.VALUE_NAME));
+
+      /* index 5 */
+      result = mapVector.getObject(5);
+      resultSet = (ArrayList<?>) result;
+      assertEquals(1, resultSet.size());
+      resultStruct = (Map<?, ?>) resultSet.get(0);
+      assertFalse(resultStruct.containsKey(MapVector.KEY_NAME));
+      assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+      assertArrayEquals(new byte[] {32, 21}, (byte[]) 
resultStruct.get(MapVector.VALUE_NAME));
+    }
+  }
+
+  @Test
+  public void testFixedSizeBinaryFirstInitialization() {
+    try (MapVector mapVector = MapVector.empty("map_vector", allocator, 
false)) {
+      UnionMapWriter writer = mapVector.getWriter();
+      writer.allocate();
+
+      // populate input vector with the following records
+      // {[11, 22] -> [32, 21]}
+      FixedSizeBinaryHolder holder1 = getFixedSizeBinaryHolder(new byte[] {11, 
22});
+      FixedSizeBinaryHolder holder2 = getFixedSizeBinaryHolder(new byte[] {32, 
21});
+
+      writer.setPosition(0); // optional
+      writer.startMap();
+      writer.startEntry();
+      // require byteWidth parameter for first-time initialization of `key` or 
`value` writers
+      assertThrows(NullPointerException.class, () -> 
writer.key().fixedSizeBinary().write(holder1));
+      assertThrows(
+          NullPointerException.class, () -> 
writer.value().fixedSizeBinary().write(holder2));
+      writer.key().fixedSizeBinary(holder1.byteWidth).write(holder1);
+      writer.value().fixedSizeBinary(holder2.byteWidth).write(holder2);
+      writer.endEntry();
+      holder1.buffer.close();
+      holder2.buffer.close();
+      writer.endMap();
+
+      writer.setValueCount(1);
+
+      // assert the output vector is correct
+      FieldReader reader = mapVector.getReader();
+      assertTrue(reader.isSet(), "shouldn't be null");
+
+      /* index 0 */
+      Object result = mapVector.getObject(0);
+      ArrayList<?> resultSet = (ArrayList<?>) result;
+      assertEquals(1, resultSet.size());
+      Map<?, ?> resultStruct = (Map<?, ?>) resultSet.get(0);
+      assertTrue(resultStruct.containsKey(MapVector.KEY_NAME));
+      assertTrue(resultStruct.containsKey(MapVector.VALUE_NAME));
+      assertArrayEquals(new byte[] {11, 22}, (byte[]) 
resultStruct.get(MapVector.KEY_NAME));
+      assertArrayEquals(new byte[] {32, 21}, (byte[]) 
resultStruct.get(MapVector.VALUE_NAME));
+    }
+  }
 }

Reply via email to