This is an automated email from the ASF dual-hosted git repository.
zstan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 601c69f6a7b IGNITE-28844 Calcite. Improve type checking in LIMIT /
OFFSET clauses (#13311)
601c69f6a7b is described below
commit 601c69f6a7bc9dca10b939f91912dfea33302f03
Author: Evgeniy Stanilovskiy <[email protected]>
AuthorDate: Thu Aug 6 12:21:46 2026 +0300
IGNITE-28844 Calcite. Improve type checking in LIMIT / OFFSET clauses
(#13311)
---
.../query/calcite/exec/LogicalRelImplementor.java | 55 ++++++++-
.../query/calcite/exec/rel/AbstractNode.java | 3 +
.../rel/AbstractRightMaterializedJoinNode.java | 3 -
.../query/calcite/exec/rel/LimitNode.java | 94 ++++++++------
.../query/calcite/exec/rel/MergeJoinNode.java | 3 -
.../query/calcite/exec/rel/SortNode.java | 37 +++---
.../query/calcite/prepare/IgniteSqlValidator.java | 105 ++++++++++------
.../query/calcite/util/IgniteResource.java | 13 +-
.../calcite/exec/LogicalRelImplementorTest.java | 2 +-
.../query/calcite/exec/rel/LimitExecutionTest.java | 9 +-
.../DynamicParametersIntegrationTest.java | 10 ++
.../integration/LimitOffsetIntegrationTest.java | 20 +--
.../integration/QueryMetadataIntegrationTest.java | 58 ++++++---
.../query/calcite/planner/AbstractPlannerTest.java | 137 +++++++++++++++++----
.../planner/DynamicParametersPlannerTest.java | 85 +++++++++++++
.../query/calcite/planner/HashJoinPlannerTest.java | 4 +-
.../apache/ignite/testsuites/PlannerTestSuite.java | 2 +
modules/calcite/src/test/sql/order/test_limit.test | 62 +++++++++-
18 files changed, 534 insertions(+), 168 deletions(-)
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
index 1e93eecaf7c..64bcc6e6526 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
@@ -44,7 +44,9 @@ import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.mapping.IntPair;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
import org.apache.ignite.internal.processors.failure.FailureProcessor;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
import org.apache.ignite.internal.processors.query.QueryUtils;
import
org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
import
org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactory;
@@ -126,6 +128,8 @@ import
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribut
import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
import
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMath;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteResource;
import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
import org.apache.ignite.internal.util.typedef.F;
import org.jetbrains.annotations.Nullable;
@@ -567,8 +571,8 @@ public class LogicalRelImplementor<Row> implements
IgniteRelVisitor<Node<Row>> {
ctx,
rowType,
idxBndRel.first() ? cmp : cmp.reversed(),
- null,
- () -> 1
+ 0,
+ 1
);
sortNode.register(scanNode);
@@ -630,8 +634,8 @@ public class LogicalRelImplementor<Row> implements
IgniteRelVisitor<Node<Row>> {
/** {@inheritDoc} */
@Override public Node<Row> visit(IgniteLimit rel) {
- Supplier<Integer> offset = (rel.offset() == null) ? null :
expressionFactory.execute(rel.offset());
- Supplier<Integer> fetch = (rel.fetch() == null) ? null :
expressionFactory.execute(rel.fetch());
+ long offset = validateAndGetOffset(rel.offset(),
LimitNode.OFFSET_DEFAULT);
+ long fetch = validateAndGetFetch(rel.fetch(), LimitNode.FETCH_DEFAULT);
LimitNode<Row> node = new LimitNode<>(ctx, rel.getRowType(), offset,
fetch);
@@ -646,8 +650,8 @@ public class LogicalRelImplementor<Row> implements
IgniteRelVisitor<Node<Row>> {
@Override public Node<Row> visit(IgniteSort rel) {
RelCollation collation = rel.getCollation();
- Supplier<Integer> offset = (rel.offset == null) ? null :
expressionFactory.execute(rel.offset);
- Supplier<Integer> fetch = (rel.fetch == null) ? null :
expressionFactory.execute(rel.fetch);
+ long offset = validateAndGetOffset(rel.offset,
SortNode.OFFSET_DEFAULT);
+ long fetch = validateAndGetFetch(rel.fetch, SortNode.FETCH_DEFAULT);
SortNode<Row> node = new SortNode<>(ctx, rel.getRowType(),
expressionFactory.comparator(collation), offset,
fetch);
@@ -659,6 +663,16 @@ public class LogicalRelImplementor<Row> implements
IgniteRelVisitor<Node<Row>> {
return node;
}
+ /** */
+ private long validateAndGetOffset(RexNode node, long defaultVal) {
+ return node == null ? defaultVal :
validateAndGetFetchOffsetParams(node, "offset");
+ }
+
+ /** */
+ private long validateAndGetFetch(RexNode node, long defaultVal) {
+ return node == null ? defaultVal :
validateAndGetFetchOffsetParams(node, "fetch");
+ }
+
/** {@inheritDoc} */
@Override public Node<Row> visit(IgniteTableSpool rel) {
TableSpoolNode<Row> node = new TableSpoolNode<>(ctx, rel.getRowType(),
rel.readType == Spool.Type.LAZY);
@@ -1050,4 +1064,33 @@ public class LogicalRelImplementor<Row> implements
IgniteRelVisitor<Node<Row>> {
otherColMapping
);
}
+
+ /** */
+ private long validateAndGetFetchOffsetParams(RexNode node, String op) {
+ Supplier<Object> scalar = expressionFactory.execute(node);
+ Object param = scalar.get();
+
+ if (!(param instanceof Number)) {
+ String actual = param == null ? "null" :
param.getClass().getSimpleName();
+ throw new
IgniteSQLException(IgniteResource.INSTANCE.incorrectDynamicParameterType("BIGINT",
actual).str(),
+ IgniteQueryErrorCode.UNEXPECTED_ELEMENT_TYPE);
+ }
+
+ long paramAsLong;
+
+ try {
+ paramAsLong = IgniteMath.convertToLongExact((Number)param);
+ }
+ catch (RuntimeException ex) {
+ throw new
IgniteSQLException(IgniteResource.INSTANCE.illegalFetchLimit(op).str(),
+ IgniteQueryErrorCode.UNEXPECTED_ELEMENT_TYPE, ex);
+ }
+
+ if (paramAsLong < 0) {
+ throw new
IgniteSQLException(IgniteResource.INSTANCE.illegalFetchLimit(op).str(),
+ IgniteQueryErrorCode.UNEXPECTED_ELEMENT_TYPE);
+ }
+
+ return paramAsLong;
+ }
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractNode.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractNode.java
index e426069a32d..d238c6576b8 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractNode.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractNode.java
@@ -37,6 +37,9 @@ import static
org.apache.ignite.IgniteSystemProperties.IGNITE_CALCITE_EXEC_MODIF
* Abstract node of execution tree.
*/
public abstract class AbstractNode<Row> implements Node<Row> {
+ /** Special flag which marks that all the rows are received. */
+ static final int NOT_WAITING = -1;
+
/** */
public static final int IN_BUFFER_SIZE =
IgniteSystemProperties.getInteger(IGNITE_CALCITE_EXEC_IN_BUFFER_SIZE, 512);
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractRightMaterializedJoinNode.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractRightMaterializedJoinNode.java
index 27c25f24e58..6e2017afd8d 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractRightMaterializedJoinNode.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractRightMaterializedJoinNode.java
@@ -29,9 +29,6 @@ public abstract class AbstractRightMaterializedJoinNode<Row>
extends MemoryTrack
/** */
protected static final int HALF_BUF_SIZE = IN_BUFFER_SIZE >> 1;
- /** Special flag which marks that all the rows are received. */
- protected static final int NOT_WAITING = -1;
-
/** */
protected final Deque<Row> leftInBuf = new ArrayDeque<>(IN_BUFFER_SIZE);
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java
index 9fa9d14090c..cee17d76289 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java
@@ -17,46 +17,56 @@
package org.apache.ignite.internal.processors.query.calcite.exec.rel;
-import java.util.function.Supplier;
import org.apache.calcite.rel.type.RelDataType;
import
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMath;
import org.apache.ignite.internal.util.typedef.F;
-import org.jetbrains.annotations.Nullable;
/** Offset, fetch|limit support node. */
public class LimitNode<Row> extends AbstractNode<Row> implements
SingleNode<Row>, Downstream<Row> {
- /** Offset if its present, otherwise 0. */
- private final int offset;
+ /** */
+ public static final long FETCH_DEFAULT = -1;
- /** Fetch if its present, otherwise 0. */
- private final int fetch;
+ /** */
+ public static final long OFFSET_DEFAULT = 0;
- /** Already processed (pushed to upstream) rows count. */
- private int rowsProcessed;
+ /** Offset param. */
+ private final long offset;
- /** Fetch can be unset, in this case we need all rows. */
- private @Nullable Supplier<Integer> fetchNode;
+ /** How many rows need to be processed, if {@code 0} it depends on {@link
#rowsSummary}. */
+ private final long fetch;
+
+ /** Summary rows to process. */
+ private final long rowsSummary;
+
+ /** Already processed (pushed to downstream) rows count. */
+ private long rowsProcessed;
/** Waiting results counter. */
private int waiting;
+ /** Upper requested rows. */
+ private int requested;
+
/**
* Constructor.
*
* @param ctx Execution context.
* @param rowType Row type.
+ * @param offset How many rows need to be skipped.
+ * @param fetch How many rows need to be processed, {@link #FETCH_DEFAULT}
if param is undefined.
*/
public LimitNode(
ExecutionContext<Row> ctx,
RelDataType rowType,
- Supplier<Integer> offsetNode,
- Supplier<Integer> fetchNode
+ long offset,
+ long fetch
) {
super(ctx, rowType);
- offset = offsetNode == null ? 0 : offsetNode.get();
- fetch = fetchNode == null ? 0 : fetchNode.get();
- this.fetchNode = fetchNode;
+ this.offset = offset;
+ rowsSummary = fetch == FETCH_DEFAULT ? Long.MAX_VALUE :
IgniteMath.addExact(fetch, offset);
+ this.fetch = fetch == FETCH_DEFAULT ? 0 : fetch;
}
/** {@inheritDoc} */
@@ -64,19 +74,22 @@ public class LimitNode<Row> extends AbstractNode<Row>
implements SingleNode<Row>
assert !F.isEmpty(sources()) && sources().size() == 1;
assert rowsCnt > 0;
- if (fetchNone()) {
+ if (!hasMoreData()) {
end();
return;
}
- if (offset > 0 && rowsProcessed == 0)
- rowsCnt = offset + rowsCnt;
+ assert requested == 0 : requested;
+ requested = rowsCnt;
- waiting = rowsCnt;
+ if (fetch > 0) {
+ long remain = rowsSummary - rowsProcessed;
- if (fetch > 0)
- rowsCnt = Math.min(rowsCnt, (fetch + offset) - rowsProcessed);
+ rowsCnt = remain > rowsCnt ? rowsCnt : (int)remain;
+ }
+
+ waiting = rowsCnt;
checkState();
@@ -85,38 +98,49 @@ public class LimitNode<Row> extends AbstractNode<Row>
implements SingleNode<Row>
/** {@inheritDoc} */
@Override public void push(Row row) throws Exception {
- if (waiting == -1)
+ if (waiting == NOT_WAITING)
return;
- ++rowsProcessed;
-
--waiting;
- checkState();
-
- if (rowsProcessed > offset) {
- if (fetchNode == null || (fetchNode != null && rowsProcessed <=
fetch + offset))
- downstream().push(row);
+ if (rowsProcessed >= offset && hasMoreData()) {
+ // This two rows can`t be swapped, cause if all requested rows
have been pushed it will trigger further request call.
+ --requested;
+ downstream().push(row);
}
- if (fetch > 0 && rowsProcessed == fetch + offset && waiting > 0)
+ ++rowsProcessed;
+
+ // There several cases are possible:
+ // 1) requested = 512, limit = 1, offset = not defined: need to pass
1 row and call end()
+ // 2) requested = 512, limit = 512, offset = not defined: just need
to pass all rows without end() call
+ // 3) requested = 512, limit = 512, offset = 1: need to request
initially 512 and further 1 row
+ if (!hasMoreData() && requested > 0)
end();
+
+ if (waiting == 0 && requested > 0)
+ source().request(waiting = requested);
}
/** {@inheritDoc} */
@Override public void end() throws Exception {
- if (waiting == -1)
+ if (waiting == NOT_WAITING)
return;
assert downstream() != null;
- waiting = -1;
+ waiting = NOT_WAITING;
+
+ if (requested > 0)
+ requested = 0;
downstream().end();
}
/** {@inheritDoc} */
@Override protected void rewindInternal() {
+ waiting = 0;
+ requested = 0;
rowsProcessed = 0;
}
@@ -128,8 +152,8 @@ public class LimitNode<Row> extends AbstractNode<Row>
implements SingleNode<Row>
return this;
}
- /** {@code True} if requested 0 results, or all already processed. */
- private boolean fetchNone() {
- return (fetchNode != null && fetch == 0) || (fetch > 0 &&
rowsProcessed == fetch + offset);
+ /** {@code True} If current rows processed is less than required or
undefined. */
+ private boolean hasMoreData() {
+ return rowsProcessed < rowsSummary;
}
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java
index 1c4d902c596..800e66e918b 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java
@@ -35,9 +35,6 @@ public abstract class MergeJoinNode<Row> extends
AbstractNode<Row> {
/** */
private static final int HALF_BUF_SIZE = IN_BUFFER_SIZE >> 1;
- /** Special value to highlights that all row were received and we are not
waiting any more. */
- protected static final int NOT_WAITING = -1;
-
/** */
protected final Comparator<Row> comp;
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java
index 1347fced4b6..d9180d25601 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java
@@ -20,21 +20,26 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
-import java.util.function.Supplier;
import org.apache.calcite.rel.type.RelDataType;
import
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMath;
import org.apache.ignite.internal.util.GridBoundedPriorityQueue;
import org.apache.ignite.internal.util.typedef.F;
-import org.jetbrains.annotations.Nullable;
/**
* Sort node.
*/
public class SortNode<Row> extends MemoryTrackingNode<Row> implements
SingleNode<Row>, Downstream<Row> {
+ /** */
+ public static final long OFFSET_DEFAULT = 0;
+
+ /** */
+ public static final long FETCH_DEFAULT = -1;
+
/** How many rows are requested by downstream. */
private int requested;
- /** How many rows are we waiting for from the upstream. {@code -1} means
end of stream. */
+ /** How many rows are we waiting for from the upstream. {@link
#NOT_WAITING} means end of stream. */
private int waiting;
/** */
@@ -44,7 +49,7 @@ public class SortNode<Row> extends MemoryTrackingNode<Row>
implements SingleNode
private final PriorityQueue<Row> rows;
/** SQL select limit. Negative if disabled. */
- private final int limit;
+ private final long limit;
/** Reverse-ordered rows in case of limited sort. */
private List<Row> reversed;
@@ -53,26 +58,26 @@ public class SortNode<Row> extends MemoryTrackingNode<Row>
implements SingleNode
* @param ctx Execution context.
* @param comp Rows comparator.
* @param offset Offset.
- * @param fetch Limit.
+ * @param fetch How many rows need to be processed, {@link #FETCH_DEFAULT}
if param is undefined.
*/
public SortNode(
ExecutionContext<Row> ctx, RelDataType rowType,
Comparator<Row> comp,
- @Nullable Supplier<Integer> offset,
- @Nullable Supplier<Integer> fetch
+ long offset,
+ long fetch
) {
super(ctx, rowType);
- assert fetch == null || fetch.get() >= 0;
- assert offset == null || offset.get() >= 0;
+ assert fetch == FETCH_DEFAULT || fetch > 0 : "Unexpected fetch = " +
fetch;
+ assert offset >= 0 : "Unexpected offset = " + offset;
- limit = fetch == null ? -1 : fetch.get() + (offset == null ? 0 :
offset.get());
+ limit = fetch == FETCH_DEFAULT ? -1 : (fetch > Long.MAX_VALUE - offset
? -1 : fetch + offset);
- if (limit < 0)
+ if (limit < 1 || limit > Integer.MAX_VALUE)
rows = new PriorityQueue<>(comp);
else {
- rows = new GridBoundedPriorityQueue<>(limit, comp == null ?
(Comparator<Row>)Comparator.reverseOrder()
- : comp.reversed());
+ rows = new
GridBoundedPriorityQueue<>(IgniteMath.convertToIntExact(limit), comp == null ?
+ (Comparator<Row>)Comparator.reverseOrder() : comp.reversed());
}
}
@@ -81,7 +86,7 @@ public class SortNode<Row> extends MemoryTrackingNode<Row>
implements SingleNode
* @param comp Rows comparator.
*/
public SortNode(ExecutionContext<Row> ctx, RelDataType rowType,
Comparator<Row> comp) {
- this(ctx, rowType, comp, null, null);
+ this(ctx, rowType, comp, OFFSET_DEFAULT, FETCH_DEFAULT);
}
/** {@inheritDoc} */
@@ -150,7 +155,7 @@ public class SortNode<Row> extends MemoryTrackingNode<Row>
implements SingleNode
checkState();
- waiting = -1;
+ waiting = NOT_WAITING;
flush();
}
@@ -160,7 +165,7 @@ public class SortNode<Row> extends MemoryTrackingNode<Row>
implements SingleNode
if (isClosed())
return;
- assert waiting == -1;
+ assert waiting == NOT_WAITING;
int processed = 0;
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
index 1c7f00da265..cd044a629bd 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
@@ -29,6 +29,7 @@ import org.apache.calcite.prepare.CalciteCatalogReader;
import org.apache.calcite.prepare.Prepare;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.runtime.Resources;
import org.apache.calcite.sql.JoinConditionType;
import org.apache.calcite.sql.JoinType;
import org.apache.calcite.sql.SqlAggFunction;
@@ -61,6 +62,7 @@ import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SelectScope;
import org.apache.calcite.sql.validate.SqlQualified;
import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorException;
import org.apache.calcite.sql.validate.SqlValidatorImpl;
import org.apache.calcite.sql.validate.SqlValidatorNamespace;
import org.apache.calcite.sql.validate.SqlValidatorScope;
@@ -74,8 +76,8 @@ import
org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
import
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlDecimalLiteral;
import
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
import org.apache.ignite.internal.processors.query.calcite.type.OtherType;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMath;
import org.apache.ignite.internal.processors.query.calcite.util.IgniteResource;
-import org.apache.ignite.internal.util.typedef.F;
import org.immutables.value.Value;
import org.jetbrains.annotations.Nullable;
@@ -84,9 +86,6 @@ import static org.apache.calcite.util.Static.RESOURCE;
/** Validator. */
@Value.Enclosing
public class IgniteSqlValidator extends SqlValidatorImpl {
- /** Decimal of Integer.MAX_VALUE for fetch/offset bounding. */
- private static final BigDecimal DEC_INT_MAX =
BigDecimal.valueOf(Integer.MAX_VALUE);
-
/** **/
private static final int MAX_LENGTH_OF_ALIASES = 256;
@@ -241,10 +240,74 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
/** {@inheritDoc} */
@Override protected void validateSelect(SqlSelect select, RelDataType
targetRowType) {
- checkIntegerLimit(select.getFetch(), "fetch / limit");
- checkIntegerLimit(select.getOffset(), "offset");
-
super.validateSelect(select, targetRowType);
+
+ validateFetchOffset(select.getFetch(), "fetch / limit");
+ validateFetchOffset(select.getOffset(), "offset");
+ }
+
+ /**
+ * Validate fetch/offset params restrictions.
+ *
+ * @param n Node to check.
+ * @param clauseName Clause name.
+ */
+ private void validateFetchOffset(@Nullable SqlNode n, String clauseName) {
+ if (n == null)
+ return;
+
+ if (n instanceof SqlLiteral) {
+ BigDecimal offsetFetchLimit = ((SqlLiteral)n).bigDecimalValue();
+
+ checkLimitOffset(offsetFetchLimit, n, clauseName);
+ }
+ else if (n instanceof SqlDynamicParam dynamicParam) {
+ // Dynamic parameters are nullable.
+ RelDataType expectType =
typeFactory.createTypeWithNullability(typeFactory.createSqlType(SqlTypeName.DECIMAL),
true);
+
+ if (definedDynParam(dynamicParam)) {
+ Object param = parameters[dynamicParam.getIndex()];
+
+ if (!(param instanceof Number)) {
+ Resources.ExInst<SqlValidatorException> err;
+
+ if (param == null)
+ err =
IgniteResource.INSTANCE.incorrectDynamicParameterType(SqlTypeName.BIGINT.toString(),
"null");
+ else {
+ SqlTypeName paramType =
typeFactory().createType(param.getClass()).getSqlTypeName();
+ err =
IgniteResource.INSTANCE.incorrectDynamicParameterType(SqlTypeName.BIGINT.toString(),
paramType.getName());
+ }
+
+ throw newValidationError(n, err);
+ }
+ else
+ checkLimitOffset((Number)param, n, clauseName);
+
+ setValidatedNodeType(dynamicParam, expectType);
+ }
+ else {
+ expectType =
typeFactory.createTypeWithNullability(typeFactory.createSqlType(SqlTypeName.BIGINT),
true);
+ setValidatedNodeType(dynamicParam, expectType);
+ }
+ }
+ }
+
+ /** Returns {@code true} if the given dynamic parameter has value set. */
+ private boolean definedDynParam(SqlDynamicParam param) {
+ return param.getIndex() < parameters.length;
+ }
+
+ /** */
+ private void checkLimitOffset(Number offsetFetchLimit, SqlNode n, String
nodeName) {
+ try {
+ long res = IgniteMath.convertToLongExact(offsetFetchLimit);
+
+ if (res < 0)
+ throw newValidationError(n,
IgniteResource.INSTANCE.illegalFetchLimit(nodeName));
+ }
+ catch (ArithmeticException e) {
+ throw newValidationError(n,
IgniteResource.INSTANCE.illegalFetchLimit(nodeName));
+ }
}
/** {@inheritDoc} */
@@ -261,34 +324,6 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
super.validateNamespace(namespace, targetRowType);
}
- /**
- * @param n Node to check limit.
- * @param nodeName Node name.
- */
- private void checkIntegerLimit(SqlNode n, String nodeName) {
- if (n instanceof SqlLiteral) {
- BigDecimal offFetchLimit = ((SqlLiteral)n).bigDecimalValue();
-
- if (offFetchLimit.compareTo(DEC_INT_MAX) > 0 ||
offFetchLimit.compareTo(BigDecimal.ZERO) < 0)
- throw newValidationError(n,
IgniteResource.INSTANCE.correctIntegerLimit(nodeName));
- }
- else if (n instanceof SqlDynamicParam) {
- // will fail in params check.
- if (F.isEmpty(parameters))
- return;
-
- int idx = ((SqlDynamicParam)n).getIndex();
-
- if (idx < parameters.length) {
- Object param = parameters[idx];
- if (parameters[idx] instanceof Integer) {
- if ((Integer)param < 0)
- throw newValidationError(n,
IgniteResource.INSTANCE.correctIntegerLimit(nodeName));
- }
- }
- }
- }
-
/** {@inheritDoc} */
@Override public void validateCall(SqlCall call, SqlValidatorScope scope) {
if (call.getKind() == SqlKind.AS) {
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
index df74d56a916..8dff42046dc 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
@@ -39,11 +39,6 @@ public interface IgniteResource {
@Resources.BaseMessage("Illegal aggregate function. {0} is unsupported at
the moment.")
Resources.ExInst<SqlValidatorException>
unsupportedAggregationFunction(String a0);
- /** */
- @Resources.BaseMessage("Illegal value of {0}. The value must be positive
and less than Integer.MAX_VALUE " +
- "(" + Integer.MAX_VALUE + ")." )
- Resources.ExInst<SqlValidatorException> correctIntegerLimit(String a0);
-
/** */
@Resources.BaseMessage("Option ''{0}'' has already been defined")
Resources.ExInst<SqlValidatorException> optionAlreadyDefined(String
optName);
@@ -84,4 +79,12 @@ public interface IgniteResource {
/** */
@Resources.BaseMessage("Operator ''CAST'' supports only the parameters:
value and target type.")
Resources.ExInst<SqlValidatorException> invalidCastParameters();
+
+ /** */
+ @Resources.BaseMessage("Illegal value of {0}. The value must be
non-negative and less than or equal to " + Long.MAX_VALUE)
+ Resources.ExInst<SqlValidatorException> illegalFetchLimit(String a0);
+
+ /** */
+ @Resources.BaseMessage("Incorrect type of a dynamic parameter. Expected
<{0}> but got <{1}>")
+ Resources.ExInst<SqlValidatorException>
incorrectDynamicParameterType(String expected, String actual);
}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java
index 06a00e8622f..3e87461ea0b 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java
@@ -196,7 +196,7 @@ public class LogicalRelImplementorTest extends
GridCommonAbstractTest {
node = relImplementor.visit(idxScan);
assertTrue(node instanceof SortNode);
- assertEquals(1, (int)U.field(node, "limit"));
+ assertEquals(1L, (long)U.field(node, "limit"));
assertTrue(node.sources() != null && node.sources().size() == 1);
assertTrue(node.sources().get(0) instanceof ScanNode);
}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java
index 6dd3c7b36cf..fda6deaacc9 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java
@@ -52,6 +52,7 @@ public class LimitExecutionTest extends AbstractExecutionTest
{
/** Tests Sort node can limit its output when fetch param is set. */
@Test
public void testSortLimit() {
+ checkLimitSort(0, 0);
checkLimitSort(0, 1);
checkLimitSort(1, 0);
checkLimitSort(1, 1);
@@ -78,10 +79,10 @@ public class LimitExecutionTest extends
AbstractExecutionTest {
RootNode<Object[]> rootNode = new RootNode<>(ctx, rowType);
- SortNode<Object[]> sortNode = new SortNode<>(ctx, rowType,
F::compareArrays, () -> offset,
- fetch == 0 ? null : () -> fetch);
+ SortNode<Object[]> sortNode = new SortNode<>(ctx, rowType,
F::compareArrays, offset,
+ fetch == 0 ? SortNode.FETCH_DEFAULT : fetch);
- List<Object[]> data = IntStream.range(0, SourceNode.IN_BUFFER_SIZE +
fetch + offset).boxed()
+ List<Object[]> data = IntStream.range(0, IN_BUFFER_SIZE + fetch +
offset).boxed()
.map(i -> new Object[] {i}).collect(Collectors.toList());
Collections.shuffle(data);
@@ -109,7 +110,7 @@ public class LimitExecutionTest extends
AbstractExecutionTest {
RelDataType rowType = TypeUtils.createRowType(tf, int.class);
RootNode<Object[]> rootNode = new RootNode<>(ctx, rowType);
- LimitNode<Object[]> limitNode = new LimitNode<>(ctx, rowType, () ->
offset, fetch == 0 ? null : () -> fetch);
+ LimitNode<Object[]> limitNode = new LimitNode<>(ctx, rowType, offset,
fetch == 0 ? LimitNode.FETCH_DEFAULT : fetch);
SourceNode srcNode = new SourceNode(ctx, rowType);
rootNode.register(limitNode);
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java
index daa39e374a0..0b68a9773ac 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java
@@ -150,6 +150,16 @@ public class DynamicParametersIntegrationTest extends
AbstractBasicIntegrationTe
assertQuery("SELECT name LIKE '%' || ? || '%' FROM person where name
is not null").withParams("go")
.returns(true).returns(false).returns(false).returns(false).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1).returns(0).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1D).returns(0).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1F).returns(0).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1L).returns(0).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1.4).returns(0).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1.5).returns(0).returns(1).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1.6).returns(0).returns(1).check();
+
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(new BigDecimal(1)).returns(0).check();
+
assertQuery("SELECT id FROM person WHERE name LIKE ? ORDER BY id LIMIT
?").withParams("I%", 1)
.returns(0).check();
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LimitOffsetIntegrationTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LimitOffsetIntegrationTest.java
index 428d4d273d9..6beccead712 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LimitOffsetIntegrationTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LimitOffsetIntegrationTest.java
@@ -17,7 +17,7 @@
package org.apache.ignite.internal.processors.query.calcite.integration;
-import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import org.apache.calcite.sql.validate.SqlValidatorException;
@@ -104,15 +104,15 @@ public class LimitOffsetIntegrationTest extends
AbstractBasicIntegrationTransact
/** Tests correctness of fetch / offset params. */
@Test
public void testInvalidLimitOffset() {
- String bigInt = BigDecimal.valueOf(10000000000L).toString();
+ BigInteger moreThanMaxLong =
BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
- assertThrows("SELECT * FROM TEST_REPL OFFSET " + bigInt + " ROWS",
+ assertThrows("SELECT * FROM TEST_REPL OFFSET " + moreThanMaxLong + "
ROWS",
SqlValidatorException.class, "Illegal value of offset");
- assertThrows("SELECT * FROM TEST_REPL FETCH FIRST " + bigInt + " ROWS
ONLY",
+ assertThrows("SELECT * FROM TEST_REPL FETCH FIRST " + moreThanMaxLong
+ " ROWS ONLY",
SqlValidatorException.class, "Illegal value of fetch / limit");
- assertThrows("SELECT * FROM TEST_REPL LIMIT " + bigInt,
+ assertThrows("SELECT * FROM TEST_REPL LIMIT " + moreThanMaxLong,
SqlValidatorException.class, "Illegal value of fetch / limit");
assertThrows("SELECT * FROM TEST_REPL OFFSET -1 ROWS FETCH FIRST -1
ROWS ONLY",
@@ -123,16 +123,6 @@ public class LimitOffsetIntegrationTest extends
AbstractBasicIntegrationTransact
assertThrows("SELECT * FROM TEST_REPL OFFSET 2+1 ROWS",
IgniteSQLException.class, null);
-
- // Check with parameters
- assertThrows("SELECT * FROM TEST_REPL OFFSET ? ROWS FETCH FIRST ? ROWS
ONLY",
- SqlValidatorException.class, "Illegal value of fetch / limit", -1,
-1);
-
- assertThrows("SELECT * FROM TEST_REPL OFFSET ? ROWS",
- SqlValidatorException.class, "Illegal value of offset", -1);
-
- assertThrows("SELECT * FROM TEST_REPL FETCH FIRST ? ROWS ONLY",
- SqlValidatorException.class, "Illegal value of fetch / limit", -1);
}
/**
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryMetadataIntegrationTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryMetadataIntegrationTest.java
index 36ee4dc647a..5947b3cc8db 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryMetadataIntegrationTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryMetadataIntegrationTest.java
@@ -23,9 +23,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
+import org.apache.calcite.rel.type.RelDataTypeSystem;
+import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
import org.apache.ignite.internal.processors.query.QueryEngine;
+import
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.util.typedef.T2;
import org.junit.Test;
@@ -40,9 +43,9 @@ import static
org.apache.calcite.rel.type.RelDataType.SCALE_NOT_SPECIFIED;
public class QueryMetadataIntegrationTest extends AbstractBasicIntegrationTest
{
/** */
@Test
- public void testJoin() throws Exception {
- executeSql("CREATE TABLE tbl1 (id DECIMAL(10, 2), val VARCHAR, val2
BIGINT, ts TIMESTAMP(14), PRIMARY KEY(id, val))");
- executeSql("CREATE TABLE tbl2 (id DECIMAL(10, 2) NOT NULL, val
VARCHAR, val2 BIGINT, ts TIMESTAMP(14), " +
+ public void testJoin() {
+ sql("CREATE TABLE tbl1 (id DECIMAL(10, 2), val VARCHAR, val2 BIGINT,
ts TIMESTAMP(14), PRIMARY KEY(id, val))");
+ sql("CREATE TABLE tbl2 (id DECIMAL(10, 2) NOT NULL, val VARCHAR, val2
BIGINT, ts TIMESTAMP(14), " +
"PRIMARY KEY(id, val))");
checker("select * from tbl1 inner join tbl2 on (tbl1.id > tbl2.id and
tbl1.id <> ?) " +
@@ -67,9 +70,9 @@ public class QueryMetadataIntegrationTest extends
AbstractBasicIntegrationTest {
/** */
@Test
- public void testMultipleConditions() throws Exception {
- executeSql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY
KEY(id))");
- executeSql("CREATE INDEX tbl_val_idx ON tbl(val)");
+ public void testMultipleConditions() {
+ sql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
+ sql("CREATE INDEX tbl_val_idx ON tbl(val)");
checker("select * from tbl where id in (?, ?) or (id > ? and id <= ?)
or (val <> ?)")
.addMeta(
@@ -87,9 +90,9 @@ public class QueryMetadataIntegrationTest extends
AbstractBasicIntegrationTest {
/** */
@Test
- public void testMultipleQueries() throws Exception {
- executeSql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY
KEY(id))");
- executeSql("CREATE INDEX tbl_val_idx ON tbl(val)");
+ public void testMultipleQueries() {
+ sql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
+ sql("CREATE INDEX tbl_val_idx ON tbl(val)");
checker("insert into tbl(id, val) values (?, ?); select * from tbl
where id > ?")
.addMeta(
@@ -111,9 +114,30 @@ public class QueryMetadataIntegrationTest extends
AbstractBasicIntegrationTest {
/** */
@Test
- public void testDml() throws Exception {
- executeSql("CREATE TABLE tbl1 (id BIGINT, val VARCHAR, PRIMARY
KEY(id))");
- executeSql("CREATE TABLE tbl2 (id BIGINT, val VARCHAR, PRIMARY
KEY(id))");
+ public void testLimitOffsetParameterMetadata() {
+ RelDataTypeSystem typeSys = IgniteTypeSystem.INSTANCE;
+
+ for (String qry : new String[] {
+ "SELECT 1 LIMIT ?",
+ "SELECT 1 OFFSET ?",
+ "SELECT 1 FETCH FIRST ? ROWS ONLY"
+ }) {
+ checker(qry)
+ .addMeta(
+ builder -> builder
+ .add(null, null, int.class, "1",
typeSys.getDefaultPrecision(SqlTypeName.INTEGER), 0, false),
+ builder -> builder
+ .add(null, null, Long.class, "?0",
typeSys.getDefaultPrecision(SqlTypeName.BIGINT), 0, true)
+ )
+ .check();
+ }
+ }
+
+ /** */
+ @Test
+ public void testDml() {
+ sql("CREATE TABLE tbl1 (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
+ sql("CREATE TABLE tbl2 (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
checker("insert into tbl1(id, val) values (?, ?)")
.addMeta(
@@ -150,8 +174,8 @@ public class QueryMetadataIntegrationTest extends
AbstractBasicIntegrationTest {
/** */
@Test
- public void testDdl() throws Exception {
- executeSql("CREATE TABLE tbl1 (id BIGINT, val VARCHAR, PRIMARY
KEY(id))");
+ public void testDdl() {
+ sql("CREATE TABLE tbl1 (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
checker("CREATE TABLE tbl2 (id BIGINT, val VARCHAR, PRIMARY KEY(id))")
.addMeta(builder -> {}, builder -> {})
@@ -166,8 +190,8 @@ public class QueryMetadataIntegrationTest extends
AbstractBasicIntegrationTest {
/** */
@Test
- public void testExplain() throws Exception {
- executeSql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY
KEY(id))");
+ public void testExplain() {
+ sql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
checker("explain plan for select * from tbl where id in (?, ?) or (id
> ? and id <= ?) or (val <> ?)")
.addMeta(
@@ -227,7 +251,7 @@ public class QueryMetadataIntegrationTest extends
AbstractBasicIntegrationTest {
}
/** */
- public void check() throws Exception {
+ public void check() {
List<List<GridQueryFieldMetadata>> actualRsMeta =
qryEngine.resultSetMetaData(null, schema, sql);
List<List<GridQueryFieldMetadata>> actualParamMeta =
qryEngine.parameterMetaData(null, schema, sql);
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
index b49af08b5c7..3da09de1218 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
@@ -220,26 +220,31 @@ public abstract class AbstractPlannerTest extends
GridCommonAbstractTest {
@Nullable RelOptListener planLsnr,
String... disabledRules
) {
- return plannerCtx(sql, Collections.singleton(publicSchema), planLsnr,
disabledRules);
+ return plannerCtx(sql, Collections.singleton(publicSchema), planLsnr,
null, ImmutableSet.copyOf(disabledRules));
}
/** */
- protected PlanningContext plannerCtx(
+ private PlanningContext plannerCtx(
String sql,
Collection<IgniteSchema> schemas,
@Nullable RelOptListener planLsnr,
- String... disabledRules
+ Collection<Object> params,
+ Collection<String> disabledRules
) {
- PlanningContext ctx = PlanningContext.builder()
+ PlanningContext.Builder ctxBuilder = PlanningContext.builder()
.parentContext(Contexts.of(baseQueryContext(schemas), planLsnr))
- .query(sql)
- .build();
+ .query(sql);
+
+ if (params != null)
+ ctxBuilder.parameters(params.toArray(Object[]::new));
+
+ PlanningContext ctx = ctxBuilder.build();
IgnitePlanner planner = ctx.planner();
assertNotNull(planner);
- planner.addDisabledRules(ImmutableSet.copyOf(disabledRules));
+ planner.addDisabledRules(disabledRules);
return ctx;
}
@@ -438,6 +443,16 @@ public abstract class AbstractPlannerTest extends
GridCommonAbstractTest {
return table;
}
+ /** */
+ @SuppressWarnings("ThrowableNotThrown")
+ void assertThrows(
+ TestPlanningContextBuilder ctxBuilder,
+ Class<? extends Throwable> cls,
+ @Nullable String msg
+ ) {
+ GridTestUtils.assertThrows(null, () -> assertPlan(ctxBuilder, rel ->
true), cls, msg);
+ }
+
/** */
protected <T extends RelNode> void assertPlan(
String sql,
@@ -445,7 +460,9 @@ public abstract class AbstractPlannerTest extends
GridCommonAbstractTest {
Predicate<T> predicate,
String... disabledRules
) throws Exception {
- assertPlan(sql, schema, null, predicate, disabledRules);
+ TestPlanningContextBuilder builder =
contextBuilder().query(sql).schema(schema).disabledRules(disabledRules);
+
+ assertPlan(builder, predicate);
}
/** */
@@ -455,20 +472,33 @@ public abstract class AbstractPlannerTest extends
GridCommonAbstractTest {
Predicate<T> predicate,
String... disabledRules
) throws Exception {
- assertPlan(sql, schemas, null, predicate, disabledRules);
+ TestPlanningContextBuilder builder =
contextBuilder().query(sql).schemas(schemas).disabledRules(disabledRules);
+
+ assertPlan(builder, predicate);
}
/** */
protected <T extends RelNode> void assertPlan(
String sql,
- Collection<IgniteSchema> schemas,
- @Nullable RelOptListener planLsnr,
+ IgniteSchema schema,
+ RelOptListener planLsnr,
Predicate<T> predicate,
String... disabledRules
) throws Exception {
- IgniteRel plan = physicalPlan(plannerCtx(sql, schemas, planLsnr,
disabledRules));
+ TestPlanningContextBuilder builder =
contextBuilder().query(sql).schema(schema).disabledRules(disabledRules)
+ .planListener(planLsnr);
+
+ assertPlan(builder, predicate);
+ }
+
+ /** */
+ protected <T extends RelNode> void assertPlan(
+ TestPlanningContextBuilder ctxBuilder,
+ Predicate<T> predicate
+ ) throws Exception {
+ IgniteRel plan = physicalPlan(ctxBuilder.build());
- checkSplitAndSerialization(plan, schemas);
+ checkSplitAndSerialization(plan, ctxBuilder.schemas);
if (!predicate.test((T)plan)) {
String invalidPlanMsg = "Invalid plan (" + lastErrorMsg + "):\n" +
@@ -478,17 +508,6 @@ public abstract class AbstractPlannerTest extends
GridCommonAbstractTest {
}
}
- /** */
- protected <T extends RelNode> void assertPlan(
- String sql,
- IgniteSchema schema,
- @Nullable RelOptListener planLsnr,
- Predicate<T> predicate,
- String... disabledRules
- ) throws Exception {
- assertPlan(sql, Collections.singletonList(schema), planLsnr,
predicate, disabledRules);
- }
-
/**
* Predicate builder for "Instance of class" condition.
*/
@@ -805,4 +824,74 @@ public abstract class AbstractPlannerTest extends
GridCommonAbstractTest {
return true;
}
}
+
+ /** Test planning context builder. */
+ public class TestPlanningContextBuilder {
+ /** */
+ private String query;
+
+ /** */
+ private Collection<IgniteSchema> schemas;
+
+ /** */
+ private Collection<Object> params = List.of();
+
+ /** */
+ private Collection<String> disabledRules = List.of();
+
+ /** */
+ @Nullable private RelOptListener planListener;
+
+ /** */
+ public TestPlanningContextBuilder query(String qry) {
+ query = qry;
+ return this;
+ }
+
+ /** */
+ public TestPlanningContextBuilder schema(IgniteSchema schemas) {
+ this.schemas = List.of(schemas);
+ return this;
+ }
+
+ /** */
+ public TestPlanningContextBuilder schemas(Collection<IgniteSchema>
schemas) {
+ this.schemas = List.copyOf(schemas);
+ return this;
+ }
+
+ /** */
+ public TestPlanningContextBuilder params(Collection<Object> params) {
+ this.params = List.copyOf(params);
+ return this;
+ }
+
+ /** */
+ public TestPlanningContextBuilder params(Object... params) {
+ this.params = Arrays.asList(params);
+ return this;
+ }
+
+ /** */
+ public TestPlanningContextBuilder disabledRules(String... rules) {
+ disabledRules = List.of(rules);
+ return this;
+ }
+
+ /** */
+ public TestPlanningContextBuilder planListener(@Nullable
RelOptListener planListener) {
+ this.planListener = planListener;
+ return this;
+ }
+
+ /** */
+ PlanningContext build() {
+ return plannerCtx(query, schemas, planListener, params,
disabledRules);
+ }
+ }
+
+ /** */
+ public TestPlanningContextBuilder contextBuilder() {
+ return new TestPlanningContextBuilder();
+ }
}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/DynamicParametersPlannerTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/DynamicParametersPlannerTest.java
new file mode 100644
index 00000000000..e27d9eb468c
--- /dev/null
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/DynamicParametersPlannerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.ignite.internal.processors.query.calcite.planner;
+
+import java.math.BigInteger;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import org.junit.Test;
+
+import static
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions.single;
+
+/** */
+public class DynamicParametersPlannerTest extends AbstractPlannerTest {
+ /** Dynamic parameters in LIMIT / OFFSET. */
+ @Test
+ public void testLimitOffset() throws Exception {
+ IgniteSchema schema = createSchema(createTable("T1", single(), "c1",
Integer.class));
+
+ TestPlanningContextBuilder builder = contextBuilder().query("SELECT *
FROM t1 LIMIT ?").schema(schema);
+
+ assertPlan(builder.params(Long.MAX_VALUE), rel -> true);
+
+ // Count of dynamic parameters need to be invalidated, remove it
after: IGNITE-28906
+ assertPlan(builder.params(Long.MAX_VALUE, -1), rel -> true);
+
+ assertThrows(builder.params("a"), IgniteException.class,
+ "Incorrect type of a dynamic parameter. Expected <BIGINT> but got
<VARCHAR>");
+
+ BigInteger moreThanMaxLong =
BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
+
+ assertThrows(builder.params(moreThanMaxLong), IgniteException.class,
+ "Illegal value of fetch / limit");
+
+ assertThrows(builder.params(-1), IgniteException.class,
+ "Illegal value of fetch / limit");
+
+ assertThrows(builder.params((Object)null), IgniteException.class,
+ "Incorrect type of a dynamic parameter. Expected <BIGINT> but got
<null>");
+
+ // OFFSET.
+ builder.query("SELECT * FROM t1 OFFSET ?");
+
+ assertThrows(builder.params(moreThanMaxLong), IgniteException.class,
+ "Illegal value of offset");
+
+ assertThrows(builder.params(-1), IgniteException.class,
+ "Illegal value of offset");
+
+ assertThrows(builder.params((Object)null), IgniteException.class,
+ "Incorrect type of a dynamic parameter. Expected <BIGINT> but got
<null>");
+
+ // OFFSET Alternate syntax.
+ builder.query("SELECT * FROM t1 OFFSET ? ROWS");
+
+ assertThrows(builder.params(moreThanMaxLong), IgniteException.class,
+ "Illegal value of offset");
+
+ assertThrows(builder.params(-1), IgniteException.class,
+ "Illegal value of offset");
+
+ assertThrows(builder.params((Object)null), IgniteException.class,
+ "Incorrect type of a dynamic parameter. Expected <BIGINT> but got
<null>");
+
+ // Expression.
+ builder.query("SELECT * FROM TEST_REPL OFFSET 2+? ROWS");
+
+ assertThrows(builder, IgniteException.class,
+ "Encountered \" \"+\"");
+ }
+}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashJoinPlannerTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashJoinPlannerTest.java
index 97a8689535e..55140639755 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashJoinPlannerTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashJoinPlannerTest.java
@@ -28,10 +28,10 @@ import
org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
import
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.testframework.GridTestUtils;
import org.junit.Test;
import static org.apache.calcite.rel.RelFieldCollation.Direction.ASCENDING;
-import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
/** */
public class HashJoinPlannerTest extends AbstractPlannerTest {
@@ -148,7 +148,7 @@ public class HashJoinPlannerTest extends
AbstractPlannerTest {
if (canBePlanned)
assertPlan(sql0, schema,
nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class)), DISABLED_RULES);
else {
- assertThrows(null, () -> physicalPlan(sql0, schema,
DISABLED_RULES), CannotPlanException.class,
+ GridTestUtils.assertThrows(null, () -> physicalPlan(sql0,
schema, DISABLED_RULES), CannotPlanException.class,
"There are not enough rules");
}
}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
index 7daf90205dd..be2d0283c15 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
@@ -23,6 +23,7 @@ import
org.apache.ignite.internal.processors.query.calcite.planner.AggregatePlan
import
org.apache.ignite.internal.processors.query.calcite.planner.CorrelatedNestedLoopJoinPlannerTest;
import
org.apache.ignite.internal.processors.query.calcite.planner.CorrelatedSubqueryPlannerTest;
import
org.apache.ignite.internal.processors.query.calcite.planner.DataTypesPlannerTest;
+import
org.apache.ignite.internal.processors.query.calcite.planner.DynamicParametersPlannerTest;
import
org.apache.ignite.internal.processors.query.calcite.planner.HashAggregatePlannerTest;
import
org.apache.ignite.internal.processors.query.calcite.planner.HashIndexSpoolPlannerTest;
import
org.apache.ignite.internal.processors.query.calcite.planner.HashJoinPlannerTest;
@@ -96,6 +97,7 @@ import org.junit.runners.Suite;
AbstractPlannerUtilityTest.class,
HintsTestSuite.class,
+ DynamicParametersPlannerTest.class,
})
public class PlannerTestSuite {
}
diff --git a/modules/calcite/src/test/sql/order/test_limit.test
b/modules/calcite/src/test/sql/order/test_limit.test
index 2c2c3d34db9..4cdfe041e35 100644
--- a/modules/calcite/src/test/sql/order/test_limit.test
+++ b/modules/calcite/src/test/sql/order/test_limit.test
@@ -17,9 +17,67 @@ SELECT a FROM test ORDER BY a LIMIT 1
----
11
-# LIMIT with non-scalar should fail
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a LIMIT 1.2
+----
+11
+
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a LIMIT 1.5
+----
+11
+12
+
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a LIMIT 1.6
+----
+11
+12
+
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a FETCH FIRST 0 ROWS ONLY
+----
+
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a LIMIT 1.2
+----
+11
+
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a FETCH FIRST 1.2 ROWS ONLY
+----
+11
+
+# decimal offset/limit
+query I
+SELECT a FROM test ORDER BY a OFFSET 1.1 ROWS FETCH FIRST 1.1 ROWS ONLY
+----
+12
+
+# Unexpected literal
+statement error
+SELECT a FROM test ORDER BY a FETCH FIRST '1'
+
+# More than max of big integer
+statement error
+SELECT a FROM test LIMIT 9223372036854775808
+
+# More than max of big integer
+statement error
+SELECT a FROM test ORDER BY a FETCH FIRST 9223372036854775808 ROWS ONLY
+
+# Unexpected operation
+statement error
+SELECT a FROM test ORDER BY a FETCH FIRST 1 + 1 ROWS ONLY
+
statement error
-SELECT a FROM test LIMIT a
+SELECT a FROM test ORDER BY a FETCH FIRST -1 ROWS ONLY
# LIMIT with non-scalar operation should also fail
statement error