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 6d22ac2d51d33..73faebcd9fb43 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 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; @@ -31,6 +32,7 @@ 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; /** @@ -94,23 +96,13 @@ public void removeTable(String tblName) { * @param func SQL function. */ public void addFunction(String name, Function func) { - for (Function existingFun : getFunctions(name)) { - List params = func.getParameters(); - List 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); @@ -145,4 +137,24 @@ public SchemaPlus register(SchemaPlus parent, FrameworkConfig frameworkCfg) { return newSchema; } + + /** */ + private static boolean sameParameters( + List params, + List 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 569420c7401b5..dc1ca87526afe 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 @@ -94,6 +94,27 @@ public void testSameSignatureNotRegistered() throws Exception { assertEquals(1, schema.getFunctions("SAMESIGN").size()); } + /** */ + @Test + public void testOverloadedFunctions() { + client.getOrCreateCache(new CacheConfiguration("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()); + + 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 +850,55 @@ private static class CustomClass { 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> overloadedTable(int i, String s) { + return List.of(List.of(i + s)); + } + + /** */ + @QuerySqlTableFunction(alias = "OVERLOADED_TABLE", columnTypes = {String.class}, columnNames = {"RESULT"}) + public static Iterable> 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> sqlEquivalentTable(int i) { + return List.of(List.of(String.valueOf(i))); + } + + /** */ + @QuerySqlTableFunction(alias = "SQL_EQUIVALENT_TABLE", columnTypes = {String.class}, columnNames = {"RESULT"}) + public static Iterable> sqlEquivalentTable(Integer i) { + return List.of(List.of(String.valueOf(i))); + } + } } 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 fdd2c188cad7c..4505c8103a6ab 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,9 @@ * cache.query(new SqlFieldsQuery("select sqr(2) where sqr(1) = 1")); * *

+ * 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}. + *

* SQL functions can use attributes set on client side: *

  *     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 bceae02916c08..2b827c86c5403 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 @@
  * 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.
  * 

+ * 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}. + *

* Note, the table functions are available currently only with Calcite. * * @see QuerySqlFunction