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

tkalkirill 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 a90f44a73d0 IGNITE-28813 Add the ability to add aggregate functions to 
Calcite via plugins (#13272)
a90f44a73d0 is described below

commit a90f44a73d0c5d4b2968ac0d3ff82aa5dbe56289
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Wed Jul 1 07:43:01 2026 +0300

    IGNITE-28813 Add the ability to add aggregate functions to Calcite via 
plugins (#13272)
---
 .../exec/exp/agg/AccumulatorFactoryProvider.java   | 39 +++++++++
 .../query/calcite/exec/exp/agg/Accumulators.java   | 20 +++--
 .../OperatorsExtensionIntegrationTest.java         | 96 ++++++++++++++++++++++
 .../internal/processors/query/QueryContext.java    | 11 +--
 4 files changed, 156 insertions(+), 10 deletions(-)

diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/AccumulatorFactoryProvider.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/AccumulatorFactoryProvider.java
new file mode 100644
index 00000000000..c1bce8b3194
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/AccumulatorFactoryProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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.exec.exp.agg;
+
+import java.util.function.Supplier;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.tools.Frameworks;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.plugin.PluginProvider;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Factory that selects and creates an accumulator supplier for an aggregate 
call. Allows overriding standard aggregate
+ * functions.
+ *
+ * <p>It can be set via {@link PluginProvider} when creating a configuration 
using
+ * {@link PluginProvider#createComponent} via {@link 
Frameworks.ConfigBuilder#context(Context)}.</p>
+ */
+@FunctionalInterface
+public interface AccumulatorFactoryProvider {
+    /** @return Accumulator supplier, {@code null} if no accumulator is 
required for this aggregate call. */
+    @Nullable <Row> Supplier<Accumulator<Row>> factory(AggregateCall call, 
ExecutionContext<Row> ctx);
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
index 9c15e4a7a23..085d212be25 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
@@ -71,6 +71,15 @@ public class Accumulators {
     ) {
         RowHandler<Row> hnd = ctx.rowHandler();
 
+        AccumulatorFactoryProvider prov = 
ctx.unwrap(AccumulatorFactoryProvider.class);
+
+        if (prov != null) {
+            Supplier<Accumulator<Row>> fac = prov.factory(call, ctx);
+
+            if (fac != null)
+                return fac;
+        }
+
         switch (call.getAggregation().getName()) {
             case "COUNT":
                 return () -> new LongCount<>(call, hnd);
@@ -280,7 +289,7 @@ public class Accumulators {
     }
 
     /** */
-    private abstract static class AbstractAccumulator<Row> implements 
Accumulator<Row> {
+    public abstract static class AbstractAccumulator<Row> implements 
Accumulator<Row> {
         /** */
         private final RowHandler<Row> hnd;
 
@@ -288,13 +297,13 @@ public class Accumulators {
         private final transient AggregateCall aggCall;
 
         /** */
-        AbstractAccumulator(AggregateCall aggCall, RowHandler<Row> hnd) {
+        protected AbstractAccumulator(AggregateCall aggCall, RowHandler<Row> 
hnd) {
             this.aggCall = aggCall;
             this.hnd = hnd;
         }
 
         /** */
-        <T> T get(int idx, Row row) {
+        protected <T> T get(int idx, Row row) {
             assert idx < arguments().size() : "idx=" + idx + "; arguments=" + 
arguments();
 
             return (T)hnd.get(arguments().get(idx), row);
@@ -311,7 +320,7 @@ public class Accumulators {
         }
 
         /** */
-        int columnCount(Row row) {
+        protected int columnCount(Row row) {
             return hnd.columnCount(row);
         }
     }
@@ -1344,8 +1353,9 @@ public class Accumulators {
                 if (builder == null)
                     builder = new StringBuilder();
 
-                if (builder.length() != 0)
+                if (!builder.isEmpty())
                     builder.append(extractSeparator(row));
+
                 builder.append(val);
             }
 
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java
index 53b6d23bcf1..d08259d0877 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java
@@ -18,12 +18,18 @@ package 
org.apache.ignite.internal.processors.query.calcite.integration;
 
 import java.math.BigDecimal;
 import java.sql.Timestamp;
+import java.util.List;
+import java.util.function.Supplier;
 import com.google.common.collect.ImmutableList;
 import org.apache.calcite.adapter.enumerable.NullPolicy;
 import org.apache.calcite.avatica.util.TimeUnitRange;
 import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.plan.Contexts;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlFunction;
 import org.apache.calcite.sql.SqlFunctionCategory;
@@ -43,12 +49,19 @@ import org.apache.calcite.sql2rel.SqlRexConvertlet;
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.Optionality;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import 
org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.RexImpTable;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.Accumulator;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorFactoryProvider;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.Accumulators;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.IgniteConvertletTable;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.IgniteSqlNodeRewriter;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.IgniteSqlValidator;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.plugin.AbstractTestPluginProvider;
 import org.apache.ignite.plugin.PluginContext;
 import org.jetbrains.annotations.Nullable;
@@ -75,6 +88,9 @@ public class OperatorsExtensionIntegrationTest extends 
AbstractBasicIntegrationT
                             .sqlValidatorConfig(
                                 
((IgniteSqlValidator.Config)CalciteQueryProcessor.FRAMEWORK_CONFIG.getSqlValidatorConfig())
                                     .withSqlNodeRewriter(new SqlRewriter()))
+                            .context(Contexts.chain(
+                                
CalciteQueryProcessor.FRAMEWORK_CONFIG.getContext(),
+                                Contexts.of(new 
AccumulatorFactoryProviderImpl())))
                             .build();
 
                         return (T)cfg;
@@ -134,6 +150,14 @@ public class OperatorsExtensionIntegrationTest extends 
AbstractBasicIntegrationT
         assertQuery("SELECT val_str from my_view").returns(new 
BigDecimal("0")).check();
     }
 
+    /** */
+    @Test
+    public void testCustomAggregateFunction() {
+        assertQuery("SELECT TEST_SUM(x) FROM (VALUES (1), (2), (3)) t(x)")
+            .returns(6L)
+            .check();
+    }
+
     /** Rewrites LTRIM with 2 parameters. */
     public static SqlCall rewriteLtrim(SqlValidator validator, SqlCall call) {
         if (call.operandCount() != 2)
@@ -193,6 +217,9 @@ public class OperatorsExtensionIntegrationTest extends 
AbstractBasicIntegrationT
             OperandTypes.STRING_STRING,
             SqlFunctionCategory.STRING
         );
+
+        /** */
+        public static final SqlAggFunction TEST_SUM = new 
SqlTestSumAggFunction();
     }
 
     /** Extended convertlet table. */
@@ -229,4 +256,73 @@ public class OperatorsExtensionIntegrationTest extends 
AbstractBasicIntegrationT
             return node;
         }
     }
+
+    /** */
+    private static class AccumulatorFactoryProviderImpl implements 
AccumulatorFactoryProvider {
+        /** {@inheritDoc} */
+        @Override public @Nullable <Row> Supplier<Accumulator<Row>> 
factory(AggregateCall call, ExecutionContext<Row> ctx) {
+            if 
(call.getAggregation().getName().equals(OperatorTable.TEST_SUM.getName()))
+                return () -> new TestSum<>(call, ctx.rowHandler());
+
+            return null;
+        }
+    }
+
+    /** */
+    public static class SqlTestSumAggFunction extends SqlAggFunction {
+        /** */
+        public SqlTestSumAggFunction() {
+            super(
+                "TEST_SUM",
+                null,
+                SqlKind.SUM,
+                ReturnTypes.AGG_SUM,
+                null,
+                OperandTypes.NUMERIC,
+                SqlFunctionCategory.NUMERIC,
+                false,
+                false,
+                Optionality.FORBIDDEN
+            );
+        }
+    }
+
+    /** */
+    private static class TestSum<Row> extends 
Accumulators.AbstractAccumulator<Row> {
+        /** */
+        private long sum;
+
+        /** */
+        protected TestSum(AggregateCall aggCall, RowHandler<Row> hnd) {
+            super(aggCall, hnd);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void add(Row row) {
+            Number val = get(0, row);
+
+            if (val != null)
+                sum += val.longValue();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void apply(Accumulator<Row> other) {
+            sum += ((TestSum<Row>)other).sum;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object end() {
+            return sum;
+        }
+
+        /** {@inheritDoc} */
+        @Override public List<RelDataType> argumentTypes(IgniteTypeFactory 
typeFactory) {
+            return 
List.of(typeFactory.createTypeWithNullability(typeFactory.createSqlType(SqlTypeName.BIGINT),
 true));
+        }
+
+        /** {@inheritDoc} */
+        @Override public RelDataType returnType(IgniteTypeFactory typeFactory) 
{
+            return 
typeFactory.createSqlType(org.apache.calcite.sql.type.SqlTypeName.BIGINT);
+        }
+    }
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryContext.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryContext.java
index a9dbe02255e..ac7dfc2eb90 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryContext.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryContext.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import org.apache.ignite.internal.util.typedef.F;
+import org.jetbrains.annotations.Nullable;
 
 /** */
 public final class QueryContext {
@@ -36,10 +37,10 @@ public final class QueryContext {
     }
 
     /**
-     * Finds an instance of an interface implemented by this object,
-     * or returns null if this object does not support that interface.
+     * Finds an instance of an interface implemented by this object
+     * or returns {@code null} if this object does not support that interface.
      */
-    public <C> C unwrap(Class<C> aClass) {
+    public <C> @Nullable C unwrap(Class<C> aClass) {
         if (Object[].class == aClass)
             return aClass.cast(params);
 
@@ -50,12 +51,12 @@ public final class QueryContext {
      * @param params Context parameters.
      * @return Query context.
      */
-    public static QueryContext of(Object... params) {
+    public static QueryContext of(@Nullable Object... params) {
         return !F.isEmpty(params) ? new QueryContext(build(null, 
params).toArray()) : new QueryContext(EMPTY);
     }
 
     /** */
-    private static List<Object> build(List<Object> dst, Object[] src) {
+    private static List<Object> build(List<Object> dst, @Nullable Object[] 
src) {
         if (dst == null)
             dst = new ArrayList<>();
 

Reply via email to