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

jackietien pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 48388f3bd1c fix: pattern_match TVF crashes and parameter validation 
(#17471)
48388f3bd1c is described below

commit 48388f3bd1cccaeacffce426ec205fdc47e0234f
Author: Lin Xintao <[email protected]>
AuthorDate: Wed Apr 15 09:28:34 2026 +0800

    fix: pattern_match TVF crashes and parameter validation (#17471)
---
 .../relational/it/db/it/IoTDBWindowTVFIT.java      | 22 ++++++++++++++
 .../function/tvf/PatternMatchTableFunction.java    |  9 ++++++
 .../function/tvf/match/model/MatchState.java       | 16 +++++-----
 .../function/tvf/match/model/RegexMatchState.java  | 34 +++++++++-------------
 4 files changed, 52 insertions(+), 29 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowTVFIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowTVFIT.java
index 9e83c8ac41b..7b6390f84e1 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowTVFIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowTVFIT.java
@@ -859,5 +859,27 @@ public class IoTDBWindowTVFIT {
         expectedHeader,
         retArray,
         DATABASE_NAME);
+
+    // test flat pattern with smooth=0.0 should not crash (was 
IndexOutOfBoundsException due to
+    // NaN from 0/0)
+    // pattern '1,1,1,1,1,2,3,4,3' is flat->up->down, no data segment matches 
this sign sequence
+    retArray = new String[] {};
+    tableResultSetEqualByDataTypeTest(
+        "select * from pattern_match(data => t1 ORDER BY time, time_col => 
'time', data_col => 'value', pattern => '1.0,1.0,1.0,1.0,1.0,2.0,3.0,4.0,3.0', 
smooth => 0.0, threshold => 1.0, smooth_on_pattern => false)",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    // test negative smooth should be rejected
+    tableAssertTestFail(
+        "select * from pattern_match(data => t1 ORDER BY time, time_col => 
'time', data_col => 'value', pattern => '1.0,2.0,1.0', smooth => -0.5, 
threshold => 10.0, width => 1000.0, height => 500.0, smooth_on_pattern => 
false)",
+        "smooth must be a non-negative number",
+        DATABASE_NAME);
+
+    // test negative threshold should be rejected
+    tableAssertTestFail(
+        "select * from pattern_match(data => t1 ORDER BY time, time_col => 
'time', data_col => 'value', pattern => '1.0,2.0,1.0', smooth => 0.5, threshold 
=> -1.1, width => 1000.0, height => 500.0, smooth_on_pattern => false)",
+        "threshold must be a non-negative number",
+        DATABASE_NAME);
   }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/PatternMatchTableFunction.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/PatternMatchTableFunction.java
index fd352f9c0b9..c2ed8c0ead0 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/PatternMatchTableFunction.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/PatternMatchTableFunction.java
@@ -105,6 +105,15 @@ public class PatternMatchTableFunction implements 
TableFunction {
             expectedDataName,
             ImmutableSet.of(Type.INT32, Type.INT64, Type.FLOAT, Type.DOUBLE));
 
