This is an automated email from the ASF dual-hosted git repository.
ppa pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 9ff0e672f4b IGNITE-25767 Sql. Fixed KeyValueGetPlan for out of range
key (#6193)
9ff0e672f4b is described below
commit 9ff0e672f4b17fea38a5b372d71a8da0a11d82b4
Author: Pavel Pereslegin <[email protected]>
AuthorDate: Mon Jul 14 17:43:18 2025 +0300
IGNITE-25767 Sql. Fixed KeyValueGetPlan for out of range key (#6193)
---
.../sql/engine/ItSqlUsesKeyValueGetTest.java | 21 +++++++++
.../ignite/internal/sql/engine/util/RexUtils.java | 51 ++++++++++++++++------
.../planner/PrimaryKeyLookupPlannerTest.java | 41 +++++++++++++++++
3 files changed, 99 insertions(+), 14 deletions(-)
diff --git
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueGetTest.java
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueGetTest.java
index dd8f4b7f7ff..3795c88cd70 100644
---
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueGetTest.java
+++
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSqlUsesKeyValueGetTest.java
@@ -21,6 +21,8 @@ import static
org.apache.ignite.internal.sql.engine.util.QueryChecker.containsSu
import java.util.concurrent.ThreadLocalRandom;
import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
+import org.apache.ignite.internal.tx.InternalTransaction;
+import org.apache.ignite.tx.Transaction;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
@@ -111,6 +113,25 @@ public class ItSqlUsesKeyValueGetTest extends
BaseSqlIntegrationTest {
.check();
}
+ @Test
+ void lookupOnOutOfRangeKey() {
+ Transaction tx = CLUSTER.aliveNode().transactions().begin();
+
+ try {
+ sql(tx, "INSERT INTO simple_key VALUES (2147483647, 0),
(-2147483648, 0);");
+
+ assertQuery((InternalTransaction) tx, "SELECT val FROM simple_key
WHERE id = 2147483648")
+ .returnNothing()
+ .check();
+
+ assertQuery((InternalTransaction) tx, "SELECT val FROM simple_key
WHERE id = -2147483649")
+ .returnNothing()
+ .check();
+ } finally {
+ tx.rollback();
+ }
+ }
+
private static int randomKey() {
int key = ThreadLocalRandom.current().nextInt(TABLE_SIZE) + 1;
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/RexUtils.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/RexUtils.java
index aa6e7ab99bc..f851c32a4e4 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/RexUtils.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/RexUtils.java
@@ -500,8 +500,8 @@ public class RexUtils {
fldIdx = fld.getIntKey();
}
- RexNode casted = addCast(cluster, pred.operands.get(1),
types.get(fldIdx));
- bounds.set(fldIdx, new ExactBounds(pred, casted));
+ SaturatedRexNode casted = addCast(cluster,
pred.operands.get(1), types.get(fldIdx));
+ bounds.set(fldIdx, new ExactBounds(pred, casted.value));
}
}
@@ -542,9 +542,13 @@ public class RexUtils {
for (RexCall pred : collFldPreds) {
RexNode val = null;
RexNode ref = pred.getOperands().get(0);
+ boolean saturatedLookup = false;
if (isBinaryComparison(pred)) {
- val = addCast(cluster, pred.operands.get(1), fldType);
+ SaturatedRexNode saturatedNode = addCast(cluster,
pred.getOperands().get(1), fldType);
+
+ val = saturatedNode.value;
+ saturatedLookup = saturatedNode.saturated;
}
SqlOperator op = pred.getOperator();
@@ -552,7 +556,9 @@ public class RexUtils {
if (op.kind == EQUALS || op.kind == IS_NOT_DISTINCT_FROM) {
assert val != null;
- return new ExactBounds(pred, val);
+ RexNode pred0 = saturatedLookup ? builder.makeCall(op, ref,
val) : pred;
+
+ return new ExactBounds(pred0, val);
} else if (op.kind == IS_NULL) {
return new ExactBounds(pred, nullValue);
} else if (op.kind == OR) {
@@ -1146,20 +1152,20 @@ public class RexUtils {
}
}
- private static RexNode addCast(RelOptCluster cluster, RexNode condition,
RelDataType type) {
+ private static SaturatedRexNode addCast(RelOptCluster cluster, RexNode
condition, RelDataType type) {
RexNode node = removeCast(condition);
assert idxOpSupports(node) : "Unsupported RexNode in index condition:
" + node;
RexBuilder builder = cluster.getRexBuilder();
- RexNode saturated = toSaturatedValue(builder, node, type);
+ SaturatedRexNode saturatedLiteral = toSaturatedValue(builder, node,
type);
- if (saturated != null) {
- return saturated;
+ if (saturatedLiteral != null) {
+ return saturatedLiteral;
} else if (TypeUtils.needCastInSearchBounds(Commons.typeFactory(),
node.getType(), type)) {
- return builder.makeCast(type, node);
+ return new SaturatedRexNode(builder.makeCast(type, node), false);
} else {
- return node;
+ return new SaturatedRexNode(node, false);
}
}
@@ -1173,7 +1179,7 @@ public class RexUtils {
* <p>Otherwise returns {@code null}.
*/
@Nullable
- private static RexLiteral toSaturatedValue(RexBuilder builder, RexNode
node, RelDataType type) {
+ private static SaturatedRexNode toSaturatedValue(RexBuilder builder,
RexNode node, RelDataType type) {
if (!SqlTypeUtil.isNumeric(node.getType()) ||
!SqlTypeUtil.isNumeric(type) || !(node instanceof RexLiteral)) {
return null;
}
@@ -1204,24 +1210,29 @@ public class RexUtils {
}
BigDecimal newVal;
+ boolean saturated = false;
if (lower.compareTo(val) > 0) {
newVal = lower;
+ saturated = true;
} else if (val.compareTo(upper) > 0) {
newVal = upper;
+ saturated = true;
} else if (!SqlTypeUtil.equalSansNullability(node.getType(), type)) {
newVal = val;
} else {
// If literal types and required type, match ignoring nullability,
// then return a literal as is.
- return lit;
+ return new SaturatedRexNode(lit, false);
}
if (exact) {
- return builder.makeExactLiteral(newVal, type);
+ lit = builder.makeExactLiteral(newVal, type);
} else {
- return builder.makeApproxLiteral(newVal, type);
+ lit = builder.makeApproxLiteral(newVal, type);
}
+
+ return new SaturatedRexNode(lit, saturated);
}
/**
@@ -1367,4 +1378,16 @@ public class RexUtils {
return wasChanged ? newSearchBounds : searchBounds;
}
+
+ private static class SaturatedRexNode {
+ final RexNode value;
+
+ /** Flag indicating whether the numeric value has been saturated or
the original value is used. */
+ final boolean saturated;
+
+ private SaturatedRexNode(RexNode value, boolean saturated) {
+ this.value = value;
+ this.saturated = saturated;
+ }
+ }
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PrimaryKeyLookupPlannerTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PrimaryKeyLookupPlannerTest.java
index 31813280a92..1f423f7b03f 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PrimaryKeyLookupPlannerTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PrimaryKeyLookupPlannerTest.java
@@ -192,6 +192,47 @@ public class PrimaryKeyLookupPlannerTest extends
AbstractPlannerTest {
assertEmptyCondition((KeyValueGetPlan) plan);
}
+ @Test
+ void optimizedGetWithOutOfRangeKey() {
+ node.initSchema("CREATE TABLE test (id TINYINT PRIMARY KEY, val INT)");
+
+ // Out of range: TINYINT_MAX + 1.
+ {
+ QueryPlan plan = node.prepare("SELECT * FROM test WHERE id = 128");
+
+ assertThat(plan, instanceOf(KeyValueGetPlan.class));
+ assertKeyExpressions((KeyValueGetPlan) plan, "127:TINYINT");
+ assertCondition((KeyValueGetPlan) plan, "=(CAST($t0):INTEGER NOT
NULL, 128)");
+ }
+
+ // Out of range: TINYINT_MIN - 1.
+ {
+ QueryPlan plan = node.prepare("SELECT * FROM test WHERE id =
-129");
+
+ assertThat(plan, instanceOf(KeyValueGetPlan.class));
+ assertKeyExpressions((KeyValueGetPlan) plan, "-128:TINYINT");
+ assertCondition((KeyValueGetPlan) plan, "=(CAST($t0):INTEGER NOT
NULL, -129)");
+ }
+
+ // TINYINT_MAX - no predicate expected.
+ {
+ QueryPlan plan = node.prepare("SELECT * FROM test WHERE id = 127");
+
+ assertThat(plan, instanceOf(KeyValueGetPlan.class));
+ assertKeyExpressions((KeyValueGetPlan) plan, "127:TINYINT");
+ assertEmptyCondition((KeyValueGetPlan) plan);
+ }
+
+ // TINYINT_MIN - no predicate expected.
+ {
+ QueryPlan plan = node.prepare("SELECT * FROM test WHERE id =
-128");
+
+ assertThat(plan, instanceOf(KeyValueGetPlan.class));
+ assertKeyExpressions((KeyValueGetPlan) plan, "-128:TINYINT");
+ assertEmptyCondition((KeyValueGetPlan) plan);
+ }
+ }
+
private static void assertKeyExpressions(KeyValueGetPlan plan, String...
expectedExpressions) {
List<String> keyExpressions = (plan.getRel()).keyExpressions().stream()
.map(RexNode::toString)