Fix: Prevent integer overflow in ConstantOperator distance calculation

Co-authored-by: huanmingwong <huanmingwong@gmail.com>
This commit is contained in:
Cursor Agent 2025-09-29 02:16:20 +00:00
parent d597f92555
commit 6ce2895b46
3 changed files with 151 additions and 3 deletions

View File

@ -677,11 +677,11 @@ public final class ConstantOperator extends ScalarOperator implements Comparable
public long distance(ConstantOperator other) {
if (type.isTinyint()) {
return other.getTinyInt() - getTinyInt();
return (long) other.getTinyInt() - (long) getTinyInt();
} else if (type.isSmallint()) {
return other.getSmallint() - getSmallint();
return (long) other.getSmallint() - (long) getSmallint();
} else if (type.isInt()) {
return other.getInt() - getInt();
return (long) other.getInt() - (long) getInt();
} else if (type.isBigint()) {
return other.getBigint() - getBigint();
} else if (type.isLargeint()) {

View File

@ -158,6 +158,12 @@ public class ConstantOperatorTest {
ConstantOperator var2 = ConstantOperator.createTinyInt((byte) 20);
Assertions.assertEquals(10, var1.distance(var2));
Assertions.assertEquals(-10, var2.distance(var1));
// tinyint edge cases
ConstantOperator tinyMax = ConstantOperator.createTinyInt(Byte.MAX_VALUE);
ConstantOperator tinyMin = ConstantOperator.createTinyInt(Byte.MIN_VALUE);
Assertions.assertEquals(255L, tinyMax.distance(tinyMin));
Assertions.assertEquals(-255L, tinyMin.distance(tinyMax));
}
{
@ -166,6 +172,12 @@ public class ConstantOperatorTest {
ConstantOperator var2 = ConstantOperator.createSmallInt((short) 20);
Assertions.assertEquals(10, var1.distance(var2));
Assertions.assertEquals(-10, var2.distance(var1));
// smallint edge cases
ConstantOperator smallMax = ConstantOperator.createSmallInt(Short.MAX_VALUE);
ConstantOperator smallMin = ConstantOperator.createSmallInt(Short.MIN_VALUE);
Assertions.assertEquals(65535L, smallMax.distance(smallMin));
Assertions.assertEquals(-65535L, smallMin.distance(smallMax));
}
{
@ -176,6 +188,27 @@ public class ConstantOperatorTest {
Assertions.assertEquals(-10, var2.distance(var1));
}
{
// int edge cases - test for integer overflow fix
ConstantOperator intMax = ConstantOperator.createInt(Integer.MAX_VALUE);
ConstantOperator intMin = ConstantOperator.createInt(Integer.MIN_VALUE);
ConstantOperator testValue = ConstantOperator.createInt(1234567890);
ConstantOperator zero = ConstantOperator.createInt(0);
// Test case that previously caused overflow: INT_MAX - INT_MIN should be 4294967295
Assertions.assertEquals(4294967295L, intMax.distance(intMin));
Assertions.assertEquals(-4294967295L, intMin.distance(intMax));
// Test case from issue #63669: test_value - INT_MIN should be 3382051538
Assertions.assertEquals(3382051538L, testValue.distance(intMin));
// Test case: INT_MAX - test_value should be 912915757
Assertions.assertEquals(912915757L, intMax.distance(testValue));
// Test case: zero - INT_MIN should be 2147483648
Assertions.assertEquals(2147483648L, zero.distance(intMin));
}
{
// long
ConstantOperator var1 = ConstantOperator.createBigint(10);

View File

@ -0,0 +1,115 @@
// Copyright 2021-present StarRocks, Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.starrocks.sql.optimizer.rule.transformation.materialization;
import com.starrocks.catalog.Type;
import com.starrocks.sql.ast.expression.BinaryType;
import com.starrocks.sql.optimizer.operator.scalar.BinaryPredicateOperator;
import com.starrocks.sql.optimizer.operator.scalar.ColumnRefOperator;
import com.starrocks.sql.optimizer.operator.scalar.ConstantOperator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* Test case for issue #63669: FE Assertion Failure when querying on int32 column in WHERE clause
*/
public class Int32PredicateTest {
@Test
public void testInt32PredicateWithLargeValue() {
// Reproduce the issue scenario: WHERE collect_api_receive_time = 1234567890
ColumnRefOperator columnRef = new ColumnRefOperator(1, Type.INT, "collect_api_receive_time", true);
ConstantOperator value = ConstantOperator.createInt(1234567890);
// Create a binary predicate: collect_api_receive_time = 1234567890
BinaryPredicateOperator pred = new BinaryPredicateOperator(BinaryType.EQ, columnRef, value);
// This should not throw an assertion failure anymore
Assertions.assertNotNull(pred);
Assertions.assertEquals(BinaryType.EQ, pred.getBinaryType());
Assertions.assertEquals(columnRef, pred.getChild(0));
Assertions.assertEquals(value, pred.getChild(1));
// Test the distance calculation that was causing the issue
ConstantOperator intMin = ConstantOperator.createInt(Integer.MIN_VALUE);
long distance = value.distance(intMin);
Assertions.assertEquals(3382051538L, distance);
}
@Test
public void testInt32PredicateExtraction() {
// Test predicate extraction which involves range canonicalization
ColumnRefOperator columnRef = new ColumnRefOperator(1, Type.INT, "test_column", true);
ConstantOperator value = ConstantOperator.createInt(1234567890);
// Create equality predicate
BinaryPredicateOperator eqPred = new BinaryPredicateOperator(BinaryType.EQ, columnRef, value);
// Create greater than predicate
BinaryPredicateOperator gtPred = new BinaryPredicateOperator(BinaryType.GT, columnRef, value);
// Create less than predicate
BinaryPredicateOperator ltPred = new BinaryPredicateOperator(BinaryType.LT, columnRef, value);
// These should not cause assertion failures
PredicateExtractor extractor = new PredicateExtractor();
PredicateExtractor.PredicateExtractorContext context = new PredicateExtractor.PredicateExtractorContext();
try {
RangePredicate eqRange = extractor.visitBinaryPredicate(eqPred, context);
RangePredicate gtRange = extractor.visitBinaryPredicate(gtPred, context);
RangePredicate ltRange = extractor.visitBinaryPredicate(ltPred, context);
Assertions.assertNotNull(eqRange);
Assertions.assertNotNull(gtRange);
Assertions.assertNotNull(ltRange);
} catch (Exception e) {
Assertions.fail("Predicate extraction should not throw exception: " + e.getMessage());
}
}
@Test
public void testInt32EdgeCasesInPredicates() {
// Test edge cases that could cause overflow in distance calculation
ColumnRefOperator columnRef = new ColumnRefOperator(1, Type.INT, "test_column", true);
// Test with INT_MAX
ConstantOperator intMax = ConstantOperator.createInt(Integer.MAX_VALUE);
BinaryPredicateOperator maxPred = new BinaryPredicateOperator(BinaryType.EQ, columnRef, intMax);
// Test with INT_MIN
ConstantOperator intMin = ConstantOperator.createInt(Integer.MIN_VALUE);
BinaryPredicateOperator minPred = new BinaryPredicateOperator(BinaryType.EQ, columnRef, intMin);
// Test with the specific value from the issue
ConstantOperator issueValue = ConstantOperator.createInt(1234567890);
BinaryPredicateOperator issuePred = new BinaryPredicateOperator(BinaryType.EQ, columnRef, issueValue);
PredicateExtractor extractor = new PredicateExtractor();
PredicateExtractor.PredicateExtractorContext context = new PredicateExtractor.PredicateExtractorContext();
try {
RangePredicate maxRange = extractor.visitBinaryPredicate(maxPred, context);
RangePredicate minRange = extractor.visitBinaryPredicate(minPred, context);
RangePredicate issueRange = extractor.visitBinaryPredicate(issuePred, context);
Assertions.assertNotNull(maxRange);
Assertions.assertNotNull(minRange);
Assertions.assertNotNull(issueRange);
} catch (Exception e) {
Assertions.fail("Edge case predicate extraction should not throw exception: " + e.getMessage());
}
}
}