This is an automated email from the ASF dual-hosted git repository.
Fokko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/parquet-java.git
The following commit(s) were added to refs/heads/master by this push:
new 3e05546cb Fix `ByteBuffer` handling in `VariantUtil` and
`VariantBuilder` (#3472)
3e05546cb is described below
commit 3e05546cb5a4d443b87c9c01abe8f04a71e29318
Author: Robert Yokota <[email protected]>
AuthorDate: Fri May 15 11:45:32 2026 -0700
Fix `ByteBuffer` handling in `VariantUtil` and `VariantBuilder` (#3472)
---
.../org/apache/parquet/variant/VariantBuilder.java | 2 +-
.../org/apache/parquet/variant/VariantUtil.java | 2 +-
.../apache/parquet/variant/TestVariantObject.java | 35 ++++++++++++++++++++++
.../parquet/variant/TestVariantScalarBuilder.java | 11 +++++++
4 files changed, 48 insertions(+), 2 deletions(-)
diff --git
a/parquet-variant/src/main/java/org/apache/parquet/variant/VariantBuilder.java
b/parquet-variant/src/main/java/org/apache/parquet/variant/VariantBuilder.java
index 4b8eb6d8c..bf42b0c44 100644
---
a/parquet-variant/src/main/java/org/apache/parquet/variant/VariantBuilder.java
+++
b/parquet-variant/src/main/java/org/apache/parquet/variant/VariantBuilder.java
@@ -374,7 +374,7 @@ public class VariantBuilder {
writePos += 1;
VariantUtil.writeLong(writeBuffer, writePos, binarySize,
VariantUtil.U32_SIZE);
writePos += VariantUtil.U32_SIZE;
- ByteBuffer.wrap(writeBuffer, writePos, binarySize).put(binary);
+ ByteBuffer.wrap(writeBuffer, writePos, binarySize).put(binary.duplicate());
writePos += binarySize;
}
diff --git
a/parquet-variant/src/main/java/org/apache/parquet/variant/VariantUtil.java
b/parquet-variant/src/main/java/org/apache/parquet/variant/VariantUtil.java
index 4744f0c28..ef1168583 100644
--- a/parquet-variant/src/main/java/org/apache/parquet/variant/VariantUtil.java
+++ b/parquet-variant/src/main/java/org/apache/parquet/variant/VariantUtil.java
@@ -843,7 +843,7 @@ class VariantUtil {
} else {
// ByteBuffer does not have an array, so we need to use the `get`
method to read the bytes.
byte[] metadataArray = new byte[nextOffset - offset];
- slice(metadata, stringStart + offset).get(metadataArray);
+ slice(metadata, pos + stringStart + offset).get(metadataArray);
result.put(new String(metadataArray), id);
}
offset = nextOffset;
diff --git
a/parquet-variant/src/test/java/org/apache/parquet/variant/TestVariantObject.java
b/parquet-variant/src/test/java/org/apache/parquet/variant/TestVariantObject.java
index 22a53976e..ddcf9f7fd 100644
---
a/parquet-variant/src/test/java/org/apache/parquet/variant/TestVariantObject.java
+++
b/parquet-variant/src/test/java/org/apache/parquet/variant/TestVariantObject.java
@@ -341,4 +341,39 @@ public class TestVariantObject {
Assert.assertEquals("Cannot read ARRAY value as OBJECT", e.getMessage());
}
}
+
+ @Test
+ public void testMetadataWithNonZeroPositionReadOnly() {
+ // Build a variant with object fields to populate the metadata dictionary
+ VariantBuilder vb = new VariantBuilder();
+ VariantObjectBuilder obj = vb.startObject();
+ obj.appendKey("name");
+ obj.appendString("Alice");
+ obj.appendKey("age");
+ obj.appendInt(30);
+ vb.endObject();
+ Variant variant = vb.build();
+
+ // Get the raw metadata bytes
+ ByteBuffer metaBuf = variant.getMetadataBuffer();
+ byte[] metaBytes = new byte[metaBuf.remaining()];
+ metaBuf.duplicate().get(metaBytes);
+
+ // Embed in a larger buffer with a non-zero position, then make read-only
+ // to force the else-branch in getMetadataMap.
+ byte[] padded = new byte[10 + metaBytes.length];
+ System.arraycopy(metaBytes, 0, padded, 10, metaBytes.length);
+ ByteBuffer offsetMetadata = ByteBuffer.wrap(padded);
+ offsetMetadata.position(10);
+ offsetMetadata.limit(10 + metaBytes.length);
+ offsetMetadata = offsetMetadata.asReadOnlyBuffer();
+
+ // ImmutableMetadata calls getMetadataMap, which had the bug.
+ // getMetadataMap builds a key->id dictionary from the metadata buffer.
+ // With a non-zero position and read-only buffer, the else-branch is taken,
+ // which previously used the wrong offset.
+ ImmutableMetadata immutableMetadata = new
ImmutableMetadata(offsetMetadata);
+ Assert.assertEquals(0, immutableMetadata.getOrInsert("name"));
+ Assert.assertEquals(1, immutableMetadata.getOrInsert("age"));
+ }
}
diff --git
a/parquet-variant/src/test/java/org/apache/parquet/variant/TestVariantScalarBuilder.java
b/parquet-variant/src/test/java/org/apache/parquet/variant/TestVariantScalarBuilder.java
index 13ceef86d..05a05d806 100644
---
a/parquet-variant/src/test/java/org/apache/parquet/variant/TestVariantScalarBuilder.java
+++
b/parquet-variant/src/test/java/org/apache/parquet/variant/TestVariantScalarBuilder.java
@@ -387,6 +387,17 @@ public class TestVariantScalarBuilder {
}
}
+ @Test
+ public void testBinaryBuilderDoesNotMutateCallerBuffer() {
+ ByteBuffer buf = ByteBuffer.wrap(new byte[] {0, 1, 2, 3});
+ int positionBefore = buf.position();
+ int remainingBefore = buf.remaining();
+ VariantBuilder vb = new VariantBuilder();
+ vb.appendBinary(buf);
+ Assert.assertEquals(positionBefore, buf.position());
+ Assert.assertEquals(remainingBefore, buf.remaining());
+ }
+
@Test
public void testStringBuilder() {
IntStream.range(VariantUtil.MAX_SHORT_STR_SIZE - 3,
VariantUtil.MAX_SHORT_STR_SIZE + 3)