diff --git a/paimon-common/src/main/java/org/apache/paimon/predicate/StringTransform.java b/paimon-common/src/main/java/org/apache/paimon/predicate/StringTransform.java index 7b87a67c2c5b..1dd2405ca6d0 100644 --- a/paimon-common/src/main/java/org/apache/paimon/predicate/StringTransform.java +++ b/paimon-common/src/main/java/org/apache/paimon/predicate/StringTransform.java @@ -158,7 +158,7 @@ public int hashCode() { @Override public String toString() { List inputs = - this.inputs.stream().map(Object::toString).collect(Collectors.toList()); + this.inputs.stream().map(String::valueOf).collect(Collectors.toList()); return name() + "(" + String.join(", ", inputs) + ')'; } } diff --git a/paimon-common/src/main/java/org/apache/paimon/predicate/SubstringTransform.java b/paimon-common/src/main/java/org/apache/paimon/predicate/SubstringTransform.java index 054422a20125..f59aba852504 100644 --- a/paimon-common/src/main/java/org/apache/paimon/predicate/SubstringTransform.java +++ b/paimon-common/src/main/java/org/apache/paimon/predicate/SubstringTransform.java @@ -71,6 +71,10 @@ public final Object transform(InternalRow row) { if (begin instanceof FieldRef) { FieldRef beginRef = (FieldRef) begin; checkArgument(beginRef.type().is(INTEGER_NUMERIC)); + // getInt on a null reads an undefined value on columnar rows + if (row.isNullAt(beginRef.index())) { + return null; + } beginIndex = row.getInt(beginRef.index()); } else { beginIndex = Integer.parseInt(inputs.get(1).toString()); @@ -85,6 +89,9 @@ public final Object transform(InternalRow row) { if (end instanceof FieldRef) { FieldRef endRef = (FieldRef) inputs.get(2); checkArgument(endRef.type().is(INTEGER_NUMERIC)); + if (row.isNullAt(endRef.index())) { + return null; + } endIndex = beginIndex + row.getInt(endRef.index()) - 1; } else { endIndex = beginIndex + Integer.parseInt(inputs.get(2).toString()) - 1; diff --git a/paimon-common/src/main/java/org/apache/paimon/predicate/TrimTransform.java b/paimon-common/src/main/java/org/apache/paimon/predicate/TrimTransform.java index 6182335bb221..01ba75e7293f 100644 --- a/paimon-common/src/main/java/org/apache/paimon/predicate/TrimTransform.java +++ b/paimon-common/src/main/java/org/apache/paimon/predicate/TrimTransform.java @@ -51,7 +51,14 @@ public BinaryString transform(List inputs) { return null; } String sourceString = inputs.get(0).toString(); - String charsToTrim = inputs.size() == 1 ? " " : inputs.get(1).toString(); + String charsToTrim = " "; + if (inputs.size() == 2) { + if (inputs.get(1) == null) { + // StringUtils.ltrim/rtrim treat a null charsToTrim as a null result + return null; + } + charsToTrim = inputs.get(1).toString(); + } switch (trimFlag) { case BOTH: return BinaryString.fromString(StringUtils.trim(sourceString, charsToTrim)); diff --git a/paimon-common/src/test/java/org/apache/paimon/predicate/ConcatTransformTest.java b/paimon-common/src/test/java/org/apache/paimon/predicate/ConcatTransformTest.java index e776040f89f0..a9cc9e7a5524 100644 --- a/paimon-common/src/test/java/org/apache/paimon/predicate/ConcatTransformTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/predicate/ConcatTransformTest.java @@ -72,4 +72,13 @@ public void testConcatHybridInputs() { BinaryString.fromString("-he"))); assertThat(result).isEqualTo(BinaryString.fromString("ha-he")); } + + @Test + public void testToStringWithNullInput() { + List inputs = new ArrayList<>(); + inputs.add(BinaryString.fromString("a")); + inputs.add(null); + + assertThat(new ConcatTransform(inputs).toString()).isEqualTo("CONCAT(a, null)"); + } } diff --git a/paimon-common/src/test/java/org/apache/paimon/predicate/SubstringTransformTest.java b/paimon-common/src/test/java/org/apache/paimon/predicate/SubstringTransformTest.java index b4d998bea9ec..ac2631228f43 100644 --- a/paimon-common/src/test/java/org/apache/paimon/predicate/SubstringTransformTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/predicate/SubstringTransformTest.java @@ -100,6 +100,27 @@ public void testSubstringRefInputs() { assertThat(result).isEqualTo(BinaryString.fromString("ell")); } + @Test + public void testNullPositionFieldYieldsNull() { + List inputs = new ArrayList<>(); + inputs.add(new FieldRef(0, "f0", DataTypes.STRING())); + inputs.add(new FieldRef(1, "f1", DataTypes.INT())); + assertThat( + new SubstringTransform(inputs) + .transform( + GenericRow.of( + BinaryString.fromString("123-45-6789"), null))) + .isNull(); + + inputs.add(new FieldRef(2, "f2", DataTypes.INT())); + assertThat( + new SubstringTransform(inputs) + .transform( + GenericRow.of( + BinaryString.fromString("123-45-6789"), 8, null))) + .isNull(); + } + @Test public void testSubstringRefInputUsesSourceFieldNullability() { List inputs = new ArrayList<>(); diff --git a/paimon-common/src/test/java/org/apache/paimon/predicate/TrimTransformTest.java b/paimon-common/src/test/java/org/apache/paimon/predicate/TrimTransformTest.java index b24fda78a7d7..597a71d280a7 100644 --- a/paimon-common/src/test/java/org/apache/paimon/predicate/TrimTransformTest.java +++ b/paimon-common/src/test/java/org/apache/paimon/predicate/TrimTransformTest.java @@ -101,6 +101,18 @@ public void testNormalInputs() { assertThat(result).isEqualTo(BinaryString.fromString(" aa")); } + @Test + public void testNullCharsToTrimYieldsNull() { + List inputs = new ArrayList<>(); + inputs.add(new FieldRef(0, "f0", DataTypes.STRING())); + inputs.add(new FieldRef(1, "f1", DataTypes.STRING())); + GenericRow row = GenericRow.of(BinaryString.fromString(" x "), null); + + for (TrimTransform.Flag flag : TrimTransform.Flag.values()) { + assertThat(new TrimTransform(inputs, flag).transform(row)).isNull(); + } + } + @Test public void testSubstringRefInputs() { List inputs = new ArrayList<>();