3131import org .apache .paimon .types .DataType ;
3232import org .apache .paimon .types .RowKind ;
3333import org .apache .paimon .types .RowType ;
34+ import org .apache .paimon .utils .ArrayUtils ;
3435import org .apache .paimon .utils .FieldsComparator ;
3536import org .apache .paimon .utils .Preconditions ;
3637import org .apache .paimon .utils .Projection ;
@@ -73,12 +74,20 @@ public class PartialUpdateMergeFunction implements MergeFunction<KeyValue> {
7374 private final Map <Integer , FieldAggregator > fieldAggregators ;
7475 private final boolean removeRecordOnDelete ;
7576 private final Set <Integer > sequenceGroupPartialDelete ;
77+ private final boolean [] nullables ;
7678
7779 private InternalRow currentKey ;
7880 private long latestSequenceNumber ;
7981 private GenericRow row ;
8082 private KeyValue reused ;
8183 private boolean currentDeleteRow ;
84+ private boolean notNullColumnFilled ;
85+ /**
86+ * If the first value is retract, and no insert record is received, the row kind should be
87+ * RowKind.DELETE. (Partial update sequence group may not correctly set currentDeleteRow if no
88+ * RowKind.INSERT value is received)
89+ */
90+ private boolean meetInsert ;
8291
8392 protected PartialUpdateMergeFunction (
8493 InternalRow .FieldGetter [] getters ,
@@ -87,19 +96,23 @@ protected PartialUpdateMergeFunction(
8796 Map <Integer , FieldAggregator > fieldAggregators ,
8897 boolean fieldSequenceEnabled ,
8998 boolean removeRecordOnDelete ,
90- Set <Integer > sequenceGroupPartialDelete ) {
99+ Set <Integer > sequenceGroupPartialDelete ,
100+ boolean [] nullables ) {
91101 this .getters = getters ;
92102 this .ignoreDelete = ignoreDelete ;
93103 this .fieldSeqComparators = fieldSeqComparators ;
94104 this .fieldAggregators = fieldAggregators ;
95105 this .fieldSequenceEnabled = fieldSequenceEnabled ;
96106 this .removeRecordOnDelete = removeRecordOnDelete ;
97107 this .sequenceGroupPartialDelete = sequenceGroupPartialDelete ;
108+ this .nullables = nullables ;
98109 }
99110
100111 @ Override
101112 public void reset () {
102113 this .currentKey = null ;
114+ this .meetInsert = false ;
115+ this .notNullColumnFilled = false ;
103116 this .row = new GenericRow (getters .length );
104117 fieldAggregators .values ().forEach (FieldAggregator ::reset );
105118 }
@@ -109,14 +122,21 @@ public void add(KeyValue kv) {
109122 // refresh key object to avoid reference overwritten
110123 currentKey = kv .key ();
111124 currentDeleteRow = false ;
112-
113125 if (kv .valueKind ().isRetract ()) {
126+
127+ if (!notNullColumnFilled ) {
128+ initRow (row , kv .value ());
129+ notNullColumnFilled = true ;
130+ }
131+
114132 // In 0.7- versions, the delete records might be written into data file even when
115133 // ignore-delete configured, so ignoreDelete still needs to be checked
116134 if (ignoreDelete ) {
117135 return ;
118136 }
119137
138+ latestSequenceNumber = kv .sequenceNumber ();
139+
120140 if (fieldSequenceEnabled ) {
121141 retractWithSequenceGroup (kv );
122142 return ;
@@ -126,6 +146,7 @@ public void add(KeyValue kv) {
126146 if (kv .valueKind () == RowKind .DELETE ) {
127147 currentDeleteRow = true ;
128148 row = new GenericRow (getters .length );
149+ initRow (row , kv .value ());
129150 }
130151 return ;
131152 }
@@ -148,13 +169,19 @@ public void add(KeyValue kv) {
148169 } else {
149170 updateWithSequenceGroup (kv );
150171 }
172+ meetInsert = true ;
173+ notNullColumnFilled = true ;
151174 }
152175
153176 private void updateNonNullFields (KeyValue kv ) {
154177 for (int i = 0 ; i < getters .length ; i ++) {
155178 Object field = getters [i ].getFieldOrNull (kv .value ());
156179 if (field != null ) {
157180 row .setField (i , field );
181+ } else {
182+ if (!nullables [i ]) {
183+ throw new IllegalArgumentException ("Field " + i + " can not be null" );
184+ }
158185 }
159186 }
160187 }
@@ -232,6 +259,7 @@ private void retractWithSequenceGroup(KeyValue kv) {
232259 && sequenceGroupPartialDelete .contains (field )) {
233260 currentDeleteRow = true ;
234261 row = new GenericRow (getters .length );
262+ initRow (row , kv .value ());
235263 return ;
236264 } else {
237265 row .setField (field , getters [field ].getFieldOrNull (kv .value ()));
@@ -263,13 +291,26 @@ private void retractWithSequenceGroup(KeyValue kv) {
263291 }
264292 }
265293
294+ private void initRow (GenericRow row , InternalRow value ) {
295+ for (int i = 0 ; i < getters .length ; i ++) {
296+ Object field = getters [i ].getFieldOrNull (value );
297+ if (!nullables [i ]) {
298+ if (field != null ) {
299+ row .setField (i , field );
300+ } else {
301+ throw new IllegalArgumentException ("Field " + i + " can not be null" );
302+ }
303+ }
304+ }
305+ }
306+
266307 @ Override
267308 public KeyValue getResult () {
268309 if (reused == null ) {
269310 reused = new KeyValue ();
270311 }
271312
272- RowKind rowKind = currentDeleteRow ? RowKind .DELETE : RowKind .INSERT ;
313+ RowKind rowKind = currentDeleteRow || ! meetInsert ? RowKind .DELETE : RowKind .INSERT ;
273314 return reused .replace (currentKey , latestSequenceNumber , rowKind , row );
274315 }
275316
@@ -442,14 +483,19 @@ public MergeFunction<KeyValue> create(@Nullable int[][] projection) {
442483 }
443484 }
444485
486+ List <DataType > projectedTypes = Projection .of (projection ).project (tableTypes );
445487 return new PartialUpdateMergeFunction (
446- createFieldGetters (Projection . of ( projection ). project ( tableTypes ) ),
488+ createFieldGetters (projectedTypes ),
447489 ignoreDelete ,
448490 projectedSeqComparators ,
449491 projectedAggregators ,
450492 !fieldSeqComparators .isEmpty (),
451493 removeRecordOnDelete ,
452- sequenceGroupPartialDelete );
494+ sequenceGroupPartialDelete ,
495+ ArrayUtils .toPrimitiveBoolean (
496+ projectedTypes .stream ()
497+ .map (DataType ::isNullable )
498+ .toArray (Boolean []::new )));
453499 } else {
454500 Map <Integer , FieldsComparator > fieldSeqComparators = new HashMap <>();
455501 this .fieldSeqComparators .forEach (
@@ -464,7 +510,11 @@ public MergeFunction<KeyValue> create(@Nullable int[][] projection) {
464510 fieldAggregators ,
465511 !fieldSeqComparators .isEmpty (),
466512 removeRecordOnDelete ,
467- sequenceGroupPartialDelete );
513+ sequenceGroupPartialDelete ,
514+ ArrayUtils .toPrimitiveBoolean (
515+ rowType .getFieldTypes ().stream ()
516+ .map (DataType ::isNullable )
517+ .toArray (Boolean []::new )));
468518 }
469519 }
470520
0 commit comments