This is an automated email from the ASF dual-hosted git repository.

xuzifu666 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 97ad6b8024 [CALCITE-6767] PERCENTILE_CONT/PERCENTILE_DISC function are 
not supported
97ad6b8024 is described below

commit 97ad6b8024a7c0c060681be049ab3003f8390689
Author: Yu Xu <[email protected]>
AuthorDate: Sat Jul 4 08:12:19 2026 +0800

    [CALCITE-6767] PERCENTILE_CONT/PERCENTILE_DISC function are not supported
---
 .../enumerable/EnumerableAggregateBase.java        |  15 +++
 .../calcite/adapter/enumerable/RexImpTable.java    |  58 +++++++++++
 .../org/apache/calcite/runtime/SqlFunctions.java   |  50 +++++++++
 .../org/apache/calcite/util/BuiltInMethod.java     |   2 +
 core/src/test/resources/sql/agg.iq                 | 113 +++++++++++++++++++++
 5 files changed, 238 insertions(+)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregateBase.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregateBase.java
index 3d0ab7e895..ae2f939988 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregateBase.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregateBase.java
@@ -30,6 +30,7 @@
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
@@ -264,6 +265,20 @@ protected void createAccumulatorAdders(
               for (int index : agg.call.getArgList()) {
                 args.add(RexInputRef.of(index, inputTypes));
               }
+              // Percentile functions such as PERCENTILE_CONT and
+              // PERCENTILE_DISC take the fraction as their only argument, but
+              // aggregate over the WITHIN GROUP (ORDER BY ...) column. Expose
+              // that collation column as an extra argument so that the
+              // accumulator can collect its values (already sorted by
+              // SourceSorter).
+              if (agg.call.getAggregation().isPercentile()) {
+                for (RelFieldCollation fieldCollation
+                    : agg.call.collation.getFieldCollations()) {
+                  args.add(
+                      RexInputRef.of(fieldCollation.getFieldIndex(),
+                          inputTypes));
+                }
+              }
               return args;
             }
 
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 acbd66cb1c..bf5de12e74 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
@@ -499,6 +499,8 @@
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.OCTET_LENGTH;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.OR;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.OVERLAY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PERCENTILE_CONT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PERCENTILE_DISC;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PI;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PLUS;
 import static org.apache.calcite.sql.fun.SqlStdOperatorTable.POSITION;
@@ -1326,6 +1328,8 @@ void populate3() {
       defineAgg(SINGLE_VALUE, SingleValueImplementor.class);
       defineAgg(COLLECT, CollectImplementor.class);
       defineAgg(ARRAY_AGG, CollectImplementor.class);
+      defineAgg(PERCENTILE_CONT, PercentileImplementor.class);
+      defineAgg(PERCENTILE_DISC, PercentileImplementor.class);
       defineAgg(LISTAGG, ListaggImplementor.class);
       defineAgg(FUSION, FusionImplementor.class);
       defineAgg(MODE, ModeImplementor.class);
@@ -1967,6 +1971,60 @@ static class CollectImplementor extends 
StrictAggImplementor {
     }
   }
 
