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 837301500c0 IGNITE-29033 SQL Calcite: Support UDF and UDTF Overloading 
(#13544)
837301500c0 is described below

commit 837301500c07e9bd522a0c341320d62b1971b95d
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Wed Sep 9 12:57:05 2026 +0300

    IGNITE-29033 SQL Calcite: Support UDF and UDTF Overloading (#13544)
---
 .../query/calcite/schema/IgniteSchema.java         | 42 ++++++----
 .../UserDefinedFunctionsIntegrationTest.java       | 89 +++++++++++++++++++++-
 .../cache/query/annotations/QuerySqlFunction.java  |  4 +
 .../query/annotations/QuerySqlTableFunction.java   |  4 +
 4 files changed, 122 insertions(+), 17 deletions(-)

diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java
index 445002902d2..fe3cff62f05 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java
@@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimaps;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.schema.Function;
 import org.apache.calcite.schema.FunctionParameter;
 import org.apache.calcite.schema.SchemaPlus;
@@ -32,6 +33,7 @@ import org.apache.calcite.schema.TableMacro;
 import org.apache.calcite.schema.impl.AbstractSchema;
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.ignite.IgniteException;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 
 /**
@@ -101,23 +103,13 @@ public class IgniteSchema extends AbstractSchema {
      * @param func SQL function.
      */
     public void addFunction(String name, Function func) {
-        for (Function existingFun : getFunctions(name)) {
-            List<FunctionParameter> params = func.getParameters();
-            List<FunctionParameter> existingParams = 
existingFun.getParameters();
-
-            if (params.size() != existingParams.size())
-                continue;
-
-            for (int i = 0; i < params.size(); ++i) {
-                FunctionParameter p = params.get(i);
-                FunctionParameter existingP = existingParams.get(i);
+        IgniteTypeFactory typeFactory = Commons.typeFactory();
 
-                if 
(!p.getType(Commons.typeFactory()).equalsSansFieldNames(existingP.getType(Commons.typeFactory())))
-                    break;
+        for (Function existingFun : getFunctions(name)) {
+            if (sameParameters(func.getParameters(), 
existingFun.getParameters(), typeFactory)) {
+                throw new IgniteException("Unable to register function '" + 
name + "'. Other function with the same " +
+                    "name and parameters is already registered in schema '" + 
schemaName + "'.");
             }
-
-            throw new IgniteException("Unable to register function '" + name + 
"'. Other function with the same " +
-                "name and parameters is already registered in schema '" + 
schemaName + "'.");
         }
 
         funcMap.put(name, func);
@@ -163,4 +155,24 @@ public class IgniteSchema extends AbstractSchema {
             schema.add(DUAL_TBL_NAME, new ViewTableMacroImpl(DUAL_TBL_VIEW, 
schema, frameworkCfg));
         }
     }
+
+    /** */
+    private static boolean sameParameters(
+        List<FunctionParameter> params,
+        List<FunctionParameter> existingParams,
+        IgniteTypeFactory typeFactory
+    ) {
+        if (params.size() != existingParams.size())
+            return false;
+
+        for (int i = 0; i < params.size(); ++i) {
+            RelDataType paramType = 
typeFactory.toSql(params.get(i).getType(typeFactory));
+            RelDataType existingParamType = 
typeFactory.toSql(existingParams.get(i).getType(typeFactory));
+
+            if 
(!paramType.equalsSansFieldNamesAndNullability(existingParamType))
+                return false;
+        }
+
+        return true;
+    }
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java
index 569420c7401..e5f8e569c99 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java
@@ -69,8 +69,7 @@ public class UserDefinedFunctionsIntegrationTest extends 
AbstractBasicIntegratio
     /** */
     @Test
     public void testSameSignatureNotRegistered() throws Exception {
-        LogListener logChecker = LogListener.matches("Unable to register 
function 'SAMESIGN'. Other function " +
-            "with the same name and parameters is already registered").build();
+        LogListener logChecker = 
createUnableRegisterFunctionLogListener("SAMESIGN");
 
         listeningLog.registerListener(logChecker);
 
@@ -94,6 +93,35 @@ public class UserDefinedFunctionsIntegrationTest extends 
AbstractBasicIntegratio
         assertEquals(1, schema.getFunctions("SAMESIGN").size());
     }
 
+    /** */
+    @Test
+    public void testOverloadedFunctions() throws Exception {
+        LogListener sqlEquivalentLogLsnr = 
createUnableRegisterFunctionLogListener("SQL_EQUIVALENT");
+        LogListener sqlEquivalentTableLogLsnr = 
createUnableRegisterFunctionLogListener("SQL_EQUIVALENT_TABLE");
+
+        listeningLog.registerAllListeners(sqlEquivalentLogLsnr, 
sqlEquivalentTableLogLsnr);
+
+        client.getOrCreateCache(new CacheConfiguration<Integer, 
Object>("overloaded-functions")
+            .setSqlSchema("UDF")
+            .setSqlFunctionClasses(OverloadedFunctionsLibrary.class));
+
+        SchemaPlus schema = 
queryProcessor(client).schemaHolder().schema("UDF");
+
+        assertEquals(2, schema.getFunctions("OVERLOADED").size());
+        assertEquals(2, schema.getFunctions("OVERLOADED_TABLE").size());
+        assertEquals(1, schema.getFunctions("SQL_EQUIVALENT").size());
+        assertEquals(1, schema.getFunctions("SQL_EQUIVALENT_TABLE").size());
+
+        assertTrue(sqlEquivalentLogLsnr.check(getTestTimeout()));
+        assertTrue(sqlEquivalentTableLogLsnr.check(getTestTimeout()));
+
+        assertQuery("SELECT UDF.OVERLOADED(1, 'a')").returns("1a").check();
+        assertQuery("SELECT UDF.OVERLOADED('a', 1)").returns("a1").check();
+
+        assertQuery("SELECT * FROM TABLE(UDF.OVERLOADED_TABLE(1, 
'a'))").returns("1a").check();
+        assertQuery("SELECT * FROM TABLE(UDF.OVERLOADED_TABLE('a', 
1))").returns("a1").check();
+    }
+
     /** */
     @Test
     public void testSystemFunctionOverriding() throws Exception {
@@ -829,4 +857,61 @@ public class UserDefinedFunctionsIntegrationTest extends 
AbstractBasicIntegratio
             return "CustomClass.toString";
         }
     }
+
+    /** */
+    public static class OverloadedFunctionsLibrary {
+        /** */
+        @QuerySqlFunction
+        public static String overloaded(int i, String s) {
+            return i + s;
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static String overloaded(String s, int i) {
+            return s + i;
+        }
+
+        /** */
+        @QuerySqlFunction(alias = "SQL_EQUIVALENT")
+        public static String sqlEquivalent(int i) {
+            return String.valueOf(i);
+        }
+
+        /** */
+        @QuerySqlFunction(alias = "SQL_EQUIVALENT")
+        public static String sqlEquivalent(Integer i) {
+            return String.valueOf(i);
+        }
+
+        /** */
+        @QuerySqlTableFunction(alias = "OVERLOADED_TABLE", columnTypes = 
{String.class}, columnNames = {"RESULT"})
+        public static Iterable<Collection<?>> overloadedTable(int i, String s) 
{
+            return List.of(List.of(i + s));
+        }
+
+        /** */
+        @QuerySqlTableFunction(alias = "OVERLOADED_TABLE", columnTypes = 
{String.class}, columnNames = {"RESULT"})
+        public static Iterable<Collection<?>> overloadedTable(String s, int i) 
{
+            return List.of(List.of(s + i));
+        }
+
+        /** */
+        @QuerySqlTableFunction(alias = "SQL_EQUIVALENT_TABLE", columnTypes = 
{String.class}, columnNames = {"RESULT"})
+        public static Iterable<Collection<?>> sqlEquivalentTable(int i) {
+            return List.of(List.of(String.valueOf(i)));
+        }
+
+        /** */
+        @QuerySqlTableFunction(alias = "SQL_EQUIVALENT_TABLE", columnTypes = 
{String.class}, columnNames = {"RESULT"})
+        public static Iterable<Collection<?>> sqlEquivalentTable(Integer i) {
+            return List.of(List.of(String.valueOf(i)));
+        }
+    }
+
+    /** */
+    private static LogListener createUnableRegisterFunctionLogListener(String 
fun) {
+        return LogListener.matches("Unable to register function '" + fun + "'. 
Other function " +
+            "with the same name and parameters is already registered").build();
+    }
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/cache/query/annotations/QuerySqlFunction.java
 
b/modules/core/src/main/java/org/apache/ignite/cache/query/annotations/QuerySqlFunction.java
index fdd2c188cad..8d9c29327c6 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/cache/query/annotations/QuerySqlFunction.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/cache/query/annotations/QuerySqlFunction.java
@@ -45,6 +45,10 @@ import 
org.apache.ignite.resources.SessionContextProviderResource;
  *     cache.query(new SqlFieldsQuery("select sqr(2) where sqr(1) = 1"));
  * </pre>
  * <p>
+ * Only Calcite supports function overloading by SQL parameter types or their 
order. Java types mapped to the same SQL
+ * type cannot define separate overloads; for example, {@code int} and {@link 
Integer} both correspond to SQL
+ * {@code INTEGER}.
+ * <p>
  * SQL functions can use attributes set on client side:
  * <pre name="code" class="java">
  *     public class MyFunctions {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/cache/query/annotations/QuerySqlTableFunction.java
 
b/modules/core/src/main/java/org/apache/ignite/cache/query/annotations/QuerySqlTableFunction.java
index bceae02916c..2b827c86c54 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/cache/query/annotations/QuerySqlTableFunction.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/cache/query/annotations/QuerySqlTableFunction.java
@@ -52,6 +52,10 @@ import 
org.apache.ignite.resources.SessionContextProviderResource;
  * by an {@code Collection}. Row length must match the defined number of 
column types. Row value types must match the
  * defined column types or be able assigned to them.
  * <p>
+ * Table functions can be overloaded by SQL parameter types or their order. 
Java types mapped to the same SQL type
+ * cannot define separate overloads; for example, {@code int} and {@link 
Integer} both correspond to SQL
+ * {@code INTEGER}.
+ * <p>
  * Note, the table functions are available currently only with Calcite.
  *
  * @see QuerySqlFunction

Reply via email to