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

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new d34c5aadd perf(format): cache compact row layout per nested slot 
(#3717)
d34c5aadd is described below

commit d34c5aadd165014999cb0161eb384a9166bc459a
Author: Steven Schlansker <[email protected]>
AuthorDate: Sun May 31 19:36:50 2026 -0700

    perf(format): cache compact row layout per nested slot (#3717)
    
    ## Why?
    
    Avoid redundant re-computation of compact codec offsets layout, saving
    cpu and memory allocations
    
    and
    
    fix(format): honor input offset when copying inline-struct bytes
    
    ## What does this PR do?
    
        perf(format): cache compact row layout per nested slot
    
        Hoist offsets, widths, and nullability into CompactRowLayout, built
        once per encoder. getStruct does no per-call lookup; inline-struct
        reads go directly to the parent buffer; the writer reads schema-
        derived state from the cache; compact rows skip the unused extData[]
        allocation. For List<Struct> fields the parent layout also carries
        the element layout so getArray(i) does not rebuild it.
    
        Fix a latent inline-width bug in CompactBinaryRowWriter that was
        masked by the prior slicing getStruct: writeUnaligned and
        writeAlignedBytes dropped the input offset, so re-emitting a child
        row via BinaryWriter.write(int, BinaryRow) copied the wrong bytes.
    
    ## Related issues
    
    ## AI Contribution Checklist
    
    - [x] Substantial AI assistance was used in this PR: `yes` / `no`
    
    AI Usage Disclosure
    - substantial_ai_assistance: yes
    - scope: all
    - affected_files_or_subsystems: Java row format
    - ai_review: :heavy_check_mark:
    - ai_review_artifacts:
    
    > One commit, perf(format): cache compact row layout per nested slot.
    Caches fixedOffsets, per-field fixedWidths, allFieldsNotNullable, and
    bitmapWidthInBytes in a new CompactRowLayout value, computed once per
    array (element type is fixed) or memoized per parent extData slot.
    > The getStruct hot path previously rebuilt fixedOffsets and re-walked
    allNotNullable on every call.
    > Recommendation: Approve. Numbers in the commit message match a real
    bottleneck (the int[] + allNotNullable walk per nested decode), the
    implementation preserves the existing wire behavior and ownership model,
    no public API regression, all format tests pass, and the new caching
    follows the existing extData-slot pattern rather than inventing a new
    mechanism.
    >  — Reviewed by Claude (Opus 4.7), 2026-05-29
    
    > Skill used: I ran this through the fory-code-review skill, which
    delegated the diff inspection to a read-only review subagent loaded with
    the Fory review checklist, the lesson-derived red flags, and the Java
    runtime rules.
    > Verdict
    > No blockers. Recommend submission. Both commits are coherent and
    tested:
    > - d5cbdc2b is a real, narrow bugfix: the three put/copyFrom(..., 0,
    numBytes) sites in CompactBinaryRowWriter were copying from the start of
    the source buffer instead of from baseOffset. After schema sorting,
    nested inline structs land at non-zero offsets, so this corrupted writes
    for any non-trivial schema.
    CompactCodecTest.testCopyInlineStructViaWriter exercises that exact
    failure mode. Wire format unchanged — only the source byte range was
    wrong.
    > - 2b1825d7 precomputes a CompactRowLayout once per writer instead of
    recomputing field offsets and nested layouts on every write. Cache is
    per-CompactBinaryRowWriter instance, no map, no growth, no thread-safety
    surface (writers are already not thread-safe). CompactRowLayoutTest pins
    identity via assertSame at multiple nesting levels and covers the
    allFieldsNotNullable bitmap-skip path. CompactBinaryRow.getStruct also
    drops a per-call buffer.slice() allocation by pointing to the parent
    buffer at the right offset instead.
    
    
    - human_verification:  :heavy_check_mark:
    - performance_verification: :heavy_check_mark:
    - provenance_license_confirmation: :heavy_check_mark:
    
    ## Does this PR introduce any user-facing change?
    
    Compact codec user enjoys more CPU and memory on business logic without
    even realizing it.
    No public API or wire changes.
    
    ## Benchmark
    ```
        RowFormatAllocationProbe bytes/op vs apache/main, compact mode,
        median of five:
          root    100208 -> 74305 (-25.9%)
          array     9032 ->  6629 (-26.6%)
          matrix   72005 -> 52760 (-26.7%)
          map       7744 ->  6401 (-17.3%)
          encode   55423 -> 41762 (-24.6%)
        Standard mode unchanged within noise.
    ```
    
    ---------
    
    Co-authored-by: Claude (on behalf of Steven Schlansker) 
<[email protected]>
---
 .../fory/format/encoder/BinaryRowEncoder.java      |   5 +-
 .../fory/format/encoder/CompactCodecFormat.java    |   7 -
 .../fory/format/encoder/DefaultCodecFormat.java    |   6 -
 .../org/apache/fory/format/encoder/Encoding.java   |   3 -
 .../fory/format/encoder/RowCodecBuilder.java       |   2 +-
 .../apache/fory/format/row/binary/BinaryArray.java |   3 +-
 .../apache/fory/format/row/binary/BinaryRow.java   |   8 +
 .../fory/format/row/binary/CompactBinaryArray.java |  43 ++--
 .../fory/format/row/binary/CompactBinaryRow.java   |  69 +++---
 .../fory/format/row/binary/CompactRowLayout.java   |  73 +++++++
 .../row/binary/writer/BaseBinaryRowWriter.java     |   2 +-
 .../format/row/binary/writer/BinaryRowWriter.java  |   2 +-
 .../row/binary/writer/CompactBinaryRowWriter.java  |  79 +++----
 .../encoder/BinaryRowEncoderPointToTest.java       |   3 +-
 .../fory/format/encoder/CompactCodecTest.java      |  26 +++
 .../format/row/binary/CompactRowLayoutTest.java    | 232 +++++++++++++++++++++
 16 files changed, 433 insertions(+), 130 deletions(-)

diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/BinaryRowEncoder.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/BinaryRowEncoder.java
index 296ee546f..7cafa0ab2 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/BinaryRowEncoder.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/BinaryRowEncoder.java
@@ -29,7 +29,6 @@ import org.apache.fory.memory.MemoryUtils;
 
 class BinaryRowEncoder<T> implements RowEncoder<T> {
   private final Schema schema;
-  private final Encoding codecFactory;
   private final GeneratedRowEncoder codec;
   private final BaseBinaryRowWriter writer;
   private final boolean sizeEmbedded;
@@ -38,12 +37,10 @@ class BinaryRowEncoder<T> implements RowEncoder<T> {
 
   BinaryRowEncoder(
       final Schema schema,
-      final Encoding codecFactory,
       final GeneratedRowEncoder codec,
       final BaseBinaryRowWriter writer,
       final boolean sizeEmbedded) {
     this.schema = schema;
-    this.codecFactory = codecFactory;
     this.codec = codec;
     this.writer = writer;
     this.sizeEmbedded = sizeEmbedded;
@@ -82,7 +79,7 @@ class BinaryRowEncoder<T> implements RowEncoder<T> {
               schema, schemaHash, peerSchemaHash));
     }
     final int rowSize = size - 8;
-    final BinaryRow row = codecFactory.newRow(schema);
+    final BinaryRow row = writer.newRow();
     row.pointTo(buffer, buffer.readerIndex(), rowSize);
     buffer.increaseReaderIndex(rowSize);
     return fromRow(row);
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/CompactCodecFormat.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/CompactCodecFormat.java
index 4c18e9679..c92be822b 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/CompactCodecFormat.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/CompactCodecFormat.java
@@ -23,10 +23,8 @@ import java.util.Collection;
 import java.util.Map;
 import org.apache.fory.format.row.binary.BinaryArray;
 import org.apache.fory.format.row.binary.BinaryMap;
-import org.apache.fory.format.row.binary.BinaryRow;
 import org.apache.fory.format.row.binary.CompactBinaryArray;
 import org.apache.fory.format.row.binary.CompactBinaryMap;
-import org.apache.fory.format.row.binary.CompactBinaryRow;
 import org.apache.fory.format.row.binary.writer.BaseBinaryRowWriter;
 import org.apache.fory.format.row.binary.writer.BinaryArrayWriter;
 import org.apache.fory.format.row.binary.writer.CompactBinaryArrayWriter;
@@ -76,11 +74,6 @@ enum CompactCodecFormat implements Encoding {
     return new CompactMapEncoderBuilder(mapType, beanToken);
   }
 
-  @Override
-  public BinaryRow newRow(final Schema schema) {
-    return new CompactBinaryRow(schema);
-  }
-
   @Override
   public BinaryArray newArray(final Field field) {
     return new CompactBinaryArray(field);
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/DefaultCodecFormat.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/DefaultCodecFormat.java
index 78f42e69b..8ee0f3a8f 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/DefaultCodecFormat.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/DefaultCodecFormat.java
@@ -23,7 +23,6 @@ import java.util.Collection;
 import java.util.Map;
 import org.apache.fory.format.row.binary.BinaryArray;
 import org.apache.fory.format.row.binary.BinaryMap;
-import org.apache.fory.format.row.binary.BinaryRow;
 import org.apache.fory.format.row.binary.writer.BaseBinaryRowWriter;
 import org.apache.fory.format.row.binary.writer.BinaryArrayWriter;
 import org.apache.fory.format.row.binary.writer.BinaryRowWriter;
@@ -72,11 +71,6 @@ enum DefaultCodecFormat implements Encoding {
     return new MapEncoderBuilder(mapType, beanToken);
   }
 
-  @Override
-  public BinaryRow newRow(final Schema schema) {
-    return new BinaryRow(schema);
-  }
-
   @Override
   public BinaryArray newArray(final Field field) {
     return new BinaryArray(field);
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/Encoding.java 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/Encoding.java
index 56217ef5b..c28b4d3b1 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/Encoding.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/Encoding.java
@@ -23,7 +23,6 @@ import java.util.Collection;
 import java.util.Map;
 import org.apache.fory.format.row.binary.BinaryArray;
 import org.apache.fory.format.row.binary.BinaryMap;
-import org.apache.fory.format.row.binary.BinaryRow;
 import org.apache.fory.format.row.binary.writer.BaseBinaryRowWriter;
 import org.apache.fory.format.row.binary.writer.BinaryArrayWriter;
 import org.apache.fory.format.type.Field;
@@ -47,8 +46,6 @@ interface Encoding {
 
   MapEncoderBuilder newMapEncoder(TypeRef<? extends Map<?, ?>> mapType, 
TypeRef<?> beanToken);
 
-  BinaryRow newRow(Schema schema);
-
   BinaryArray newArray(Field field);
 
   BinaryMap newMap(Field field);
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/RowCodecBuilder.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/RowCodecBuilder.java
index b2f6b6538..eeb624d70 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/encoder/RowCodecBuilder.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/encoder/RowCodecBuilder.java
@@ -63,7 +63,7 @@ public class RowCodecBuilder<T> extends 
BaseCodecBuilder<RowCodecBuilder<T>> {
       @Override
       public RowEncoder<T> apply(final BaseBinaryRowWriter writer) {
         return new BinaryRowEncoder<T>(
-            schema, codecFormat, rowEncoderFactory.apply(writer), writer, 
sizeEmbedded);
+            schema, rowEncoderFactory.apply(writer), writer, sizeEmbedded);
       }
     };
   }
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/BinaryArray.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/BinaryArray.java
index fdb85c8f2..6d65c5339 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/BinaryArray.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/BinaryArray.java
@@ -58,12 +58,13 @@ public class BinaryArray extends UnsafeTrait implements 
ArrayData {
 
   public BinaryArray(Field field) {
     this(field, elementSize(field));
+    initializeExtData(1); // Only require at most one slot to cache the schema 
for array type.
   }
 
+  /** Skips {@code extData}; subclass must override {@code getStruct(int, 
Field, int)}. */
   protected BinaryArray(Field field, int elementSize) {
     this.field = field;
     this.elementSize = elementSize;
-    initializeExtData(1); // Only require at most one slot to cache the schema 
for array type.
   }
 
   private static int elementSize(Field field) {
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/BinaryRow.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/BinaryRow.java
index dc099913b..fdac982a2 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/BinaryRow.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/BinaryRow.java
@@ -76,6 +76,14 @@ public class BinaryRow extends UnsafeTrait implements Row {
     initializeExtData(numFields);
   }
 
+  /** Skips {@code extData}; subclass must override {@code getStruct(int, 
Field, int)}. */
+  protected BinaryRow(Schema schema, int bitmapWidthInBytes) {
+    this.schema = schema;
+    this.numFields = schema.numFields();
+    Preconditions.checkArgument(numFields > 0);
+    this.bitmapWidthInBytes = bitmapWidthInBytes;
+  }
+
   protected int computeBitmapWidthInBytes() {
     return BitUtils.calculateBitmapWidthInBytes(numFields);
   }
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactBinaryArray.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactBinaryArray.java
index 40d10de9c..277c5d788 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactBinaryArray.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactBinaryArray.java
@@ -24,7 +24,6 @@ import 
org.apache.fory.format.row.binary.writer.CompactBinaryArrayWriter;
 import org.apache.fory.format.row.binary.writer.CompactBinaryRowWriter;
 import org.apache.fory.format.type.DataTypes;
 import org.apache.fory.format.type.Field;
-import org.apache.fory.format.type.Schema;
 import org.apache.fory.memory.MemoryBuffer;
 
 public class CompactBinaryArray extends BinaryArray {
@@ -33,12 +32,30 @@ public class CompactBinaryArray extends BinaryArray {
   private final boolean elementNullable;
   private int headerInBytes;
 
+  /** {@code null} when the element is not a struct. */
+  private final CompactRowLayout elementLayout;
+
+  /** Cold path. Hot reads use {@link #CompactBinaryArray(Field, 
CompactRowLayout)}. */
   public CompactBinaryArray(final Field field) {
+    this(field, null);
+  }
+
+  /** {@code prebuiltElementLayout} may be {@code null}; built on demand for 
struct elements. */
+  CompactBinaryArray(final Field field, final CompactRowLayout 
prebuiltElementLayout) {
     super(field, CompactBinaryArrayWriter.elementWidth(field));
     DataTypes.ListType listType = (DataTypes.ListType) field.type();
     elementField = listType.valueField();
     fixedWidth = CompactBinaryRowWriter.fixedWidthFor(elementField);
     elementNullable = elementField.nullable();
+    if (elementField.type() instanceof DataTypes.StructType) {
+      elementLayout =
+          prebuiltElementLayout != null
+              ? prebuiltElementLayout
+              : new CompactRowLayout(
+                  
CompactBinaryRowWriter.sortSchema(DataTypes.createSchema(elementField)));
+    } else {
+      elementLayout = null;
+    }
   }
 
   @Override
@@ -105,28 +122,18 @@ public class CompactBinaryArray extends BinaryArray {
       return null;
     }
     assert field == elementField;
+    final CompactBinaryRow row = elementLayout.newRow();
     if (fixedWidth == -1) {
-      return super.getStruct(ordinal, field, extDataSlot);
-    }
-    if (extData[extDataSlot] == null) {
-      extData[extDataSlot] = newSchema(field);
+      final long offsetAndSize = getInt64(ordinal);
+      final int relativeOffset = (int) (offsetAndSize >> 32);
+      final int size = (int) offsetAndSize;
+      row.pointTo(getBuffer(), getBaseOffset() + relativeOffset, size);
+    } else {
+      row.pointTo(getBuffer(), getOffset(ordinal), fixedWidth);
     }
-    final BinaryRow row = newRow((Schema) extData[extDataSlot]);
-    row.pointTo(getBuffer(), getOffset(ordinal), fixedWidth);
     return row;
   }
 
-  @Override
-  protected Schema newSchema(final Field field) {
-    return CompactBinaryRowWriter.sortSchema(super.newSchema(field));
-  }
-
-  @Override
-  protected BinaryRow newRow(final Schema schema) {
-    // TODO: don't re-compute fixed offsets
-    return new CompactBinaryRow(schema, 
CompactBinaryRowWriter.fixedOffsets(schema));
-  }
-
   @Override
   protected BinaryArray newArray(final Field field) {
     return new CompactBinaryArray(field);
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactBinaryRow.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactBinaryRow.java
index 946418b2b..7cdff675f 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactBinaryRow.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactBinaryRow.java
@@ -19,8 +19,6 @@
 
 package org.apache.fory.format.row.binary;
 
-import org.apache.fory.format.row.binary.writer.CompactBinaryRowWriter;
-import org.apache.fory.format.type.DataTypes;
 import org.apache.fory.format.type.Field;
 import org.apache.fory.format.type.Schema;
 import org.apache.fory.memory.MemoryBuffer;
@@ -41,47 +39,39 @@ import org.apache.fory.memory.MemoryBuffer;
  * <b>The compact format is still under development and may not be stable 
yet.</b>
  */
 public class CompactBinaryRow extends BinaryRow {
-  private final boolean allFieldsNotNullable;
-  private final int[] fixedOffsets;
+  private final CompactRowLayout layout;
   private final int bitmapOffset;
 
   public CompactBinaryRow(final Schema schema) {
-    this(schema, CompactBinaryRowWriter.fixedOffsets(schema));
+    this(new CompactRowLayout(schema));
   }
 
-  public CompactBinaryRow(final Schema schema, final int[] fixedOffsets) {
-    super(schema);
-    this.fixedOffsets = fixedOffsets;
-    bitmapOffset = fixedOffsets[fixedOffsets.length - 1];
-    allFieldsNotNullable = 
CompactBinaryRowWriter.allNotNullable(schema.fields());
+  CompactBinaryRow(CompactRowLayout layout) {
+    super(layout.schema, layout.bitmapWidthInBytes);
+    this.layout = layout;
+    this.bitmapOffset = layout.fixedOffsets[layout.fixedOffsets.length - 1];
   }
 
-  @Override
-  protected int computeBitmapWidthInBytes() {
-    // cannot use field due to initialization order
-    if (CompactBinaryRowWriter.allNotNullable(schema.fields())) {
-      return 0;
-    }
-    return CompactBinaryRowWriter.headerBytes(schema);
+  CompactRowLayout getLayout() {
+    return layout;
   }
 
   @Override
   public boolean isNullAt(final int ordinal) {
-    if (allFieldsNotNullable) {
+    if (layout.allFieldsNotNullable) {
       return false;
     }
     return super.isNullAt(ordinal);
   }
 
-  // TODO: this should use StableValue once it's available
   @Override
   public int getOffset(final int ordinal) {
-    return baseOffset + fixedOffsets[ordinal];
+    return baseOffset + layout.fixedOffsets[ordinal];
   }
 
   @Override
   public MemoryBuffer getBuffer(final int ordinal) {
-    final int fixedWidthBinary = CompactBinaryRowWriter.fixedWidthFor(schema, 
ordinal);
+    final int fixedWidthBinary = layout.fixedWidths[ordinal];
     if (fixedWidthBinary >= 0) {
       if (isNullAt(ordinal)) {
         return null;
@@ -94,7 +84,7 @@ public class CompactBinaryRow extends BinaryRow {
 
   @Override
   public byte[] getBinary(final int ordinal) {
-    final int fixedWidthBinary = CompactBinaryRowWriter.fixedWidthFor(schema, 
ordinal);
+    final int fixedWidthBinary = layout.fixedWidths[ordinal];
     if (fixedWidthBinary >= 0) {
       if (isNullAt(ordinal)) {
         return null;
@@ -112,27 +102,30 @@ public class CompactBinaryRow extends BinaryRow {
     if (isNullAt(ordinal)) {
       return null;
     }
-    final int fixedWidthBinary = CompactBinaryRowWriter.fixedWidthFor(schema, 
ordinal);
+    final CompactBinaryRow row = layout.childLayouts[ordinal].newRow();
+    final int fixedWidthBinary = layout.fixedWidths[ordinal];
     if (fixedWidthBinary == -1) {
-      return super.getStruct(ordinal, field, extDataSlot);
-    }
-    if (extData[extDataSlot] == null) {
-      extData[extDataSlot] = DataTypes.createSchema(field);
+      final long offsetAndSize = getInt64(ordinal);
+      final int relativeOffset = (int) (offsetAndSize >> 32);
+      final int size = (int) offsetAndSize;
+      row.pointTo(getBuffer(), getBaseOffset() + relativeOffset, size);
+    } else {
+      row.pointTo(getBuffer(), getOffset(ordinal), fixedWidthBinary);
     }
-    final BinaryRow row = newRow((Schema) extData[extDataSlot]);
-    row.pointTo(getBuffer().slice(getOffset(ordinal), fixedWidthBinary), 0, 
fixedWidthBinary);
     return row;
   }
 
   @Override
-  protected Schema newSchema(final Field field) {
-    return CompactBinaryRowWriter.sortSchema(super.newSchema(field));
-  }
-
-  @Override
-  protected BinaryRow newRow(final Schema schema) {
-    // TODO: avoid re-computing these offsets
-    return new CompactBinaryRow(schema, 
CompactBinaryRowWriter.fixedOffsets(schema));
+  BinaryArray getArray(final int ordinal, final Field field) {
+    if (isNullAt(ordinal)) {
+      return null;
+    }
+    final long offsetAndSize = getInt64(ordinal);
+    final int relativeOffset = (int) (offsetAndSize >> 32);
+    final int size = (int) offsetAndSize;
+    final CompactBinaryArray array = new CompactBinaryArray(field, 
layout.childLayouts[ordinal]);
+    array.pointTo(getBuffer(), getBaseOffset() + relativeOffset, size);
+    return array;
   }
 
   @Override
@@ -152,6 +145,6 @@ public class CompactBinaryRow extends BinaryRow {
 
   @Override
   protected BinaryRow rowForCopy() {
-    return new CompactBinaryRow(schema, fixedOffsets);
+    return new CompactBinaryRow(layout);
   }
 }
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactRowLayout.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactRowLayout.java
new file mode 100644
index 000000000..1fa02b22a
--- /dev/null
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/CompactRowLayout.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.format.row.binary;
+
+import java.util.List;
+import org.apache.fory.annotation.Internal;
+import org.apache.fory.format.row.binary.writer.CompactBinaryRowWriter;
+import org.apache.fory.format.type.DataTypes;
+import org.apache.fory.format.type.Field;
+import org.apache.fory.format.type.Schema;
+
+/**
+ * Schema-derived layout for {@link CompactBinaryRow}, shared across rows. 
Read-only after
+ * construction.
+ */
+@Internal
+public final class CompactRowLayout {
+  public final Schema schema;
+  public final int[] fixedOffsets;
+  public final int[] fixedWidths;
+  public final boolean allFieldsNotNullable;
+  public final int bitmapWidthInBytes;
+
+  /**
+   * Nested layout per field: struct row for struct fields, element row for 
{@code List<Struct>}.
+   */
+  public final CompactRowLayout[] childLayouts;
+
+  public CompactRowLayout(Schema schema) {
+    this.schema = schema;
+    this.fixedOffsets = CompactBinaryRowWriter.fixedOffsets(schema);
+    final List<Field> fields = schema.fields();
+    this.fixedWidths = new int[fields.size()];
+    this.childLayouts = new CompactRowLayout[fields.size()];
+    for (int i = 0; i < fields.size(); i++) {
+      final Field field = fields.get(i);
+      fixedWidths[i] = CompactBinaryRowWriter.fixedWidthFor(field);
+      if (field.type() instanceof DataTypes.StructType) {
+        childLayouts[i] = new CompactRowLayout(DataTypes.createSchema(field));
+      } else if (field.type() instanceof DataTypes.ListType) {
+        final Field elementField = ((DataTypes.ListType) 
field.type()).valueField();
+        if (elementField.type() instanceof DataTypes.StructType) {
+          childLayouts[i] =
+              new CompactRowLayout(
+                  
CompactBinaryRowWriter.sortSchema(DataTypes.createSchema(elementField)));
+        }
+      }
+    }
+    this.allFieldsNotNullable = CompactBinaryRowWriter.allNotNullable(fields);
+    this.bitmapWidthInBytes = allFieldsNotNullable ? 0 : 
CompactBinaryRowWriter.headerBytes(schema);
+  }
+
+  public CompactBinaryRow newRow() {
+    return new CompactBinaryRow(this);
+  }
+}
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/BaseBinaryRowWriter.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/BaseBinaryRowWriter.java
index 81616bc36..7bcc25b4b 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/BaseBinaryRowWriter.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/BaseBinaryRowWriter.java
@@ -133,5 +133,5 @@ public abstract class BaseBinaryRowWriter extends 
BinaryWriter {
     return row;
   }
 
-  protected abstract BinaryRow newRow();
+  public abstract BinaryRow newRow();
 }
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/BinaryRowWriter.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/BinaryRowWriter.java
index 9bc3cdb9b..2c466da24 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/BinaryRowWriter.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/BinaryRowWriter.java
@@ -122,7 +122,7 @@ public class BinaryRowWriter extends BaseBinaryRowWriter {
   }
 
   @Override
-  protected BinaryRow newRow() {
+  public BinaryRow newRow() {
     return new BinaryRow(getSchema());
   }
 }
diff --git 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/CompactBinaryRowWriter.java
 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/CompactBinaryRowWriter.java
index 481029ef9..ed65775a7 100644
--- 
a/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/CompactBinaryRowWriter.java
+++ 
b/java/fory-format/src/main/java/org/apache/fory/format/row/binary/writer/CompactBinaryRowWriter.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import java.util.List;
 import org.apache.fory.format.row.binary.BinaryRow;
 import org.apache.fory.format.row.binary.CompactBinaryRow;
+import org.apache.fory.format.row.binary.CompactRowLayout;
 import org.apache.fory.format.type.DataType;
 import org.apache.fory.format.type.DataTypes;
 import org.apache.fory.format.type.Field;
@@ -33,54 +34,36 @@ import org.apache.fory.memory.MemoryBuffer;
 /** Writer class to produce {@link CompactBinaryRow}-formatted rows. */
 public class CompactBinaryRowWriter extends BaseBinaryRowWriter {
 
-  private final int headerSize;
-  private final boolean allFieldsFixedSize;
-  private final boolean allFieldsNotNullable;
   private final int fixedSize;
-  private final int[] fixedOffsets;
+  private final CompactRowLayout layout;
 
   public CompactBinaryRowWriter(final Schema schema) {
     super(sortSchema(schema), computeFixedRegionSize(schema));
-    headerSize = headerBytes(schema);
-    allFieldsFixedSize = allFieldsFixedSize(schema);
-    fixedSize = computeAlignedFixedRegionSize();
-    fixedOffsets = fixedOffsets(schema);
-    allFieldsNotNullable = allNotNullable(schema.fields());
+    this.layout = new CompactRowLayout(getSchema());
+    this.fixedSize = computeAlignedFixedRegionSize(layout, bytesBeforeBitMap);
   }
 
   public CompactBinaryRowWriter(final Schema schema, final BinaryWriter 
writer) {
     super(sortSchema(schema), writer, computeFixedRegionSize(schema));
-    headerSize = headerBytes(schema);
-    allFieldsFixedSize = allFieldsFixedSize(schema);
-    fixedSize = computeAlignedFixedRegionSize();
-    fixedOffsets = fixedOffsets(schema);
-    allFieldsNotNullable = allNotNullable(schema.fields());
+    this.layout = new CompactRowLayout(getSchema());
+    this.fixedSize = computeAlignedFixedRegionSize(layout, bytesBeforeBitMap);
   }
 
   public CompactBinaryRowWriter(final Schema schema, final MemoryBuffer 
buffer) {
     super(sortSchema(schema), buffer, computeFixedRegionSize(schema));
-    headerSize = headerBytes(schema);
-    allFieldsFixedSize = allFieldsFixedSize(schema);
-    fixedSize = computeAlignedFixedRegionSize();
-    fixedOffsets = fixedOffsets(schema);
-    allFieldsNotNullable = allNotNullable(schema.fields());
+    this.layout = new CompactRowLayout(getSchema());
+    this.fixedSize = computeAlignedFixedRegionSize(layout, bytesBeforeBitMap);
   }
 
-  private static boolean allFieldsFixedSize(final Schema schema) {
-    for (final Field f : schema.fields()) {
-      if (fixedWidthFor(f) == -1) {
-        return false;
+  private static int computeAlignedFixedRegionSize(
+      final CompactRowLayout layout, final int bytesBeforeBitMap) {
+    final int totalUnalignedSize = layout.bitmapWidthInBytes + 
bytesBeforeBitMap;
+    for (final int width : layout.fixedWidths) {
+      if (width == -1) {
+        return roundNumberOfBytesToNearestWord(totalUnalignedSize);
       }
     }
-    return true;
-  }
-
-  private int computeAlignedFixedRegionSize() {
-    final int totalUnalignedSize = headerSize + bytesBeforeBitMap;
-    if (allFieldsFixedSize) {
-      return totalUnalignedSize;
-    }
-    return roundNumberOfBytesToNearestWord(totalUnalignedSize);
+    return totalUnalignedSize;
   }
 
   // TODO: this should use StableValue once it's available
@@ -115,12 +98,12 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
 
   @Override
   protected int headerInBytes() {
-    return headerSize;
+    return layout.bitmapWidthInBytes;
   }
 
   @Override
   public int getOffset(final int ordinal) {
-    return startIndex + fixedOffsets[ordinal];
+    return startIndex + layout.fixedOffsets[ordinal];
   }
 
   /** Total size of fixed region: fixed size inline values plus variable sized 
values' pointers. */
@@ -207,7 +190,7 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
   }
 
   public void resetFor(final CompactBinaryRowWriter nestedWriter, final int 
ordinal) {
-    if (fixedWidthFor(getSchema(), ordinal) == -1) {
+    if (layout.fixedWidths[ordinal] == -1) {
       nestedWriter.reset();
     } else {
       nestedWriter.startIndex = getOffset(ordinal);
@@ -218,7 +201,7 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
   @Override
   protected void resetHeader() {
     final int bitmapStart = startIndex + bytesBeforeBitMap;
-    final int end = startIndex + computeAlignedFixedRegionSize();
+    final int end = startIndex + fixedSize;
     for (int i = bitmapStart; i < end; i += 1) {
       buffer.putByte(i, 0);
     }
@@ -226,7 +209,7 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
 
   @Override
   public void setNullAt(final int ordinal) {
-    if (allFieldsNotNullable) {
+    if (layout.allFieldsNotNullable) {
       throw new IllegalStateException(
           "Field " + getSchema().field(ordinal) + " has null value for 
not-null field");
     }
@@ -235,7 +218,7 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
 
   @Override
   protected void clearValue(final int ordinal) {
-    final int fixedWidth = fixedWidthFor(getSchema(), ordinal);
+    final int fixedWidth = layout.fixedWidths[ordinal];
     if (fixedWidth > 0) {
       final int off = getOffset(ordinal);
       for (int i = off; i < off + fixedWidth; i++) {
@@ -248,7 +231,7 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
 
   @Override
   public void setNotNullAt(final int ordinal) {
-    if (allFieldsNotNullable) {
+    if (layout.allFieldsNotNullable) {
       return;
     }
     super.setNotNullAt(ordinal);
@@ -256,7 +239,7 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
 
   @Override
   public boolean isNullAt(final int ordinal) {
-    if (allFieldsNotNullable) {
+    if (layout.allFieldsNotNullable) {
       return false;
     }
     return super.isNullAt(ordinal);
@@ -295,9 +278,9 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
   @Override
   public void writeUnaligned(
       final int ordinal, final byte[] input, final int offset, final int 
numBytes) {
-    final int inlineWidth = fixedWidthFor(getSchema(), ordinal);
+    final int inlineWidth = layout.fixedWidths[ordinal];
     if (inlineWidth > 0) {
-      buffer.put(getOffset(ordinal), input, 0, numBytes);
+      buffer.put(getOffset(ordinal), input, offset, numBytes);
     } else {
       super.writeUnaligned(ordinal, input, offset, numBytes);
     }
@@ -306,10 +289,10 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
   @Override
   public void writeUnaligned(
       final int ordinal, final MemoryBuffer input, final int offset, final int 
numBytes) {
-    final int inlineWidth = fixedWidthFor(getSchema(), ordinal);
+    final int inlineWidth = layout.fixedWidths[ordinal];
     if (inlineWidth > 0) {
       assert inlineWidth == numBytes;
-      buffer.copyFrom(getOffset(ordinal), input, 0, numBytes);
+      buffer.copyFrom(getOffset(ordinal), input, offset, numBytes);
     } else {
       super.writeUnaligned(ordinal, input, offset, numBytes);
     }
@@ -318,16 +301,16 @@ public class CompactBinaryRowWriter extends 
BaseBinaryRowWriter {
   @Override
   public void writeAlignedBytes(
       final int ordinal, final MemoryBuffer input, final int baseOffset, final 
int numBytes) {
-    final int inlineWidth = fixedWidthFor(getSchema(), ordinal);
+    final int inlineWidth = layout.fixedWidths[ordinal];
     if (inlineWidth > 0) {
-      buffer.copyFrom(getOffset(ordinal), input, 0, numBytes);
+      buffer.copyFrom(getOffset(ordinal), input, baseOffset, numBytes);
     } else {
       super.writeAlignedBytes(ordinal, input, baseOffset, numBytes);
     }
   }
 
   @Override
-  protected BinaryRow newRow() {
-    return new CompactBinaryRow(getSchema(), fixedOffsets);
+  public BinaryRow newRow() {
+    return layout.newRow();
   }
 }
diff --git 
a/java/fory-format/src/test/java/org/apache/fory/format/encoder/BinaryRowEncoderPointToTest.java
 
b/java/fory-format/src/test/java/org/apache/fory/format/encoder/BinaryRowEncoderPointToTest.java
index ff31773c2..645e7d28c 100644
--- 
a/java/fory-format/src/test/java/org/apache/fory/format/encoder/BinaryRowEncoderPointToTest.java
+++ 
b/java/fory-format/src/test/java/org/apache/fory/format/encoder/BinaryRowEncoderPointToTest.java
@@ -62,8 +62,7 @@ public class BinaryRowEncoderPointToTest {
         };
 
     BinaryRowEncoder<Tiny> encoder =
-        new BinaryRowEncoder<>(
-            schema, DefaultCodecFormat.INSTANCE, captor, new 
BinaryRowWriter(schema), false);
+        new BinaryRowEncoder<>(schema, captor, new BinaryRowWriter(schema), 
false);
     encoder.decode(payload);
 
     Assert.assertNotNull(captured[0], "decode must hand the row to fromRow");
diff --git 
a/java/fory-format/src/test/java/org/apache/fory/format/encoder/CompactCodecTest.java
 
b/java/fory-format/src/test/java/org/apache/fory/format/encoder/CompactCodecTest.java
index a1f582777..60c3f0719 100644
--- 
a/java/fory-format/src/test/java/org/apache/fory/format/encoder/CompactCodecTest.java
+++ 
b/java/fory-format/src/test/java/org/apache/fory/format/encoder/CompactCodecTest.java
@@ -41,6 +41,7 @@ import org.apache.fory.format.row.binary.BinaryArray;
 import org.apache.fory.format.row.binary.BinaryMap;
 import org.apache.fory.format.row.binary.BinaryRow;
 import org.apache.fory.format.row.binary.CompactBinaryRow;
+import org.apache.fory.format.row.binary.writer.CompactBinaryRowWriter;
 import org.apache.fory.format.type.DataTypes;
 import org.apache.fory.format.type.Field;
 import org.apache.fory.memory.MemoryBuffer;
@@ -266,6 +267,31 @@ public class CompactCodecTest {
     assertEquals(buffer.size(), 7);
   }
 
+  /** After sort, f1 sits at non-zero baseOffset; re-emitting it must use that 
offset. */
+  @Test
+  public void testCopyInlineStructViaWriter() {
+    final InlineNestedType bean = new InlineNestedType();
+    bean.f1 = new Nested1();
+    bean.f1.f1 = (short) 0x4242;
+    bean.f2 = new Nested2();
+    bean.f2.f1 = 0x12345678;
+    final RowEncoder<InlineNestedType> encoder =
+        
Encoders.buildBeanCodec(InlineNestedType.class).compactEncoding().build().get();
+    final CompactBinaryRow source = (CompactBinaryRow) encoder.toRow(bean);
+    final MemoryBuffer sourceBuffer = MemoryUtils.wrap(source.toBytes());
+    source.pointTo(sourceBuffer, 0, sourceBuffer.size());
+
+    final CompactBinaryRowWriter writer = new 
CompactBinaryRowWriter(encoder.schema());
+    writer.reset();
+    writer.write(0, source.getStruct(0));
+    writer.write(1, source.getStruct(1));
+    final BinaryRow copy = writer.getRow();
+    final MemoryBuffer copyBuffer = MemoryUtils.wrap(copy.toBytes());
+    copy.pointTo(copyBuffer, 0, copyBuffer.size());
+
+    assertEquals(encoder.fromRow(copy), bean);
+  }
+
   @Data
   public static class InlineNestedArrayType {
     public InlineNestedType f1;
diff --git 
a/java/fory-format/src/test/java/org/apache/fory/format/row/binary/CompactRowLayoutTest.java
 
b/java/fory-format/src/test/java/org/apache/fory/format/row/binary/CompactRowLayoutTest.java
new file mode 100644
index 000000000..78183107b
--- /dev/null
+++ 
b/java/fory-format/src/test/java/org/apache/fory/format/row/binary/CompactRowLayoutTest.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.fory.format.row.binary;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import lombok.Data;
+import org.apache.fory.format.encoder.Encoders;
+import org.apache.fory.format.encoder.RowEncoder;
+import org.apache.fory.memory.MemoryBuffer;
+import org.apache.fory.memory.MemoryUtils;
+import org.testng.annotations.Test;
+
+public class CompactRowLayoutTest {
+
+  @Data
+  public static class Nested1 {
+    public short f1;
+  }
+
+  @Data
+  public static class Nested2 {
+    public int f1;
+  }
+
+  @Data
+  public static class InlineNestedType {
+    public Nested1 f1;
+    public Nested2 f2;
+  }
+
+  @Test
+  public void nestedStructCopyRoundTrip() {
+    final InlineNestedType bean = new InlineNestedType();
+    bean.f1 = new Nested1();
+    bean.f1.f1 = 42;
+    bean.f2 = new Nested2();
+    bean.f2.f1 = 75;
+    final RowEncoder<InlineNestedType> encoder =
+        
Encoders.buildBeanCodec(InlineNestedType.class).compactEncoding().build().get();
+    final CompactBinaryRow row = (CompactBinaryRow) encoder.toRow(bean);
+    final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+    row.pointTo(buffer, 0, buffer.size());
+    final CompactBinaryRow copy = (CompactBinaryRow) row.copy();
+    assertNotSame(copy, row);
+    assertSame(copy.getLayout(), row.getLayout());
+    assertEquals(encoder.fromRow(copy), bean);
+  }
+
+  @Test
+  public void nestedStructLayoutPrebuilt() {
+    final InlineNestedType bean = new InlineNestedType();
+    bean.f1 = new Nested1();
+    bean.f1.f1 = 42;
+    bean.f2 = new Nested2();
+    bean.f2.f1 = 75;
+    final RowEncoder<InlineNestedType> encoder =
+        
Encoders.buildBeanCodec(InlineNestedType.class).compactEncoding().build().get();
+    final CompactBinaryRow row = (CompactBinaryRow) encoder.toRow(bean);
+    final CompactRowLayout layout = row.getLayout();
+    final CompactRowLayout child0 = layout.childLayouts[0];
+    final CompactRowLayout child1 = layout.childLayouts[1];
+    assertNotNull(child0);
+    assertNotNull(child1);
+    final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+    row.pointTo(buffer, 0, buffer.size());
+    assertSame(((CompactBinaryRow) row.getStruct(0)).getLayout(), child0);
+    assertSame(((CompactBinaryRow) row.getStruct(1)).getLayout(), child1);
+    final CompactBinaryRow row2 = (CompactBinaryRow) encoder.toRow(bean);
+    assertSame(row2.getLayout().childLayouts[0], child0);
+    assertSame(row2.getLayout().childLayouts[1], child1);
+  }
+
+  @Data
+  public static class VarWidthInner {
+    public int i;
+    public String s;
+  }
+
+  @Data
+  public static class VarWidthOuter {
+    public VarWidthInner f1;
+  }
+
+  @Test
+  public void varWidthNestedStructRead() {
+    final VarWidthOuter bean = new VarWidthOuter();
+    bean.f1 = new VarWidthInner();
+    bean.f1.i = 42;
+    bean.f1.s = "luna";
+    final RowEncoder<VarWidthOuter> encoder =
+        
Encoders.buildBeanCodec(VarWidthOuter.class).compactEncoding().build().get();
+    final CompactBinaryRow row = (CompactBinaryRow) encoder.toRow(bean);
+    assertEquals(row.getLayout().fixedWidths[0], -1);
+    final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+    row.pointTo(buffer, 0, buffer.size());
+    final CompactBinaryRow inner = (CompactBinaryRow) row.getStruct(0);
+    assertSame(inner.getLayout(), row.getLayout().childLayouts[0]);
+    assertEquals(encoder.fromRow(row), bean);
+  }
+
+  /**
+   * Pins {@code getBaseOffset() +} in the var-width getStruct branch by 
giving the parent a
+   * non-zero offset.
+   */
+  @Test
+  public void varWidthNestedStructReadWithNonZeroParentOffset() {
+    final VarWidthOuter inner = new VarWidthOuter();
+    inner.f1 = new VarWidthInner();
+    inner.f1.i = 0x12345678;
+    inner.f1.s = "luna";
+    final OuterHolder bean = new OuterHolder();
+    bean.f1 = inner;
+    final RowEncoder<OuterHolder> encoder =
+        
Encoders.buildBeanCodec(OuterHolder.class).compactEncoding().build().get();
+    final CompactBinaryRow root = (CompactBinaryRow) encoder.toRow(bean);
+    final MemoryBuffer buffer = MemoryUtils.wrap(root.toBytes());
+    root.pointTo(buffer, 0, buffer.size());
+
+    final CompactBinaryRow middle = (CompactBinaryRow) root.getStruct(0);
+    assertTrue(middle.getBaseOffset() > 0);
+    assertEquals(middle.getLayout().fixedWidths[0], -1);
+    assertNotNull(middle.getStruct(0));
+    assertEquals(encoder.fromRow(root), bean);
+  }
+
+  @Data
+  public static class OuterHolder {
+    public VarWidthOuter f1;
+  }
+
+  @Data
+  public static class ListOfStructHolder {
+    public List<VarWidthInner> elements;
+  }
+
+  @Test
+  public void listOfStructElementLayoutPrebuilt() {
+    final ListOfStructHolder bean = new ListOfStructHolder();
+    final VarWidthInner inner = new VarWidthInner();
+    inner.i = 7;
+    inner.s = "luna";
+    bean.elements = List.of(inner);
+    final RowEncoder<ListOfStructHolder> encoder =
+        
Encoders.buildBeanCodec(ListOfStructHolder.class).compactEncoding().build().get();
+    final CompactBinaryRow row = (CompactBinaryRow) encoder.toRow(bean);
+    final CompactRowLayout elementLayout = row.getLayout().childLayouts[0];
+    assertNotNull(elementLayout);
+
+    final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+    row.pointTo(buffer, 0, buffer.size());
+    final CompactBinaryRow element = (CompactBinaryRow) 
row.getArray(0).getStruct(0);
+    assertSame(element.getLayout(), elementLayout);
+    assertEquals(encoder.fromRow(row), bean);
+  }
+
+  @Test
+  public void listOfStructMultipleElements() {
+    final ListOfStructHolder bean = new ListOfStructHolder();
+    final VarWidthInner a = new VarWidthInner();
+    a.i = 1;
+    a.s = "a";
+    final VarWidthInner b = new VarWidthInner();
+    b.i = 2;
+    b.s = "bbb";
+    final VarWidthInner c = new VarWidthInner();
+    c.i = 3;
+    c.s = "cccccc";
+    bean.elements = List.of(a, b, c);
+    final RowEncoder<ListOfStructHolder> encoder =
+        
Encoders.buildBeanCodec(ListOfStructHolder.class).compactEncoding().build().get();
+    final CompactBinaryRow row = (CompactBinaryRow) encoder.toRow(bean);
+    final CompactRowLayout elementLayout = row.getLayout().childLayouts[0];
+    final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+    row.pointTo(buffer, 0, buffer.size());
+    for (int i = 0; i < 3; i++) {
+      final CompactBinaryRow element = (CompactBinaryRow) 
row.getArray(0).getStruct(i);
+      assertSame(element.getLayout(), elementLayout);
+    }
+    assertEquals(encoder.fromRow(row), bean);
+  }
+
+  @Data
+  public static class AllPrimitive {
+    public int i;
+    public long l;
+    public boolean b;
+  }
+
+  @Test
+  public void allFieldsNotNullableSkipsBitmap() {
+    final AllPrimitive bean = new AllPrimitive();
+    bean.i = 42;
+    bean.l = 0x1122334455667788L;
+    bean.b = true;
+    final RowEncoder<AllPrimitive> encoder =
+        
Encoders.buildBeanCodec(AllPrimitive.class).compactEncoding().build().get();
+    final CompactBinaryRow row = (CompactBinaryRow) encoder.toRow(bean);
+    final CompactRowLayout layout = row.getLayout();
+    assertTrue(layout.allFieldsNotNullable);
+    assertEquals(layout.bitmapWidthInBytes, 0);
+    final MemoryBuffer buffer = MemoryUtils.wrap(row.toBytes());
+    row.pointTo(buffer, 0, buffer.size());
+    assertFalse(row.isNullAt(0));
+    assertFalse(row.isNullAt(1));
+    assertFalse(row.isNullAt(2));
+    assertEquals(encoder.fromRow(row), bean);
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to