andygrove commented on code in PR #1419:
URL: https://github.com/apache/arrow-java/pull/1419#discussion_r4432993801


##########
c/src/main/java/org/apache/arrow/c/ArrayImporter.java:
##########
@@ -84,15 +84,40 @@ private void importChild(ArrayImporter parent, ArrowArray 
src) {
   }
 
   private void doImport(ArrowArray.Snapshot snapshot) {
-    // A non-zero offset is only meaningful for arrays that have buffers to 
offset into.
-    // Null arrays carry no buffers, so their offset is inert and safe to 
ignore.
-    checkState(
-        snapshot.offset == 0
-            || snapshot.length == 0
-            || vector.getField().getType().getTypeID() == 
ArrowType.ArrowTypeID.Null,
-        "ArrowArray struct has non-zero offset (%s), which is not supported",
-        snapshot.offset);
+    checkState(snapshot.offset >= 0, "ArrowArray struct has negative offset 
(%s)", snapshot.offset);
+    if (snapshot.offset != 0 && snapshot.length != 0) {
+      importSliced(snapshot);
+    } else {
+      importUnsliced(snapshot);
+    }
+  }
+
+  /**
+   * Java vectors cannot represent an offset, so the leading {@code offset + 
length} values are
+   * imported into a temporary vector and the requested window is then 
transferred into the target.
+   */
+  private void importSliced(ArrowArray.Snapshot snapshot) {
+    int offset = checkedCastToInt(snapshot.offset);
+    int length = checkedCastToInt(snapshot.length);
+
+    snapshot.length = Math.addExact(snapshot.offset, snapshot.length);
+    snapshot.offset = 0;
+    // The producer's null count only covers the slice. Without a validity 
bitmap the null count
+    // must be 0 for the buffers to load, otherwise it is unknown.
+    boolean hasBitmap = snapshot.n_buffers > 0 && 
MemoryUtil.getLong(snapshot.buffers) != NULL;

Review Comment:
   Applied in 63806a6.
   



##########
c/src/main/java/org/apache/arrow/c/ArrayImporter.java:
##########
@@ -84,15 +84,40 @@ private void importChild(ArrayImporter parent, ArrowArray 
src) {
   }
 
   private void doImport(ArrowArray.Snapshot snapshot) {
-    // A non-zero offset is only meaningful for arrays that have buffers to 
offset into.
-    // Null arrays carry no buffers, so their offset is inert and safe to 
ignore.
-    checkState(
-        snapshot.offset == 0
-            || snapshot.length == 0
-            || vector.getField().getType().getTypeID() == 
ArrowType.ArrowTypeID.Null,
-        "ArrowArray struct has non-zero offset (%s), which is not supported",
-        snapshot.offset);
+    checkState(snapshot.offset >= 0, "ArrowArray struct has negative offset 
(%s)", snapshot.offset);
+    if (snapshot.offset != 0 && snapshot.length != 0) {
+      importSliced(snapshot);
+    } else {
+      importUnsliced(snapshot);
+    }
+  }
+
+  /**
+   * Java vectors cannot represent an offset, so the leading {@code offset + 
length} values are
+   * imported into a temporary vector and the requested window is then 
transferred into the target.
+   */
+  private void importSliced(ArrowArray.Snapshot snapshot) {
+    int offset = checkedCastToInt(snapshot.offset);
+    int length = checkedCastToInt(snapshot.length);
+
+    snapshot.length = Math.addExact(snapshot.offset, snapshot.length);
+    snapshot.offset = 0;
+    // The producer's null count only covers the slice. Without a validity 
bitmap the null count
+    // must be 0 for the buffers to load, otherwise it is unknown.
+    boolean hasBitmap = snapshot.n_buffers > 0 && 
MemoryUtil.getLong(snapshot.buffers) != NULL;
+    snapshot.null_count = hasBitmap ? -1 : 0;

Review Comment:
   Fixed in e110114. The importer now advances the buffer pointers past the 
leading rows that fill whole bytes of a bitmap, so the temporary vector starts 
at most 7 rows before the slice. A bitmap synthesized for it covers at most 
`length + 7` rows. The imported buffers shrink the same way, so the target no 
longer keeps a wrap of every row before the offset either.
   
   Children whose rows line up with the parent's rows (struct, sparse union, 
fixed-size list) get the same skip. They are also trimmed to the slice, because 
a sliced struct exports them at full length. Run-end encoded arrays keep the 
split-only path, since their children are not indexed by row.
   
   `testImportSliceWithoutValidityBitmapAllocatesOnlyTheSlice` and 
`testImportStructSliceWithoutValidityBitmapsAllocatesOnlyTheSlice` import 10 
rows at offsets around 65,536 from arrays without bitmaps, and check that the 
importing allocator holds less than 1 KB. Before the change it held about 260 
KB.
   



##########
c/src/main/java/org/apache/arrow/c/ArrayImporter.java:
##########
@@ -84,15 +84,40 @@ private void importChild(ArrayImporter parent, ArrowArray 
src) {
   }
 
   private void doImport(ArrowArray.Snapshot snapshot) {
-    // A non-zero offset is only meaningful for arrays that have buffers to 
offset into.
-    // Null arrays carry no buffers, so their offset is inert and safe to 
ignore.
-    checkState(
-        snapshot.offset == 0
-            || snapshot.length == 0
-            || vector.getField().getType().getTypeID() == 
ArrowType.ArrowTypeID.Null,
-        "ArrowArray struct has non-zero offset (%s), which is not supported",
-        snapshot.offset);
+    checkState(snapshot.offset >= 0, "ArrowArray struct has negative offset 
(%s)", snapshot.offset);
+    if (snapshot.offset != 0 && snapshot.length != 0) {
+      importSliced(snapshot);
+    } else {
+      importUnsliced(snapshot);
+    }
+  }
+
+  /**
+   * Java vectors cannot represent an offset, so the leading {@code offset + 
length} values are
+   * imported into a temporary vector and the requested window is then 
transferred into the target.
+   */
+  private void importSliced(ArrowArray.Snapshot snapshot) {
+    int offset = checkedCastToInt(snapshot.offset);
+    int length = checkedCastToInt(snapshot.length);
+
+    snapshot.length = Math.addExact(snapshot.offset, snapshot.length);
+    snapshot.offset = 0;
+    // The producer's null count only covers the slice. Without a validity 
bitmap the null count
+    // must be 0 for the buffers to load, otherwise it is unknown.
+    boolean hasBitmap = snapshot.n_buffers > 0 && 
MemoryUtil.getLong(snapshot.buffers) != NULL;
+    snapshot.null_count = hasBitmap ? -1 : 0;
+
+    try (FieldVector unslicedVector = 
vector.getField().createVector(allocator)) {
+      ArrayImporter unslicedImporter =
+          new ArrayImporter(allocator, unslicedVector, dictionaryProvider);
+      unslicedImporter.recursionLevel = recursionLevel;
+      unslicedImporter.underlyingAllocation = underlyingAllocation;
+      unslicedImporter.importUnsliced(snapshot);
+      unslicedVector.makeTransferPair(vector).splitAndTransfer(offset, length);

Review Comment:
   Fixed in aead27c and e110114:
   
   - `ExtensionTypeVector.makeTransferPair` now transfers into the underlying 
vector of an extension vector target, and `getTo()` still returns the extension 
vector. That covers `OpaqueVector` and any other vector that does not override 
it, at any depth.
   - The importer creates the temporary vector from the imported field when it 
has one, which `importVector`, `importVectorSchemaRoot` and 
`importIntoVectorSchemaRoot` do. That field has the extension type even when 
the vector's `getField()` reports only its storage type, so the temporary 
vector has the same classes as the target.
   - At the top level the importer transfers between the storage vectors, so 
`importIntoVector` into such a vector works as well.
   
   One case is left: `importIntoVector` into a caller's vector that nests such 
an extension vector below the top level. No field reports that vector's type 
there, so the import now throws an `UnsupportedOperationException` that names 
the vector. Before, the struct's transfer pair silently replaced the child: the 
target's extension vector became a plain `VarCharVector`.
   
   The tests use a `PlainVector` that overrides neither `getField()` nor 
`makeTransferPair()`. They cover it at the top level, in a struct, in a list, 
in a sliced record batch, and through `importIntoVector`. `OpaqueVector` is 
covered at the top level, in a struct and in a list. `TestExtensionType` tests 
the transfer pair itself with `LocationVector`.
   



##########
c/src/main/java/org/apache/arrow/c/ArrayImporter.java:
##########
@@ -84,15 +84,40 @@ private void importChild(ArrayImporter parent, ArrowArray 
src) {
   }
 
   private void doImport(ArrowArray.Snapshot snapshot) {
-    // A non-zero offset is only meaningful for arrays that have buffers to 
offset into.
-    // Null arrays carry no buffers, so their offset is inert and safe to 
ignore.
-    checkState(
-        snapshot.offset == 0
-            || snapshot.length == 0
-            || vector.getField().getType().getTypeID() == 
ArrowType.ArrowTypeID.Null,
-        "ArrowArray struct has non-zero offset (%s), which is not supported",
-        snapshot.offset);
+    checkState(snapshot.offset >= 0, "ArrowArray struct has negative offset 
(%s)", snapshot.offset);
+    if (snapshot.offset != 0 && snapshot.length != 0) {
+      importSliced(snapshot);
+    } else {
+      importUnsliced(snapshot);
+    }
+  }
+
+  /**
+   * Java vectors cannot represent an offset, so the leading {@code offset + 
length} values are
+   * imported into a temporary vector and the requested window is then 
transferred into the target.
+   */
+  private void importSliced(ArrowArray.Snapshot snapshot) {
+    int offset = checkedCastToInt(snapshot.offset);
+    int length = checkedCastToInt(snapshot.length);
+
+    snapshot.length = Math.addExact(snapshot.offset, snapshot.length);
+    snapshot.offset = 0;
+    // The producer's null count only covers the slice. Without a validity 
bitmap the null count
+    // must be 0 for the buffers to load, otherwise it is unknown.
+    boolean hasBitmap = snapshot.n_buffers > 0 && 
MemoryUtil.getLong(snapshot.buffers) != NULL;
+    snapshot.null_count = hasBitmap ? -1 : 0;
+
+    try (FieldVector unslicedVector = 
vector.getField().createVector(allocator)) {
+      ArrayImporter unslicedImporter =
+          new ArrayImporter(allocator, unslicedVector, dictionaryProvider);
+      unslicedImporter.recursionLevel = recursionLevel;
+      unslicedImporter.underlyingAllocation = underlyingAllocation;
+      unslicedImporter.importUnsliced(snapshot);
+      unslicedVector.makeTransferPair(vector).splitAndTransfer(offset, length);

Review Comment:
   Fixed both in c8083d5, in `DenseUnionVector.TransferImpl`:
   
   1. The split maps each type id to the position of its child through 
`typeMapFields`. `createTransferPairs()` and `transfer()` had the same mix-up, 
which put the children of a new target under their positions rather than their 
type ids, so those are fixed as well.
   2. The split checks whether the values a window selects from each child are 
contiguous. Contiguous values are still split without copying. Values with gaps 
are copied with `copyValueSafe` and renumbered from 0.
   
   Copying keeps the value count of each child equal to the number of rows that 
select it. `DenseUnionVector.setValueCount()` enforces that, and a parent 
struct or list calls it after its own split, so keeping the gaps would have 
been undone there.
   
   With your example the slice now returns 102 and 107. `TestDenseUnionVector` 
covers type ids [5, 10], gaps at the top level and inside a struct, and a 
transfer pair that creates its target. `RoundtripTest` imports both cases at 
every offset.
   



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