diff --git a/docs/_docs/code-snippets/java/src/main/java/org/apache/ignite/snippets/SqlAPI.java b/docs/_docs/code-snippets/java/src/main/java/org/apache/ignite/snippets/SqlAPI.java index 9de660bf2dbf3..eff7d0376767a 100644 --- a/docs/_docs/code-snippets/java/src/main/java/org/apache/ignite/snippets/SqlAPI.java +++ b/docs/_docs/code-snippets/java/src/main/java/org/apache/ignite/snippets/SqlAPI.java @@ -174,6 +174,14 @@ public static Iterable table_function(int i) { new Object[] {i * 10, "empty"} ); } + + @QuerySqlTableFunction(alias = "TABLE_FUNC_WITH_ARRAY", columnTypes = {String.class}, columnNames = {"RES_COL"}) + public static Iterable table_function_with_arr(List array) { + return array.stream() + .map(Object::toString) + .map(str -> new Object[]{str}) + .collect(Collectors.toList()); + } } // end::sql-table-function-example[] @@ -202,7 +210,11 @@ IgniteCache testSqlTableFunction(Ignite ignite) { IgniteCache cache = ignite.createCache(cfg); - SqlFieldsQuery query = new SqlFieldsQuery("SELECT STR_COL FROM TABLE_FUNCTION(10) WHERE INT_COL > 50"); + SqlFieldsQuery query = new SqlFieldsQuery("SELECT STR_COL FROM TABLE(TABLE_FUNCTION(10)) WHERE INT_COL > 50"); + + cache.query(query).getAll(); + + query = new SqlFieldsQuery("SELECT RES_COL FROM TABLE(TABLE_FUNC_WITH_ARRAY(?))").setArgs(List.of("row1", "row2")); cache.query(query).getAll(); // end::sql-table-function-config-query[] 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 41aef29959634..278b3bac52f7d 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 @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.sql.validate.SqlValidatorException; import org.apache.ignite.IgniteCache; @@ -92,7 +93,6 @@ public void testSameSignatureNotRegistered() throws Exception { assertEquals(1, schema.getFunctions("SAMESIGN").size()); } - /** */ @Test public void testSystemFunctionOverriding() throws Exception { @@ -105,18 +105,25 @@ public void testSystemFunctionOverriding() throws Exception { // Make sure that the new functions didn't affect schema 'PUBLIC'. assertQuery("SELECT UPPER(?)").withParams("abc").returns("ABC").check(); - assertQuery("select UNIX_SECONDS(TIMESTAMP '2021-01-01 00:00:00')").returns(1609459200L).check(); - assertQuery("select * from table(SYSTEM_RANGE(1, 2))").returns(1L).returns(2L).check(); - assertQuery("select TYPEOF(?)").withParams(1L).returns("BIGINT").check(); - assertQuery("select ? + ?").withParams(1, 2).returns(3).check(); - assertThrows("select PLUS(?, ?)", SqlValidatorException.class, "No match found for function signature", 1, 2); + assertQuery("SELECT UNIX_SECONDS(TIMESTAMP '2021-01-01 00:00:00')").returns(1609459200L).check(); + assertQuery("SELECT * FROM TABLE(SYSTEM_RANGE(1, 2))").returns(1L).returns(2L).check(); + assertQuery("SELECT TYPEOF(?)").withParams(1L).returns("BIGINT").check(); + assertQuery("SELECT ? + ?").withParams(1, 2).returns(3).check(); + assertThrows("SELECT PLUS(?, ?)", SqlValidatorException.class, "No match found for function signature", 1, 2); // Ensure that new functions are successfully created in a custom schema. assertQuery("SELECT \"OWN_SCHEMA\".UPPER(?)").withParams("abc").returns(3).check(); - assertQuery("select \"OWN_SCHEMA\".UNIX_SECONDS(TIMESTAMP '2021-01-01 00:00:00')").returns(1).check(); - assertQuery("select * from table(\"OWN_SCHEMA\".SYSTEM_RANGE(1, 2))").returns(100L).check(); - assertQuery("select \"OWN_SCHEMA\".TYPEOF('ABC')").returns(1).check(); - assertQuery("select \"OWN_SCHEMA\".PLUS(?, ?)").withParams(1, 2).returns(100).check(); + assertQuery("SELECT \"OWN_SCHEMA\".UNIX_SECONDS(TIMESTAMP '2021-01-01 00:00:00')").returns(1).check(); + assertQuery("SELECT * FROM TABLE(\"OWN_SCHEMA\".SYSTEM_RANGE(1, 2))").returns(100L).check(); + assertQuery("SELECT \"OWN_SCHEMA\".TYPEOF('ABC')").returns(1).check(); + assertQuery("SELECT \"OWN_SCHEMA\".PLUS(?, ?)").withParams(1, 2).returns(100).check(); + + assertQuery("SELECT * FROM TABLE(\"OWN_SCHEMA\".STR_ARRAY_CONSUME_TABLE(?)) as t").withParams(List.of("row1", "row2")) + .returns("row1").returns( "row2").check(); + assertQuery("SELECT * FROM TABLE(\"OWN_SCHEMA\".OBJ_ARRAY_CONSUME_TABLE(?)) as t").withParams(List.of(new CustomClass(), "row2")) + .returns("CustomClass.toString").returns( "row2").check(); + assertThrows("SELECT * FROM TABLE(\"OWN_SCHEMA\".STR_ARRAY_CONSUME_TABLE(?)) as t", IgniteSQLException.class, + "An error occurred while query executing", List.of(new CustomClass(), "row2")); LogListener logChecker0 = LogListener.matches("Unable to add user-defined SQL function 'upper'") .andMatches("Unable to add user-defined SQL function 'unix_seconds'") @@ -713,6 +720,24 @@ public static int typeof(Object o) { public static int plus(int x, int y) { return 100; } + + /** Table function with String array as input. */ + @QuerySqlTableFunction(alias = "STR_ARRAY_CONSUME_TABLE", columnTypes = {String.class}, columnNames = {"RESULT"}) + public static Iterable strArrConsumeTable(List array) { + return array.stream() + .map(Object::toString) + .map(str -> new Object[]{str}) + .collect(Collectors.toList()); + } + + /** Table function with Object array as input. */ + @QuerySqlTableFunction(alias = "OBJ_ARRAY_CONSUME_TABLE", columnTypes = {String.class}, columnNames = {"RESULT"}) + public static Iterable objArrConsumeTable(List array) { + return array.stream() + .map(Object::toString) + .map(str -> new Object[]{str}) + .collect(Collectors.toList()); + } } /** */ @@ -726,4 +751,12 @@ public static double salary(String ignite, int key) { .getAll().get(0).get(0); } } + + /** */ + private static class CustomClass { + /** */ + @Override public String toString() { + return "CustomClass.toString"; + } + } } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcQueryTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcQueryTest.java index cd457f62a38f0..c7d39bfa20876 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcQueryTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcQueryTest.java @@ -40,7 +40,9 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.ignite.cache.query.annotations.QuerySqlTableFunction; import org.apache.ignite.calcite.CalciteQueryEngineConfiguration; +import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.configuration.SqlConfiguration; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; @@ -69,8 +71,14 @@ public class JdbcQueryTest extends GridCommonAbstractTest { /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + CacheConfiguration ccfg = new CacheConfiguration<>("TEST_CACHE_OWN") + .setSqlSchema("OWN_SCHEMA") + .setSqlFunctionClasses(FunctionsLibrary.class); + return super.getConfiguration(igniteInstanceName).setSqlConfiguration( - new SqlConfiguration().setQueryEnginesConfiguration(new CalciteQueryEngineConfiguration())); + new SqlConfiguration() + .setQueryEnginesConfiguration(new CalciteQueryEngineConfiguration())) + .setCacheConfiguration(ccfg); } /** {@inheritDoc} */ @@ -113,6 +121,21 @@ private void connect(String url) throws Exception { stopAllGrids(); } + /** Test user defined table through jdbc. */ + @Test + public void testUdt() throws Exception { + try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM TABLE(\"OWN_SCHEMA\".STR_ARRAY_CONSUME_TABLE(?))")) { + ps.setObject(1, List.of("row1", "row2")); + ResultSet rs = ps.executeQuery(); + + assertTrue(rs.next()); + assertEquals("row1", rs.getString(1)); + + assertTrue(rs.next()); + assertEquals("row2", rs.getString(1)); + } + } + /** * @throws SQLException If failed. */ @@ -490,4 +513,16 @@ public ObjectToStore(int id, String name, double val) { return Objects.hash(id, name, val); } } + + /** User defined functions. */ + public static class FunctionsLibrary { + /** Function consume String array and output it row by row. */ + @QuerySqlTableFunction(alias = "STR_ARRAY_CONSUME_TABLE", columnTypes = {String.class}, columnNames = {"RESULT"}) + public static Iterable strArrConsumeTable(List array) { + return array.stream() + .map(Object::toString) + .map(str -> new Object[]{str}) + .collect(Collectors.toList()); + } + } } 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 7c41bf954ef22..bceae02916c08 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 @@ -45,7 +45,7 @@ * cacheCfg.setSqlFunctionClasses(MyTableFunctions.class); * * // And use in queries. - * cache.query(new SqlFieldsQuery("select S_VAL from MY_TABLE(1, 5.0f, "ext") where F_VAL is not null")); + * cache.query(new SqlFieldsQuery("select S_VAL from TABLE(MY_TABLE(1, 5.0f, "ext")) where F_VAL is not null")); * *

* Table function must return an {@code Iterable} as a row set. Each row can be represented by an {@code Object[]} or