+    Double smoothValue = (Double) ((ScalarArgument) 
arguments.get(SMOOTH_PARAM)).getValue();
+    Double thresholdValue = (Double) ((ScalarArgument) 
arguments.get(THRESHOLD_PARAM)).getValue();
+    if (smoothValue < 0) {
+      throw new UDFException("smooth must be a non-negative number, but got: " 
+ smoothValue);
+    }
+    if (thresholdValue < 0) {
+      throw new UDFException("threshold must be a non-negative number, but 
got: " + thresholdValue);
+    }
+
     // outputColumnSchema description
     DescribedSchema properColumnSchema =
         new DescribedSchema.Builder()
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/match/model/MatchState.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/match/model/MatchState.java
index 7650b25b0e8..4e3b0e1f851 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/match/model/MatchState.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/match/model/MatchState.java
@@ -165,7 +165,9 @@ public class MatchState {
     double localHeightUp = Math.max(section.getHeightBound(), smoothValue);
     double localHeightDown =
         Math.max(patternSectionNow.getHeightBound() * globalHeightRadio, 
smoothValue);
-    double localHeightRadio = localHeightUp / localHeightDown;
+    // When both are 0 (both sections are flat with smooth=0), ratio is 1.0 
(perfect match)
+    double localHeightRadio =
+        (localHeightUp == 0 && localHeightDown == 0) ? 1.0 : localHeightUp / 
localHeightDown;
 
     double led = Math.pow(Math.log(localWidthRadio), 2) + 
Math.pow(Math.log(localHeightRadio), 2);
 
@@ -195,12 +197,10 @@ public class MatchState {
                   - section.getPoints().get(i).y);
     }
 
-    shapeError =
-        shapeError
-            / (((dataMaxHeight - dataMinHeight) == 0
-                    ? smoothValue
-                    : (dataMaxHeight - dataMinHeight))
-                * (section.getPoints().size() - 1));
+    double heightNorm =
+        (dataMaxHeight - dataMinHeight) == 0 ? smoothValue : (dataMaxHeight - 
dataMinHeight);
+    double seDenominator = heightNorm * (section.getPoints().size() - 1);
+    shapeError = seDenominator == 0 ? 0 : shapeError / seDenominator;
 
     // calc the match value for a section
     matchValue = matchValue + led + shapeError;
@@ -212,7 +212,7 @@ public class MatchState {
       patternSectionNow = null;
     }
 
-    if (isFinish || matchValue > threshold) {
+    if (isFinish || matchValue > threshold || Double.isNaN(matchValue)) {
       return true;
     } else {
       patternSectionNow = patternSectionNow.getNextSectionList().get(0);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/match/model/RegexMatchState.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/match/model/RegexMatchState.java
index e82f15d091a..ecc6d766185 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/match/model/RegexMatchState.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/function/tvf/match/model/RegexMatchState.java
@@ -320,19 +320,22 @@ public class RegexMatchState {
       double localHeightUp = Math.max(dataSection.getHeightBound(), 
smoothValue);
       double localHeightDown =
           Math.max(patternSection.getHeightBound() * globalHeightRadio, 
smoothValue);
-      double localHeightRadio = localHeightUp / localHeightDown;
+      // When both are 0 (both sections are flat with smooth=0), ratio is 1.0 
(perfect match)
+      double localHeightRadio =
+          (localHeightUp == 0 && localHeightDown == 0) ? 1.0 : localHeightUp / 
localHeightDown;
 
       double led = Math.pow(Math.log(localWidthRadio), 2) + 
Math.pow(Math.log(localHeightRadio), 2);
 
       // different way
       double shapeError = 0.0;
+      double heightNorm =
+          (dataMaxHeight - dataMinHeight) == 0 ? smoothValue : (dataMaxHeight 
- dataMinHeight);
       if (CALC_SE_USING_MORE_MEMORY
           && dataSection.getCalcResult().get(patternSection.getId()) != null) {
         shapeError =
-            dataSection.getCalcResult().get(patternSection.getId())
-                / ((dataMaxHeight - dataMinHeight) == 0
-                    ? smoothValue
-                    : (dataMaxHeight - dataMinHeight));
+            heightNorm == 0
+                ? 0
+                : dataSection.getCalcResult().get(patternSection.getId()) / 
heightNorm;
       } else {
         // calc the SE
         // align the first point or the centroid, it's same because the 
calculation is just an avg
@@ -348,7 +351,7 @@ public class RegexMatchState {
 
         double numRadio = ((double) patternPointNum) / ((double) dataPointNum);
 
-        for (int i = 1; i < dataPointNum; i++) {
+        for (int i = 1; i <= dataPointNum; i++) {
           double patternIndex = i * numRadio;
           int leftIndex = (int) patternIndex;
           double leftRadio = patternIndex - leftIndex;
@@ -366,27 +369,16 @@ public class RegexMatchState {
                       - dataSection.getPoints().get(i).y);
         }
 
-        shapeError =
-            shapeError
-                / (((dataMaxHeight - dataMinHeight) == 0
-                        ? smoothValue
-                        : (dataMaxHeight - dataMinHeight))
-                    * (dataSection.getPoints().size() - 1));
+        double seDenominator = heightNorm * (dataSection.getPoints().size() - 
1);
+        shapeError = seDenominator == 0 ? 0 : shapeError / seDenominator;
 
         if (CALC_SE_USING_MORE_MEMORY) {
-          dataSection
-              .getCalcResult()
-              .put(
-                  patternSection.getId(),
-                  shapeError
-                      * (((dataMaxHeight - dataMinHeight) == 0
-                          ? smoothValue
-                          : (dataMaxHeight - dataMinHeight))));
+          dataSection.getCalcResult().put(patternSection.getId(), shapeError * 
heightNorm);
         }
       }
 
       matchValue = matchValue + led + shapeError;
-      return matchValue > threshold;
+      return matchValue > threshold || Double.isNaN(matchValue);
     }
 
     public double getMatchValue() {

Reply via email to