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

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


The following commit(s) were added to refs/heads/main by this push:
     new e0242201b20 NIFI-16067 Adjusted PutIcebergRecord to map fields by 
ordinal position instead of column name (#11387)
e0242201b20 is described below

commit e0242201b20603f89fe4505a858633fe363a427c
Author: maltesander <[email protected]>
AuthorDate: Sat Jul 25 20:05:25 2026 +0200

    NIFI-16067 Adjusted PutIcebergRecord to map fields by ordinal position 
instead of column name (#11387)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../processors/iceberg/record/DelegatedRecord.java | 21 +++---
 .../iceberg/record/DelegatedRecordTest.java        | 83 +++++++++++++++++++++-
 2 files changed, 94 insertions(+), 10 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-iceberg-bundle/nifi-iceberg-processors/src/main/java/org/apache/nifi/processors/iceberg/record/DelegatedRecord.java
 
b/nifi-extension-bundles/nifi-iceberg-bundle/nifi-iceberg-processors/src/main/java/org/apache/nifi/processors/iceberg/record/DelegatedRecord.java
index 830377e19f6..eff7652be1a 100644
--- 
a/nifi-extension-bundles/nifi-iceberg-bundle/nifi-iceberg-processors/src/main/java/org/apache/nifi/processors/iceberg/record/DelegatedRecord.java
+++ 
b/nifi-extension-bundles/nifi-iceberg-bundle/nifi-iceberg-processors/src/main/java/org/apache/nifi/processors/iceberg/record/DelegatedRecord.java
@@ -19,7 +19,6 @@ package org.apache.nifi.processors.iceberg.record;
 import org.apache.iceberg.data.Record;
 import org.apache.iceberg.types.Types;
 import org.apache.nifi.serialization.record.MapRecord;
-import org.apache.nifi.serialization.record.RecordField;
 
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -70,15 +69,17 @@ public class DelegatedRecord implements Record {
     }
 
     /**
-     * Get Field value for specified position from supporting Record
+     * Get Field value for specified position from supporting Record. The 
position refers to the Iceberg Table struct,
+     * so the field is resolved by the Iceberg column name to align incoming 
Record fields with Table columns
+     * regardless of the incoming Record field ordering. Columns not present 
in the incoming Record return null.
      *
-     * @param position Field position
+     * @param position Field position in the Iceberg Table struct
      * @return Field value or null when not found
      */
     @Override
     public Object get(final int position) {
-        final RecordField recordField = record.getSchema().getField(position);
-        return record.getValue(recordField);
+        final Types.NestedField field = struct.fields().get(position);
+        return record.getValue(field.name());
     }
 
     /**
@@ -133,16 +134,18 @@ public class DelegatedRecord implements Record {
     }
 
     /**
-     * Set Field value for specified position
+     * Set Field value for specified position. The position refers to the 
Iceberg Table struct, so the field is resolved
+     * by the Iceberg column name to remain symmetric with {@link #get(int)} 
regardless of the incoming Record field
+     * ordering.
      *
-     * @param position Field position
+     * @param position Field position in the Iceberg Table struct
      * @param value Field value
      * @param <T> Field Value Type
      */
     @Override
     public <T> void set(final int position, final T value) {
-        final RecordField recordField = record.getSchema().getField(position);
-        record.setValue(recordField, value);
+        final Types.NestedField field = struct.fields().get(position);
+        record.setValue(field.name(), value);
     }
 
     @Override
diff --git 
a/nifi-extension-bundles/nifi-iceberg-bundle/nifi-iceberg-processors/src/test/java/org/apache/nifi/processors/iceberg/record/DelegatedRecordTest.java
 
b/nifi-extension-bundles/nifi-iceberg-bundle/nifi-iceberg-processors/src/test/java/org/apache/nifi/processors/iceberg/record/DelegatedRecordTest.java
index 7e43a5c4448..3b678521387 100644
--- 
a/nifi-extension-bundles/nifi-iceberg-bundle/nifi-iceberg-processors/src/test/java/org/apache/nifi/processors/iceberg/record/DelegatedRecordTest.java
+++ 
b/nifi-extension-bundles/nifi-iceberg-bundle/nifi-iceberg-processors/src/test/java/org/apache/nifi/processors/iceberg/record/DelegatedRecordTest.java
@@ -25,6 +25,7 @@ import org.apache.nifi.serialization.record.RecordFieldType;
 import org.apache.nifi.serialization.record.RecordSchema;
 import org.junit.jupiter.api.Test;
 
+import java.math.BigDecimal;
 import java.sql.Date;
 import java.sql.Time;
 import java.sql.Timestamp;
@@ -36,6 +37,7 @@ import java.util.List;
 import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 class DelegatedRecordTest {
 
@@ -53,6 +55,18 @@ class DelegatedRecordTest {
     private static final Time STOPPED = Time.valueOf("23:30:45");
     private static final LocalTime STOPPED_CONVERTED = STOPPED.toLocalTime();
 
+    private static final String ID_FIELD = "id";
+    private static final String AMOUNT_FIELD = "amount";
+    private static final int ID = 7;
+    private static final int ID_MODIFIED = 99;
+    private static final BigDecimal AMOUNT = new BigDecimal("12.34");
+
+    private static final Types.StructType TABLE_STRUCT = Types.StructType.of(
+            Types.NestedField.required(1, ID_FIELD, Types.IntegerType.get()),
+            Types.NestedField.optional(2, AMOUNT_FIELD, 
Types.DecimalType.of(10, 2)),
+            Types.NestedField.optional(3, LABEL_FIELD, Types.StringType.get())
+    );
+
     @Test
     void testCopyEmptyRecord() {
         final List<RecordField> recordFields = List.of();
@@ -82,7 +96,9 @@ class DelegatedRecordTest {
 
         final Record record = new MapRecord(recordSchema, values);
 
-        final Types.StructType structType = Types.StructType.of();
+        final Types.StructType structType = Types.StructType.of(
+                Types.NestedField.optional(1, LABEL_FIELD, 
Types.StringType.get())
+        );
         final DelegatedRecord delegatedRecord = new DelegatedRecord(record, 
structType);
 
         final Types.StructType recordStruct = delegatedRecord.struct();
@@ -135,4 +151,69 @@ class DelegatedRecordTest {
         final Object stopped = delegatedRecord.getField(STOPPED_FIELD);
         assertEquals(STOPPED_CONVERTED, stopped);
     }
+
+    /**
+     * Iceberg writers read values positionally against the table struct, so 
position 0 must always return the value of
+     * the table's first column ("id"), independent of the field ordering in 
the incoming Record schema.
+     */
+    @Test
+    void testGetByPositionMatchesTableColumnNameRegardlessOfInputOrder() {
+        final DelegatedRecord delegatedRecord = new 
DelegatedRecord(shuffledInputRecord(), TABLE_STRUCT);
+
+        assertEquals(ID, delegatedRecord.get(0));
+        assertEquals(AMOUNT, delegatedRecord.get(1));
+        assertEquals(LABEL, delegatedRecord.get(2));
+    }
+
+    /**
+     * When the incoming Record does not contain a column present in the table 
schema, positional access must return
+     * null for that column rather than shifting subsequent input values into 
it.
+     */
+    @Test
+    void testGetByPositionReturnsNullForColumnMissingFromInput() {
+        final RecordSchema recordSchema = new SimpleRecordSchema(List.of(
+                new RecordField(ID_FIELD, RecordFieldType.INT.getDataType()),
+                new RecordField(LABEL_FIELD, 
RecordFieldType.STRING.getDataType())
+        ));
+        final Map<String, Object> values = new LinkedHashMap<>();
+        values.put(ID_FIELD, ID);
+        values.put(LABEL_FIELD, LABEL);
+        final DelegatedRecord delegatedRecord = new DelegatedRecord(new 
MapRecord(recordSchema, values), TABLE_STRUCT);
+
+        assertEquals(ID, delegatedRecord.get(0));
+        assertNull(delegatedRecord.get(1), "Missing '" + AMOUNT_FIELD + "' 
column must be null, not shifted input data");
+        assertEquals(LABEL, delegatedRecord.get(2));
+    }
+
+    /**
+     * Positional set must resolve the target field by the Iceberg table 
column name for the given position, independent
+     * of the incoming Record field ordering, so that set(position) is 
symmetric with get(position).
+     */
+    @Test
+    void testSetByPositionMatchesTableColumnNameRegardlessOfInputOrder() {
+        final DelegatedRecord delegatedRecord = new 
DelegatedRecord(shuffledInputRecord(), TABLE_STRUCT);
+
+        delegatedRecord.set(0, ID_MODIFIED);
+
+        assertEquals(ID_MODIFIED, delegatedRecord.getField(ID_FIELD));
+        assertEquals(AMOUNT, delegatedRecord.getField(AMOUNT_FIELD));
+        assertEquals(LABEL, delegatedRecord.getField(LABEL_FIELD));
+    }
+
+    /**
+     * Build an incoming Record whose field ordering (amount, label, id) 
intentionally differs from the
+     * {@link #TABLE_STRUCT} ordering (id, amount, label) so positional access 
must resolve by column name.
+     */
+    private static Record shuffledInputRecord() {
+        final RecordSchema recordSchema = new SimpleRecordSchema(List.of(
+                new RecordField(AMOUNT_FIELD, 
RecordFieldType.DECIMAL.getDataType()),
+                new RecordField(LABEL_FIELD, 
RecordFieldType.STRING.getDataType()),
+                new RecordField(ID_FIELD, RecordFieldType.INT.getDataType())
+        ));
+        final Map<String, Object> values = new LinkedHashMap<>();
+        values.put(AMOUNT_FIELD, AMOUNT);
+        values.put(LABEL_FIELD, LABEL);
+        values.put(ID_FIELD, ID);
+        return new MapRecord(recordSchema, values);
+    }
 }

Reply via email to