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