+  /** Implementor for the {@code PERCENTILE_CONT} and {@code PERCENTILE_DISC}
+   * aggregate functions.
+   *
+   * <p>The fraction is the sole argument of the aggregate call, while the
+   * values whose percentile is computed come from the
+   * {@code WITHIN GROUP (ORDER BY ...)} column, which
+   * {@link EnumerableAggregateBase#createAccumulatorAdders} exposes as an 
extra
+   * argument. The input rows are sorted by {@code SourceSorter} before being
+   * accumulated, so the collected values are already in order. */
+  static class PercentileImplementor extends StrictAggImplementor {
+    @Override public List<Type> getNotNullState(AggContext info) {
+      final List<Type> types = new ArrayList<>();
+      types.add(List.class);
+      types.add(double.class);
+      return types;
+    }
+
+    @Override protected void implementNotNullReset(AggContext info,
+        AggResetContext reset) {
+      reset.currentBlock().add(
+          Expressions.statement(
+              Expressions.assign(reset.accumulator().get(0),
+                  Expressions.new_(ArrayList.class))));
+      reset.currentBlock().add(
+          Expressions.statement(
+              Expressions.assign(reset.accumulator().get(1),
+                  Expressions.constant(0d))));
+    }
+
+    @Override protected void implementNotNullAdd(AggContext info,
+        AggAddContext add) {
+      add.currentBlock().add(
+          Expressions.statement(
+              Expressions.assign(add.accumulator().get(1),
+                  EnumUtils.convert(add.arguments().get(0), double.class))));
+
+      add.currentBlock().add(
+          Expressions.statement(
+              Expressions.call(add.accumulator().get(0),
+                  BuiltInMethod.COLLECTION_ADD.method,
+                  Expressions.box(add.arguments().get(1)))));
+    }
+
+    @Override protected Expression implementNotNullResult(AggContext info,
+        AggResultContext result) {
+      final BuiltInMethod method =
+          info.aggregation().kind == SqlKind.PERCENTILE_DISC
+              ? BuiltInMethod.PERCENTILE_DISC
+              : BuiltInMethod.PERCENTILE_CONT;
+      return Expressions.call(method.method, result.accumulator().get(0),
+          result.accumulator().get(1));
+    }
+  }
+
   /** Implementor for the {@code LISTAGG} aggregate function. */
   static class ListaggImplementor extends StrictAggImplementor {
     @Override protected void implementNotNullReset(AggContext info,
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java 
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index ed6a3ba16f..c41ab84d71 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -133,6 +133,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
@@ -4069,6 +4070,55 @@ public static BigDecimal mod(BigDecimal b0, BigDecimal 
b1) {
     return bigDecimals[1];
   }
 
+  // PERCENTILE_CONT / PERCENTILE_DISC
+
+  /** Support the PERCENTILE_CONT aggregate function.
+   *
+   * <p>The {@code values} list must already be sorted according to the
+   * {@code WITHIN GROUP (ORDER BY ...)} clause. The fraction must be in the
+   * range 0 to 1 inclusive. The result is a linear interpolation between the
+   * two values that surround the desired position. */
+  public static BigDecimal percentileCont(List<? extends Number> values,
+      double fraction) {
+    final int n = values.size();
+    if (n == 0) {
+      throw new NoSuchElementException(
+          "PERCENTILE_CONT is not defined on an empty group");
+    }
+    final double rank = fraction * (n - 1);
+    final int lo = (int) Math.floor(rank);
+    final int hi = (int) Math.ceil(rank);
+    final BigDecimal loValue = toBigDecimal(values.get(lo));
+    if (lo == hi) {
+      return loValue;
+    }
+    final BigDecimal hiValue = toBigDecimal(values.get(hi));
+    final BigDecimal frac = BigDecimal.valueOf(rank - lo);
+    return loValue.add(hiValue.subtract(loValue).multiply(frac));
+  }
+
+  /** Support the PERCENTILE_DISC aggregate function.
+   *
+   * <p>The {@code values} list must already be sorted according to the
+   * {@code WITHIN GROUP (ORDER BY ...)} clause. The fraction must be in the
+   * range 0 to 1 inclusive. The result is an actual value from the group: the
+   * first whose cumulative distribution is greater than or equal to the
+   * fraction. */
+  public static Object percentileDisc(List<?> values, double fraction) {
+    final int n = values.size();
+    if (n == 0) {
+      throw new NoSuchElementException(
+          "PERCENTILE_DISC is not defined on an empty group");
+    }
+    int index = (int) Math.ceil(fraction * n) - 1;
+    if (index < 0) {
+      index = 0;
+    } else if (index >= n) {
+      index = n - 1;
+    }
+    return requireNonNull(values.get(index));
+  }
+
   // FLOOR
 
   public static double floor(double b0) {
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java 
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 2b07069b65..3c3a3e363e 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -415,6 +415,8 @@ public enum BuiltInMethod {
   COLLECTION_ADD(Collection.class, "add", Object.class),
   COLLECTION_ADDALL(Collection.class, "addAll", Collection.class),
   COLLECTION_RETAIN_ALL(Collection.class, "retainAll", Collection.class),
+  PERCENTILE_CONT(SqlFunctions.class, "percentileCont", List.class, 
double.class),
+  PERCENTILE_DISC(SqlFunctions.class, "percentileDisc", List.class, 
double.class),
   LIST_CONTAINS(List.class, "contains", Object.class),
   LIST_GET(List.class, "get", int.class),
   LIST_TO_ARRAY(List.class, "toArray"),
diff --git a/core/src/test/resources/sql/agg.iq 
b/core/src/test/resources/sql/agg.iq
index b637a6cbc1..9355ffedae 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -3844,6 +3844,119 @@ select distinct sum(deptno + '1') as deptsum from dept 
order by 1;
 
 !ok
 
+# [CALCITE-6767] PERCENTILE_CONT/PERCENTILE_DISC syntax not supported.
+# These sql programs were validated in PostgreSQL
+select
+  percentile_cont(0.5) within group (order by empno) as c,
+  percentile_disc(0.5) within group (order by empno) as d
+from emp;
++------+------+
+| C    | D    |
++------+------+
+| 7785 | 7782 |
++------+------+
+(1 row)
+
+!ok
+
+# PERCENTILE_CONT / PERCENTILE_DISC with GROUP BY.
+select deptno,
+  percentile_cont(0.5) within group (order by empno) as c,
+  percentile_disc(0.5) within group (order by empno) as d
+from emp
+group by deptno
+order by deptno;
++--------+------+------+
+| DEPTNO | C    | D    |
++--------+------+------+
+|     10 | 7839 | 7839 |
+|     20 | 7788 | 7788 |
+|     30 | 7676 | 7654 |
++--------+------+------+
+(3 rows)
+
+!ok
+
+# PERCENTILE_CONT / PERCENTILE_DISC honour a descending ORDER BY.
+select
+  percentile_disc(0.25) within group (order by empno desc) as d
+from emp;
++------+
+| D    |
++------+
+| 7876 |
++------+
+(1 row)
+
+!ok
+
+# PERCENTILE_CONT / PERCENTILE_DISC on DOUBLE values.
+select
+  percentile_cont(0.5) within group (order by v) as c,
+  percentile_disc(0.5) within group (order by v) as d
+from (values (cast(1.5 as double)),
+             (cast(2.5 as double)),
+             (cast(4.5 as double)),
+             (cast(8.5 as double))) as t(v);
++-----+-----+
+| C   | D   |
++-----+-----+
+| 3.5 | 2.5 |
++-----+-----+
+(1 row)
+
+!ok
+
+# PERCENTILE_CONT / PERCENTILE_DISC on large DECIMAL values that are very
+# close together. These 19-digit values exceed the ~15-16 significant digits a
+# double can hold, so the linear interpolation must be done in BigDecimal;
+# converting to double would round 9999999999999999990 to 1.0E19 and lose the
+# trailing digits.
+select
+  percentile_cont(0.5) within group (order by v) as c,
+  percentile_disc(0.5) within group (order by v) as d
+from (values (cast('9999999999999999990' as decimal(19, 0))),
+             (cast('9999999999999999992' as decimal(19, 0)))) as t(v);
++-----------------------+---------------------+
+| C                     | D                   |
++-----------------------+---------------------+
+| 9999999999999999991.0 | 9999999999999999990 |
++-----------------------+---------------------+
+(1 row)
+
+!ok
+
+# A three-row large DECIMAL group whose 0.75 percentile interpolates between
+# two adjacent, nearly-equal values.
+select
+  percentile_cont(0.75) within group (order by v) as c
+from (values (cast('9999999999999999990' as decimal(19, 0))),
+             (cast('9999999999999999994' as decimal(19, 0))),
+             (cast('9999999999999999998' as decimal(19, 0)))) as t(v);
++-----------------------+
+| C                     |
++-----------------------+
+| 9999999999999999996.0 |
++-----------------------+
+(1 row)
+
+!ok
+
+# PERCENTILE_CONT / PERCENTILE_DISC on UNSIGNED values.
+select
+  percentile_cont(0.5) within group (order by v) as c,
+  percentile_disc(0.5) within group (order by v) as d
+from (values (cast(10 as int unsigned)),
+             (cast(20 as int unsigned))) as t(v);
++----+----+
+| C  | D  |
++----+----+
+| 15 | 10 |
++----+----+
+(1 row)
+
+!ok
+
 # [CALCITE-6839] The SUM function sometimes throws overflow exceptions due
 # to incorrect return types
 !use scott-spark

Reply via email to