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() {