This is an automated email from the ASF dual-hosted git repository.
mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new fc95bb0e68 [CALCITE-7682] SESSION table function without the optional
key descriptor fails at runtime
fc95bb0e68 is described below
commit fc95bb0e68c838da4232aabf47ee5037cf73b778
Author: Mihai Budiu <[email protected]>
AuthorDate: Fri Jul 31 13:43:29 2026 -0700
[CALCITE-7682] SESSION table function without the optional key descriptor
fails at runtime
Signed-off-by: Mihai Budiu <[email protected]>
---
.../calcite/adapter/enumerable/EnumUtils.java | 12 ++++++--
.../calcite/adapter/enumerable/RexImpTable.java | 16 +++++++---
core/src/test/resources/sql/stream.iq | 36 ++++++++++++++++++++++
3 files changed, 57 insertions(+), 7 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
index 40a824536b..d1c395c226 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
@@ -1100,6 +1100,9 @@ static Expression tumblingWindowSelector(
* Creates enumerable implementation that applies sessionization to elements
from the input
* enumerator based on a specified key. Elements are windowed into sessions
separated by
* periods with no input for at least the duration specified by gap
parameter.
+ *
+ * <p>The key is optional: pass -1 for {@code indexOfKeyColumn} to place
every
+ * element on a single session timeline.
*/
public static Enumerable<@Nullable Object[]> sessionize(
Enumerator<@Nullable Object[]> inputEnumerator,
@@ -1144,7 +1147,9 @@ private static class SessionizationEnumerator implements
Enumerator<@Nullable Ob
*
* @param inputEnumerator the enumerator to provide an array of objects as
input
* @param indexOfWatermarkedColumn the index of timestamp column upon
which a watermark is built
- * @param indexOfKeyColumn the index of column that acts as grouping key
+ * @param indexOfKeyColumn the index of column that acts as grouping key,
+ * or -1 if there is no key and all rows belong to
+ * a single session timeline
* @param gap gap parameter
*/
SessionizationEnumerator(Enumerator<@Nullable Object[]> inputEnumerator,
@@ -1194,8 +1199,11 @@ private void initialize() {
Map<@Nullable Object, SortedMultiMap<Pair<Long, Long>, @Nullable
Object[]>> sessionKeyMap =
new HashMap<>();
for (@Nullable Object[] element : elements) {
+ // A key column index of -1 means that there is no key; every element
+ // then maps to the same (null) key, forming one session timeline.
+ Object key = indexOfKeyColumn < 0 ? null : element[indexOfKeyColumn];
SortedMultiMap<Pair<Long, Long>, @Nullable Object[]> session =
- sessionKeyMap.computeIfAbsent(element[indexOfKeyColumn], k -> new
SortedMultiMap<>());
+ sessionKeyMap.computeIfAbsent(key, k -> new SortedMultiMap<>());
Object watermark =
requireNonNull(element[indexOfWatermarkedColumn],
"element[indexOfWatermarkedColumn]");
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index 2ca2528d5c..7a5ea10f14 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -5113,14 +5113,20 @@ private static Method getMethod(Type
comparisonReturnType, SqlKind kind) {
private static class SessionImplementor implements
TableFunctionCallImplementor {
@Override public Expression implement(RexToLixTranslator translator,
Expression inputEnumerable, RexCall call, PhysType inputPhysType,
PhysType outputPhysType) {
- RexCall timestampDescriptor = (RexCall) call.getOperands().get(0);
- RexCall keyDescriptor = (RexCall) call.getOperands().get(1);
- Expression gapInterval = translator.translate(call.getOperands().get(2));
+ final List<RexNode> operands = call.getOperands();
+ RexCall timestampDescriptor = (RexCall) operands.get(0);
+ // The gap is always the last operand; the key descriptor between them is
+ // optional. Without a key every row belongs to a single session
+ // timeline, which a key column index of -1 denotes.
+ Expression gapInterval = translator.translate(Util.last(operands));
+ final int keyColIndex =
+ operands.size() > 2 && operands.get(1).getKind() ==
SqlKind.DESCRIPTOR
+ ? ((RexInputRef) ((RexCall)
operands.get(1)).getOperands().get(0)).getIndex()
+ : -1;
Expression wmColIndexExpr =
Expressions.constant(((RexInputRef)
timestampDescriptor.getOperands().get(0)).getIndex());
- Expression keyColIndexExpr =
- Expressions.constant(((RexInputRef)
keyDescriptor.getOperands().get(0)).getIndex());
+ Expression keyColIndexExpr = Expressions.constant(keyColIndex);
return Expressions.call(BuiltInMethod.SESSIONIZATION.method,
Expressions.list(
diff --git a/core/src/test/resources/sql/stream.iq
b/core/src/test/resources/sql/stream.iq
index 3941143762..b9df0692cc 100644
--- a/core/src/test/resources/sql/stream.iq
+++ b/core/src/test/resources/sql/stream.iq
@@ -291,3 +291,39 @@ SELECT * FROM TABLE(SESSION((SELECT * FROM ORDERS),
DESCRIPTOR(ROWTIME), DESCRIP
(5 rows)
!ok
+
+# Test case for [CALCITE-7682] SESSION table function without the optional key
+# descriptor fails at runtime.
+SELECT * FROM TABLE(SESSION(TABLE ORDERS, DESCRIPTOR(ROWTIME), INTERVAL '1'
HOUR));
++---------------------+----+---------+-------+---------------------+---------------------+
+| ROWTIME | ID | PRODUCT | UNITS | window_start |
window_end |
++---------------------+----+---------+-------+---------------------+---------------------+
+| 2015-02-15 10:15:00 | 1 | paint | 10 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
+| 2015-02-15 10:24:15 | 2 | paper | 5 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
+| 2015-02-15 10:24:45 | 3 | brush | 12 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
+| 2015-02-15 10:58:00 | 4 | paint | 3 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
+| 2015-02-15 11:10:00 | 5 | paint | 3 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
++---------------------+----+---------+-------+---------------------+---------------------+
+(5 rows)
+
+!ok
+
+# As above, but with named parameters, so the KEY parameter is omitted rather
+# than simply absent.
+SELECT * FROM TABLE(
+ SESSION(
+ DATA => TABLE ORDERS,
+ TIMECOL => DESCRIPTOR(ROWTIME),
+ SIZE => INTERVAL '1' HOUR));
++---------------------+----+---------+-------+---------------------+---------------------+
+| ROWTIME | ID | PRODUCT | UNITS | window_start |
window_end |
++---------------------+----+---------+-------+---------------------+---------------------+
+| 2015-02-15 10:15:00 | 1 | paint | 10 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
+| 2015-02-15 10:24:15 | 2 | paper | 5 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
+| 2015-02-15 10:24:45 | 3 | brush | 12 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
+| 2015-02-15 10:58:00 | 4 | paint | 3 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
+| 2015-02-15 11:10:00 | 5 | paint | 3 | 2015-02-15 10:15:00 |
2015-02-15 12:10:00 |
++---------------------+----+---------+-------+---------------------+---------------------+
+(5 rows)
+
+!ok