diff --git a/flavors/template-Exasol-8-python-3.10-cuda-conda/ci.json b/flavors/template-Exasol-8-python-3.10-cuda-conda/ci.json index 91ab757f8..477358605 100644 --- a/flavors/template-Exasol-8-python-3.10-cuda-conda/ci.json +++ b/flavors/template-Exasol-8-python-3.10-cuda-conda/ci.json @@ -41,9 +41,9 @@ { "name": "generic", "files": [], - "folders": [], + "folders": ["generic/python3"], "goal": "release", - "generic_language_tests": ["python3"] + "generic_language_tests": [] }, { "name": "gpu", diff --git a/flavors/template-Exasol-8-python-3.12-cuda-conda/ci.json b/flavors/template-Exasol-8-python-3.12-cuda-conda/ci.json index 91ab757f8..477358605 100644 --- a/flavors/template-Exasol-8-python-3.12-cuda-conda/ci.json +++ b/flavors/template-Exasol-8-python-3.12-cuda-conda/ci.json @@ -41,9 +41,9 @@ { "name": "generic", "files": [], - "folders": [], + "folders": ["generic/python3"], "goal": "release", - "generic_language_tests": ["python3"] + "generic_language_tests": [] }, { "name": "gpu", diff --git a/flavors/template-Exasol-all-python-3.10-conda/ci.json b/flavors/template-Exasol-all-python-3.10-conda/ci.json index 773900816..33a91a994 100644 --- a/flavors/template-Exasol-all-python-3.10-conda/ci.json +++ b/flavors/template-Exasol-all-python-3.10-conda/ci.json @@ -41,9 +41,9 @@ { "name": "generic", "files": [], - "folders": [], + "folders": ["generic/python3"], "goal": "release", - "generic_language_tests": ["python3"] + "generic_language_tests": [] } ] } diff --git a/flavors/template-Exasol-all-python-3.10/ci.json b/flavors/template-Exasol-all-python-3.10/ci.json index ddddf6df6..0695644ac 100644 --- a/flavors/template-Exasol-all-python-3.10/ci.json +++ b/flavors/template-Exasol-all-python-3.10/ci.json @@ -41,9 +41,9 @@ { "name": "generic", "files": [], - "folders": [], + "folders": ["generic/python3"], "goal": "release", - "generic_language_tests": ["python3"] + "generic_language_tests": [] } ] } diff --git a/flavors/template-Exasol-all-python-3.12-conda/ci.json b/flavors/template-Exasol-all-python-3.12-conda/ci.json index 634fc08cd..bc677241d 100644 --- a/flavors/template-Exasol-all-python-3.12-conda/ci.json +++ b/flavors/template-Exasol-all-python-3.12-conda/ci.json @@ -41,9 +41,9 @@ { "name": "generic", "files": [], - "folders": [], + "folders": ["generic/python3"], "goal": "release", - "generic_language_tests": ["python3"] + "generic_language_tests": [] } ] } diff --git a/flavors/template-Exasol-all-python-3.12/ci.json b/flavors/template-Exasol-all-python-3.12/ci.json index 4ce917c7b..41f72a5e1 100644 --- a/flavors/template-Exasol-all-python-3.12/ci.json +++ b/flavors/template-Exasol-all-python-3.12/ci.json @@ -41,9 +41,9 @@ { "name": "generic", "files": [], - "folders": [], + "folders": ["generic/python3"], "goal": "release", - "generic_language_tests": ["python3"] + "generic_language_tests": [] } ] } diff --git a/flavors/test-Exasol-8-cuda-ml/ci.json b/flavors/test-Exasol-8-cuda-ml/ci.json index ba75da971..86710242b 100644 --- a/flavors/test-Exasol-8-cuda-ml/ci.json +++ b/flavors/test-Exasol-8-cuda-ml/ci.json @@ -49,11 +49,9 @@ { "name": "generic", "files": [], - "folders": [], + "folders": ["generic/python3"], "goal": "release", - "generic_language_tests": [ - "python3" - ] + "generic_language_tests": [] }, { "name": "gpu", diff --git a/test_container/tests/test/generic/basic.py b/test_container/tests/test/generic/basic.py index a0e121ce9..334d306a6 100755 --- a/test_container/tests/test/generic/basic.py +++ b/test_container/tests/test/generic/basic.py @@ -13,7 +13,7 @@ def test_basic_scalar_emits(self): FROM DUAL ''') self.assertRowsEqual([(x,) for x in range(3)], sorted(rows)) - + @requires('BASIC_SUM') def test_basic_set_returns(self): rows = self.query(''' diff --git a/test_container/tests/test/generic/java/basic.py b/test_container/tests/test/generic/java/basic.py new file mode 100755 index 000000000..31ffa79a2 --- /dev/null +++ b/test_container/tests/test/generic/java/basic.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _JavaUdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + basic_emit_several_groups(a INTEGER, b INTEGER) + EMITS (i INTEGER, j VARCHAR(40)) AS + class BASIC_EMIT_SEVERAL_GROUPS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + for (int n = 0; n < ctx.getInteger("a"); n++) + for (int i = 0; i < ctx.getInteger("b"); i++) + ctx.emit(i, exa.getVmId()); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + basic_emit_two_ints() + EMITS (i INTEGER, j INTEGER) AS + class BASIC_EMIT_TWO_INTS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(1,2); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + basic_nth_partial_sum(n INTEGER) + RETURNS INTEGER as + class BASIC_NTH_PARTIAL_SUM { + static int run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getInteger("n") != null) + return ctx.getInteger("n") * (ctx.getInteger("n") + 1) / 2; + return 0; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + basic_range(n INTEGER) + EMITS (n INTEGER) AS + class BASIC_RANGE { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getInteger("n") != null) + for (int i = 0; i < ctx.getInteger("n"); i++) + ctx.emit(i); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + basic_sum(x INTEGER) + RETURNS INTEGER AS + class BASIC_SUM { + static int run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int s = 0; + while (true) { + if (ctx.getInteger("x") != null) + s += ctx.getInteger("x"); + if (!ctx.next()) + break; + } + return s; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + basic_sum_grp(x INTEGER) + EMITS (s INTEGER) AS + class BASIC_SUM_GRP { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int s = 0; + while (true) { + if (ctx.getInteger("x") != null) + s += ctx.getInteger("x"); + if (!ctx.next()) + break; + } + ctx.emit(s); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + basic_test_reset(i INTEGER, j VARCHAR(40)) + EMITS (k INTEGER) AS + class BASIC_TEST_RESET { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getInteger("i")); + ctx.next(); + ctx.emit(ctx.getInteger("i")); + ctx.reset(); + ctx.emit(ctx.getInteger("i")); + ctx.next(); + ctx.emit(ctx.getInteger("i")); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + performance_map_characters(text VARCHAR(1000)) + EMITS (w CHAR(1), c INTEGER) AS + class PERFORMANCE_MAP_CHARACTERS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + String text = ctx.getString("text"); + if (text != null) { + for (int i = 0; i < text.length(); i++) { + if (Character.isHighSurrogate(text.charAt(i))) { + ctx.emit(text.substring(i, i + 2), 1); + i++; + } + else { + ctx.emit(text.substring(i, i + 1), 1); + } + } + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + performance_reduce_characters(w CHAR(1), c INTEGER) + EMITS (w CHAR(1), c INTEGER) AS + class PERFORMANCE_REDUCE_CHARACTERS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int c = 0; + String w = ctx.getString("w"); + if (w != null) { + do { + c += 1; + } while (ctx.next()); + ctx.emit(w, c); + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + set_emits_has_empty_input(a double) EMITS (x double, y varchar(10)) AS + class SET_EMITS_HAS_EMPTY_INPUT { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getDouble("x") == null) + ctx.emit(1,"1") + else + ctx.emit(2,"2") + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + set_returns_has_empty_input(a double) RETURNS boolean AS + class SET_RETURNS_HAS_EMPTY_INPUT { + static boolean run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return ctx.getInteger("x") == null; + } + } + / + ''')) + +class BasicTest(_JavaUdfSetup): + + def test_basic_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_range(3) + FROM DUAL + ''') + self.assertRowsEqual([(x,) for x in range(3)], sorted(rows)) + + def test_basic_set_returns(self): + rows = self.query(''' + SELECT fn1.basic_sum(3) + FROM DUAL + ''') + self.assertRowsEqual([(3,)], rows) + + def test_emit_two_ints(self): + rows = self.query(''' + SELECT fn1.basic_emit_two_ints() + FROM DUAL''') + self.assertRowsEqual([(1, 2)], rows) + + def test_simple_combination(self): + rows = self.query(''' + SELECT fn1.basic_sum(psum) + FROM ( + SELECT fn1.basic_nth_partial_sum(n) AS PSUM + FROM ( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(165,)], rows) + + def test_simple_combination_grouping(self): + rows = self.query(''' + SELECT fn1.BASIC_SUM_GRP(psum) + FROM ( + SELECT MOD(N, 3) AS n, + fn1.basic_nth_partial_sum(n) AS psum + FROM ( + SELECT fn1.basic_range(10) + FROM DUAL + ) + ) + GROUP BY n + ORDER BY 1''') + self.assertRowsEqual([(39.0,), (54.0,), (72.0,)], rows) + + def test_reset(self): + rows = self.query(''' + SELECT fn1.basic_test_reset(i, j) + FROM (SELECT fn1.basic_emit_several_groups(16, 8) FROM DUAL) + GROUP BY i + ORDER BY 1''') + self.assertRowsEqual([(0.0,), (0.0,), (0.0,), (0.0,), (1.0,), (1.0,), (1.0,), (1.0,), (2.0,)], rows[:9]) + + def test_order_by_clause(self): + rows = self.query(''' + SELECT fn1.performance_reduce_characters(w, c) + FROM ( + SELECT fn1.performance_map_characters('hello hello hello abc') + FROM DUAL + ) + GROUP BY w + ORDER BY c DESC''') + + unsorted_list = [tuple(x) for x in rows] + sorted_list = sorted(unsorted_list, key=lambda x: x[1], reverse=True) + #for x in zip(unsorted_list, sorted_list): + # print x + + self.assertEqual(sorted_list, unsorted_list) + + +class SetWithEmptyInput(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('OPEN SCHEMA FN1') + self.query('CREATE TABLE FN2.empty_table(c int)') + + # Create UDFs needed for SetWithEmptyInput tests + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + set_returns_has_empty_input(a double) RETURNS boolean AS + class SET_RETURNS_HAS_EMPTY_INPUT { + static boolean run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return ctx.getDouble("a") == null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + set_emits_has_empty_input(a double) EMITS (x double, y varchar(10)) AS + class SET_EMITS_HAS_EMPTY_INPUT { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getDouble("a") == null) + ctx.emit(1,"1"); + else + ctx.emit(2,"2"); + } + } + / + ''')) + + def test_set_returns_has_empty_input_group_by(self): + self.query("""select FN1.set_returns_has_empty_input(c) from FN2.empty_table group by 'X'""") + self.assertEqual(0, self.rowcount()) + + def test_set_returns_has_empty_input_no_group_by(self): + rows = self.query('''select FN1.set_returns_has_empty_input(c) from FN2.empty_table''') + self.assertRowsEqual([(None,)], rows) + + + def test_set_emits_has_empty_input_group_by(self): + self.query("""select FN1.set_emits_has_empty_input(c) from FN2.empty_table group by 'X'""") + self.assertEqual(0, self.rowcount()) + + def test_set_emits_has_empty_input_no_group_by(self): + rows = self.query('''select FN1.set_emits_has_empty_input(c) from FN2.empty_table''') + self.assertRowsEqual([(None,None)], rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/combinations.py b/test_container/tests/test/generic/java/combinations.py new file mode 100755 index 000000000..cdcfd9333 --- /dev/null +++ b/test_container/tests/test/generic/java/combinations.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import useData + + +class _JavaUdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + basic_range(n INTEGER) + EMITS (n INTEGER) AS + class BASIC_RANGE { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getInteger("n") != null) + for (int i = 0; i < ctx.getInteger("n"); i++) + ctx.emit(i); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + basic_sum(x INTEGER) + RETURNS INTEGER AS + class BASIC_SUM { + static int run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int s = 0; + while (true) { + if (ctx.getInteger("x") != null) + s += ctx.getInteger("x"); + if (!ctx.next()) + break; + } + return s; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SET SCRIPT + basic_sum_grp(x INTEGER) + EMITS (s INTEGER) AS + class BASIC_SUM_GRP { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int s = 0; + while (true) { + if (ctx.getInteger("x") != null) + s += ctx.getInteger("x"); + if (!ctx.next()) + break; + } + ctx.emit(s); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + SCALAR_EMITS(x double, y double) + emits (x double, y double) as + class SCALAR_EMITS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + for (int i = ctx.getDouble("x").intValue(); i < (int)(ctx.getDouble("y") + 1); i++) + ctx.emit((float) i, (float) (i * i)); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + SCALAR_RETURNS(x double, y double) + returns double as + class SCALAR_RETURNS { + static double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return ctx.getDouble("x") + ctx.getDouble("y"); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java set script + SET_EMITS(x double, y double) + emits (x double, y double) as + class SET_EMITS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + while (true) { + ctx.emit(ctx.getDouble("y"), ctx.getDouble("x")); + if (!ctx.next()) + break; + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java set script + SET_RETURNS(x double, y double) + returns double as + class SET_RETURNS { + static double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + double acc = 0.0; + while (true) { + acc = acc + ctx.getDouble("x") + ctx.getDouble("y"); + if (!ctx.next()) + break; + } + return acc; + } + } + / + ''')) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE combinations.small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO combinations.small VALUES (0.1, 0.2), (0.2, 0.1)') + + +class Combinations_1_ary(_JavaUdfSetup): + def test_set_returns(self): + rows = self.query(''' + SELECT fn1.SET_RETURNS(x,y) + FROM combinations.small''') + self.assertEqual(round(0.6 / 2), round(rows[0][0] / 2)) + + + def test_scalar_returns(self): + rows = self.query(''' + SELECT round(fn1.scalar_returns(x,y) / 2) + FROM combinations.small''') + self.assertRowsEqual([(round(0.3 / 2),), (round(0.3 / 2),)], rows) + + def test_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10 ,y * 10) + FROM combinations.small''') + self.assertRowsEqual([(1, 1,), (2, 4,)], rows) + + def test_set_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x * 10 ,y * 10) + FROM combinations.small''') + self.assertRowsEqual([(2.0, 1.0,), (1.0, 2.0,)], rows) + + def test_two_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.scalar_returns(fn1.scalar_returns(x * 10 ,y * 10), + fn1.scalar_returns(y * 10 ,x * 10)) + FROM combinations.small''') + self.assertRowsEqual([(6,), (6,)], rows) + + +class Combinations_2_ary_scalar_returns(_JavaUdfSetup): + def test_scalar_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_returns(x * 10 ,y * 10 ) + FROM ( + SELECT fn1.scalar_emits(x * 10 ,y * 10) + FROM combinations.small + )''') + self.assertRowsEqual([(20,), (60,)], rows) + + def test_scalar_returns_set_returns_inline(self): + rows = self.query(''' + SELECT + fn1.scalar_returns( + fn1.set_returns(x * 10, y * 10), + fn1.set_returns(x * 10, y * 10) + ) + FROM combinations.small''') + self.assertRowsEqual([(12,)], rows) + + def test_scalar_returns_set_returns_1(self): + rows = self.query(''' + SELECT fn1.scalar_returns(a, 5) + FROM ( + SELECT fn1.set_returns(x * 10, y * 10) AS a + FROM combinations.SMALL + ) + ''') + self.assertRowsEqual([(11,)], rows) + + def test_scalar_returns_set_returns_2(self): + rows = self.query(''' + SELECT fn1.scalar_returns(aa.a, bb.a) + FROM ( + SELECT fn1.set_returns(x * 10, y * 20) AS a + FROM combinations.SMALL + ) AS aa, + ( + SELECT fn1.set_returns(x * 20, y * 10) AS a + FROM combinations.SMALL + ) AS bb + ''') + self.assertRowsEqual([(18,)], rows) + + def test_scalar_returns_set_emits_1(self): + rows = self.query(''' + SELECT fn1.scalar_returns(x * 10, y * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM combinations.small + )''') + self.assertRowsEqual([(30,), (30,)], rows) + + def test_scalar_returns_set_emits_2(self): + rows = self.query(''' + SELECT fn1.scalar_returns(aa.x * 10, bb.x * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM combinations.small + ) AS aa, + ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM combinations.small + ) AS bb + WHERE aa.x = bb.y and aa.y = bb.x + ''') + self.assertRowsEqual([(30,), (30,)], rows) + + +class Combinations_2_ary_scalar_emits(_JavaUdfSetup): + def test_scalar_emits_scalar_returns_inline(self): + rows = self.query(''' + SELECT + fn1.scalar_emits( + fn1.scalar_returns(x * 10, y * 10), + fn1.scalar_returns(y * 10, x * 10) + ) + FROM combinations.small''') + self.assertRowsEqual([(3, 9,), (3, 9,)], rows) + + def test_scalar_emits_scalar_returns(self): + rows = self.query(''' + SELECT fn1.scalar_emits(a, b) + FROM ( + SELECT + fn1.scalar_returns(x * 10 ,y * 10) AS A, + fn1.scalar_returns(y * 10 ,x * 10) AS B + FROM combinations.small + ) + ''') + self.assertRowsEqual([(3, 9,), (3, 9,)], rows) + + def test_scalar_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10,y * 10) + FROM ( + SELECT fn1.scalar_emits(x * 10,y * 10) + FROM combinations.small + ) + ORDER by x,y''') + r = [(10.0, 100.0)] + r.extend([(i, i * i) for i in range(20, 41)]) + self.assertRowsEqual(r, rows) + + def test_scalar_emits_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.scalar_emits( + fn1.set_returns(x * 10, y * 10), + fn1.set_returns(x * 10, y * 10) + ) + FROM combinations.small''') + + def test_scalar_emits_set_returns(self): + rows = self.query(''' + SELECT fn1.scalar_emits(a, b) + FROM ( + SELECT fn1.set_returns(x * 10, y * 10) AS a + FROM combinations.small + ), + ( + SELECT fn1.set_returns(x * 10, y *10) AS b + FROM combinations.small + ) + ''') + self.assertRowsEqual([(6, 36)], rows) + + def test_scalar_emits_set_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10, y * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM combinations.small + )''') + r = ([(i, i * i) for i in range(10, 21)]) + self.assertRowsEqual(r, rows) + + +class Combinations_2_ary_set_returns(_JavaUdfSetup): + def test_set_returns_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.set_returns( + fn1.scalar_returns(x * 10, y * 10), + fn1.scalar_returns(y * 10, x * 10) + ) + FROM combinations.small''') + self.assertRowsEqual([(12,)], rows) + + def test_set_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.set_returns(x*10, y*10) + FROM ( + SELECT fn1.scalar_emits(x*10, y*10) + FROM combinations.small + )''') + self.assertRowsEqual([(80,)], rows) + + def test_set_returns_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.set_returns( + fn1.set_returns(x*10, y*10), + fn1.set_returns(x*10, y*10) + ) + FROM combinations.small''') + + def test_set_returns_set_returns(self): + rows = self.query(''' + SELECT fn1.set_returns(a, b) + FROM( + SELECT fn1.set_returns(x*20, y*30) AS a + FROM combinations.small + ), + ( + SELECT fn1.set_returns(x*50, y*70) AS b + FROM combinations.small + )''') + self.assertRowsEqual([(51,)], rows) + + def test_set_returns_set_emits(self): + rows = self.query(''' + SELECT fn1.set_returns(x*10, y*10) + FROM ( + SELECT fn1.set_emits(x*10, y*10) + FROM combinations.small + )''') + self.assertRowsEqual([(60,)], rows) + + +class Combinations_2_ary_set_emits(_JavaUdfSetup): + def test_set_emits_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.set_emits( + fn1.scalar_returns(x*10, y*10), + fn1.scalar_returns(y*10, x*10) + ) + FROM combinations.small''') + self.assertRowsEqual([(3, 3,), (3, 3,)], rows) + + def test_set_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x*10, y*10) + FROM ( + SELECT fn1.scalar_emits(x*10, 10*y) + FROM combinations.small + ) + ORDER BY x, y;''') + self.assertRowsEqual([(10, 10,), (40, 20,)], rows) + + def test_set_emits_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.set_emits( + fn1.set_returns(x*10, 10*y), + fn1.set_returns(10*x, y*10) + ) + FROM combinations.small''') + + def test_set_emits_set_returns(self): + rows = self.query(''' + SELECT fn1.set_emits(a, b) + FROM ( + SELECT fn1.set_returns(x*20, 30*y) AS a + FROM combinations.small + ), + ( + SELECT fn1.set_returns(50*x, y*70) AS b + FROM combinations.small + ) + ''') + self.assertRowsEqual([(36, 15,)], rows) + + def test_set_emits_set_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x * 10 , 10 * y) + FROM ( + SELECT fn1.set_emits(x * 10, y *10) + FROM combinations.small + ) + ORDER BY x, y;''') + self.assertRowsEqual([(10, 20,), (20, 10,)], rows) + + +class Combinations_3_ary(_JavaUdfSetup): + def test_set_returns_set_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(s) + FROM ( + SELECT fn1.basic_sum_grp(n) + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(45,)], rows) + + def test_set_returns_scalar_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(x) + FROM ( + SELECT fn1.scalar_emits(n, n+2) + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(165,)], rows) + + def test_set_returns_scalar_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(x) + FROM ( + SELECT fn1.scalar_returns(n, 2) AS x + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(65,)], rows) + + +class Combinations_n_ary(_JavaUdfSetup): + @staticmethod + def partial_sum(n, degree): + def basic_range(n, d): + if d == 0: + return list(range(n)) + else: + return sum([list(range(x)) for x in basic_range(n + 1, d - 1)], []) + + return len(basic_range(n, degree)) + + @useData((i,) for i in range(10)) + def test_n_scalar_emits(self, n): + + self.query( + 'SELECT fn1.basic_range(n+1) FROM (\n' * n + + 'SELECT fn1.basic_range(5) FROM DUAL\n' + + ')' * n) + self.assertEqual(self.partial_sum(5, n), self.rowcount()) + + @useData((i,) for i in range(10)) + def test_set_returns_n_scalar_emits(self, n): + + rows = self.query( + 'SELECT max(n) FROM (' + + 'SELECT fn1.basic_range(n+1) FROM (\n' * n + + 'SELECT fn1.basic_range(5) FROM DUAL\n' + + ')' * (n + 1)) + self.assertEqual(4, rows[0][0]) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/dynamic_input.py b/test_container/tests/test/generic/java/dynamic_input.py new file mode 100755 index 000000000..5e03e5e14 --- /dev/null +++ b/test_container/tests/test/generic/java/dynamic_input.py @@ -0,0 +1,517 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _JavaUdfSetup(udf.TestCase): + LANG = 'java' + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + basic_scalar_emit( ... ) + EMITS ("v" VARCHAR(2000)) as + import java.math.BigDecimal; + import java.sql.Date; + import java.sql.Timestamp; + class BASIC_SCALAR_EMIT { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int i = 0; + while (i < exa.getInputColumnCount()) { + Class cls = exa.getInputColumnType(i); + if (cls == Integer.class) { + ctx.emit(ctx.getInteger(i).toString()); + } + else if (cls == Long.class) { + ctx.emit(ctx.getLong(i).toString()); + } + else if (cls == Class.forName("java.math.BigDecimal")) { + ctx.emit(ctx.getBigDecimal(i).toString()); + } + else if (cls == Double.class) { + ctx.emit(ctx.getDouble(i).toString()); + } + else if (cls == String.class) { + ctx.emit(ctx.getString(i)); + } + else if (cls == Boolean.class) { + ctx.emit(ctx.getBoolean(i).toString()); + } + else if (cls == Class.forName("java.sql.Date")) { + ctx.emit(ctx.getDate(i).toString()); + } + else if (cls == Class.forName("java.sql.Timestamp")) { + ctx.emit(ctx.getTimestamp(i).toString()); + } + i = i + 1; + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + basic_scalar_return( ... ) + RETURNS VARCHAR(2000) as + class BASIC_SCALAR_RETURN { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int col = (int) exa.getInputColumnCount() - 1; + Class cls = exa.getInputColumnType(col); + if (cls == Integer.class) { + return ctx.getInteger(col).toString(); + } + else if (cls == Long.class) { + return ctx.getLong(col).toString(); + } + else if (cls == Class.forName("java.math.BigDecimal")) { + return ctx.getBigDecimal(col).toString(); + } + else if (cls == Double.class) { + return ctx.getDouble(col).toString(); + } + else if (cls == String.class) { + return ctx.getString(col); + } + else if (cls == Boolean.class) { + return ctx.getBoolean(col).toString(); + } + else if (cls == Class.forName("java.sql.Date")) { + return ctx.getDate(col).toString(); + } + else if (cls == Class.forName("java.sql.Timestamp")) { + return ctx.getTimestamp(col).toString(); + } + else { + return null; + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SET SCRIPT + basic_set_emit( ... ) + EMITS ("v" VARCHAR(2000)) as + import java.math.BigDecimal; + import java.sql.Date; + import java.sql.Timestamp; + class BASIC_SET_EMIT { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + String var = "result: "; + while (true) { + for (int i = 0; i < exa.getInputColumnCount(); i++) { + Class cls = exa.getInputColumnType(i); + if (cls == Integer.class) { + ctx.emit(ctx.getInteger(i).toString()); + var += ctx.getInteger(i).toString() + " , "; + } + else if (cls == Long.class) { + ctx.emit(ctx.getLong(i).toString()); + var += ctx.getLong(i).toString() + " , "; + } + else if (cls == Class.forName("java.math.BigDecimal")) { + ctx.emit(ctx.getBigDecimal(i).toString()); + var += ctx.getBigDecimal(i).toString() + " , "; + } + else if (cls == Double.class) { + ctx.emit(ctx.getDouble(i).toString()); + var += ctx.getDouble(i).toString() + " , "; + } + else if (cls == String.class) { + ctx.emit(ctx.getString(i)); + var += ctx.getString(i) + " , "; + } + else if (cls == Boolean.class) { + ctx.emit(ctx.getBoolean(i).toString()); + var += ctx.getBoolean(i).toString() + " , "; + } + else if (cls == Class.forName("java.sql.Date")) { + ctx.emit(ctx.getDate(i).toString()); + var += ctx.getDate(i).toString() + " , "; + } + else if (cls == Class.forName("java.sql.Timestamp")) { + ctx.emit(ctx.getTimestamp(i).toString()); + var += ctx.getTimestamp(i).toString() + " , "; + } + } + if (!ctx.next()) + break; + } + ctx.emit(var); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SET SCRIPT + basic_set_return( ... ) + RETURNS VARCHAR(2000) as + import java.math.BigDecimal; + import java.sql.Date; + import java.sql.Timestamp; + class BASIC_SET_RETURN { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + String var = "result: "; + while (true) { + for (int i = 0; i < exa.getInputColumnCount(); i++) { + Class cls = exa.getInputColumnType(i); + if (cls == Integer.class) { + var += ctx.getInteger(i).toString() + " , "; + } + else if (cls == Long.class) { + var += ctx.getLong(i).toString() + " , "; + } + else if (cls == Class.forName("java.math.BigDecimal")) { + var += ctx.getBigDecimal(i).toString() + " , "; + } + else if (cls == Double.class) { + var += ctx.getDouble(i).toString() + " , "; + } + else if (cls == String.class) { + var += ctx.getString(i) + " , "; + } + else if (cls == Boolean.class) { + var += ctx.getBoolean(i).toString() + " , "; + } + else if (cls == Class.forName("java.sql.Date")) { + var += ctx.getDate(i).toString() + " , "; + } + else if (cls == Class.forName("java.sql.Timestamp")) { + var += ctx.getTimestamp(i).toString() + " , "; + } + } + if (!ctx.next()) + break; + } + return var; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SET SCRIPT + empty_set_emits( ... ) + emits (x varchar(2000)) as + class EMPTY_SET_EMITS { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + emit(Integer.toString(1)); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SET SCRIPT + empty_set_returns( ... ) + returns varchar(2000) as + class EMPTY_SET_RETURNS { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return Integer.toString(1); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + metadata_scalar_emit (...) + EMITS("v" VARCHAR(2000)) AS + class METADATA_SCALAR_EMIT { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(Long.toString(exa.getInputColumnCount())); + for (int i = 0; i < exa.getInputColumnCount(); i++) { + ctx.emit(exa.getInputColumnName(i)); + ctx.emit(exa.getInputColumnType(i).getCanonicalName()); + ctx.emit(exa.getInputColumnSqlType(i)); + ctx.emit(Long.toString(exa.getInputColumnPrecision(i))); + ctx.emit(Long.toString(exa.getInputColumnScale(i))); + ctx.emit(Long.toString(exa.getInputColumnLength(i))); + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + metadata_scalar_return (...) + RETURNS VARCHAR(2000) AS + class METADATA_SCALAR_RETURN { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return Long.toString(exa.getInputColumnCount()); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SET SCRIPT + type_specific_add(...) + RETURNS VARCHAR(2000) as + import java.math.BigDecimal; + import java.sql.Date; + import java.sql.Timestamp; + class TYPE_SPECIFIC_ADD { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + String var = "result: "; + Class cls = exa.getInputColumnType(0); + if (cls == String.class) { + while (true) { + for (int i = 0; i < exa.getInputColumnCount(); i++) { + var += ctx.getString(i) + " , "; + } + if (!ctx.next()) + break; + } + } + else if (cls == Integer.class || cls == Long.class || cls == Double.class) { + double sum = 0; + while (true) { + for (int i = 0; i < exa.getInputColumnCount(); i++) { + sum += ctx.getDouble(i); + } + if (!ctx.next()) + break; + } + var += Double.toString(sum); + } + return var; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + wrong_arg(...) + returns varchar(2000) as + class WRONG_ARG { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return ctx.getString(1); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + wrong_operation(...) + returns varchar(2000) as + class WRONG_OPERATION { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return ctx.getString(0) * ctx.getString(1); + } + } + / + ''')) + + self.query('DROP SCHEMA dynamic_input CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_input') + self.query('CREATE TABLE dynamic_input.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_input.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_input.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_input.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + + +class DynamicMetadataTest(_JavaUdfSetup): + + def test_meta_scalar_return(self): + rows = self.query(''' + SELECT fn1.metadata_scalar_return('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertRowEqual(('2',), rows[0]) + + def test_meta_scalar_emit(self): + rows = self.query(''' + SELECT fn1.metadata_scalar_emit('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertRowEqual(('2',), rows[0]) + self.assertRowEqual(('0',), rows[1]) + self.assertTrue(rows[2][0] == "string" or rows[2][0] == "" or rows[2][0] == "character" or rows[2][0] == "java.lang.String" or rows[2][0] == "") + self.assertRowEqual(('CHAR(3) ASCII',), rows[3]) + self.assertRowEqual(('3',), rows[6]) + self.assertRowEqual(('1',), rows[7]) + self.assertTrue(rows[8][0] == 'number' or rows[8][0] == "" or rows[8][0] == "double" or rows[8][0] == "java.lang.Double" or rows[8][0] == "") + self.assertRowEqual(('DOUBLE',), rows[9]) + + +class DynamicInputBasic(_JavaUdfSetup): + def test_basic_scalar_emit_constants(self): + rows = self.query(''' + SELECT fn1.basic_scalar_emit('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertTrue(rows[0][0] == 'abc' or rows[0][0] == "u'abc'" or rows[0][0] == "'abc'") + self.assertTrue(rows[1][0] == '99' or rows[1][0] == "99.0") + + def test_basic_scalar_emit(self): + rows = self.query(''' + SELECT fn1.basic_scalar_emit(x, y) + FROM dynamic_input.small + ''') + self.assertTrue(rows[0][0] == 'Some string ... and some more' or rows[0][0] == "u'Some string ... and some more'" or rows[0][0] == "'Some string ... and some more'") + self.assertRowEqual(('2.2',), rows[1]) + + def test_basic_scalar_return_constants(self): + rows = self.query(''' + SELECT fn1.basic_scalar_return('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertTrue(rows[0][0] == '99' or rows[0][0] == "99.0") + + def test_basic_scalar_return(self): + rows = self.query(''' + SELECT fn1.basic_scalar_return(x, y, x, y, x, y, x, y, x, y, x, y, x, y, x, y) + FROM dynamic_input.small + ''') + self.assertRowEqual(('2.2',), rows[0]) + + def test_basic_set_emit_constants(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(cast(99 as double),'77','aaaa') + FROM DUAL + ''') + print("0---:"+str(rows[3][0])) + self.assertTrue(rows[0][0] == '99' or rows[0][0] == "99.0") + self.assertTrue(rows[1][0] == '77' or rows[1][0] == "u'77'" or rows[1][0] == "'77'") + self.assertTrue(rows[2][0] == 'aaaa' or rows[2][0] == "u'aaaa'" or rows[2][0] == "'aaaa'") + self.assertTrue(rows[3][0] == 'result: , 99 , 77 , aaaa' or rows[3][0] == "result: 99.0 , u'77' , u'aaaa' , " or rows[3][0] == "result: 99 , 77 , aaaa , " or rows[3][0] == "result: 99.0 , 77 , aaaa , " or rows[3][0] == "result: 99.0 , '77' , 'aaaa' , ") + + def test_basic_set_emit(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(n, v) + FROM dynamic_input.groupt GROUP BY id ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == '1' or rows[0][0] == "1.0" or rows[0][0] == "'aa'") + self.assertTrue(rows[1][0] == '2' or rows[1][0] == "2.0" or rows[1][0] == "'ab'") + self.assertTrue(rows[2][0] == '2' or rows[2][0] == "2.0" or rows[2][0] == "'ba'") + self.assertTrue(rows[3][0] == 'aa' or rows[3][0] == "result: 1.0 , u'aa' , 2.0 , u'ab' , " or rows[3][0] == "1.0") + self.assertTrue(rows[4][0] == 'ab' or rows[4][0] == "result: 2.0 , u'ba' , " or rows[4][0] == "2.0") + self.assertTrue(rows[5][0] == 'ba' or rows[5][0] == "u'aa'" or rows[5][0] == "2.0") + self.assertTrue(rows[6][0] == 'result: , 1 , aa , 2 , ab' or rows[6][0] == "u'ab'" or rows[6][0] == "result: 1 , aa , 2 , ab , " or rows[6][0] == "result: 1.0 , aa , 2.0 , ab , " or rows[6][0] == "result: 1.0 , 'aa' , 2.0 , 'ab' , ") + self.assertTrue(rows[7][0] == 'result: , 2 , ba' or rows[7][0] == "u'ba'" or rows[7][0] == "result: 2 , ba , " or rows[7][0] == "result: 2.0 , ba , " or rows[7][0] == "result: 2.0 , 'ba' , ") + + def test_basic_set_emit_one_group(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(cast(id as double), n, v) + FROM dynamic_input.groupt ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == '1' or rows[0][0] == "1.0" or rows[0][0] == "'aa'") + self.assertTrue(rows[1][0] == '1' or rows[1][0] == "1.0" or rows[1][0] == "'ab'") + self.assertTrue(rows[2][0] == '1' or rows[2][0] == "1.0" or rows[2][0] == "'ba'") + self.assertTrue(rows[3][0] == '2' or rows[3][0] == "2.0" or rows[3][0] == "1.0") + self.assertTrue(rows[4][0] == '2' or rows[4][0] == "2.0" or rows[4][0] == "1.0") + self.assertTrue(rows[5][0] == '2' or rows[5][0] == "2.0" or rows[5][0] == "1.0") + self.assertTrue(rows[6][0] == 'aa' or rows[7][0] == "u'aa'" or rows[6][0] == "2.0") + self.assertTrue(rows[7][0] == 'ab' or rows[8][0] == "u'ab'" or rows[7][0] == "2.0") + self.assertTrue(rows[8][0] == 'ba' or rows[9][0] == "u'ba'" or rows[8][0] == "2.0") + self.assertTrue(rows[9][0] == 'result: , 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab' \ + or rows[6][0] == "result: 1.0 , 1.0 , u'aa' , 2.0 , 2.0 , u'ba' , 1.0 , 2.0 , u'ab' , " \ + or rows[9][0] == "result: 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab , " \ + or rows[9][0] == "result: 1.0 , 1.0 , aa , 2.0 , 2.0 , ba , 1.0 , 2.0 , ab , " or rows[9][0] == "result: 1.0 , 1.0 , 'aa' , 2.0 , 2.0 , 'ba' , 1.0 , 2.0 , 'ab' , ") + + def test_basic_set_return_constants(self): + rows = self.query(''' + SELECT fn1.basic_set_return(cast(99 as double),'77','aaaa') + FROM DUAL + ''') + self.assertTrue(rows[0][0] == 'result: , 99 , 77 , aaaa ' \ + or rows[0][0] == "result: 99.0 , u'77' , u'aaaa' , " \ + or rows[0][0] == "result: 99 , 77 , aaaa , " \ + or rows[0][0] == "result: 99.0 , 77 , aaaa , " or rows[0][0] == "result: 99.0 , '77' , 'aaaa' , ") + + def test_basic_set_return(self): + rows = self.query(''' + SELECT fn1.basic_set_return(n, v) + FROM dynamic_input.groupt GROUP BY id ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , aa , 2 , ab ' \ + or rows[0][0] == "result: 1.0 , u'aa' , 2.0 , u'ab' , " \ + or rows[0][0] == "result: 1 , aa , 2 , ab , " \ + or rows[0][0] == "result: 1.0 , aa , 2.0 , ab , " or rows[0][0] == "result: 1.0 , 'aa' , 2.0 , 'ab' , ") + self.assertTrue(rows[1][0] == 'result: , 2 , ba ' \ + or rows[1][0] == "result: 2.0 , u'ba' , " \ + or rows[1][0] == "result: 2 , ba , " \ + or rows [1][0] == "result: 2.0 , ba , " or rows [1][0] == "result: 2.0 , 'ba' , ") + + def test_basic_set_return_one_group(self): + rows = self.query(''' + SELECT fn1.basic_set_return(cast(id as double), n, v) + FROM dynamic_input.groupt + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab ' \ + or rows[0][0] == "result: 1.0 , 1.0 , u'aa' , 2.0 , 2.0 , u'ba' , 1.0 , 2.0 , u'ab' , " \ + or rows[0][0] == "result: 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab , " \ + or rows[0][0] == "result: 1.0 , 1.0 , aa , 2.0 , 2.0 , ba , 1.0 , 2.0 , ab , " or rows[0][0] == "result: 1.0 , 1.0 , 'aa' , 2.0 , 2.0 , 'ba' , 1.0 , 2.0 , 'ab' , ") + + +class DynamicInputDatatypeSpecific(_JavaUdfSetup): + def test_type_specific_add_string(self): + rows = self.query(''' + SELECT fn1.type_specific_add(v, v, v) + FROM dynamic_input.groupt + ''') + self.assertTrue('result: , aa , aa , aa , ba , ba , ba , ab , ab , ab' == rows[0][0] or 'result: aa , aa , aa , ba , ba , ba , ab , ab , ab , ' == rows[0][0]) + + def test_type_specific_add_number(self): + rows = self.query(''' + SELECT fn1.type_specific_add(n,n,n,n,n,n,n,n,n,n) + FROM dynamic_input.groupt + ''') + self.assertTrue(rows[0][0] == 'result: 50' or rows[0][0] == "result: 50.0" or rows[0][0] == 'result: 50') + + +class DynamicInputErrors(_JavaUdfSetup): + def test_exception_wrong_arg(self): + if self.LANG == 'r': + raise udf.SkipTest('does not work with R currently') + err_text = { + 'lua': 'out of range', + 'python3': 'does not exist', + 'java': 'does not exist', + } + with self.assertRaisesRegex(Exception, err_text[self.LANG]): + self.query('''select fn1.wrong_arg('a') from dual''') + + def test_exception_wrong_operation(self): + err_text = { + 'lua': 'attempt to perform arithmetic on field', + 'r': 'non-numeric argument to binary operator', + 'python3': 'multiply sequence by non-int of type', + 'java': 'bad operand types for binary operator', + } + with self.assertRaisesRegex(Exception, err_text[self.LANG]): + self.query('''select fn1.wrong_operation('a','b') from dual''') + + def test_exception_empty_set_returns(self): + with self.assertRaisesRegex(Exception, 'data exception - missing input parameters for SET UDF script'): + self.query('''select fn1.empty_set_returns() from dynamic_input.groupt''') + + def test_exception_empty_set_emits(self): + with self.assertRaisesRegex(Exception, 'data exception - missing input parameters for SET UDF script'): + self.query('''select fn1.empty_set_emits() from dynamic_input.groupt''') + +class DynamicInputOptimizations(_JavaUdfSetup): + def test_mapreduce_optimization(self): + rows = self.query(''' + select fn1.basic_set_return("v") from ( select fn1.basic_scalar_emit(n,n,n,n,n,n,n,n,n,n) from dynamic_input.groupt) + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ' \ + or rows[0][0] == "result: u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , " \ + or rows[0][0] == "result: 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , " \ + or rows[0][0] == "result: 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , " or rows[0][0] == "result: '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , ") + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/emit.py b/test_container/tests/test/generic/java/emit.py new file mode 100644 index 000000000..d0d875c68 --- /dev/null +++ b/test_container/tests/test/generic/java/emit.py @@ -0,0 +1,739 @@ +#!/usr/bin/env python3 + +import datetime + +from exasol_python_test_framework import udf + + +class _JavaUdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + create java scalar script dob_1i_1o(x double) emits(y double) + as + class DOB_1I_1O{ + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script line_1i_1o(x double) emits(y double) + as + class LINE_1I_1O{ + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script line_1i_2o(x double) emits(y double, z double) + as + class LINE_1I_2O{ + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x"),ctx.getDouble("x")); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script line_2i_1o(x double, y double) emits(z double) + as + class LINE_2I_1O{ + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")+ctx.getDouble("y")); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script line_3i_2o(x double, y double, z double) emits(z1 double, z2 double) + as + class LINE_3I_2O{ + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")+ctx.getDouble("y"),3000); + } + } + / + ''')) + +class InputOutputMatchingTest(_JavaUdfSetup): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + class DOB_1I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + class LINE_1I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + class LINE_1I_2O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x"), ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + class LINE_2I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x") + ctx.getDouble("y")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + class LINE_3I_2O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x") + ctx.getDouble("y"), 3000.0); + } + } + / + ''')) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create table fn2.t(id double, x double)') + self.query('insert into fn2.t values (100,1),(100,2),(200,3)') + + def test_iomatch_1i_1o(self): + rows = self.query(''' + select x*2, fn1.line_1i_1o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,3,), (6,3,9,), (4,2,6,)]), sorted(rows)) + + def test_iomatch_1i_2o(self): + rows = self.query(''' + select x*2, fn1.line_1i_2o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,1,3,), (6,3,3,9,), (4,2,2,6,)]), sorted(rows)) + + def test_iomatch_2i_1o(self): + rows = self.query(''' + select x*2, fn1.line_2i_1o(x,id), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,101,3,), (6,203,9,), (4,102,6,)]), sorted(rows)) + + def test_iomatch_3i_2o(self): + rows = self.query(''' + select x*2, fn1.line_3i_2o(x,id,id), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,101,3000,3,), (6,203,3000,9,), (4,102,3000,6,)]), sorted(rows)) + + def test_iomatch_dob_1i_1o(self): + rows = self.query(''' + select x*2, fn1.dob_1i_1o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,3,), (2,1,3,), (6,3,9,), (6,3,9,), (4,2,6,), (4,2,6,)]), sorted(rows)) + + +class ColumnNamesTest(_JavaUdfSetup): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + class DOB_1I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + class LINE_1I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + class LINE_1I_2O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x"), ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + class LINE_2I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x") + ctx.getDouble("y")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + class LINE_3I_2O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x") + ctx.getDouble("y"), 3000.0); + } + } + / + ''')) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create table fn2.t(id double, x double)') + self.query('insert into fn2.t values (100,1),(100,2),(200,3)') + + def test_col_names(self): + self.query(''' + create or replace table fn2.foo as select x*2 a, fn1.line_3i_2o(x,id,id), x*3 b + FROM fn2.t + ''') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('A',rows[0][0]) + self.assertEqual('Z1',rows[1][0]) + self.assertEqual('Z2',rows[2][0]) + self.assertEqual('B',rows[3][0]) + + +class DatatypesTest(_JavaUdfSetup): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + class DOB_1I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + class LINE_1I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + class LINE_1I_2O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x"), ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + class LINE_2I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x") + ctx.getDouble("y")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + class LINE_3I_2O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x") + ctx.getDouble("y"), 3000.0); + } + } + / + ''')) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + + def test_boolean(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x BOOLEAN)') + self.query('insert into fn2.dt values false') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(False,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('BOOLEAN', rows[0][1]) + + def test_double(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DOUBLE)') + self.query('insert into fn2.dt values 32768e100') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(3.2768e+104,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DOUBLE', rows[0][1]) + + def test_dec_32bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(9,0)', rows[0][1]) + + def test_dec_64bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(18,0)', rows[0][1]) + + def test_dec_128bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(36,0)', rows[0][1]) + + def test_dec_32bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,1))') + self.query('insert into fn2.dt values 99999999.1') + rows = self.query(''' + select x = 99999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(9,1)', rows[0][1]) + + def test_dec_64bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,1))') + self.query('insert into fn2.dt values 9999999999999999.1') + rows = self.query(''' + select x = 9999999999999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(18,1)', rows[0][1]) + + def test_dec_128bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,1))') + self.query('insert into fn2.dt values 999999999999999999999999999999999.1') + rows = self.query(''' + select x = 999999999999999999999999999999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(36,1)', rows[0][1]) + + def test_timestamp(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x TIMESTAMP)') + self.query(''' + insert into fn2.dt values '2010-01-01 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.datetime(2010, 1, 1, 23, 33, 33),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('TIMESTAMP(3)', rows[0][1]) + + def test_timestamp_with_timezone(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x TIMESTAMP WITH LOCAL TIME ZONE)') + self.query(''' + insert into fn2.dt values '2010-01-01 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.datetime(2010, 1, 1, 23, 33, 33),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('TIMESTAMP(3) WITH LOCAL TIME ZONE', rows[0][1]) + + def test_date(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DATE)') + self.query(''' + insert into fn2.dt values '2010-01-01' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.date(2010, 1, 1),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DATE', rows[0][1]) + + def test_varchar_utf8(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x VARCHAR(3000) UTF8)') + self.query('insert into fn2.dt values repeat(5,300)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*300,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('VARCHAR(3000) UTF8', rows[0][1]) + + def test_varchar_ascii(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x VARCHAR(3000) ASCII)') + self.query('insert into fn2.dt values repeat(5,300)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*300,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('VARCHAR(3000) ASCII', rows[0][1]) + + def test_char_utf8(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x CHAR(2000) UTF8)') + self.query('insert into fn2.dt values repeat(5,2000)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*2000,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('CHAR(2000) UTF8', rows[0][1]) + + def test_char_ascii(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x CHAR(2000) ASCII)') + self.query('insert into fn2.dt values repeat(5,2000)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*2000,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('CHAR(2000) ASCII', rows[0][1]) + + def test_interval_ym(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x INTERVAL YEAR TO MONTH)') + self.query(''' + insert into fn2.dt values '23-11' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('+23-11',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('INTERVAL YEAR(2) TO MONTH', rows[0][1]) + + def test_interval_ds(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x INTERVAL DAY TO SECOND)') + self.query(''' + insert into fn2.dt values '30 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('+30 23:33:33.000',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('INTERVAL DAY(2) TO SECOND(3)', rows[0][1]) + + def test_geometry(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x GEOMETRY)') + self.query(''' + insert into fn2.dt values 'POINT(1 1)' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('POINT (1 1)',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('GEOMETRY', rows[0][1]) + + +class NullTest(_JavaUdfSetup): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + class DOB_1I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + class LINE_1I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + class LINE_1I_2O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x"), ctx.getDouble("x")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + class LINE_2I_1O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x") + ctx.getDouble("y")); + } + } + / + ''')) + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + class LINE_3I_2O { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(ctx.getDouble("x") + ctx.getDouble("y"), 3000.0); + } + } + / + ''')) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + + def test_boolean_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x BOOLEAN)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,True,),(None,0,True,)], rows) + + def test_double_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DOUBLE)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(x), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,None,1,),(None,None,1,)], rows) + + def test_int32_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_int64_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_int128_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_timestamp_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x timestamp)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_date_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x date)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_intervalym_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x interval year to month)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_intervalds_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x interval day to second)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_geo_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x geometry)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_varchar_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x varchar(2000))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_char_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x char(2000))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/export_alias.py b/test_container/tests/test/generic/java/export_alias.py new file mode 100644 index 000000000..cb8325ce3 --- /dev/null +++ b/test_container/tests/test/generic/java/export_alias.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class ExportAliasTest(udf.TestCase): + result_unknown = 0 + result_ok = 1 + result_failed = 2 + result_test_error = 3 + + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create or replace table fn2.t(a int, z varchar(3000))') + self.query("insert into fn2.t values (1, 'x')") + self.query('create or replace table fn2.\"tl\"(a int, \"z\" varchar(3000))') + self.query("insert into fn2.\"tl\" values (1, 'x')") + self.query("create connection FOOCONN to 'a' user 'b' identified by 'c'", ignore_errors=True) + + self.query('OPEN SCHEMA FN1') + + # Create all EXPORT UDF scripts + self.query(udf.fixindent(''' + create or replace java set script expal_test_pass_fail(res varchar(100)) emits (x int) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_TEST_PASS_FAIL { + public static void run(ExaMetadata meta, ExaIterator iter) throws Exception { + String result = iter.getString("res"); + if (result.equals("ok")) { + iter.emit(1); + } + else if (result.equals("failed")) { + iter.emit(2); + } + else { + iter.emit(3); + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script expal_use_param_foo_bar(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_USE_PARAM_FOO_BAR { + static String generateSqlForExportSpec(ExaMetadata exa, ExaExportSpecification exportSpecification) { + if (exportSpecification.getParameters().size() == 2 && + exportSpecification.getParameters().get("FOO").equals("bar") && + exportSpecification.getParameters().get("BAR").equals("foo") && + !exportSpecification.hasConnectionName() && + exportSpecification.getConnectionName() == null && + !exportSpecification.hasConnectionInformation() && + exportSpecification.getConnectionInformation() == null && + !exportSpecification.hasTruncate() && + !exportSpecification.hasReplace() && + !exportSpecification.hasCreatedBy() && + exportSpecification.getSourceColumnNames().size() == 2 && + exportSpecification.getSourceColumnNames().get(0).equals("\\"T\\".\\"A\\"") && + exportSpecification.getSourceColumnNames().get(1).equals("\\"T\\".\\"Z\\"")) { + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('ok')"; + } + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('failed')"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script expal_use_connection_name(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_USE_CONNECTION_NAME { + static String generateSqlForExportSpec(ExaMetadata exa, ExaExportSpecification exportSpecification) { + if (exportSpecification.getParameters().size() == 2 && + exportSpecification.getParameters().get("FOO").equals("bar") && + exportSpecification.getParameters().get("BAR").equals("foo") && + exportSpecification.hasConnectionName() && + exportSpecification.getConnectionName().equals("FOOCONN") && + !exportSpecification.hasConnectionInformation() && + exportSpecification.getConnectionInformation() == null && + !exportSpecification.hasTruncate() && + !exportSpecification.hasReplace() && + !exportSpecification.hasCreatedBy() && + exportSpecification.getSourceColumnNames().size() == 2 && + exportSpecification.getSourceColumnNames().get(0).equals("\\"T\\".\\"A\\"") && + exportSpecification.getSourceColumnNames().get(1).equals("\\"T\\".\\"Z\\"")) { + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('ok')"; + } + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('failed')"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script expal_use_connection_info(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_USE_CONNECTION_INFO { + static String generateSqlForExportSpec(ExaMetadata exa, ExaExportSpecification exportSpecification) { + if (exportSpecification.getParameters().size() == 2 && + exportSpecification.getParameters().get("FOO").equals("bar") && + exportSpecification.getParameters().get("BAR").equals("foo") && + !exportSpecification.hasConnectionName() && + exportSpecification.getConnectionName() == null && + exportSpecification.hasConnectionInformation() && + exportSpecification.getConnectionInformation().getAddress().equals("a") && + exportSpecification.getConnectionInformation().getUser().equals("b") && + exportSpecification.getConnectionInformation().getPassword().equals("c") && + !exportSpecification.hasTruncate() && + !exportSpecification.hasReplace() && + !exportSpecification.hasCreatedBy() && + exportSpecification.getSourceColumnNames().size() == 2 && + exportSpecification.getSourceColumnNames().get(0).equals("\\"T\\".\\"A\\"") && + exportSpecification.getSourceColumnNames().get(1).equals("\\"T\\".\\"Z\\"")) { + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('ok')"; + } + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('failed')"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script expal_use_has_truncate(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_USE_HAS_TRUNCATE { + static String generateSqlForExportSpec(ExaMetadata exa, ExaExportSpecification exportSpecification) { + if (exportSpecification.getParameters().size() == 2 && + exportSpecification.getParameters().get("FOO").equals("bar") && + exportSpecification.getParameters().get("BAR").equals("foo") && + !exportSpecification.hasConnectionName() && + exportSpecification.getConnectionName() == null && + !exportSpecification.hasConnectionInformation() && + exportSpecification.getConnectionInformation() == null && + exportSpecification.hasTruncate() && + !exportSpecification.hasReplace() && + !exportSpecification.hasCreatedBy() && + exportSpecification.getSourceColumnNames().size() == 2 && + exportSpecification.getSourceColumnNames().get(0).equals("\\"T\\".\\"A\\"") && + exportSpecification.getSourceColumnNames().get(1).equals("\\"T\\".\\"Z\\"")) { + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('ok')"; + } + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('failed')"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script expal_use_replace_created_by(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_USE_REPLACE_CREATED_BY { + static String generateSqlForExportSpec(ExaMetadata exa, ExaExportSpecification exportSpecification) { + if (exportSpecification.getParameters().size() == 2 && + exportSpecification.getParameters().get("FOO").equals("bar") && + exportSpecification.getParameters().get("BAR").equals("foo") && + !exportSpecification.hasConnectionName() && + exportSpecification.getConnectionName() == null && + !exportSpecification.hasConnectionInformation() && + exportSpecification.getConnectionInformation() == null && + !exportSpecification.hasTruncate() && + exportSpecification.hasReplace() && + exportSpecification.hasCreatedBy() && + exportSpecification.getCreatedBy().equals("create table t(a int, z varchar(3000))") && + exportSpecification.getSourceColumnNames().size() == 2 && + exportSpecification.getSourceColumnNames().get(0).equals("\\"T\\".\\"A\\"") && + exportSpecification.getSourceColumnNames().get(1).equals("\\"T\\".\\"Z\\"")) { + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('ok')"; + } + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('failed')"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script expal_use_column_name_lower_case(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_USE_COLUMN_NAME_LOWER_CASE { + static String generateSqlForExportSpec(ExaMetadata exa, ExaExportSpecification exportSpecification) { + if (exportSpecification.getParameters().size() == 2 && + exportSpecification.getParameters().get("FOO").equals("bar") && + exportSpecification.getParameters().get("BAR").equals("foo") && + !exportSpecification.hasConnectionName() && + exportSpecification.getConnectionName() == null && + !exportSpecification.hasConnectionInformation() && + exportSpecification.getConnectionInformation() == null && + !exportSpecification.hasTruncate() && + !exportSpecification.hasReplace() && + !exportSpecification.hasCreatedBy() && + exportSpecification.getSourceColumnNames().size() == 2 && + exportSpecification.getSourceColumnNames().get(0).equals("\\"tl\\".\\"A\\"") && + exportSpecification.getSourceColumnNames().get(1).equals("\\"tl\\".\\"z\\"")) { + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('ok')"; + } + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('failed')"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script expal_use_column_selection(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_USE_COLUMN_SELECTION { + static String generateSqlForExportSpec(ExaMetadata exa, ExaExportSpecification exportSpecification) { + if (exportSpecification.getParameters().size() == 2 && + exportSpecification.getParameters().get("FOO").equals("bar") && + exportSpecification.getParameters().get("BAR").equals("foo") && + !exportSpecification.hasConnectionName() && + exportSpecification.getConnectionName() == null && + !exportSpecification.hasConnectionInformation() && + exportSpecification.getConnectionInformation() == null && + !exportSpecification.hasTruncate() && + !exportSpecification.hasReplace() && + !exportSpecification.hasCreatedBy() && + exportSpecification.getSourceColumnNames().size() == 2 && + exportSpecification.getSourceColumnNames().get(0).equals("\\"tl\\".\\"A\\"") && + exportSpecification.getSourceColumnNames().get(1).equals("\\"tl\\".\\"z\\"")) { + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('ok')"; + } + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('failed')"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script expal_use_query(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class EXPAL_USE_QUERY { + static String generateSqlForExportSpec(ExaMetadata exa, ExaExportSpecification exportSpecification) { + if (exportSpecification.getParameters().size() == 2 && + exportSpecification.getParameters().get("FOO").equals("bar") && + exportSpecification.getParameters().get("BAR").equals("foo") && + !exportSpecification.hasConnectionName() && + exportSpecification.getConnectionName() == null && + !exportSpecification.hasConnectionInformation() && + exportSpecification.getConnectionInformation() == null && + !exportSpecification.hasTruncate() && + !exportSpecification.hasReplace() && + !exportSpecification.hasCreatedBy() && + exportSpecification.getSourceColumnNames().size() == 2 && + exportSpecification.getSourceColumnNames().get(0).equals("\\"col1\\"") && + exportSpecification.getSourceColumnNames().get(1).equals("\\"col2\\"")) { + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('ok')"; + } + return "select " + exa.getScriptSchema() + ".expal_test_pass_fail('failed')"; + } + } + / + ''')) + +# ATTENTION! +# ATTENTION! +# The logic for the tests had to be put in the export_alias.sql files for each language. +# This was required because EXPORT INTO SCRIPT can only return a single integer. + + def test_export_use_params(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_param_foo_bar with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_connection_name(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_connection_name AT FOOCONN with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_connection_info(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_connection_info AT 'a' USER 'b' IDENTIFIED BY 'c' with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_has_truncate(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_has_truncate with foo='bar' bar='foo' truncate") + self.assertEqual(self.result_ok, rows) + + def test_export_use_replace_created_by(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_replace_created_by with foo='bar' bar='foo' replace created by 'create table t(a int, z varchar(3000))'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_column_name_lower_case(self): + rows = self.executeStatement("EXPORT fn2.\"tl\" INTO SCRIPT fn1.expal_use_column_name_lower_case with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_column_selection(self): + rows = self.executeStatement("EXPORT fn2.\"tl\"(a, \"z\") INTO SCRIPT fn1.expal_use_column_selection with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_query(self): + rows = self.executeStatement("EXPORT (select a as 'col1', \"z\" as 'col2' from fn2.\"tl\") INTO SCRIPT fn1.expal_use_query with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/generic_types.py b/test_container/tests/test/generic/java/generic_types.py new file mode 100755 index 000000000..4ee3dc0d9 --- /dev/null +++ b/test_container/tests/test/generic/java/generic_types.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _JavaUdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + bottleneck_char10(i VARCHAR(20)) + RETURNS CHAR(10) AS + class BOTTLENECK_CHAR10 { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return ctx.getString("i"); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + bottleneck_decimal5(i DECIMAL(20, 0)) + RETURNS DECIMAL(5, 0) AS + import java.math.BigDecimal; + class BOTTLENECK_DECIMAL5 { + static BigDecimal run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return ctx.getBigDecimal("i"); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + bottleneck_varchar10(i VARCHAR(20)) + RETURNS VARCHAR(10) AS + class BOTTLENECK_VARCHAR10 { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return ctx.getString("i"); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_boolean(x BOOLEAN) RETURNS BOOLEAN AS + class ECHO_BOOLEAN { + static Boolean run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getBoolean("x") != null) + return ctx.getBoolean("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_char1(x CHAR(1)) RETURNS CHAR(1) AS + class ECHO_CHAR1 { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getString("x") != null) + return ctx.getString("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_char10(x CHAR(10)) RETURNS CHAR(10) AS + class ECHO_CHAR10 { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getString("x") != null && ctx.getString("x").length() == 10) + return ctx.getString("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_date(x DATE) RETURNS DATE AS + import java.sql.Date; + class ECHO_DATE { + static Date run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getDate("x") != null) + return ctx.getDate("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_decimal_36_0(x DECIMAL(36,0)) RETURNS DECIMAL(36,0) AS + import java.math.BigDecimal; + class ECHO_DECIMAL_36_0 { + static BigDecimal run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getBigDecimal("x") != null) + return ctx.getBigDecimal("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_decimal_36_36(x DECIMAL(36,36)) RETURNS DECIMAL(36,36) AS + import java.math.BigDecimal; + class ECHO_DECIMAL_36_36 { + static BigDecimal run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getBigDecimal("x") != null) + return ctx.getBigDecimal("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_double(x DOUBLE) RETURNS DOUBLE AS + class ECHO_DOUBLE { + static Double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getDouble("x") != null) + return ctx.getDouble("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_integer(x INTEGER) RETURNS INTEGER AS + class ECHO_INTEGER { + static Long run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getLong("x") != null) + return ctx.getLong("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_timestamp(x TIMESTAMP) RETURNS TIMESTAMP AS + import java.sql.Timestamp; + class ECHO_TIMESTAMP { + static Timestamp run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getTimestamp("x") != null) + return ctx.getTimestamp("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT echo_varchar10(x VARCHAR(10)) RETURNS VARCHAR(10) AS + class ECHO_VARCHAR10 { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getString("x") != null) + return ctx.getString("x"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT run_func_is_empty() RETURNS DOUBLE AS + class RUN_FUNC_IS_EMPTY { + static Double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return null; + } + } + / + ''')) + +class TestEcho(_JavaUdfSetup): + + def test_echo_boolean(self): + rows = self.query(''' + SELECT + fn1.echo_boolean(Null) is NULL, + fn1.echo_boolean(True) = True, + fn1.echo_boolean(False) = False + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + def test_echo_char1(self): + rows = self.query(''' + SELECT + fn1.echo_char1(NULL) is NULL, + fn1.echo_char1('a') = 'a' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_char10(self): + rows = self.query(''' + SELECT + fn1.echo_char10(NULL) is NULL, + fn1.echo_char10('ab') = 'ab ' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_date(self): + rows = self.query(''' + SELECT + fn1.echo_date(NULL) is NULL, + fn1.echo_date(current_date()) = current_date() + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_integer_basic(self): + rows = self.query(''' + SELECT + fn1.echo_integer(NULL) is NULL, + fn1.echo_integer(-1) = -1, + fn1.echo_integer(0) = 0, + fn1.echo_integer(1) = 1 + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_integer_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_integer(-(1e18 - 1)) = -(1e18 - 1), + fn1.echo_integer( 1e18 - 1) = 1e18 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_double(self): + rows = self.query(''' + SELECT + fn1.echo_double(NULL) is NULL, + fn1.echo_double(CAST(1.5 AS DOUBLE)) = CAST(1.5 AS DOUBLE), + fn1.echo_double(0) = 0.0, + fn1.echo_double(0.0) = 0.0, + fn1.echo_double(-1.7e-308) = -1.7e-308, + fn1.echo_double(+1.7e-308) = +1.7e-308 + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True, True, True)], rows) + + def test_echo_decimal_36_0_basic(self): + rows = self.query(''' + SELECT + fn1.echo_decimal_36_0(NULL) is NULL, + fn1.echo_decimal_36_0(0) = 0, + fn1.echo_decimal_36_0(0.0) = 0.0 + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_decimal_36_0_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_decimal_36_0(-(1e35 - 1)) = -(1e35 - 1), + fn1.echo_decimal_36_0( 1e35 - 1) = 1e35 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_decimal_36_36_basic(self): + rows = self.query(''' + SELECT + fn1.echo_decimal_36_36(NULL) is NULL, + fn1.echo_decimal_36_36(0) = 0, + fn1.echo_decimal_36_36(0.0) = 0.0 + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_decimal_36_36_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_decimal_36_36(-(1e-35 - 1)) = -(1e-35 - 1), + fn1.echo_decimal_36_36( 1e-35 - 1) = 1e-35 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_varchar10(self): + rows = self.query(''' + SELECT + fn1.echo_varchar10(NULL) is NULL, + fn1.echo_varchar10('') is NULL, + fn1.echo_varchar10(' ') = ' ', + fn1.echo_varchar10('a') = 'a', + fn1.echo_varchar10('a ') = 'a ', + fn1.echo_varchar10(' a ') = ' a ' + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True, True, True)], rows) + + def test_echo_timestamp(self): + rows = self.query(''' + SELECT fn1.echo_timestamp(NULL) is NULL + FROM DUAL''') + self.assertRowsEqual([(True,)], rows) + + rows = self.query(''' + SELECT fn1.echo_timestamp('2017-08-01 13:13:50.910') = '2017-08-01 13:13:50.910', + fn1.echo_timestamp('2017-08-01 13:13:50.983') = '2017-08-01 13:13:50.983' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + self.query('DROP SCHEMA ECHO_TEST CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA ECHO_TEST') + self.query('''CREATE OR REPLACE TABLE ECHO_TEST.N1 (now VARCHAR(255))''') + self.query('''INSERT INTO ECHO_TEST.N1 VALUES (SELECT now() AS x FROM DUAL)''') + self.query(''' + SELECT x1 = x2, x1, x2, x3 + FROM (SELECT fn1.echo_timestamp(x) AS x1, x AS x2, x AS x3 + FROM (SELECT now AS x FROM ECHO_TEST.N1)) + ''') + self.assertEqual(True, rows[0][0], str(rows)) + self.query('''DROP SCHEMA ECHO_TEST CASCADE''') + + +class EmptyTest(_JavaUdfSetup): + def test_run_func_is_empty(self): + rows = self.query(''' + SELECT + fn1.run_func_is_empty() IS NULL + FROM DUAL''') + self.assertRowsEqual([(True,)], rows) + + +class BottleneckTest(_JavaUdfSetup): + def test_varchar10(self): + for i in 0, 1, 5, 10: + rows = self.query(''' + SELECT fn1.bottleneck_varchar10('%s') + FROM DUAL''' % ('x' * i)) + self.assertEqual('x' * i if i > 0 else None, rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_varchar10('%s') + FROM DUAL''' % ('x' * 11)) + + def test_char10(self): + for i in 0, 1, 5, 10: + rows = self.query(''' + SELECT fn1.bottleneck_char10('%s') + FROM DUAL''' % ('x' * i)) + self.assertEqual( + ('x' * i + ' ' * 10)[:10] if i > 0 else None, + rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_char10('%s') + FROM DUAL''' % ('x' * 11)) + + def test_decimal5(self): + for i in 3, 4: + rows = self.query(''' + SELECT fn1.bottleneck_decimal5(%d) + FROM DUAL''' % (10 ** i)) + self.assertEqual(10 ** i, rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_decimal5(%d) + FROM DUAL''' % (10 ** 5)) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/import_alias.py b/test_container/tests/test/generic/java/import_alias.py new file mode 100644 index 000000000..71ca73254 --- /dev/null +++ b/test_container/tests/test/generic/java/import_alias.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework import exatest +from exasol_python_test_framework.udf import skip + + +class ImportAliasTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create or replace table fn2.t(z varchar(3000))') + self.query('create or replace table fn2.t2(y varchar(2000), z varchar(3000))') + self.query(''' + create connection FOOCONN to 'a' user 'b' identified by 'c' + ''', ignore_errors=True) + + self.query('OPEN SCHEMA FN1') + + # Create all IMPORT UDF scripts + self.query(udf.fixindent(''' + create or replace java set script impal_use_is_subselect(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class IMPAL_USE_IS_SUBSELECT { + static String generateSqlForImportSpec(ExaMetadata exa, ExaImportSpecification importSpecification) { + return "select " + importSpecification.isSubselect(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script impal_use_param_foo_bar(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class IMPAL_USE_PARAM_FOO_BAR { + static String generateSqlForImportSpec(ExaMetadata exa, ExaImportSpecification importSpecification) { + return "select '" + importSpecification.getParameters().get("FOO") + "', '" + importSpecification.getParameters().get("BAR") + "'"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script impal_use_connection_name(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class IMPAL_USE_CONNECTION_NAME { + static String generateSqlForImportSpec(ExaMetadata exa, ExaImportSpecification importSpecification) { + return "select '" + importSpecification.getConnectionName() + "'"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script impal_use_connection_fooconn(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class IMPAL_USE_CONNECTION_FOOCONN { + static String generateSqlForImportSpec(ExaMetadata exa, ExaImportSpecification importSpecification) throws Exception { + ExaConnectionInformation c = exa.getConnection("FOOCONN"); + return "select '" + c.getAddress() + c.getUser() + c.getPassword() + "'"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script impal_use_connection(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class IMPAL_USE_CONNECTION { + static String generateSqlForImportSpec(ExaMetadata exa, ExaImportSpecification importSpecification) { + ExaConnectionInformation conn = importSpecification.getConnectionInformation(); + return "select '" + conn.getUser() + conn.getPassword() + conn.getAddress() + conn.getType().toString().toLowerCase() + "'"; + } + } + / + ''')) + + self.query(udf.fixindent(''' + create or replace java set script impal_use_all(...) emits (x varchar(2000)) as + %jvmoption -Xms64m -Xmx128m -Xss512k; + class IMPAL_USE_ALL { + static String generateSqlForImportSpec(ExaMetadata exa, ExaImportSpecification importSpecification) { + String is_sub = "FALSE"; + if (importSpecification.isSubselect()) { + is_sub = "TRUE"; + } + String connection_string = "X"; + String connection_name = "Y"; + String foo = "Z"; + String types = "T"; + String names = "N"; + if (importSpecification.hasConnectionInformation()) { + ExaConnectionInformation conn = importSpecification.getConnectionInformation(); + connection_string = conn.getUser() + conn.getPassword() + conn.getAddress() + conn.getType().toString().toLowerCase(); + } + if (importSpecification.hasConnectionName()) { + connection_name = importSpecification.getConnectionName(); + } + if (importSpecification.getParameters().get("FOO") != null) { + foo = importSpecification.getParameters().get("FOO"); + } + if (importSpecification.getSubselectColumnNames().size() > 0) { + for (int i = 0; i < importSpecification.getSubselectColumnNames().size(); i++) { + types = types + importSpecification.getSubselectColumnSqlTypes().get(i); + names = names + importSpecification.getSubselectColumnNames().get(i); + } + } + return "select 1, '" + is_sub + '_' + connection_name + '_' + connection_string + '_' + foo + '_' + types + '_' + names + "'"; + } + } + / + ''')) + + def test_import_use_is_subselect(self): + self.query(''' + IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_is_subselect + ''') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FALSE',)], rows) + self.query('truncate table fn2.t') + + def test_import_use_is_subselect_subselect(self): + rows = self.query(''' + SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_is_subselect) + ''') + self.assertRowsEqual([(True,)], rows) + rows = self.query(''' + IMPORT FROM SCRIPT fn1.impal_use_is_subselect + ''') + self.assertRowsEqual([(True,)], rows) + + def test_import_use_params(self): + self.query("IMPORT INTO fn2.t2 FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo'") + rows = self.query('select * from fn2.t2') + self.assertRowsEqual([('bar','foo')], rows) + self.query('truncate table fn2.t2') + + def test_import_use_params_subselect(self): + rows = self.query("SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo')") + self.assertRowsEqual([('bar','foo')], rows) + rows = self.query("IMPORT FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo'") + self.assertRowsEqual([('bar','foo')], rows) + + def test_import_use_connection_name(self): + self.query('IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_connection_name at fooconn') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FOOCONN',)], rows) + self.query('truncate table fn2.t') + + def test_import_use_connection_fooconn(self): + rows = self.query('IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.assertRowsEqual([('abc',)], rows) + self.query('truncate table fn2.t') + + def getConnection(self, username, password): + client = exatest.ODBCClient('exatest') + self.log.debug('connecting to DSN "exa" for user {username}'.format(username=username)) + client.connect(uid = username, pwd = password) + return client + + def createUser(self, username, password): + self.query('DROP USER IF EXISTS {username} CASCADE'.format(username = username)) + self.query('CREATE USER {username} IDENTIFIED BY "{password}"'.format(username = username, password = password)) + self.query('GRANT CREATE SESSION TO {username}'.format(username=username)) + + def test_import_use_connection_fooconn_fails_for_user_foo(self): + self.createUser('foo','foo') + self.commit() + foo_conn = self.getConnection('foo','foo') + with self.assertRaisesRegex(Exception, 'insufficient privileges'): + foo_conn.query('IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.query('drop user foo cascade') + + @skip("IMPORT FROM SCRIPT cannot be used in view definitions") + def test_import_use_connection_fooconn_for_user_foo_and_view(self): + self.query('create view fn2.fooconn_import_view as IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.createUser('foo','foo') + self.commit() + foo_conn = self.getConnection('foo','foo') + rows = foo_conn.query('select * from fn2.fooconn_import_view') + self.assertRowsEqual([('abc',)], rows) + self.query('drop user foo cascade') + self.query('drop view fn2.fooconn_import_view') + + def test_import_use_connection_name_subselect(self): + rows = self.query('SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_connection_name at fooconn)') + self.assertRowsEqual([('FOOCONN',)], rows) + rows = self.query('IMPORT FROM SCRIPT fn1.impal_use_connection_name at fooconn') + self.assertRowsEqual([('FOOCONN',)], rows) + + def test_import_use_connection(self): + self.query(''' + IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser' + ''') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + self.query('truncate table fn2.t') + + def test_import_use_connection_subselect(self): + rows = self.query(''' SELECT * FROM ( + IMPORT FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser') + ''') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + rows = self.query(''' + IMPORT FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser' + ''') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + + def test_import_use_all(self): + self.query(''' + IMPORT INTO fn2.t2 FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value' + ''') + rows = self.query('select * from fn2.t2') + self.assertRowsEqual([('1','FALSE_Y_hansmeiserfooconnpassword_a value_T_N')], rows) + self.query('truncate table fn2.t2') + + def test_import_use_all_subselect(self): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value') + ''') + self.assertRowsEqual([(1, 'TRUE_Y_hansmeiserfooconnpassword_a value_TDOUBLEVARCHAR(3000) UTF8_NAB')], rows) + rows = self.query(''' + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value' + ''') + self.assertRowsEqual([(1, 'TRUE_Y_hansmeiserfooconnpassword_a value_TDOUBLEVARCHAR(3000) UTF8_NAB')], rows) + + def test_prepared_statement_params(self): + with self.assertRaisesRegex(Exception, 'syntax error, unexpected \'?\''): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo=?) + ''', 'bar') + + def test_prepared_statement_conn(self): + with self.assertRaisesRegex(Exception, 'syntax error, unexpected \'?\''): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at ? user ? identified by ? with foo='bar') + ''', 'fooconn', 'hans', 'meiser', 'bar') + + def test_import_in_lua_scripting(self): + self.query(''' + create or replace script s1() as + res = pquery [[ IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_is_subselect ]] + ''') + self.query('execute script s1()') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FALSE',)], rows) + self.query('truncate table fn2.t') + + +if __name__ == '__main__': + udf.main() + + diff --git a/test_container/tests/test/generic/java/metadata.py b/test_container/tests/test/generic/java/metadata.py new file mode 100755 index 000000000..e9b6ae7d5 --- /dev/null +++ b/test_container/tests/test/generic/java/metadata.py @@ -0,0 +1,508 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _JavaUdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + create java scalar script + get_char_length(text char(10)) + emits(len1 number, len2 number, dummy char(20)) + as + class GET_CHAR_LENGTH { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + long v = exa.getInputColumnLength(0); + long w = exa.getOutputColumnLength(2); + ctx.emit(v, w, "9876543210"); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_current_schema() returns varchar(200) as + class GET_CURRENT_SCHEMA { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getCurrentSchema(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_current_user() returns varchar(200) as + class GET_CURRENT_USER { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getCurrentUser(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + get_database_name() returns varchar(300) AS + class GET_DATABASE_NAME { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getDatabaseName(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_database_version() returns varchar(20) as + class GET_DATABASE_VERSION { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getDatabaseVersion(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_input_column_count_scalar(c1 double, c2 varchar(100)) + returns number as + class GET_INPUT_COLUMN_COUNT_SCALAR { + static long run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getInputColumnCount(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java set script + get_input_column_count_set(c1 double, c2 varchar(100)) + returns number as + class GET_INPUT_COLUMN_COUNT_SET { + static long run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getInputColumnCount(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_input_columns(c1 double, c2 varchar(200)) + emits (column_id number, column_name varchar(200), column_type varchar(20), + column_sql_type varchar(20), column_precision number, column_scale number, + column_length number) as + class GET_INPUT_COLUMNS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + for (int i = 0; i < exa.getInputColumnCount(); i++) { + String name = exa.getInputColumnName(i); + long precision = exa.getInputColumnPrecision(i); + String thetype = exa.getInputColumnType(i).getCanonicalName(); + String sql_type = exa.getInputColumnSqlType(i); + long scale = exa.getInputColumnScale(i); + long length = exa.getInputColumnLength(i); + if (name == null) + name = "no-name"; + if (thetype == null) + thetype = "no-type"; + if (sql_type == null) + sql_type = "no-sql-type"; + ctx.emit(i + 1, name, thetype, sql_type, precision, scale, length); + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_input_type_scalar() returns varchar(200) as + class GET_INPUT_TYPE_SCALAR { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getInputType(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java set script + get_input_type_set(a double) returns varchar(200) as + class GET_INPUT_TYPE_SET { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getInputType(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_node_id() returns number as + class GET_NODE_ID { + static long run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getNodeId(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_output_column_count_emit() + emits (x number, y number, z number) as + class GET_OUTPUT_COLUMN_COUNT_EMIT { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(exa.getOutputColumnCount(), exa.getOutputColumnCount(), exa.getOutputColumnCount()); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_output_column_count_return() + returns number as + class GET_OUTPUT_COLUMN_COUNT_RETURN { + static long run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getOutputColumnCount(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_output_columns() + emits (column_id number, column_name varchar(200), column_type varchar(20), + column_sql_type varchar(20), column_precision number, column_scale number, + column_length number) as + class GET_OUTPUT_COLUMNS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + for (int i = 0; i < exa.getOutputColumnCount(); i++) { + String name = exa.getOutputColumnName(i); + long precision = exa.getOutputColumnPrecision(i); + String thetype = exa.getOutputColumnType(i).getCanonicalName(); + String sql_type = exa.getOutputColumnSqlType(i); + long scale = exa.getOutputColumnScale(i); + long length = exa.getOutputColumnLength(i); + if (name == null) + name = "no-name"; + if (thetype == null) + thetype = "no-type"; + if (sql_type == null) + sql_type = "no-sql-type"; + ctx.emit(i + 1, name, thetype, sql_type, precision, scale, length); + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_output_type_emit() + emits (t varchar(200)) as + class GET_OUTPUT_TYPE_EMIT { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(exa.getOutputType()); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_output_type_return() + returns varchar(200) as + class GET_OUTPUT_TYPE_RETURN { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getOutputType(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_precision_scale_length(n decimal(6,3), v varchar(10)) + emits (precision1 number, scale1 number, length1 number, precision2 number, scale2 number, length2 number) as + class GET_PRECISION_SCALE_LENGTH { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + long precision1 = exa.getInputColumnPrecision(0); + long scale1 = exa.getInputColumnScale(0); + long length1 = exa.getInputColumnLength(0); + long precision2 = exa.getInputColumnPrecision(1); + long scale2 = exa.getInputColumnScale(1); + long length2 = exa.getInputColumnLength(1); + ctx.emit(precision1, scale1, length1, precision2, scale2, length2); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_scope_user() returns varchar(200) as + class GET_SCOPE_USER { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getScopeUser(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_script_code() returns varchar(2000) as + class GET_SCRIPT_CODE { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getScriptCode(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_script_language() emits (s1 varchar(300), s2 varchar(300)) as + class GET_SCRIPT_LANGUAGE { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + ctx.emit(exa.getScriptLanguage(), "Java"); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_script_name() returns varchar(200) as + class GET_SCRIPT_NAME { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getScriptName(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_script_schema() returns varchar(200) as + class GET_SCRIPT_SCHEMA { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getScriptSchema(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_session_id() returns varchar(200) as + class GET_SESSION_ID { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getSessionId(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_statement_id() returns number as + class GET_STATEMENT_ID { + static long run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getStatementId(); + } + } + / + ''')) + + self.query(udf.fixindent(''' + create java scalar script + get_vm_id() returns varchar(200) as + class GET_VM_ID { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return exa.getVmId(); + } + } + / + ''')) + +class MetaDataTest(_JavaUdfSetup): + + def test_database_name(self): + rows = self.query('''SELECT fn1.get_database_name() FROM DUAL''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_database_version(self): + rows = self.query('''select fn1.get_database_version() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_script_language(self): + rows = self.query('''select fn1.get_script_language() from dual''') + self.assertTrue((rows[0][0]).upper().startswith((rows[0][1]).upper())) + + def test_script_name(self): + rows = self.query('''select fn1.get_script_name() from dual''') + self.assertRowEqual(('GET_SCRIPT_NAME',), rows[0]) + + def test_script_schema(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_script_schema() from dual''') + self.assertRowEqual(('FN1',), rows[0]) + + def test_script_user(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_current_user() from dual''') + self.assertRowEqual(('SYS',), rows[0]) + + def test_scope_user(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_scope_user() from dual''') + self.assertRowEqual(('SYS',), rows[0]) + + def test_current_schema_null(self): + if (udf.opts.is_compat_mode != "true"): + self.query('''CLOSE SCHEMA''') + rows = self.query('''select fn1.get_current_schema() from dual''') + self.assertRowEqual(('NULL',), rows[0]) + + def test_current_schema(self): + if (udf.opts.is_compat_mode != "true"): + self.query('''create schema test_schema''') + rows = self.query('''select fn1.get_current_schema() from dual''') + self.assertRowEqual(('TEST_SCHEMA',), rows[0]) + self.query('''drop schema test_schema cascade''') + + def test_script_code(self): + rows = self.query('''select fn1.get_script_code() from dual''') + self.assertTrue((rows[0][0]).upper().find('CTX') >= 0) + + def test_session_id(self): + rows = self.query('''select fn1.get_session_id() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_statement_id(self): + rows = self.query('''select fn1.get_statement_id() from dual''') + self.assertTrue(rows[0][0] >= 0) + + def test_node_id(self): + rows = self.query('''select fn1.get_node_id() from dual''') + self.assertTrue(rows[0][0] >= 0) + + def test_vm_id(self): + rows = self.query('''select fn1.get_vm_id() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_input_type_scalar(self): + rows = self.query('''select fn1.get_input_type_scalar() from dual''') + self.assertRowEqual(('SCALAR',), rows[0]) + + def test_input_type_set(self): + rows = self.query('''select fn1.get_input_type_set(x) from (values 1,2,3) as t(x)''') + self.assertRowEqual(('SET',), rows[0]) + + def test_input_column_count_scalar(self): + rows = self.query('''select fn1.get_input_column_count_scalar(12.3, 'hihihi') from dual''') + self.assertRowEqual((2,), rows[0]) + + + def test_input_column_count_set(self): + rows = self.query('''select fn1.get_input_column_count_set(x, y) from (values (12.3, 'hihihi')) as t(x,y)''') + self.assertRowEqual((2,), rows[0]) + + def test_input_columns(self): + rows = self.query('''select fn1.get_input_columns(1.2, '123') from dual order by column_id''') + r0 = rows[0] + r1 = rows[1] + self.assertTrue(r0[0] == 1) + self.assertTrue(r0[1].upper() == 'C1') + self.assertTrue(r0[2].upper() == 'NUMBER' or r0[2].upper() == "" or r0[2].upper() == "DOUBLE" or r0[2].upper() == "JAVA.LANG.DOUBLE" or r0[2].upper() == "") + self.assertTrue(r0[3].upper() == 'DOUBLE') + self.assertTrue(r1[0] == 2) + self.assertTrue(r1[1].upper() == 'C2') + self.assertTrue(r1[2].upper() == 'STRING' or r1[2].upper() == "" or r1[2].upper() == "CHARACTER" or r1[2].upper() == "JAVA.LANG.STRING" or r1[2].upper() == "") + self.assertTrue(r1[3].upper().startswith('VARCHAR(200)')) + self.assertTrue(r0[6] == 0) + self.assertTrue(r1[6] == 200) + + def test_output_type_return(self): + rows = self.query('''select fn1.get_output_type_return() from dual''') + self.assertTrue(rows[0][0] == 'RETURN') + + + def test_output_type_emit(self): + rows = self.query('''select fn1.get_output_type_emit() from dual''') + self.assertTrue(rows[0][0] == 'EMIT') + + + def test_output_column_count_return(self): + rows = self.query('''select fn1.get_output_column_count_return() from dual''') + self.assertRowEqual((1,),rows[0]) + + + def test_output_column_count_emit(self): + rows = self.query('''select fn1.get_output_column_count_emit() from dual''') + self.assertRowEqual((3,3,3),rows[0]) + + def test_output_columns(self): + rows = self.query('''select fn1.get_output_columns() from dual order by column_id''') + r0 = rows[0] + r1 = rows[1] + r2 = rows[2] + r3 = rows[3] + r4 = rows[4] + r5 = rows[5] + r6 = rows[6] + self.assertTrue(r0[0] == 1) + self.assertTrue(r1[0] == 2) + self.assertTrue(r2[0] == 3) + self.assertTrue(r3[0] == 4) + self.assertTrue(r4[0] == 5) + self.assertTrue(r5[0] == 6) + self.assertTrue(r6[0] == 7) + self.assertTrue(r0[1].upper() == 'COLUMN_ID') + self.assertTrue(r1[1].upper() == 'COLUMN_NAME') + self.assertTrue(r2[1].upper() == 'COLUMN_TYPE') + self.assertTrue(r3[1].upper() == 'COLUMN_SQL_TYPE') + self.assertTrue(r4[1].upper() == 'COLUMN_PRECISION') + self.assertTrue(r5[1].upper() == 'COLUMN_SCALE') + self.assertTrue(r6[1].upper() == 'COLUMN_LENGTH') + self.assertTrue(r0[2].upper() == 'NUMBER' or r0[2].upper() == "" or r0[2].upper() == "DOUBLE" or r0[2].upper() == "JAVA.LANG.DOUBLE" or r0[2].upper() == "") + self.assertTrue(r1[2].upper() == 'STRING' or r1[2].upper() == "" or r1[2].upper() == "CHARACTER" or r1[2].upper() == "JAVA.LANG.STRING" or r1[2].upper() == "") + self.assertTrue(r2[2].upper() == 'STRING' or r2[2].upper() == "" or r2[2].upper() == "CHARACTER" or r2[2].upper() == "JAVA.LANG.STRING" or r2[2].upper() == "") + self.assertTrue(r3[2].upper() == 'STRING' or r3[2].upper() == "" or r3[2].upper() == "CHARACTER" or r3[2].upper() == "JAVA.LANG.STRING" or r3[2].upper() == "") + self.assertTrue(r4[2].upper() == 'NUMBER' or r4[2].upper() == "" or r4[2].upper() == "DOUBLE" or r4[2].upper() == "JAVA.LANG.DOUBLE" or r4[2].upper() == "") + self.assertTrue(r5[2].upper() == 'NUMBER' or r5[2].upper() == "" or r5[2].upper() == "DOUBLE" or r5[2].upper() == "JAVA.LANG.DOUBLE" or r5[2].upper() == "") + self.assertTrue(r6[2].upper() == 'NUMBER' or r6[2].upper() == "" or r6[2].upper() == "DOUBLE" or r6[2].upper() == "JAVA.LANG.DOUBLE" or r6[2].upper() == "") + self.assertTrue(r0[3].upper() == 'DOUBLE') + self.assertTrue(r1[3].upper().startswith('VARCHAR(200)')) + self.assertTrue(r2[3].upper().startswith('VARCHAR(20)')) + self.assertTrue(r3[3].upper().startswith('VARCHAR(20)')) + self.assertTrue(r4[3].upper() == 'DOUBLE') + self.assertTrue(r5[3].upper() == 'DOUBLE') + self.assertTrue(r6[3].upper() == 'DOUBLE') + self.assertTrue(r1[6] == 200) + self.assertTrue(r2[6] == 20) + self.assertTrue(r3[6] == 20) + + + + def test_precision_scale_length(self): + rows = self.query('''select fn1.get_precision_scale_length(2.5, '0123456789') from dual''') + self.assertRowEqual((6,3,0,0,0,10), rows[0]) + + + def test_char_length(self): + rows = self.query('''select fn1.get_char_length('0123456789') from dual''') + self.assertRowEqual((10,20,'9876543210 '), (int(rows[0][0]), int(rows[0][1]), rows[0][2])) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/numeric_functions.py b/test_container/tests/test/generic/java/numeric_functions.py new file mode 100755 index 000000000..a8663e429 --- /dev/null +++ b/test_container/tests/test/generic/java/numeric_functions.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _JavaUdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + add_three_doubles(x DOUBLE, y DOUBLE, z DOUBLE) + RETURNS DOUBLE AS + class ADD_THREE_DOUBLES { + static Double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getDouble("x") != null && ctx.getDouble("y") != null && ctx.getDouble("z") != null) + return (double) ctx.getDouble("x") + (double) ctx.getDouble("y") + (double) ctx.getDouble("z"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT add_two_doubles(x DOUBLE, y DOUBLE) RETURNS DOUBLE AS + class ADD_TWO_DOUBLES { + static Double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getDouble("x") != null && ctx.getDouble("y") != null) + return (double) ctx.getDouble("x") + (double) ctx.getDouble("y"); + return null; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + double_mult("x" double, "y" double) + RETURNS double AS + class DOUBLE_MULT { + static Double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getDouble("x") == null || ctx.getDouble("y") == null) + return null; + else + return (double) ctx.getDouble("x") * (double) ctx.getDouble("y"); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + pi() + RETURNS double AS + class PI { + static double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return Math.PI; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + split_integer_into_digits("x" INTEGER) + EMITS (y INTEGER) AS + class SPLIT_INTEGER_INTO_DIGITS { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getInteger("x") != null) { + int y = Math.abs(ctx.getInteger("x")); + while (y > 0) { + ctx.emit(y % 10); + y /= 10; + } + } + } + } + / + ''')) + +class Test(_JavaUdfSetup): + def test_pi(self): + rows = self.query(''' + SELECT fn1.pi() + FROM dual''') + result = rows[0][0] + self.assertAlmostEqual(3.1415926535, result) + + def test_select(self): + rows = self.query(''' + SELECT DISTINCT + FN1.double_mult(float1, float2) = float1 * float2 AS a + FROM test.enginetablebig1 + ORDER BY a + ''') + self.assertRowsEqual([(True,), (None,)], rows) + + def test_select_into(self): + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('CREATE TABLE FN2.t(diff double)') + self.query(''' + INSERT INTO FN2.t + SELECT + fn1.double_mult(float1, float2) - float1 * float2 AS a + FROM test.enginetable + ''') + self.query(''' + SELECT DISTINCT diff + FROM FN2.t + WHERE diff != 0 AND diff IS NOT NULL + ''') + self.assertEqual(0, self.rowcount()) + + def test_subselect(self): + rows = self.query(''' + SELECT i, a + FROM ( + SELECT int_index AS i, + (fn1.double_mult(float1, float2) - float1 * float2) AS a + FROM test.enginetable) + WHERE a IS NOT NULL + ORDER BY a + LIMIT 20''') + for row in rows: + rows2 = self.query(''' + SELECT + float1, + float2, + fn1.double_mult(float1, float2) - float1 * float2 AS a + FROM test.enginetable + WHERE int_index = ?''', + row.I) + self.assertEqual(row.A, rows2[0].A) + + def test_udf_with_two_doubles(self): + rows = self.query(''' + SELECT + fn1.add_two_doubles(NULL, NULL) IS NULL, + fn1.add_two_doubles(NULL, 0) IS NULL, + fn1.add_two_doubles( 0, NULL) IS NULL, + fn1.add_two_doubles(0,0) = 0, + fn1.add_two_doubles(1,0) = 1, + fn1.add_two_doubles(0,2) = 2, + fn1.add_two_doubles(2,3) = 5 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 7)], rows) + + def test_udf_with_three_doubles_part1(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles(NULL, NULL, NULL) is NULL, + fn1.add_three_doubles(NULL, NULL, 0) is NULL, + fn1.add_three_doubles(NULL, 0, NULL) is NULL, + fn1.add_three_doubles( 0, NULL, NULL) is NULL, + fn1.add_three_doubles(NULL, 0, 0) is NULL + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_udf_with_three_doubles_part2(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles( 0, NULL, 0) is NULL, + fn1.add_three_doubles( 0, 0, NULL) is NULL, + fn1.add_three_doubles(0, 0, 0) = 0, + fn1.add_three_doubles(1, 0, 0) = 1, + fn1.add_three_doubles(0, 2, 0) = 2 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_udf_with_three_doubles_part3(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles(0, 0, 3) = 3, + fn1.add_three_doubles(1, 2, 0) = 3, + fn1.add_three_doubles(1, 0, 3) = 4, + fn1.add_three_doubles(0, 2, 3) = 5, + fn1.add_three_doubles(1, 2, 3) = 6 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_right_number_of_emitted_rows(self): + rows = self.query(''' + SELECT fn1.split_integer_into_digits(123) + FROM DUAL''') + self.assertRowsEqual([(3,), (2,), (1,)], rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/pathological_functions.py b/test_container/tests/test/generic/java/pathological_functions.py new file mode 100755 index 000000000..522470c3c --- /dev/null +++ b/test_container/tests/test/generic/java/pathological_functions.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class Test(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + sleep("sec" double) + RETURNS double AS + class SLEEP { + static double run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int sec = ctx.getInteger("sec"); + Thread.sleep(sec * 1000); + return sec; + } + } + / + ''')) + + def test_query_timeout(self): + self.query('ALTER SESSION SET QUERY_TIMEOUT = 10') + try: + with self.assertRaisesRegex(Exception, 'Successfully reconnected after query timeout'): + self.query('SELECT fn1.sleep(100) FROM dual') + finally: + self.query('ALTER SESSION SET QUERY_TIMEOUT = 0') + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/java/unicode.py b/test_container/tests/test/generic/java/unicode.py new file mode 100755 index 000000000..92949828c --- /dev/null +++ b/test_container/tests/test/generic/java/unicode.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import useData + + +class _JavaUdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + unicode_count(word VARCHAR(1000), convert_ INT) + EMITS (uchar VARCHAR(1), count INT) AS + import java.util.Map; + import java.util.HashMap; + import java.util.Set; + import java.util.Iterator; + class UNICODE_COUNT { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + String s = ctx.getString("word"); + if (s == null) + return; + if (ctx.getInteger("convert_") > 0) + s = s.toUpperCase(); + else if (ctx.getInteger("convert_") < 0) + s = s.toLowerCase(); + + Map count = new HashMap(); + for (int i = 0; i < s.length(); i++) { + Integer codepoint = s.codePointAt(i); + Integer num = count.get(codepoint); + count.put(codepoint, (num == null ? 1 : num + 1)); + if (Character.isHighSurrogate(s.charAt(i))) + i++; + } + Iterator> iter = count.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry item = iter.next(); + ctx.emit(new String(Character.toChars(item.getKey())), item.getValue()); + } + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + unicode_len(word VARCHAR(1000)) + RETURNS INT AS + class UNICODE_LEN { + static int run(ExaMetadata exa, ExaIterator ctx) throws Exception { + String s = ctx.getString("word"); + return (s == null) ? 0 : s.codePointCount(0, s.length()); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + unicode_upper(word VARCHAR(1000)) + RETURNS VARCHAR(1000) AS + class UNICODE_UPPER { + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + String s = ctx.getString("word"); + return (s == null) ? null : s.toUpperCase(); + } + } + / + ''')) + +# coding: utf-8 + +import csv +import locale +import logging +import os +import subprocess +import sys +import tempfile +import unicodedata +import re +import argparse + +from exasol_python_test_framework import udf + +udf.pythonVersionInUdf = -1 +from exasol_python_test_framework.exatest.testcase import skipIf + + +locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') + + +def getPythonVersionInUDFs(server, script_languages): + log = logging.getLogger('unicodedata') + log.info("trying to figure out python version of python in UDFs") + sql = udf.fixindent(''' + alter session set script_languages='%(sl)s'; + drop schema if exists pyversion_schema cascade; + create schema pyversion_schema; + create or replace python3 scalar script pyversion_schema.python_version() returns varchar(1000) as + import sys + def run(ctx): + return 'Python='+str(sys.version_info[0]) + / + select pyversion_schema.python_version(); + ''' % {'sl': script_languages}) + cmd = '''%(exaplus)s -c %(conn)s -u sys -P exasol + -no-config -autocommit ON -L -pipe -jdbcparam validateservercertificate=0''' % { + 'exaplus': os.environ.get('EXAPLUS', + '/usr/opt/EXASuite-4/EXASolution-4.2.9/bin/Console/exaplus'), + 'conn': server + } + env = os.environ.copy() + # env['PATH'] = '/usr/opt/jdk1.8.0_latest/bin:' + env['PATH'] + exaplus = subprocess.Popen( + cmd.split(), + env=env, + + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, _err = exaplus.communicate(sql.encode('utf-8')) + pythonVersionInUdf = -1 + for line in out.strip().decode('utf-8').split(sep="\n"): + m = re.search(r'Python=(\d)', line) + if m: + pythonVersionInUdf = int(m.group(1)) + continue + + if pythonVersionInUdf not in [2, 3]: + print('cannot set pythonVersionInUdf: %s' % pythonVersionInUdf) + sys.exit(1) + + return pythonVersionInUdf + + +def setUpModule(): + log = logging.getLogger('unicodedata') + + log.info('generating unicodedata CSV') + with tempfile.NamedTemporaryFile(prefix='unicode-', suffix='.csv', encoding='utf-8', mode='w+', + delete=False) as csvfile: + c = csv.writer(csvfile, quoting=csv.QUOTE_ALL) + for i in range(sys.maxunicode + 1): + if i >= 5024 and i <= 5119: + continue # the Unicode Cherokee-Block is broken in Python 2.7 and Python 3.4 (maybe also 3.5) + u = chr(i) + if unicodedata.category(u).startswith('C'): + # [Cc]Other, Control + # [Cf]Other, Format + # [Cn]Other, Not Assigned + # [Co]Other, Private Use + # [Cs]Other, Surrogate + continue + row = (i, # INT 0-1114111 + unicodedata.name(u, 'UNICODE U+%08X' % i), # VARCHAR(100) ASCII + u, # VARCHAR(1) UNICODE + u.upper(), # VARCHAR(1) UNICODE + u.lower(), # VARCHAR(1) UNICODE + unicodedata.decimal(u, None), # INT + unicodedata.numeric(u, None), # DOUBLE + unicodedata.category(u), # VARCHAR(3) ASCII + unicodedata.bidirectional(u), # VARCHAR(3) ASCII + unicodedata.combining(u), # VARCHAR(3) ASCII + unicodedata.east_asian_width(u), # VARCHAR(1) ASCII + bool(unicodedata.mirrored), # BOOLEAN + unicodedata.decomposition(u), # VARCHAR(10) ASCII + unicodedata.normalize('NFC', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFD', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFKC', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFKD', u), # VARCHAR(3) UNICODE + ) + c.writerow(row) + csvfile.flush() + + log.info('loading CSV') + sql = ''' + DROP SCHEMA utest CASCADE; + CREATE SCHEMA utest; + CREATE TABLE utest.unicodedata ( + codepoint INT NOT NULL, + name VARCHAR(100) ASCII, + uchar VARCHAR(1) UTF8, + to_upper VARCHAR(1) UTF8, + to_lower VARCHAR(1) UTF8, + decimal_value INT, + numeric_value INT, + category VARCHAR(3) ASCII, + bidirectional VARCHAR(3) ASCII, + combining VARCHAR(10) ASCII, + east_asian_width VARCHAR(2) ASCII, mirrored BOOLEAN, + decomposition VARCHAR(100) ASCII, + NFC VARCHAR(10) UTF8, + NFD VARCHAR(10) UTF8, + NFKC VARCHAR(20) UTF8, + NFKD VARCHAR(20) UTF8 + ); + IMPORT INTO utest.unicodedata + FROM LOCAL CSV FILE '%s' + ROW SEPARATOR = 'CRLF'; + ''' % os.path.join(os.getcwd(), csvfile.name) + cmd = '''%(exaplus)s -c %(conn)s -u sys -P exasol + -no-config -autocommit ON -L -pipe -jdbcparam validateservercertificate=0''' % { + 'exaplus': os.environ.get('EXAPLUS', + '/usr/opt/EXASuite-4/EXASolution-4.2.9/bin/Console/exaplus'), + 'conn': udf.opts.server + } + env = os.environ.copy() + env['PATH'] = '/usr/opt/jdk1.8.0_latest/bin:' + env['PATH'] + exaplus = subprocess.Popen( + cmd.split(), + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, _err = exaplus.communicate(sql.encode('utf-8')) + if exaplus.returncode != 0 or _err is not None: + log.critical('EXAplus error: %d', exaplus.returncode) + log.error(out) + else: + log.debug(out) + + +def add_uniname(data): + return [(n, unicodedata.name(chr(n), 'U+%04X' % n)) + for n in data] + + +class Unicode(_JavaUdfSetup): + + def query_unicode_char(self, u): + rows = self.query(''' + SELECT count, unicode(uchar) AS u + FROM ( + SELECT fn1.unicode_count(unicodechr(%d), 0) + FROM dual) + ''' % u) + self.assertEqual(1, self.rowcount()) + self.assertEqual(1, rows[0].COUNT) + self.assertEqual(u, rows[0].U) + + data = add_uniname(( + 65, + 255, + 382, + 65279, + 63882, + 65534, + 66432, + 173746, + 1114111, + )) + + @useData(data) + def test_unicode(self, codepoint, _name): + self.query_unicode_char(codepoint) + + def test_unicode_count(self): + self.maxDiff = 1024 + rows = self.query(''' + SELECT + c1_integer AS i, + len(c2_varchar100) AS len_exa, + fn1.unicode_len(c2_varchar100) AS len + FROM test.enginetablebigunicodevarchar + WHERE len(c2_varchar100) != fn1.unicode_len(c2_varchar100) + ORDER BY c1_integer + LIMIT 100 + ''') + self.assertRowsEqual([], rows) + + +class UnicodeData(_JavaUdfSetup): + + # @udf.TestCase.expectedFailureIfLang('lua') + def test_unicode_upper_is_subset_of_Unicode520_part2(self): + """DWA-13388 (Lua); DWA-13702 (Lua)""" + rows = self.query(''' + SELECT + codepoint, + name, + unicode(to_upper), + unicode(fn1.unicode_upper(uchar)) + FROM utest.unicodedata + WHERE codepoint in (181, 8126) + and (to_upper != fn1.unicode_upper(uchar)) + and (uchar != fn1.unicode_upper(uchar)) + ORDER BY codepoint + LIMIT 50 + ''') + self.assertRowsEqual([], rows) + + @udf.TestCase.expectedFailureIfLang('lua') + def test_unicode_upper_is_subset_of_Unicode520_part3(self): + """DWA-13388 (Lua); DWA-13702 (Lua); DWA-13782 (R)""" + rows = self.query(''' + SELECT + codepoint, + name, + unicode(to_upper), + unicode(fn1.unicode_upper(uchar)) + FROM utest.unicodedata + WHERE codepoint in (1010) + and (to_upper != fn1.unicode_upper(uchar)) + and (uchar != fn1.unicode_upper(uchar)) + ORDER BY codepoint + LIMIT 50 + ''') + self.assertRowsEqual([], rows) + + def test_unicode_len(self): + rows = self.query(''' + SELECT codepoint, name + FROM utest.unicodedata + WHERE codepoint not between 55296 and 57343 + and len(uchar) != fn1.unicode_len(uchar) + ORDER BY codepoint + LIMIT 100 + ''') + self.assertRowsEqual([], rows) + + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--server', help='connection string') + parser.add_argument('--script-languages', help='definition of the SCRIPT_LANGUAGES variable') + opts, _unknown = parser.parse_known_args() + setattr(udf, 'pythonVersionInUdf', getPythonVersionInUDFs(opts.server, opts.script_languages)) + udf.main() diff --git a/test_container/tests/test/generic/java/vectorsize.py b/test_container/tests/test/generic/java/vectorsize.py new file mode 100755 index 000000000..8e6ca6792 --- /dev/null +++ b/test_container/tests/test/generic/java/vectorsize.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 + +import sys + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import useData + + +class _JavaUdfSetup(udf.TestCase): + LANG = 'java' + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE java SCALAR SCRIPT + basic_range(n INTEGER) + EMITS (n INTEGER) AS + class BASIC_RANGE { + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + if (ctx.getInteger("n") != null) + for (int i = 0; i < ctx.getInteger("n"); i++) + ctx.emit(i); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + vectorsize(length DOUBLE, dummy DOUBLE) + RETURNS VARCHAR(2000000) AS + import java.util.Map; + import java.util.HashMap; + + class VECTORSIZE { + static Map cache = new HashMap(); + + static void fillCache(int len) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + sb.append(Integer.toString(i)); + } + cache.put(len, sb.toString()); + } + + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int len = ctx.getInteger("length"); + if (!cache.containsKey(len)) + fillCache(len); + return cache.get(len); + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT vectorsize5000(A DOUBLE) + RETURNS VARCHAR(2000000) AS + class VECTORSIZE5000 { + static String numbers; + + static { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 5000; i++) { + sb.append(Integer.toString(i)); + } + numbers = sb.toString(); + } + + static String run(ExaMetadata exa, ExaIterator ctx) throws Exception { + return numbers; + } + } + / + ''')) + + self.query(udf.fixindent(''' + CREATE JAVA SCALAR SCRIPT + vectorsize_set(length DOUBLE, n DOUBLE, dummy DOUBLE) + EMITS (o VARCHAR(2000000)) AS + import java.util.Map; + import java.util.HashMap; + + class VECTORSIZE_SET { + static Map cache = new HashMap(); + + static void fillCache(int len) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) { + sb.append(Integer.toString(i)); + } + cache.put(len, sb.toString()); + } + + static void run(ExaMetadata exa, ExaIterator ctx) throws Exception { + int len = ctx.getInteger("length"); + if (!cache.containsKey(len)) + fillCache(len); + int n = ctx.getInteger("n"); + for (int i = 0; i < n; i++) { + ctx.emit(cache.get(len)); + } + } + } + / + ''')) + +class Vectorsize(_JavaUdfSetup): + + def test_vectorsize_5000(self): + self.query(''' + SELECT max(fn1.vectorsize5000(float1)) + FROM TEST.ENGINETABLEBIG1''') + + data = [ + (10,), + (30,), + (100,), + (300,), + (1000,), + (3000,), + (10000,), + (30000,), + (100000,), + (200000,), + (351850,), + ] + + @useData(data) + def test_vectorsize(self, size): + limits = { + 'lua': 100000, + 'python3': 8000, + 'r': 3000, + 'java': 3000 + } + if size > limits.get(self.LANG, sys.maxsize): + raise udf.SkipTest('test is to slow') + + self.query(''' + SELECT max(fn1.vectorsize(%d, float1)) + FROM TEST.ENGINETABLEBIG1 + ''' % size) + + data = [ + (10, 10, 10), + (100, 100, 100), + (1000, 100, 100), + (10000, 100, 100), + (100000, 100, 100), + (351850, 100, 100), + (100, 10, 100000), + (100, 100, 10000), + (100, 1000, 1000), + (100, 10000, 100), + (100, 100000, 10), + ] + + @useData(data) + def test_vectorsize_set(self, a, b, c): + q = ''' + SELECT max(o) + FROM ( + SELECT fn1.vectorsize_set(%d, %d, n) + FROM ( + SELECT fn1.basic_range(%d) + FROM DUAL + ) + ) + ''' % (a, b, c) + self.query(q) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/basic.py b/test_container/tests/test/generic/python3/basic.py new file mode 100755 index 000000000..30769c6f0 --- /dev/null +++ b/test_container/tests/test/generic/python3/basic.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import requires + + +class BasicTest(udf.TestCase): + + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create all UDFs needed for BasicTest + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT basic_range(n INTEGER) + EMITS (n INTEGER) AS + def run(ctx): + if ctx.n is not None: + for i in range(ctx.n): + ctx.emit(i) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT basic_sum(x INTEGER) + RETURNS INTEGER AS + def run(ctx): + s = 0 + while True: + if ctx.x is not None: + s += ctx.x + if not ctx.next(): + break + return s + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT basic_emit_two_ints() + EMITS (i INTEGER, j INTEGER) AS + def run(ctx): + ctx.emit(1,2) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT basic_nth_partial_sum(n INTEGER) + RETURNS INTEGER AS + def run(ctx): + if ctx.n is not None: + return ctx.n * (ctx.n + 1) / 2 + return 0 + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT basic_sum_grp(x INTEGER) + EMITS (s INTEGER) AS + def run(ctx): + s = 0 + while True: + if ctx.x is not None: + s += ctx.x + if not ctx.next(): + break + ctx.emit(s) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT basic_emit_several_groups(a INTEGER, b INTEGER) + EMITS (i INTEGER, j VARCHAR(40)) AS + def run(ctx): + for n in range(ctx.a): + for i in range(ctx.b): + ctx.emit(i, repr((exa.meta.vm_id, exa.meta.node_count, exa.meta.node_id))) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT basic_test_reset(i INTEGER, j VARCHAR(40)) + EMITS (k INTEGER) AS + def run(ctx): + ctx.emit(ctx.i) + ctx.next() + ctx.emit(ctx.i) + ctx.reset() + ctx.emit(ctx.i) + ctx.next() + ctx.emit(ctx.i) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT performance_map_characters(text VARCHAR(1000)) + EMITS (w CHAR(1), c INTEGER) AS + def run(ctx): + if ctx.text is not None: + for c in ctx.text: + ctx.emit(c, 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT performance_reduce_characters(w CHAR(1), c INTEGER) + EMITS (w CHAR(1), c INTEGER) AS + def run(ctx): + c = 0 + w = ctx.w + if w is not None: + while True: + c += 1 + if not ctx.next(): break + ctx.emit(w, c) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT set_returns_has_empty_input(a double) + RETURNS boolean AS + def run(ctx): + return bool(ctx.x is None) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT set_emits_has_empty_input(a double) + EMITS (x double, y varchar(10)) AS + def run(ctx): + if ctx.x is None: + ctx.emit(1,'1') + else: + ctx.emit(2,'2') + / + ''')) + + def test_basic_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_range(3) + FROM DUAL + ''') + self.assertRowsEqual([(x,) for x in range(3)], sorted(rows)) + + def test_basic_set_returns(self): + rows = self.query(''' + SELECT fn1.basic_sum(3) + FROM DUAL + ''') + self.assertRowsEqual([(3,)], rows) + + def test_emit_two_ints(self): + rows = self.query(''' + SELECT fn1.basic_emit_two_ints() + FROM DUAL''') + self.assertRowsEqual([(1, 2)], rows) + + def test_simple_combination(self): + rows = self.query(''' + SELECT fn1.basic_sum(psum) + FROM ( + SELECT fn1.basic_nth_partial_sum(n) AS PSUM + FROM ( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(165,)], rows) + + def test_simple_combination_grouping(self): + rows = self.query(''' + SELECT fn1.BASIC_SUM_GRP(psum) + FROM ( + SELECT MOD(N, 3) AS n, + fn1.basic_nth_partial_sum(n) AS psum + FROM ( + SELECT fn1.basic_range(10) + FROM DUAL + ) + ) + GROUP BY n + ORDER BY 1''') + self.assertRowsEqual([(39.0,), (54.0,), (72.0,)], rows) + + def test_reset(self): + rows = self.query(''' + SELECT fn1.basic_test_reset(i, j) + FROM (SELECT fn1.basic_emit_several_groups(16, 8) FROM DUAL) + GROUP BY i + ORDER BY 1''') + self.assertRowsEqual([(0.0,), (0.0,), (0.0,), (0.0,), (1.0,), (1.0,), (1.0,), (1.0,), (2.0,)], rows[:9]) + + def test_order_by_clause(self): + rows = self.query(''' + SELECT fn1.performance_reduce_characters(w, c) + FROM ( + SELECT fn1.performance_map_characters('hello hello hello abc') + FROM DUAL + ) + GROUP BY w + ORDER BY c DESC''') + + unsorted_list = [tuple(x) for x in rows] + sorted_list = sorted(unsorted_list, key=lambda x: x[1], reverse=True) + #for x in zip(unsorted_list, sorted_list): + # print x + + self.assertEqual(sorted_list, unsorted_list) + + +class SetWithEmptyInput(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('OPEN SCHEMA FN1') + self.query('CREATE TABLE FN2.empty_table(c int)') + + # Create UDFs needed for SetWithEmptyInput tests + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT set_returns_has_empty_input(a double) + RETURNS boolean AS + def run(ctx): + return bool(ctx.x is None) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT set_emits_has_empty_input(a double) + EMITS (x double, y varchar(10)) AS + def run(ctx): + if ctx.x is None: + ctx.emit(1,'1') + else: + ctx.emit(2,'2') + / + ''')) + + def test_set_returns_has_empty_input_group_by(self): + self.query("""select FN1.set_returns_has_empty_input(c) from FN2.empty_table group by 'X'""") + self.assertEqual(0, self.rowcount()) + + def test_set_returns_has_empty_input_no_group_by(self): + rows = self.query('''select FN1.set_returns_has_empty_input(c) from FN2.empty_table''') + self.assertRowsEqual([(None,)], rows) + + + def test_set_emits_has_empty_input_group_by(self): + self.query("""select FN1.set_emits_has_empty_input(c) from FN2.empty_table group by 'X'""") + self.assertEqual(0, self.rowcount()) + + def test_set_emits_has_empty_input_no_group_by(self): + rows = self.query('''select FN1.set_emits_has_empty_input(c) from FN2.empty_table''') + self.assertRowsEqual([(None,None)], rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/combinations.py b/test_container/tests/test/generic/python3/combinations.py new file mode 100755 index 000000000..da9793ef9 --- /dev/null +++ b/test_container/tests/test/generic/python3/combinations.py @@ -0,0 +1,450 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import useData + + +class _Python3UdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT basic_range(n INTEGER) + EMITS (n INTEGER) AS + def run(ctx): + if ctx.n is not None: + for i in range(ctx.n): + ctx.emit(i) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT basic_sum(x INTEGER) + RETURNS INTEGER AS + def run(ctx): + s = 0 + while True: + if ctx.x is not None: + s += ctx.x + if not ctx.next(): + break + return s + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT basic_sum_grp(x INTEGER) + EMITS (s INTEGER) AS + def run(ctx): + s = 0 + while True: + if ctx.x is not None: + s += ctx.x + if not ctx.next(): + break + ctx.emit(s) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT SCALAR_EMITS(x double, y double) + EMITS (x double, y double) AS + def run(ctx): + for i in range(int(ctx.x), int(ctx.y+1)): + ctx.emit(float(i), float(i * i)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT SCALAR_RETURNS(x double, y double) + RETURNS double AS + def run(ctx): + return ctx.x + ctx.y + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT SET_EMITS(x double, y double) + EMITS (x double, y double) AS + def run(ctx): + while True: + ctx.emit(ctx.y, ctx.x) + if not ctx.next(): break + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT SET_RETURNS(x double, y double) + RETURNS double AS + def run(ctx): + acc = 0.0 + while True: + acc = acc + ctx.x + ctx.y + if not ctx.next(): break + return acc + / + ''')) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE combinations.small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO combinations.small VALUES (0.1, 0.2), (0.2, 0.1)') + + + +class Combinations_1_ary(_Python3UdfSetup): + def test_set_returns(self): + rows = self.query(''' + SELECT fn1.SET_RETURNS(x,y) + FROM combinations.small''') + self.assertEqual(round(0.6 / 2), round(rows[0][0] / 2)) + + + def test_scalar_returns(self): + rows = self.query(''' + SELECT round(fn1.scalar_returns(x,y) / 2) + FROM combinations.small''') + self.assertRowsEqual([(round(0.3 / 2),), (round(0.3 / 2),)], rows) + + def test_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10 ,y * 10) + FROM combinations.small''') + self.assertRowsEqual([(1, 1,), (2, 4,)], rows) + + def test_set_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x * 10 ,y * 10) + FROM combinations.small''') + self.assertRowsEqual([(2.0, 1.0,), (1.0, 2.0,)], rows) + + def test_two_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.scalar_returns(fn1.scalar_returns(x * 10 ,y * 10), + fn1.scalar_returns(y * 10 ,x * 10)) + FROM combinations.small''') + self.assertRowsEqual([(6,), (6,)], rows) + + +class Combinations_2_ary_scalar_returns(_Python3UdfSetup): + def test_scalar_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_returns(x * 10 ,y * 10 ) + FROM ( + SELECT fn1.scalar_emits(x * 10 ,y * 10) + FROM combinations.small + )''') + self.assertRowsEqual([(20,), (60,)], rows) + + def test_scalar_returns_set_returns_inline(self): + rows = self.query(''' + SELECT + fn1.scalar_returns( + fn1.set_returns(x * 10, y * 10), + fn1.set_returns(x * 10, y * 10) + ) + FROM combinations.small''') + self.assertRowsEqual([(12,)], rows) + + def test_scalar_returns_set_returns_1(self): + rows = self.query(''' + SELECT fn1.scalar_returns(a, 5) + FROM ( + SELECT fn1.set_returns(x * 10, y * 10) AS a + FROM combinations.SMALL + ) + ''') + self.assertRowsEqual([(11,)], rows) + + def test_scalar_returns_set_returns_2(self): + rows = self.query(''' + SELECT fn1.scalar_returns(aa.a, bb.a) + FROM ( + SELECT fn1.set_returns(x * 10, y * 20) AS a + FROM combinations.SMALL + ) AS aa, + ( + SELECT fn1.set_returns(x * 20, y * 10) AS a + FROM combinations.SMALL + ) AS bb + ''') + self.assertRowsEqual([(18,)], rows) + + def test_scalar_returns_set_emits_1(self): + rows = self.query(''' + SELECT fn1.scalar_returns(x * 10, y * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM combinations.small + )''') + self.assertRowsEqual([(30,), (30,)], rows) + + def test_scalar_returns_set_emits_2(self): + rows = self.query(''' + SELECT fn1.scalar_returns(aa.x * 10, bb.x * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM combinations.small + ) AS aa, + ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM combinations.small + ) AS bb + WHERE aa.x = bb.y and aa.y = bb.x + ''') + self.assertRowsEqual([(30,), (30,)], rows) + + +class Combinations_2_ary_scalar_emits(_Python3UdfSetup): + def test_scalar_emits_scalar_returns_inline(self): + rows = self.query(''' + SELECT + fn1.scalar_emits( + fn1.scalar_returns(x * 10, y * 10), + fn1.scalar_returns(y * 10, x * 10) + ) + FROM combinations.small''') + self.assertRowsEqual([(3, 9,), (3, 9,)], rows) + + def test_scalar_emits_scalar_returns(self): + rows = self.query(''' + SELECT fn1.scalar_emits(a, b) + FROM ( + SELECT + fn1.scalar_returns(x * 10 ,y * 10) AS A, + fn1.scalar_returns(y * 10 ,x * 10) AS B + FROM combinations.small + ) + ''') + self.assertRowsEqual([(3, 9,), (3, 9,)], rows) + + def test_scalar_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10,y * 10) + FROM ( + SELECT fn1.scalar_emits(x * 10,y * 10) + FROM combinations.small + ) + ORDER by x,y''') + r = [(10.0, 100.0)] + r.extend([(i, i * i) for i in range(20, 41)]) + self.assertNotEqual(r, rows) + + def test_scalar_emits_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.scalar_emits( + fn1.set_returns(x * 10, y * 10), + fn1.set_returns(x * 10, y * 10) + ) + FROM combinations.small''') + + def test_scalar_emits_set_returns(self): + rows = self.query(''' + SELECT fn1.scalar_emits(a, b) + FROM ( + SELECT fn1.set_returns(x * 10, y * 10) AS a + FROM combinations.small + ), + ( + SELECT fn1.set_returns(x * 10, y *10) AS b + FROM combinations.small + ) + ''') + self.assertRowsEqual([(6, 36)], rows) + + def test_scalar_emits_set_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10, y * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM combinations.small + )''') + r = ([(i, i * i) for i in range(10, 21)]) + self.assertRowsEqual(r, rows) + + +class Combinations_2_ary_set_returns(_Python3UdfSetup): + def test_set_returns_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.set_returns( + fn1.scalar_returns(x * 10, y * 10), + fn1.scalar_returns(y * 10, x * 10) + ) + FROM combinations.small''') + self.assertRowsEqual([(12,)], rows) + + def test_set_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.set_returns(x*10, y*10) + FROM ( + SELECT fn1.scalar_emits(x*10, y*10) + FROM combinations.small + )''') + self.assertRowsEqual([(80,)], rows) + + def test_set_returns_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.set_returns( + fn1.set_returns(x*10, y*10), + fn1.set_returns(x*10, y*10) + ) + FROM combinations.small''') + + def test_set_returns_set_returns(self): + rows = self.query(''' + SELECT fn1.set_returns(a, b) + FROM( + SELECT fn1.set_returns(x*20, y*30) AS a + FROM combinations.small + ), + ( + SELECT fn1.set_returns(x*50, y*70) AS b + FROM combinations.small + )''') + self.assertRowsEqual([(51,)], rows) + + def test_set_returns_set_emits(self): + rows = self.query(''' + SELECT fn1.set_returns(x*10, y*10) + FROM ( + SELECT fn1.set_emits(x*10, y*10) + FROM combinations.small + )''') + self.assertRowsEqual([(60,)], rows) + + +class Combinations_2_ary_set_emits(_Python3UdfSetup): + def test_set_emits_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.set_emits( + fn1.scalar_returns(x*10, y*10), + fn1.scalar_returns(y*10, x*10) + ) + FROM combinations.small''') + self.assertRowsEqual([(3, 3,), (3, 3,)], rows) + + def test_set_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x*10, y*10) + FROM ( + SELECT fn1.scalar_emits(x*10, 10*y) + FROM combinations.small + ) + ORDER BY x, y;''') + self.assertRowsEqual([(10, 10,), (40, 20,)], rows) + + def test_set_emits_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.set_emits( + fn1.set_returns(x*10, 10*y), + fn1.set_returns(10*x, y*10) + ) + FROM combinations.small''') + + def test_set_emits_set_returns(self): + rows = self.query(''' + SELECT fn1.set_emits(a, b) + FROM ( + SELECT fn1.set_returns(x*20, 30*y) AS a + FROM combinations.small + ), + ( + SELECT fn1.set_returns(50*x, y*70) AS b + FROM combinations.small + ) + ''') + self.assertRowsEqual([(36, 15,)], rows) + + def test_set_emits_set_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x * 10 , 10 * y) + FROM ( + SELECT fn1.set_emits(x * 10, y *10) + FROM combinations.small + ) + ORDER BY x, y;''') + self.assertRowsEqual([(10, 20,), (20, 10,)], rows) + + +class Combinations_3_ary(_Python3UdfSetup): + def test_set_returns_set_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(s) + FROM ( + SELECT fn1.basic_sum_grp(n) + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(45,)], rows) + + def test_set_returns_scalar_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(x) + FROM ( + SELECT fn1.scalar_emits(n, n+2) + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(165,)], rows) + + def test_set_returns_scalar_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(x) + FROM ( + SELECT fn1.scalar_returns(n, 2) AS x + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(65,)], rows) + + +class Combinations_n_ary(_Python3UdfSetup): + @staticmethod + def partial_sum(n, degree): + def basic_range(n, d): + if d == 0: + return list(range(n)) + else: + return sum([list(range(x)) for x in basic_range(n + 1, d - 1)], []) + + return len(basic_range(n, degree)) + + @useData((i,) for i in range(10)) + def test_n_scalar_emits(self, n): + + self.query( + 'SELECT fn1.basic_range(n+1) FROM (\n' * n + + 'SELECT fn1.basic_range(5) FROM DUAL\n' + + ')' * n) + self.assertEqual(self.partial_sum(5, n), self.rowcount()) + + @useData((i,) for i in range(10)) + def test_set_returns_n_scalar_emits(self, n): + + rows = self.query( + 'SELECT max(n) FROM (' + + 'SELECT fn1.basic_range(n+1) FROM (\n' * n + + 'SELECT fn1.basic_range(5) FROM DUAL\n' + + ')' * (n + 1)) + self.assertEqual(4, rows[0][0]) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/dynamic_input.py b/test_container/tests/test/generic/python3/dynamic_input.py new file mode 100755 index 000000000..d61fa53a2 --- /dev/null +++ b/test_container/tests/test/generic/python3/dynamic_input.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _Python3UdfSetup(udf.TestCase): + LANG = 'python3' + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT + basic_scalar_emit( ... ) + EMITS ("v" VARCHAR(2000)) as + def run(ctx): + i = 0 + while i < exa.meta.input_column_count: + ctx.emit(repr(ctx[i])) + i = i + 1 + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT + basic_scalar_return( ... ) + RETURNS VARCHAR(2000) AS + def run(ctx): + return repr(ctx[exa.meta.input_column_count-1]) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT + basic_set_emit( ... ) + EMITS ("v" VARCHAR(2000)) AS + def run(ctx): + var = 'result: ' + while True: + for i in range (0,exa.meta.input_column_count): + ctx.emit(repr(ctx[i])) + var = var + repr(ctx[i]) + ' , ' + if not ctx.next(): break + ctx.emit(var) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT + basic_set_return( ... ) + RETURNS VARCHAR(2000) AS + def run(ctx): + var = 'result: ' + while True: + for i in range (0,exa.meta.input_column_count): + var = var + repr(ctx[i]) + ' , ' + if not ctx.next(): break + return var + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT + empty_set_emits( ... ) + EMITS (x varchar(2000)) AS + def run(ctx): + return 1 + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT + empty_set_returns( ... ) + RETURNS varchar(2000) AS + def run(ctx): + return 1 + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT + metadata_scalar_emit (...) + EMITS("v" VARCHAR(2000)) AS + def run(ctx): + ctx.emit(repr(exa.meta.input_column_count)) + for i in range (0,exa.meta.input_column_count): + ctx.emit(exa.meta.input_columns[i].name) + ctx.emit(repr(exa.meta.input_columns[i].type)) + ctx.emit(exa.meta.input_columns[i].sql_type) + ctx.emit(repr(exa.meta.input_columns[i].precision)) + ctx.emit(repr(exa.meta.input_columns[i].scale)) + ctx.emit(repr(exa.meta.input_columns[i].length)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT + metadata_scalar_return (...) + RETURNS VARCHAR(2000) AS + def run(ctx): + return repr(exa.meta.input_column_count) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT + type_specific_add(...) + RETURNS VARCHAR(2000) AS + def run(ctx): + var = 'result: ' + if repr(exa.meta.input_columns[0].type) == "" or repr(exa.meta.input_columns[0].type) == "": + while True: + for i in range (0,exa.meta.input_column_count): + var = var + ctx[i] + ' , ' + if not ctx.next(): break + else: + sum = 0 + while True: + for i in range (0,exa.meta.input_column_count): + sum = sum + ctx[i] + if not ctx.next(): break + var = var + repr(sum) + return var + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT + wrong_arg(...) + RETURNS varchar(2000) AS + def run(ctx): + return ctx[1] + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT + wrong_operation(...) + RETURNS varchar(2000) AS + def run(ctx): + return ctx[0] * ctx[1] + / + ''')) + + self.query('DROP SCHEMA dynamic_input CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_input') + self.query('CREATE TABLE dynamic_input.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_input.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_input.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_input.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + + + +class DynamicMetadataTest(_Python3UdfSetup): + + def test_meta_scalar_return(self): + rows = self.query(''' + SELECT fn1.metadata_scalar_return('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertRowEqual(('2',), rows[0]) + + def test_meta_scalar_emit(self): + rows = self.query(''' + SELECT fn1.metadata_scalar_emit('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertRowEqual(('2',), rows[0]) + self.assertRowEqual(('0',), rows[1]) + self.assertTrue(rows[2][0] == "string" or rows[2][0] == "" or rows[2][0] == "character" or rows[2][0] == "java.lang.String" or rows[2][0] == "") + self.assertRowEqual(('CHAR(3) ASCII',), rows[3]) + self.assertRowEqual(('3',), rows[6]) + self.assertRowEqual(('1',), rows[7]) + self.assertTrue(rows[8][0] == 'number' or rows[8][0] == "" or rows[8][0] == "double" or rows[8][0] == "java.lang.Double" or rows[8][0] == "") + self.assertRowEqual(('DOUBLE',), rows[9]) + + +class DynamicInputBasic(_Python3UdfSetup): + def test_basic_scalar_emit_constants(self): + rows = self.query(''' + SELECT fn1.basic_scalar_emit('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertTrue(rows[0][0] == 'abc' or rows[0][0] == "u'abc'" or rows[0][0] == "'abc'") + self.assertTrue(rows[1][0] == '99' or rows[1][0] == "99.0") + + def test_basic_scalar_emit(self): + rows = self.query(''' + SELECT fn1.basic_scalar_emit(x, y) + FROM dynamic_input.small + ''') + self.assertTrue(rows[0][0] == 'Some string ... and some more' or rows[0][0] == "u'Some string ... and some more'" or rows[0][0] == "'Some string ... and some more'") + self.assertRowEqual(('2.2',), rows[1]) + + def test_basic_scalar_return_constants(self): + rows = self.query(''' + SELECT fn1.basic_scalar_return('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertTrue(rows[0][0] == '99' or rows[0][0] == "99.0") + + def test_basic_scalar_return(self): + rows = self.query(''' + SELECT fn1.basic_scalar_return(x, y, x, y, x, y, x, y, x, y, x, y, x, y, x, y) + FROM dynamic_input.small + ''') + self.assertRowEqual(('2.2',), rows[0]) + + def test_basic_set_emit_constants(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(cast(99 as double),'77','aaaa') + FROM DUAL + ''') + print("0---:"+str(rows[3][0])) + self.assertTrue(rows[0][0] == '99' or rows[0][0] == "99.0") + self.assertTrue(rows[1][0] == '77' or rows[1][0] == "u'77'" or rows[1][0] == "'77'") + self.assertTrue(rows[2][0] == 'aaaa' or rows[2][0] == "u'aaaa'" or rows[2][0] == "'aaaa'") + self.assertTrue(rows[3][0] == 'result: , 99 , 77 , aaaa' or rows[3][0] == "result: 99.0 , u'77' , u'aaaa' , " or rows[3][0] == "result: 99 , 77 , aaaa , " or rows[3][0] == "result: 99.0 , 77 , aaaa , " or rows[3][0] == "result: 99.0 , '77' , 'aaaa' , ") + + def test_basic_set_emit(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(n, v) + FROM groupt GROUP BY id ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == '1' or rows[0][0] == "1.0" or rows[0][0] == "'aa'") + self.assertTrue(rows[1][0] == '2' or rows[1][0] == "2.0" or rows[1][0] == "'ab'") + self.assertTrue(rows[2][0] == '2' or rows[2][0] == "2.0" or rows[2][0] == "'ba'") + self.assertTrue(rows[3][0] == 'aa' or rows[3][0] == "result: 1.0 , u'aa' , 2.0 , u'ab' , " or rows[3][0] == "1.0") + self.assertTrue(rows[4][0] == 'ab' or rows[4][0] == "result: 2.0 , u'ba' , " or rows[4][0] == "2.0") + self.assertTrue(rows[5][0] == 'ba' or rows[5][0] == "u'aa'" or rows[5][0] == "2.0") + self.assertTrue(rows[6][0] == 'result: , 1 , aa , 2 , ab' or rows[6][0] == "u'ab'" or rows[6][0] == "result: 1 , aa , 2 , ab , " or rows[6][0] == "result: 1.0 , aa , 2.0 , ab , " or rows[6][0] == "result: 1.0 , 'aa' , 2.0 , 'ab' , ") + self.assertTrue(rows[7][0] == 'result: , 2 , ba' or rows[7][0] == "u'ba'" or rows[7][0] == "result: 2 , ba , " or rows[7][0] == "result: 2.0 , ba , " or rows[7][0] == "result: 2.0 , 'ba' , ") + + def test_basic_set_emit_one_group(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(cast(id as double), n, v) + FROM groupt ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == '1' or rows[0][0] == "1.0" or rows[0][0] == "'aa'") + self.assertTrue(rows[1][0] == '1' or rows[1][0] == "1.0" or rows[1][0] == "'ab'") + self.assertTrue(rows[2][0] == '1' or rows[2][0] == "1.0" or rows[2][0] == "'ba'") + self.assertTrue(rows[3][0] == '2' or rows[3][0] == "2.0" or rows[3][0] == "1.0") + self.assertTrue(rows[4][0] == '2' or rows[4][0] == "2.0" or rows[4][0] == "1.0") + self.assertTrue(rows[5][0] == '2' or rows[5][0] == "2.0" or rows[5][0] == "1.0") + self.assertTrue(rows[6][0] == 'aa' or rows[7][0] == "u'aa'" or rows[6][0] == "2.0") + self.assertTrue(rows[7][0] == 'ab' or rows[8][0] == "u'ab'" or rows[7][0] == "2.0") + self.assertTrue(rows[8][0] == 'ba' or rows[9][0] == "u'ba'" or rows[8][0] == "2.0") + self.assertTrue(rows[9][0] == 'result: , 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab' \ + or rows[6][0] == "result: 1.0 , 1.0 , u'aa' , 2.0 , 2.0 , u'ba' , 1.0 , 2.0 , u'ab' , " \ + or rows[9][0] == "result: 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab , " \ + or rows[9][0] == "result: 1.0 , 1.0 , aa , 2.0 , 2.0 , ba , 1.0 , 2.0 , ab , " or rows[9][0] == "result: 1.0 , 1.0 , 'aa' , 2.0 , 2.0 , 'ba' , 1.0 , 2.0 , 'ab' , ") + + def test_basic_set_return_constants(self): + rows = self.query(''' + SELECT fn1.basic_set_return(cast(99 as double),'77','aaaa') + FROM DUAL + ''') + self.assertTrue(rows[0][0] == 'result: , 99 , 77 , aaaa ' \ + or rows[0][0] == "result: 99.0 , u'77' , u'aaaa' , " \ + or rows[0][0] == "result: 99 , 77 , aaaa , " \ + or rows[0][0] == "result: 99.0 , 77 , aaaa , " or rows[0][0] == "result: 99.0 , '77' , 'aaaa' , ") + + def test_basic_set_return(self): + rows = self.query(''' + SELECT fn1.basic_set_return(n, v) + FROM groupt GROUP BY id ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , aa , 2 , ab ' \ + or rows[0][0] == "result: 1.0 , u'aa' , 2.0 , u'ab' , " \ + or rows[0][0] == "result: 1 , aa , 2 , ab , " \ + or rows[0][0] == "result: 1.0 , aa , 2.0 , ab , " or rows[0][0] == "result: 1.0 , 'aa' , 2.0 , 'ab' , ") + self.assertTrue(rows[1][0] == 'result: , 2 , ba ' \ + or rows[1][0] == "result: 2.0 , u'ba' , " \ + or rows[1][0] == "result: 2 , ba , " \ + or rows [1][0] == "result: 2.0 , ba , " or rows [1][0] == "result: 2.0 , 'ba' , ") + + def test_basic_set_return_one_group(self): + rows = self.query(''' + SELECT fn1.basic_set_return(cast(id as double), n, v) + FROM groupt + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab ' \ + or rows[0][0] == "result: 1.0 , 1.0 , u'aa' , 2.0 , 2.0 , u'ba' , 1.0 , 2.0 , u'ab' , " \ + or rows[0][0] == "result: 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab , " \ + or rows[0][0] == "result: 1.0 , 1.0 , aa , 2.0 , 2.0 , ba , 1.0 , 2.0 , ab , " or rows[0][0] == "result: 1.0 , 1.0 , 'aa' , 2.0 , 2.0 , 'ba' , 1.0 , 2.0 , 'ab' , ") + + +class DynamicInputDatatypeSpecific(_Python3UdfSetup): + def test_type_specific_add_string(self): + rows = self.query(''' + SELECT fn1.type_specific_add(v, v, v) + FROM groupt + ''') + self.assertTrue('result: , aa , aa , aa , ba , ba , ba , ab , ab , ab' == rows[0][0] or 'result: aa , aa , aa , ba , ba , ba , ab , ab , ab , ' == rows[0][0]) + + def test_type_specific_add_number(self): + rows = self.query(''' + SELECT fn1.type_specific_add(n,n,n,n,n,n,n,n,n,n) + FROM groupt + ''') + self.assertTrue(rows[0][0] == 'result: 50' or rows[0][0] == "result: 50.0" or rows[0][0] == 'result: 50') + + +class DynamicInputErrors(_Python3UdfSetup): + def test_exception_wrong_arg(self): + if self.LANG == 'r': + raise udf.SkipTest('does not work with R currently') + err_text = { + 'lua': 'out of range', + 'python3': 'does not exist', + 'java': 'does not exist', + } + with self.assertRaisesRegex(Exception, err_text[self.LANG]): + self.query('''select fn1.wrong_arg('a') from dual''') + + def test_exception_wrong_operation(self): + err_text = { + 'lua': 'attempt to perform arithmetic on field', + 'r': 'non-numeric argument to binary operator', + 'python3': 'multiply sequence by non-int of type', + 'java': 'bad operand types for binary operator', + } + with self.assertRaisesRegex(Exception, err_text[self.LANG]): + self.query('''select fn1.wrong_operation('a','b') from dual''') + + def test_exception_empty_set_returns(self): + with self.assertRaisesRegex(Exception, 'data exception - missing input parameters for SET UDF script'): + self.query('''select fn1.empty_set_returns() from dynamic_input.groupt''') + + def test_exception_empty_set_emits(self): + with self.assertRaisesRegex(Exception, 'data exception - missing input parameters for SET UDF script'): + self.query('''select fn1.empty_set_emits() from dynamic_input.groupt''') + +class DynamicInputOptimizations(_Python3UdfSetup): + def test_mapreduce_optimization(self): + rows = self.query(''' + select fn1.basic_set_return("v") from ( select fn1.basic_scalar_emit(n,n,n,n,n,n,n,n,n,n) from dynamic_input.groupt) + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ' \ + or rows[0][0] == "result: u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , " \ + or rows[0][0] == "result: 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , " \ + or rows[0][0] == "result: 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , " or rows[0][0] == "result: '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , ") + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/dynamic_output.py b/test_container/tests/test/generic/python3/dynamic_output.py new file mode 100755 index 000000000..6b02d0047 --- /dev/null +++ b/test_container/tests/test/generic/python3/dynamic_output.py @@ -0,0 +1,3574 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework import exatest + + +class DynamicOutputCreateScript(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + def test_create_script_set(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='VAREMIT_SIMPLE_SET' and SCRIPT_TEXT LIKE 'CREATE % SET SCRIPT "VAREMIT_SIMPLE_SET" ("a" DOUBLE) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_scalar(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='VAREMIT_SIMPLE_SCALAR' and SCRIPT_TEXT LIKE 'CREATE % SCALAR SCRIPT "VAREMIT_SIMPLE_SCALAR" ("a" DOUBLE) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_all_dyn(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='VAREMIT_SIMPLE_ALL_DYN' and SCRIPT_TEXT LIKE 'CREATE % SCALAR SCRIPT "VAREMIT_SIMPLE_ALL_DYN" (...) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_syntax_var(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='VAREMIT_SIMPLE_SYNTAX_VAR' and SCRIPT_TEXT LIKE 'CREATE % SET SCRIPT "VAREMIT_SIMPLE_SYNTAX_VAR" (...) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + +class DynamicOutputTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + self.query('OPEN SCHEMA FN1') + + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + def test_generic_emit(self): + rows = self.query(''' + SELECT fn1.VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100)); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + + def test_all_generic(self): + rows = self.query(''' + SELECT fn1.VAREMIT_ALL_GENERIC('SUPERDYNAMIC') EMITS (a varchar(100)); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + + def test_correctness_emits_subquery(self): + rows = self.query(''' + SELECT "A" || 'x' || "B" FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100), b varchar(100))); + ''') + self.assertRowEqual(('SUPERDYNAMICxSUPERDYNAMIC',), rows[0]) + + def test_correctness_emits_with_grouping(self): + rows = self.query(''' + SELECT 'X' || count(a) || 'X' FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100)) + FROM dynamic_output.groupt GROUP BY id + ) where a = 'SUPERDYNAMIC'; + ''') + self.assertRowEqual(('X2X',), rows[0]) + + + def test_correctness_nested(self): + rows = self.query(''' + SELECT fn1.VAREMIT_GENERIC_EMIT(c || 'D') EMITS (d varchar(100)) FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT(b || 'C') EMITS (c varchar(100)) FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT(a || 'B') EMITS(b varchar(100)) FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT('A') EMITS (a varchar(100)) + ) + ) + ); + ''') + self.assertRowEqual(('ABCD',), rows[0]) + + + def test_metadata_correctness(self): + rows = self.query(''' + SELECT fn1.VAREMIT_METADATA_SET_EMIT(1) EMITS (a varchar(123), b double) + FROM DUAL + ''') + stringType = { + 'python3': ["", ""], + 'r': ["character"], + 'lua': ["string"], + 'java': ["java.lang.String"] + } + numType = { + 'python3': ["", ""], + 'r': ["double"], + 'lua': ["number"], + 'java': ["java.lang.Double"] + } + self.assertRowEqual(('2',1.0), rows[0]) + self.assertRowEqual(('A',1.0), rows[1]) + self.assertTrue(rows[2][0] in stringType.get(udf.opts.lang or 'python3')) + self.assertRowEqual(('VARCHAR(123) UTF8',1), rows[3]) + self.assertRowEqual(('123',1.0), rows[6]) + self.assertRowEqual(('B',1.0), rows[7]) + self.assertTrue(rows[8][0] in numType.get(udf.opts.lang or 'python3')) + self.assertRowEqual(('DOUBLE',1.0), rows[9]) + + +class DynamicOutputWrongUsage(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_error_emit_missing(self): + #with self.assertRaisesRegex(Exception, 'The script has dynamic return arguments. Either specify the return arguments in the query via EMITS or implement the method (default_output_columns|getDefaultOutputColumns|defaultOutputColumns) in the UDF'): + with self.assertRaisesRegex(Exception, 'The script has dynamic return arguments. Either specify the return arguments in the query via EMITS or implement the method'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1)''') + + def test_error_empty_emit(self): + with self.assertRaisesRegex(Exception, 'Empty return argument definition is not allowed'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1) EMITS ();''') + + def test_error_empty_emit_2(self): + with self.assertRaisesRegex(Exception, 'syntax error'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1) EMITS (a);''') + + def test_error_wrong_emit(self): + with self.assertRaisesRegex(Exception, 'syntax error'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1) EMITS (int);''') + + def test_error_redundant_name(self): + with self.assertRaisesRegex(Exception, 'Return argument A is declared more than once'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1) EMITS (a int, b int, a int);''') + + def test_error_non_var_emit(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''SELECT fn1.VAREMIT_NON_VAR_EMIT(1) EMITS (a double);''') + + def test_error_non_var_emit_2(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''SELECT fn1.VAREMIT_NON_VAR_EMIT(1) EMITS ();''') + + def test_error_returns_not_supported(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''select fn1.VAREMIT_SIMPLE_RETURNS(1) EMITS (a INT);''') + + def test_error_built_in_set_not_supported(self): + with self.assertRaisesRegex(Exception, 'emits specification is not allowed for built-in functions'): + self.query('''SELECT AVG(a) EMITS(a int) FROM VAREMITS;''') + + def test_error_built_in_scalar_not_supported(self): + with self.assertRaisesRegex(Exception, 'emits specification is not allowed for built-in functions'): + self.query('''SELECT -ABS(a) EMITS(a int) FROM VAREMITS;''') + + +class DynamicOutputInsertInto(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_insert_basic(self): + self.query('''delete from dynamic_output.target;''') + self.query(''' + insert into dynamic_output.target select fn1.VAREMIT_EMIT_INPUT(1, CAST (1.1 AS DOUBLE), 'a'); + ''') + rows = self.query(''' + select * from dynamic_output.target; + ''') + self.assertRowEqual((1, 1.1, 'a'), rows[0]) + self.query('''delete from dynamic_output.target;''') + + def test_insert_metadata_correctness(self): + self.query(''' + insert into dynamic_output.target select fn1.VAREMIT_EMIT_INPUT_WITH_META_CHECK(cast (2 as int), CAST (2.2 AS DOUBLE), cast ('b' as varchar(100))); + ''') + rows = self.query(''' + select * from dynamic_output.target; + ''') + self.assertRowEqual((2, 2.2, 'b'), rows[0]) + self.query('''delete from dynamic_output.target;''') + + def test_insert_target_columns_change_order(self): + self.query(''' + insert into dynamic_output.target (c, b, a) select fn1.VAREMIT_EMIT_INPUT('c', CAST (3.3 AS DOUBLE), 3); + ''') + rows = self.query(''' + select * from dynamic_output.target; + ''') + self.assertRowEqual((3, 3.3, 'c'), rows[0]) + self.query('''delete from dynamic_output.target;''') + + def test_insert_target_columns_subset(self): + self.query(''' + insert into dynamic_output.target (b) select fn1.VAREMIT_EMIT_INPUT(CAST (4.4 AS DOUBLE)); + ''') + rows = self.query(''' + select * from dynamic_output.target; + ''') + self.assertRowEqual((None, 4.4, None), rows[0]) + self.query('''delete from dynamic_output.target;''') + + def test_insert_emits_not_allowed(self): + with self.assertRaisesRegex(Exception, 'The return arguments for EMITS functions are inferred from the table to insert into. Specification of EMITS is not allowed in this case.'): + self.query('''insert into dynamic_output.target select FN1.VAREMIT_EMIT_INPUT(1) emits (a int);''') + + +class DynamicOutputCreateTableAs(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_insert_basic(self): + self.query('''drop table if exists dynamic_output.targetcreated;''') + self.query(''' + create table dynamic_output.targetcreated as select fn1.VAREMIT_EMIT_INPUT(1, CAST (1.1 AS DOUBLE), 'a') emits (a decimal(20,0), b double, c varchar(100)); + ''') + rows = self.query(''' + select * from dynamic_output.targetcreated; + ''') + self.assertRowEqual((1, 1.1, 'a'), rows[0]) + rows = self.query(''' + describe dynamic_output.targetcreated; + ''') + self.assertRowEqual(('A', 'DECIMAL(20,0)'), rows[0][0:2]) + self.assertRowEqual(('B', 'DOUBLE'), rows[1][0:2]) + self.assertRowEqual(('C', 'VARCHAR(100) UTF8'), rows[2][0:2]) + +## ##################################################### +## The same as above but now with default output columns +## ##################################################### + +class DefaultDynamicOutputCreateScript(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_create_script_set(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='DEFAULT_VAREMIT_SIMPLE_SET' and SCRIPT_TEXT LIKE 'CREATE % SET SCRIPT "DEFAULT_VAREMIT_SIMPLE_SET" ("a" DOUBLE) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_scalar(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='DEFAULT_VAREMIT_SIMPLE_SCALAR' and SCRIPT_TEXT LIKE 'CREATE % SCALAR SCRIPT "DEFAULT_VAREMIT_SIMPLE_SCALAR" ("a" DOUBLE) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_all_dyn(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='DEFAULT_VAREMIT_SIMPLE_ALL_DYN' and SCRIPT_TEXT LIKE 'CREATE % SCALAR SCRIPT "DEFAULT_VAREMIT_SIMPLE_ALL_DYN" (...) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_syntax_var(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR' and SCRIPT_TEXT LIKE 'CREATE % SET SCRIPT "DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR" (...) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + +class DefaultDynamicOutputTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_generic_emit(self): + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100)); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC'); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + + def test_all_generic(self): + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_ALL_GENERIC('SUPERDYNAMIC') EMITS (a varchar(100)); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_ALL_GENERIC('SUPERDYNAMIC'); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + + + def test_correctness_emits_subquery(self): + rows = self.query(''' + SELECT "A" || 'x' || "B" FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100), b varchar(100))); + ''') + self.assertRowEqual(('SUPERDYNAMICxSUPERDYNAMIC',), rows[0]) + rows = self.query(''' + SELECT "A" || 'x' || "A" FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC')); + ''') + self.assertRowEqual(('SUPERDYNAMICxSUPERDYNAMIC',), rows[0]) + + def test_correctness_emits_with_grouping(self): + rows = self.query(''' + SELECT 'X' || count(a) || 'X' FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100)) + FROM dynamic_output.groupt GROUP BY id + ) where a = 'SUPERDYNAMIC'; + ''') + self.assertRowEqual(('X2X',), rows[0]) + rows = self.query(''' + SELECT 'X' || count(a) || 'X' FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') + FROM dynamic_output.groupt GROUP BY id + ) where a = 'SUPERDYNAMIC'; + ''') + self.assertRowEqual(('X2X',), rows[0]) + + + def test_correctness_nested(self): + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(c || 'D') EMITS (d varchar(100)) FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(b || 'C') EMITS (c varchar(100)) FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(a || 'B') EMITS(b varchar(100)) FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('A') EMITS (a varchar(100)) + ) + ) + ); + ''') + self.assertRowEqual(('ABCD',), rows[0]) + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(a || 'D') FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(a || 'C') FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(a || 'B') FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('A') + ) + ) + ); + ''') + self.assertRowEqual(('ABCD',), rows[0]) + + + def test_metadata_correctness(self): + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_METADATA_SET_EMIT(1) EMITS (a varchar(123), b double) + FROM DUAL + ''') + stringType = { + 'python3': ["", ""], + 'r': ["character"], + 'lua': ["string"], + 'java': ["java.lang.String"] + } + numType = { + 'python3': ["", ""], + 'r': ["double"], + 'lua': ["number"], + 'java': ["java.lang.Double"] + } + self.assertRowEqual(('2',1.0), rows[0]) + self.assertRowEqual(('A',1.0), rows[1]) + self.assertTrue(rows[2][0] in stringType.get(udf.opts.lang or 'python3')) + self.assertRowEqual(('VARCHAR(123) UTF8',1), rows[3]) + self.assertRowEqual(('123',1.0), rows[6]) + self.assertRowEqual(('B',1.0), rows[7]) + self.assertTrue(rows[8][0] in numType.get(udf.opts.lang or 'python3')) + self.assertRowEqual(('DOUBLE',1.0), rows[9]) + # now again with intrinsic emits clause + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_METADATA_SET_EMIT(1) + FROM DUAL + ''') + self.assertRowEqual(('2',1.0), rows[0]) + self.assertRowEqual(('A',1.0), rows[1]) + self.assertTrue(rows[2][0] in stringType.get(udf.opts.lang or 'python3')) + self.assertRowEqual(('VARCHAR(123) UTF8',1), rows[3]) + self.assertRowEqual(('123',1.0), rows[6]) + self.assertRowEqual(('B',1.0), rows[7]) + self.assertTrue(rows[8][0] in numType.get(udf.opts.lang or 'python3')) + self.assertRowEqual(('DOUBLE',1.0), rows[9]) + + +class DefaultDynamicOutputWrongUsage(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + ## def test_error_emit_missing(self): + ## with self.assertRaisesRegex(Exception, 'The script has dynamic return args, but EMITS specification is missing in the query'): + ## self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1)''') + + def test_error_empty_emit(self): + with self.assertRaisesRegex(Exception, 'Empty return argument definition is not allowed'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1) EMITS ();''') + + def test_error_empty_emit_2(self): + with self.assertRaisesRegex(Exception, 'syntax error'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1) EMITS (a);''') + + def test_error_wrong_emit(self): + with self.assertRaisesRegex(Exception, 'syntax error'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1) EMITS (int);''') + + def test_error_redundant_name(self): + with self.assertRaisesRegex(Exception, 'Return argument A is declared more than once'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1) EMITS (a int, b int, a int);''') + + def test_error_non_var_emit(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_NON_VAR_EMIT(1) EMITS (a double);''') + + def test_error_non_var_emit_2(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_NON_VAR_EMIT(1) EMITS ();''') + + def test_error_returns_not_supported(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''select fn1.DEFAULT_VAREMIT_SIMPLE_RETURNS(1) EMITS (a INT);''') + + +class DefaultDynamicOutputInsertInto(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_insert_basic(self): + self.query('''delete from dynamic_output.target;''') + self.query(''' + insert into dynamic_output.target select fn1.DEFAULT_VAREMIT_EMIT_INPUT(1, CAST (1.1 AS DOUBLE), 'a'); + ''') + rows = self.query(''' + select * from dynamic_output.target; + ''') + self.assertRowEqual((1, 1.1, 'a'), rows[0]) + self.query('''delete from dynamic_output.target;''') + + def test_insert_metadata_correctness(self): + self.query(''' + insert into dynamic_output.target select fn1.DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK(cast (2 as int), CAST (2.2 AS DOUBLE), cast ('b' as varchar(100))); + ''') + rows = self.query(''' + select * from dynamic_output.target; + ''') + self.assertRowEqual((2, 2.2, 'b'), rows[0]) + self.query('''delete from dynamic_output.target;''') + + def test_insert_target_columns_change_order(self): + self.query(''' + insert into dynamic_output.target (c, b, a) select fn1.DEFAULT_VAREMIT_EMIT_INPUT('c', CAST (3.3 AS DOUBLE), 3); + ''') + rows = self.query(''' + select * from dynamic_output.target; + ''') + self.assertRowEqual((3, 3.3, 'c'), rows[0]) + self.query('''delete from dynamic_output.target;''') + + def test_insert_target_columns_subset(self): + self.query(''' + insert into dynamic_output.target (b) select fn1.DEFAULT_VAREMIT_EMIT_INPUT(CAST (4.4 AS DOUBLE)); + ''') + rows = self.query(''' + select * from dynamic_output.target; + ''') + self.assertRowEqual((None, 4.4, None), rows[0]) + self.query('''delete from dynamic_output.target;''') + + def test_insert_emits_not_allowed(self): + with self.assertRaisesRegex(Exception, 'The return arguments for EMITS functions are inferred from the table to insert into. Specification of EMITS is not allowed in this case.'): + self.query('''insert into dynamic_output.target select FN1.DEFAULT_VAREMIT_EMIT_INPUT(1) emits (a int);''') + + +class DefaultDynamicOutputCreateTableAs(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_insert_basic(self): + self.query('''drop table if exists dynamic_output.targetcreated;''') + self.query(''' + create table dynamic_output.targetcreated as select fn1.DEFAULT_VAREMIT_EMIT_INPUT(1, CAST (1.1 AS DOUBLE), 'a') emits (a decimal(20,0), b double, c varchar(100)); + ''') + rows = self.query(''' + select * from dynamic_output.targetcreated; + ''') + self.assertRowEqual((1, 1.1, 'a'), rows[0]) + rows = self.query(''' + describe dynamic_output.targetcreated; + ''') + self.assertRowEqual(('A', 'DECIMAL(20,0)'), rows[0][0:2]) + self.assertRowEqual(('B', 'DOUBLE'), rows[1][0:2]) + self.assertRowEqual(('C', 'VARCHAR(100) UTF8'), rows[2][0:2]) + + +class DefaultDynamicOutputEmptyStringResult(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_empty_string_error(self): + with self.assertRaisesRegex(Exception, 'Empty default output columns'): + self.query('''select fn1.DEFAULT_VAREMIT_EMPTY_DEF(42.42);''') + rows = self.query('''select fn1.DEFAULT_VAREMIT_EMPTY_DEF(42.42) emits (x double);''') + self.assertRowEqual((1.4,), rows[0]) + + +class DefaultDynamicOutputFromInputMeta(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA dynamic_output CASCADE', ignore_errors=True) + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_output') + self.query('CREATE TABLE dynamic_output.small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO dynamic_output.small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table dynamic_output.groupt(id int, n double, v varchar(999))') + self.query('''insert into dynamic_output.groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table dynamic_output.target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + self.query('OPEN SCHEMA FN1') + # Create all Python3 UDFs for dynamic output testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SET (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_SCALAR (a double) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT DEFAULT_VAREMIT_SIMPLE_ALL_DYN (...) EMITS (...) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR (...) EMITS ( ... ) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS + def run(ctx): + ctx.emit(*([ctx[0]]*exa.meta.output_column_count)) + def default_output_columns(): + return "a varchar(100)" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS + def run(ctx): + ctx.emit(repr(exa.meta.output_column_count), 1) + for i in range (0,exa.meta.output_column_count): + ctx.emit(exa.meta.output_columns[i].name, 1) + ctx.emit(repr(exa.meta.output_columns[i].type), 1) + ctx.emit(exa.meta.output_columns[i].sql_type, 1) + ctx.emit(repr(exa.meta.output_columns[i].precision), 1) + ctx.emit(repr(exa.meta.output_columns[i].scale), 1) + ctx.emit(repr(exa.meta.output_columns[i].length), 1) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS + def run(ctx): + ctx.emit(1) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "x double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS + def run(ctx): + return 1 + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a int" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + def default_output_columns(): + return "a varchar(123), b double" + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT DEFAULT_VAREMIT_EMPTY_DEF(X DOUBLE) EMITS (...) AS + def run(ctx): + ctx.emit(1.4) + + def default_output_columns(): + return '' + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT COPY_RELATION (...) EMITS (...) AS + def run(ctx): + record = list() + for col in range(0,exa.meta.input_column_count): + assert exa.meta.input_columns[col].sql_type == exa.meta.output_columns[col].sql_type + record.append(ctx[col]) + ctx.emit(*record) + + def default_output_columns(): + res = list() + for col in range(0,exa.meta.input_column_count): + col_name = exa.meta.input_columns[col].name + try: + col_name = "col_%s" % (int(col_name)) + except ValueError: pass + res.append("%s %s" % (col_name, exa.meta.input_columns[col].sql_type)) + return ",".join(res) + / + ''')) + + + + def test_copy_relation(self): + rows = self.query(''' + select fn1.copy_relation(1,2,3); + ''') + self.assertRowEqual((1, 2, 3), rows[0]) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/emit.py b/test_container/tests/test/generic/python3/emit.py new file mode 100644 index 000000000..b1c3929a9 --- /dev/null +++ b/test_container/tests/test/generic/python3/emit.py @@ -0,0 +1,680 @@ +#!/usr/bin/env python3 + +import datetime + +from exasol_python_test_framework import udf + + +class _Python3UdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + def run(ctx): + ctx.emit(ctx.x, ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y, 3000) + / + ''')) + +class InputOutputMatchingTest(_Python3UdfSetup): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + def run(ctx): + ctx.emit(ctx.x, ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y, 3000) + / + ''')) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create table fn2.t(id double, x double)') + self.query('insert into fn2.t values (100,1),(100,2),(200,3)') + + def test_iomatch_1i_1o(self): + rows = self.query(''' + select x*2, fn1.line_1i_1o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,3,), (6,3,9,), (4,2,6,)]), sorted(rows)) + + def test_iomatch_1i_2o(self): + rows = self.query(''' + select x*2, fn1.line_1i_2o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,1,3,), (6,3,3,9,), (4,2,2,6,)]), sorted(rows)) + + def test_iomatch_2i_1o(self): + rows = self.query(''' + select x*2, fn1.line_2i_1o(x,id), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,101,3,), (6,203,9,), (4,102,6,)]), sorted(rows)) + + def test_iomatch_3i_2o(self): + rows = self.query(''' + select x*2, fn1.line_3i_2o(x,id,id), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,101,3000,3,), (6,203,3000,9,), (4,102,3000,6,)]), sorted(rows)) + + def test_iomatch_dob_1i_1o(self): + rows = self.query(''' + select x*2, fn1.dob_1i_1o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,3,), (2,1,3,), (6,3,9,), (6,3,9,), (4,2,6,), (4,2,6,)]), sorted(rows)) + + +class ColumnNamesTest(_Python3UdfSetup): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + def run(ctx): + ctx.emit(ctx.x, ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y, 3000) + / + ''')) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create table fn2.t(id double, x double)') + self.query('insert into fn2.t values (100,1),(100,2),(200,3)') + + def test_col_names(self): + self.query(''' + create or replace table fn2.foo as select x*2 a, fn1.line_3i_2o(x,id,id), x*3 b + FROM fn2.t + ''') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('A',rows[0][0]) + self.assertEqual('Z1',rows[1][0]) + self.assertEqual('Z2',rows[2][0]) + self.assertEqual('B',rows[3][0]) + + +class DatatypesTest(_Python3UdfSetup): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + def run(ctx): + ctx.emit(ctx.x, ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y, 3000) + / + ''')) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + + def test_boolean(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x BOOLEAN)') + self.query('insert into fn2.dt values false') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(False,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('BOOLEAN', rows[0][1]) + + def test_double(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DOUBLE)') + self.query('insert into fn2.dt values 32768e100') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(3.2768e+104,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DOUBLE', rows[0][1]) + + def test_dec_32bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(9,0)', rows[0][1]) + + def test_dec_64bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(18,0)', rows[0][1]) + + def test_dec_128bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(36,0)', rows[0][1]) + + def test_dec_32bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,1))') + self.query('insert into fn2.dt values 99999999.1') + rows = self.query(''' + select x = 99999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(9,1)', rows[0][1]) + + def test_dec_64bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,1))') + self.query('insert into fn2.dt values 9999999999999999.1') + rows = self.query(''' + select x = 9999999999999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(18,1)', rows[0][1]) + + def test_dec_128bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,1))') + self.query('insert into fn2.dt values 999999999999999999999999999999999.1') + rows = self.query(''' + select x = 999999999999999999999999999999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(36,1)', rows[0][1]) + + def test_timestamp(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x TIMESTAMP)') + self.query(''' + insert into fn2.dt values '2010-01-01 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.datetime(2010, 1, 1, 23, 33, 33),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('TIMESTAMP(3)', rows[0][1]) + + def test_timestamp_with_timezone(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x TIMESTAMP WITH LOCAL TIME ZONE)') + self.query(''' + insert into fn2.dt values '2010-01-01 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.datetime(2010, 1, 1, 23, 33, 33),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('TIMESTAMP(3) WITH LOCAL TIME ZONE', rows[0][1]) + + def test_date(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DATE)') + self.query(''' + insert into fn2.dt values '2010-01-01' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.date(2010, 1, 1),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DATE', rows[0][1]) + + def test_varchar_utf8(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x VARCHAR(3000) UTF8)') + self.query('insert into fn2.dt values repeat(5,300)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*300,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('VARCHAR(3000) UTF8', rows[0][1]) + + def test_varchar_ascii(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x VARCHAR(3000) ASCII)') + self.query('insert into fn2.dt values repeat(5,300)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*300,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('VARCHAR(3000) ASCII', rows[0][1]) + + def test_char_utf8(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x CHAR(2000) UTF8)') + self.query('insert into fn2.dt values repeat(5,2000)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*2000,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('CHAR(2000) UTF8', rows[0][1]) + + def test_char_ascii(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x CHAR(2000) ASCII)') + self.query('insert into fn2.dt values repeat(5,2000)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*2000,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('CHAR(2000) ASCII', rows[0][1]) + + def test_interval_ym(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x INTERVAL YEAR TO MONTH)') + self.query(''' + insert into fn2.dt values '23-11' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('+23-11',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('INTERVAL YEAR(2) TO MONTH', rows[0][1]) + + def test_interval_ds(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x INTERVAL DAY TO SECOND)') + self.query(''' + insert into fn2.dt values '30 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('+30 23:33:33.000',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('INTERVAL DAY(2) TO SECOND(3)', rows[0][1]) + + def test_geometry(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x GEOMETRY)') + self.query(''' + insert into fn2.dt values 'POINT(1 1)' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('POINT (1 1)',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('GEOMETRY', rows[0][1]) + + +class NullTest(_Python3UdfSetup): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT dob_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_1o(x double) + EMITS (y double) AS + def run(ctx): + ctx.emit(ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_1i_2o(x double) + EMITS (y double, z double) AS + def run(ctx): + ctx.emit(ctx.x, ctx.x) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_2i_1o(x double, y double) + EMITS (z double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT line_3i_2o(x double, y double, z double) + EMITS (z1 double, z2 double) AS + def run(ctx): + ctx.emit(ctx.x + ctx.y, 3000) + / + ''')) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + + def test_boolean_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x BOOLEAN)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,True,),(None,0,True,)], rows) + + def test_double_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DOUBLE)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(x), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,None,1,),(None,None,1,)], rows) + + def test_int32_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_int64_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_int128_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_timestamp_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x timestamp)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_date_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x date)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_intervalym_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x interval year to month)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_intervalds_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x interval day to second)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_geo_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x geometry)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_varchar_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x varchar(2000))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_char_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x char(2000))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/export_alias.py b/test_container/tests/test/generic/python3/export_alias.py new file mode 100644 index 000000000..642413761 --- /dev/null +++ b/test_container/tests/test/generic/python3/export_alias.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class ExportAliasTest(udf.TestCase): + result_unknown = 0 + result_ok = 1 + result_failed = 2 + result_test_error = 3 + + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create or replace table fn2.t(a int, z varchar(3000))') + self.query("insert into fn2.t values (1, 'x')") + self.query('create or replace table fn2."tl"(a int, "z" varchar(3000))') + self.query("insert into fn2.\"tl\" values (1, 'x')") + self.query("create connection FOOCONN to 'a' user 'b' identified by 'c'", ignore_errors=True) + + self.query('OPEN SCHEMA FN1') + + # Create all EXPORT UDF scripts + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_test_pass_fail(res varchar(100)) emits (x int) as + def run(ctx): + result = ctx.res + if result == "ok": + ctx.emit(1) + elif result == "failed": + ctx.emit(2) + else: + ctx.emit(3) + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_use_param_foo_bar(...) returns int as + def generate_sql_for_export_spec(export_spec): + if (export_spec.parameters['FOO'] == 'bar' and + export_spec.parameters['BAR'] == 'foo' and + export_spec.connection_name is None and + export_spec.connection is None and + export_spec.has_truncate is False and + export_spec.has_replace is False and + export_spec.created_by is None and + export_spec.source_column_names[0] == '\\"T\\".\\"A\\"' and + export_spec.source_column_names[1] == '\\"T\\".\\"Z\\"'): + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('ok')" + else: + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('failed')" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_use_connection_name(...) returns int as + def generate_sql_for_export_spec(export_spec): + if (export_spec.parameters['FOO'] == 'bar' and + export_spec.parameters['BAR'] == 'foo' and + export_spec.connection_name == 'FOOCONN' and + export_spec.connection is None and + export_spec.has_truncate is False and + export_spec.has_replace is False and + export_spec.created_by is None and + export_spec.source_column_names[0] == '\\"T\\".\\"A\\"' and + export_spec.source_column_names[1] == '\\"T\\".\\"Z\\"'): + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('ok')" + else: + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('failed')" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_use_connection_info(...) returns int as + def generate_sql_for_export_spec(export_spec): + if (export_spec.parameters['FOO'] == 'bar' and + export_spec.parameters['BAR'] == 'foo' and + export_spec.connection_name is None and + export_spec.connection.type == 'password'and + export_spec.connection.address == 'a' and + export_spec.connection.user == 'b' and + export_spec.connection.password == 'c' and + export_spec.has_truncate is False and + export_spec.has_replace is False and + export_spec.created_by is None and + export_spec.source_column_names[0] == '\\"T\\".\\"A\\"' and + export_spec.source_column_names[1] == '\\"T\\".\\"Z\\"'): + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('ok')" + else: + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('failed')" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_use_has_truncate(...) returns int as + def generate_sql_for_export_spec(export_spec): + if (export_spec.parameters['FOO'] == 'bar' and + export_spec.parameters['BAR'] == 'foo' and + export_spec.connection_name is None and + export_spec.connection is None and + export_spec.has_truncate is True and + export_spec.has_replace is False and + export_spec.created_by is None and + export_spec.source_column_names[0] == '\\"T\\".\\"A\\"' and + export_spec.source_column_names[1] == '\\"T\\".\\"Z\\"'): + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('ok')" + else: + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('failed')" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_use_replace_created_by(...) returns int as + def generate_sql_for_export_spec(export_spec): + if (export_spec.parameters['FOO'] == 'bar' and + export_spec.parameters['BAR'] == 'foo' and + export_spec.connection_name is None and + export_spec.connection is None and + export_spec.has_truncate is False and + export_spec.has_replace is True and + export_spec.created_by == 'create table t(a int, z varchar(3000))' and + export_spec.source_column_names[0] == '\\"T\\".\\"A\\"' and + export_spec.source_column_names[1] == '\\"T\\".\\"Z\\"'): + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('ok')" + else: + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('failed')" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_use_column_name_lower_case(...) returns int as + def generate_sql_for_export_spec(export_spec): + if (export_spec.parameters['FOO'] == 'bar' and + export_spec.parameters['BAR'] == 'foo' and + export_spec.connection_name is None and + export_spec.connection is None and + export_spec.has_truncate is False and + export_spec.has_replace is False and + export_spec.created_by is None and + export_spec.source_column_names[0] == '\\"tl\\".\\"A\\"' and + export_spec.source_column_names[1] == '\\"tl\\".\\"z\\"'): + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('ok')" + else: + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('failed')" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_use_column_selection(...) returns int as + def generate_sql_for_export_spec(export_spec): + if (export_spec.parameters['FOO'] == 'bar' and + export_spec.parameters['BAR'] == 'foo' and + export_spec.connection_name is None and + export_spec.connection is None and + export_spec.has_truncate is False and + export_spec.has_replace is False and + export_spec.created_by is None and + export_spec.source_column_names[0] == '\\"tl\\".\\"A\\"' and + export_spec.source_column_names[1] == '\\"tl\\".\\"z\\"'): + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('ok')" + else: + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('failed')" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script expal_use_query(...) returns int as + def generate_sql_for_export_spec(export_spec): + if (export_spec.parameters['FOO'] == 'bar' and + export_spec.parameters['BAR'] == 'foo' and + export_spec.connection_name is None and + export_spec.connection is None and + export_spec.has_truncate is False and + export_spec.has_replace is False and + export_spec.created_by is None and + export_spec.source_column_names[0] == '\\"col1\\"' and + export_spec.source_column_names[1] == '\\"col2\\"'): + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('ok')" + else: + return "select " + exa.meta.script_schema + ".expal_test_pass_fail('failed')" + / + ''')) + + def test_export_use_params(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_param_foo_bar with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_connection_name(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_connection_name AT FOOCONN with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_connection_info(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_connection_info AT 'a' USER 'b' IDENTIFIED BY 'c' with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_has_truncate(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_has_truncate with foo='bar' bar='foo' truncate") + self.assertEqual(self.result_ok, rows) + + def test_export_use_replace_created_by(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_replace_created_by with foo='bar' bar='foo' replace created by 'create table t(a int, z varchar(3000))'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_column_name_lower_case(self): + rows = self.executeStatement("EXPORT fn2.\"tl\" INTO SCRIPT fn1.expal_use_column_name_lower_case with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_column_selection(self): + rows = self.executeStatement("EXPORT fn2.\"tl\"(a, \"z\") INTO SCRIPT fn1.expal_use_column_selection with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_query(self): + rows = self.executeStatement("EXPORT (select a as 'col1', \"z\" as 'col2' from fn2.\"tl\") INTO SCRIPT fn1.expal_use_query with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/generic_types.py b/test_container/tests/test/generic/python3/generic_types.py new file mode 100755 index 000000000..c4f6f2780 --- /dev/null +++ b/test_container/tests/test/generic/python3/generic_types.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _Python3UdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT bottleneck_char10(i VARCHAR(20)) + RETURNS CHAR(10) AS + def run(ctx): + return ctx.i + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT bottleneck_decimal5(i DECIMAL(20, 0)) + RETURNS DECIMAL(5, 0) AS + def run(ctx): + return ctx.i + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT bottleneck_varchar10(i VARCHAR(20)) + RETURNS VARCHAR(10) AS + def run(ctx): + return ctx.i + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_boolean(x BOOLEAN) + RETURNS BOOLEAN AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_char1(x CHAR(1)) + RETURNS CHAR(1) AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_char10(x CHAR(10)) + RETURNS CHAR(10) AS + def run(ctx): + if ctx.x is not None: + if len(ctx.x) != 10: + return None + else: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_date(x DATE) + RETURNS DATE AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_decimal_36_0(x DECIMAL(36,0)) + RETURNS DECIMAL(36,0) AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_decimal_36_36(x DECIMAL(36,36)) + RETURNS DECIMAL(36,36) AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_double(x DOUBLE) + RETURNS DOUBLE AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_integer(x INTEGER) + RETURNS INTEGER AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_timestamp(x TIMESTAMP) + RETURNS TIMESTAMP AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT echo_varchar10(x VARCHAR(10)) + RETURNS VARCHAR(10) AS + def run(ctx): + if ctx.x is not None: + return ctx.x + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT run_func_is_empty() + RETURNS DOUBLE AS + def run(ctx): + pass + / + ''')) + +class TestEcho(_Python3UdfSetup): + + def test_echo_boolean(self): + rows = self.query(''' + SELECT + fn1.echo_boolean(Null) is NULL, + fn1.echo_boolean(True) = True, + fn1.echo_boolean(False) = False + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + def test_echo_char1(self): + rows = self.query(''' + SELECT + fn1.echo_char1(NULL) is NULL, + fn1.echo_char1('a') = 'a' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_char10(self): + rows = self.query(''' + SELECT + fn1.echo_char10(NULL) is NULL, + fn1.echo_char10('ab') = 'ab ' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_date(self): + rows = self.query(''' + SELECT + fn1.echo_date(NULL) is NULL, + fn1.echo_date(current_date()) = current_date() + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_integer_basic(self): + rows = self.query(''' + SELECT + fn1.echo_integer(NULL) is NULL, + fn1.echo_integer(-1) = -1, + fn1.echo_integer(0) = 0, + fn1.echo_integer(1) = 1 + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_integer_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_integer(-(1e18 - 1)) = -(1e18 - 1), + fn1.echo_integer( 1e18 - 1) = 1e18 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_double(self): + rows = self.query(''' + SELECT + fn1.echo_double(NULL) is NULL, + fn1.echo_double(CAST(1.5 AS DOUBLE)) = CAST(1.5 AS DOUBLE), + fn1.echo_double(0) = 0.0, + fn1.echo_double(0.0) = 0.0, + fn1.echo_double(-1.7e-308) = -1.7e-308, + fn1.echo_double(+1.7e-308) = +1.7e-308 + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True, True, True)], rows) + + def test_echo_decimal_36_0_basic(self): + rows = self.query(''' + SELECT + fn1.echo_decimal_36_0(NULL) is NULL, + fn1.echo_decimal_36_0(0) = 0, + fn1.echo_decimal_36_0(0.0) = 0.0 + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_decimal_36_0_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_decimal_36_0(-(1e35 - 1)) = -(1e35 - 1), + fn1.echo_decimal_36_0( 1e35 - 1) = 1e35 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_decimal_36_36_basic(self): + rows = self.query(''' + SELECT + fn1.echo_decimal_36_36(NULL) is NULL, + fn1.echo_decimal_36_36(0) = 0, + fn1.echo_decimal_36_36(0.0) = 0.0 + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_decimal_36_36_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_decimal_36_36(-(1e-35 - 1)) = -(1e-35 - 1), + fn1.echo_decimal_36_36( 1e-35 - 1) = 1e-35 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_varchar10(self): + rows = self.query(''' + SELECT + fn1.echo_varchar10(NULL) is NULL, + fn1.echo_varchar10('') is NULL, + fn1.echo_varchar10(' ') = ' ', + fn1.echo_varchar10('a') = 'a', + fn1.echo_varchar10('a ') = 'a ', + fn1.echo_varchar10(' a ') = ' a ' + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True, True, True)], rows) + + def test_echo_timestamp(self): + rows = self.query(''' + SELECT fn1.echo_timestamp(NULL) is NULL + FROM DUAL''') + self.assertRowsEqual([(True,)], rows) + + rows = self.query(''' + SELECT fn1.echo_timestamp('2017-08-01 13:13:50.910') = '2017-08-01 13:13:50.910', + fn1.echo_timestamp('2017-08-01 13:13:50.983') = '2017-08-01 13:13:50.983' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + self.query('DROP SCHEMA ECHO_TEST CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA ECHO_TEST') + self.query('''CREATE OR REPLACE TABLE ECHO_TEST.N1 (now VARCHAR(255))''') + self.query('''INSERT INTO ECHO_TEST.N1 VALUES (SELECT now() AS x FROM DUAL)''') + self.query(''' + SELECT x1 = x2, x1, x2, x3 + FROM (SELECT fn1.echo_timestamp(x) AS x1, x AS x2, x AS x3 + FROM (SELECT now AS x FROM ECHO_TEST.N1)) + ''') + self.assertEqual(True, rows[0][0], str(rows)) + self.query('''DROP SCHEMA ECHO_TEST CASCADE''') + + +class EmptyTest(_Python3UdfSetup): + def test_run_func_is_empty(self): + rows = self.query(''' + SELECT + fn1.run_func_is_empty() IS NULL + FROM DUAL''') + self.assertRowsEqual([(True,)], rows) + + +class BottleneckTest(_Python3UdfSetup): + def test_varchar10(self): + for i in 0, 1, 5, 10: + rows = self.query(''' + SELECT fn1.bottleneck_varchar10('%s') + FROM DUAL''' % ('x' * i)) + self.assertEqual('x' * i if i > 0 else None, rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_varchar10('%s') + FROM DUAL''' % ('x' * 11)) + + def test_char10(self): + for i in 0, 1, 5, 10: + rows = self.query(''' + SELECT fn1.bottleneck_char10('%s') + FROM DUAL''' % ('x' * i)) + self.assertEqual( + ('x' * i + ' ' * 10)[:10] if i > 0 else None, + rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_char10('%s') + FROM DUAL''' % ('x' * 11)) + + def test_decimal5(self): + for i in 3, 4: + rows = self.query(''' + SELECT fn1.bottleneck_decimal5(%d) + FROM DUAL''' % (10 ** i)) + self.assertEqual(10 ** i, rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_decimal5(%d) + FROM DUAL''' % (10 ** 5)) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/get_connection.py b/test_container/tests/test/generic/python3/get_connection.py new file mode 100644 index 000000000..9d50f336b --- /dev/null +++ b/test_container/tests/test/generic/python3/get_connection.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework import exatest + + +class GetConnectionMemoryBug(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create Python3 UDFs for get_connection testing + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SCALAR SCRIPT PRINT_CONNECTION(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SET SCRIPT PRINT_CONNECTION_SET_EMITS(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query( + '''CREATE OR REPLACE CONNECTION test_get_connection_bug_connection TO '' USER 'ialjksdhfalskdjhflaskdjfhalskdjhflaksjdhflaksdjfhalksjdfhlaksjdhflaksjdhfalskjdfhalskdjhflaksjdhflaksjdfhlaksjsadajksdhfaksjdfhalksdjfhalksdjfhalksjdfhqwiueryqw;er;lkjqwe;rdhflaksjdfhlaksdjfhaabcdefghijklmnopqrstuvwxyz' IDENTIFIED BY 'abcdeoqsdfgsdjfglksjdfhglskjdfhglskdjfglskjdfghuietyewlrkjthertrewerlkjhqwelrkjhqwerlkjnwqerlkjhqwerkjlhqwerlkjhqwerlkhqwerkljhqwerlkjhqwerfghijklmnopqrstuvwxyz';''') + + def test_get_connection(self): + for x in range(10): + row = self.query( + '''with ten as (values 0,1,2,3,4,5,6,7,8,9 as p(x)) select count(*) from (select fn1.print_connection_set_emits('test_get_connection_bug_connection') from (select a.x from ten a, ten, ten, ten, ten) v group by mod(v.rownum,4019))''')[ + 0] + self.assertEqual(4019, row[0]) + + +class AccessConnectionSysPriv(udf.TestCase): + def testSysPrivExists(self): + sys_priv = self.query("SELECT * FROM EXA_DBA_SYS_PRIVS WHERE PRIVILEGE = 'ACCESS ANY CONNECTION'") + self.assertRowsEqual([("DBA", "ACCESS ANY CONNECTION", True)], sys_priv) + + +class GetConnectionTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create Python3 UDFs for get_connection testing + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SCALAR SCRIPT PRINT_CONNECTION(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SET SCRIPT PRINT_CONNECTION_SET_EMITS(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(''' + create connection FOOCONN to 'a' user 'b' identified by 'c' + ''') + + def tearDown(self): + self.query('drop connection FOOCONN') + + def test_print_existing_connection(self): + rows = self.query(''' + SELECT fn1.print_connection('FOOCONN') + ''') + self.assertRowsEqual([('password', 'a', 'b', 'c')], rows) + + def test_connection_not_found(self): + with self.assertRaisesRegex(Exception, 'connection FOO does not exist'): + self.query(''' + SELECT fn1.print_connection('FOO') + ''') + + +class GetConnectionAccessControlTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create Python3 UDFs for get_connection testing + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SCALAR SCRIPT PRINT_CONNECTION(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SET SCRIPT PRINT_CONNECTION_SET_EMITS(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(''' + create connection AC_FOOCONN to 'a' user 'b' identified by 'c' + ''', ignore_errors=True) + + def getConnection(self, username, password): + client = exatest.ODBCClient('exatest') + self.log.debug('connecting to DSN "exa" for user {username}'.format(username=username)) + client.connect(uid=username, pwd=password) + return client + + def createUser(self, username, password): + self.query('DROP USER IF EXISTS {username} CASCADE'.format(username=username)) + self.query('CREATE USER {username} IDENTIFIED BY "{password}"'.format(username=username, password=password)) + self.query('GRANT CREATE SESSION TO {username}'.format(username=username)) + + def testUseConnectionWithoutRights(self): + self.createUser("foo", "foo") + self.query('grant create schema to foo') + self.query('grant create script to foo') + self.query('grant execute on script fn1.print_connection to foo') + self.commit() + foo_conn = self.getConnection('foo', 'foo') + with self.assertRaisesRegex(Exception, + 'insufficient privileges for using connection AC_FOOCONN in script PRINT_CONNECTION'): + foo_conn.query(''' + SELECT fn1.print_connection('AC_FOOCONN') + ''') + foo_conn.commit() + self.query('drop user foo cascade') + self.commit() + + def testUseConnectionWithOldRight(self): + self.createUser("foo", "foo") + self.query('grant create schema to foo') + self.query('grant create script to foo') + self.query('grant connection ac_fooconn to foo') + self.query('grant execute on script fn1.print_connection to foo') + self.commit() + foo_conn = self.getConnection('foo', 'foo') + with self.assertRaisesRegex(Exception, + 'insufficient privileges for using connection AC_FOOCONN in script PRINT_CONNECTION'): + foo_conn.query(''' + select fn1.print_connection('AC_FOOCONN') + ''') + foo_conn.commit() + self.query('drop user foo cascade') + self.commit() + + def testUseConnectionWithNewRight(self): + self.createUser("foo", "foo") + self.query('grant create schema to foo') + self.query('grant create script to foo') + self.query('grant execute on script fn1.print_connection to foo') + self.query('GRANT ACCESS ON CONNECTION ac_fooconn to foo') + self.commit() + foo_conn = self.getConnection('foo', 'foo') + rows = foo_conn.query(''' + select fn1.print_connection('AC_FOOCONN') + ''') + self.assertRowsEqual([('password', 'a', 'b', 'c')], rows) + foo_conn.commit() + self.query('drop user foo cascade') + self.commit() + + def testUseConnectionInOldImportWithNewRight(self): + self.createUser("foo", "foo") + self.query('grant insert any table to foo') + self.query('grant import to foo', ignore_errors=True) # only supported and needed since 6.1 + self.query('GRANT ACCESS ON CONNECTION ac_fooconn to foo') + self.commit() + foo_conn = self.getConnection('foo', 'foo') + with self.assertRaisesRegex(Exception, 'insufficient privileges for using connection'): + foo_conn.query(''' + import from fbv at ac_fooconn file 'foo' + ''') + foo_conn.commit() + self.query('drop user foo cascade') + self.commit() + + +class BigConnectionTest(udf.TestCase): + # Should be max. size 2.000.000, but this will cause our odbc driver to crash (sigsegv) during logging (DWA-20290). + # Will be increased to max size when bug is fixed + address = "a" * 2 * 1000 * 100 + user = "u" * 2 * 1000 * 100 + password = "p" * 2 * 1000 * 100 + + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create Python3 UDFs for get_connection testing + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SCALAR SCRIPT PRINT_CONNECTION(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SET SCRIPT PRINT_CONNECTION_SET_EMITS(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(''' + create connection LARGEST_CONN to '{address}' user '{user}' identified by '{password}' + '''.format(address=self.address, user=self.user, password=self.password)) + + def tearDown(self): + self.query("DROP CONNECTION LARGEST_CONN") + + def testGetBigConnection(self): + rows = self.query(''' + SELECT fn1.print_connection('LARGEST_CONN') + ''') + self.assertRowsEqual([('password', self.address, self.user, self.password)], rows) + + +class ConnectionTest(udf.TestCase): + + def testAccessConnectionInAdapter(self): + self.query("CREATE SCHEMA IF NOT EXISTS ADAPTER") + self.query("CREATE CONNECTION my_conn TO 'MYADDRESS' USER 'MYUSER' IDENTIFIED BY 'MYPASSWORD'") + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 ADAPTER SCRIPT adapter.fast_adapter_conn AS + import json + import string + encodeUTF8 = lambda x: x + + def adapter_call(request): + root = json.loads(request) + if root["type"] == "createVirtualSchema": + c = exa.get_connection("MY_CONN") + res = { + "type": "createVirtualSchema", + "schemaMetadata": { + "tables": [ + { + "name": "T1", + "columns": [{ + "name": c.address, + "dataType": {"type": "VARCHAR", "size": 2000000} + },{ + "name": c.user, + "dataType": {"type": "VARCHAR", "size": 2000000} + },{ + "name": c.password, + "dataType": {"type": "VARCHAR", "size": 2000000} + }] + }] + } + } + return encodeUTF8(json.dumps(res)) + elif root["type"] == "dropVirtualSchema": + return encodeUTF8(json.dumps({"type": "dropVirtualSchema"})) + else: + raise ValueError('Unsupported callback') + / + ''')) + self.query("CREATE VIRTUAL SCHEMA VS USING ADAPTER.FAST_ADAPTER_CONN") + rows = self.query("SELECT COLUMN_NAME FROM EXA_ALL_COLUMNS WHERE COLUMN_TABLE='T1' ORDER BY COLUMN_NAME") + self.assertRowsEqual([('MYADDRESS',), ('MYPASSWORD',), ('MYUSER',)], rows) + self.query("DROP FORCE VIRTUAL SCHEMA VS CASCADE") + + +class OptionalUSERandIDENTIFIEDBYTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create Python3 UDFs for get_connection testing + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SCALAR SCRIPT PRINT_CONNECTION(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SET SCRIPT PRINT_CONNECTION_SET_EMITS(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + def testNoUSERandNoIDENTIFIEDBY(self): + self.query("CREATE or replace CONNECTION my_conn1 TO 'MYADDRESS'") + rows = self.query('''SELECT fn1.print_connection('MY_CONN1')''') + self.assertRowsEqual([('password', 'MYADDRESS', None, None)], rows) + self.query("drop CONNECTION my_conn1") + + def testNoUSER(self): + self.query("CREATE or replace CONNECTION my_conn2 TO 'MYADDRESS' identified by 'MYPASSWORD'") + rows = self.query('''SELECT fn1.print_connection('MY_CONN2')''') + self.assertRowsEqual([('password', 'MYADDRESS', None, 'MYPASSWORD')], rows) + self.query("drop CONNECTION my_conn2") + + def testNoIDENIFIEDBY(self): + self.query("CREATE or replace CONNECTION my_conn3 TO 'MYADDRESS' USER 'MYUSER'") + rows = self.query('''SELECT fn1.print_connection('MY_CONN3')''') + self.assertRowsEqual([('password', 'MYADDRESS', "MYUSER", None)], rows) + self.query("drop CONNECTION my_conn3") + + +class GetConnectionAccessControlWithViewsTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create Python3 UDFs for get_connection testing + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SCALAR SCRIPT PRINT_CONNECTION(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 SET SCRIPT PRINT_CONNECTION_SET_EMITS(conn varchar(1000)) + EMITS(type varchar(200), addr varchar(2000000), usr varchar(2000000), pwd varchar(2000000)) + AS + def run(ctx): + c = exa.get_connection(ctx.conn) + ctx.emit(c.type, c.address, c.user, c.password) + / + ''')) + + self.query(''' + create connection AC_FOOCONN to 'a' user 'b' identified by 'c' + ''', ignore_errors=True) + + def getConnection(self, username, password): + client = exatest.ODBCClient('exatest') + self.log.debug('connecting to DSN "exa" for user {username}'.format(username=username)) + client.connect(uid=username, pwd=password) + return client + + def createUser(self, username, password): + self.query('DROP USER IF EXISTS {username} CASCADE'.format(username=username)) + self.query('CREATE USER {username} IDENTIFIED BY "{password}"'.format(username=username, password=password)) + self.query('GRANT CREATE SESSION TO {username}'.format(username=username)) + + def testUseConnectionUDFsInView(self): + self.createUser("foo", "foo") + self.query('create schema if not exists spot42542') + self.query( + "create or replace view spot42542.print_connection_wrapper as select fn1.print_connection('AC_FOOCONN')") + self.query("grant select on spot42542.print_connection_wrapper to foo") + self.commit() + foo_conn = self.getConnection('foo', 'foo') + rows = foo_conn.query('''select * from spot42542.print_connection_wrapper''') + foo_conn.commit() + self.assertRowsEqual([('password', 'a', 'b', 'c')], rows) + foo_conn.commit() + self.query('drop schema spot42542 cascade') + self.commit() + + +if __name__ == '__main__': + udf.main() + diff --git a/test_container/tests/test/generic/python3/import_alias.py b/test_container/tests/test/generic/python3/import_alias.py new file mode 100644 index 000000000..ba1438212 --- /dev/null +++ b/test_container/tests/test/generic/python3/import_alias.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework import exatest +from exasol_python_test_framework.udf import skip + + +class ImportAliasTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create or replace table fn2.t(z varchar(3000))') + self.query('create or replace table fn2.t2(y varchar(2000), z varchar(3000))') + self.query(''' + create connection FOOCONN to 'a' user 'b' identified by 'c' + ''', ignore_errors=True) + + self.query('OPEN SCHEMA FN1') + + # Create all IMPORT UDF scripts + self.query(udf.fixindent(''' + create or replace python3 set script impal_use_is_subselect(...) emits (x varchar(2000)) as + def generate_sql_for_import_spec(import_spec): + return "select " + str(import_spec.is_subselect) + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 scalar script impal_use_param_foo_bar(...) returns varchar(2000) as + def generate_sql_for_import_spec(import_spec): + return "select '" + import_spec.parameters['FOO'] + "', '" + import_spec.parameters['BAR'] + "'" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 set script impal_use_connection_name(...) emits (x varchar(2000)) as + def generate_sql_for_import_spec(import_spec): + return "select '" + import_spec.connection_name + "'" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 set script impal_use_connection_fooconn(...) emits (x varchar(2000)) as + def generate_sql_for_import_spec(import_spec): + c = exa.get_connection('FOOCONN') + return "select '" + str(c.address) + str(c.user) + str(c.password) + "'" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 set script impal_use_connection(...) emits (x varchar(2000)) as + def generate_sql_for_import_spec(import_spec): + return "select '" + import_spec.connection.user + import_spec.connection.password + import_spec.connection.address + import_spec.connection.type + "'" + / + ''')) + + self.query(udf.fixindent(''' + create or replace python3 set script impal_use_all(...) emits (x varchar(2000)) as + def generate_sql_for_import_spec(import_spec): + is_sub = str(import_spec.is_subselect).upper() + connection_string = 'X' + connection_name = 'Y' + foo = 'Z' + types = 'T' + names = 'N' + if import_spec.connection is not None: + connection_string = import_spec.connection.user + import_spec.connection.password + import_spec.connection.address + import_spec.connection.type + if import_spec.connection_name is not None: + connection_name = import_spec.connection_name + if import_spec.parameters['FOO'] is not None: + foo = import_spec.parameters['FOO'] + if import_spec.subselect_column_types is not None: + for i in range(0, len(import_spec.subselect_column_types)): + types = types + import_spec.subselect_column_types[i] + names = names + import_spec.subselect_column_names[i] + return "select 1, '" + is_sub + '_' + connection_name + '_' + connection_string + '_' + foo + '_' + types + '_' + names + "'" + / + ''')) + + def test_import_use_is_subselect(self): + self.query(''' + IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_is_subselect + ''') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FALSE',)], rows) + self.query('truncate table fn2.t') + + def test_import_use_is_subselect_subselect(self): + rows = self.query(''' + SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_is_subselect) + ''') + self.assertRowsEqual([(True,)], rows) + rows = self.query(''' + IMPORT FROM SCRIPT fn1.impal_use_is_subselect + ''') + self.assertRowsEqual([(True,)], rows) + + def test_import_use_params(self): + self.query("IMPORT INTO fn2.t2 FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo'") + rows = self.query('select * from fn2.t2') + self.assertRowsEqual([('bar','foo')], rows) + self.query('truncate table fn2.t2') + + def test_import_use_params_subselect(self): + rows = self.query("SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo')") + self.assertRowsEqual([('bar','foo')], rows) + rows = self.query("IMPORT FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo'") + self.assertRowsEqual([('bar','foo')], rows) + + def test_import_use_connection_name(self): + self.query('IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_connection_name at fooconn') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FOOCONN',)], rows) + self.query('truncate table fn2.t') + + def test_import_use_connection_fooconn(self): + rows = self.query('IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.assertRowsEqual([('abc',)], rows) + self.query('truncate table fn2.t') + + def getConnection(self, username, password): + client = exatest.ODBCClient('exatest') + self.log.debug('connecting to DSN "exa" for user {username}'.format(username=username)) + client.connect(uid = username, pwd = password) + return client + + def createUser(self, username, password): + self.query('DROP USER IF EXISTS {username} CASCADE'.format(username = username)) + self.query('CREATE USER {username} IDENTIFIED BY "{password}"'.format(username = username, password = password)) + self.query('GRANT CREATE SESSION TO {username}'.format(username=username)) + + def test_import_use_connection_fooconn_fails_for_user_foo(self): + self.createUser('foo','foo') + self.commit() + foo_conn = self.getConnection('foo','foo') + with self.assertRaisesRegex(Exception, 'insufficient privileges'): + foo_conn.query('IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.query('drop user foo cascade') + + @skip("IMPORT FROM SCRIPT cannot be used in view definitions") + def test_import_use_connection_fooconn_for_user_foo_and_view(self): + self.query('create view fn2.fooconn_import_view as IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.createUser('foo','foo') + self.commit() + foo_conn = self.getConnection('foo','foo') + rows = foo_conn.query('select * from fn2.fooconn_import_view') + self.assertRowsEqual([('abc',)], rows) + self.query('drop user foo cascade') + self.query('drop view fn2.fooconn_import_view') + + def test_import_use_connection_name_subselect(self): + rows = self.query('SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_connection_name at fooconn)') + self.assertRowsEqual([('FOOCONN',)], rows) + rows = self.query('IMPORT FROM SCRIPT fn1.impal_use_connection_name at fooconn') + self.assertRowsEqual([('FOOCONN',)], rows) + + def test_import_use_connection(self): + self.query(''' + IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser' + ''') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + self.query('truncate table fn2.t') + + def test_import_use_connection_subselect(self): + rows = self.query(''' SELECT * FROM ( + IMPORT FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser') + ''') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + rows = self.query(''' + IMPORT FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser' + ''') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + + def test_import_use_all(self): + self.query(''' + IMPORT INTO fn2.t2 FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value' + ''') + rows = self.query('select * from fn2.t2') + self.assertRowsEqual([('1','FALSE_Y_hansmeiserfooconnpassword_a value_T_N')], rows) + self.query('truncate table fn2.t2') + + def test_import_use_all_subselect(self): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value') + ''') + self.assertRowsEqual([(1, 'TRUE_Y_hansmeiserfooconnpassword_a value_TDOUBLEVARCHAR(3000) UTF8_NAB')], rows) + rows = self.query(''' + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value' + ''') + self.assertRowsEqual([(1, 'TRUE_Y_hansmeiserfooconnpassword_a value_TDOUBLEVARCHAR(3000) UTF8_NAB')], rows) + + def test_prepared_statement_params(self): + with self.assertRaisesRegex(Exception, 'syntax error, unexpected \'?\''): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo=?) + ''', 'bar') + + def test_prepared_statement_conn(self): + with self.assertRaisesRegex(Exception, 'syntax error, unexpected \'?\''): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at ? user ? identified by ? with foo='bar') + ''', 'fooconn', 'hans', 'meiser', 'bar') + + def test_import_in_lua_scripting(self): + self.query(''' + create or replace script s1() as + res = pquery [[ IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_is_subselect ]] + ''') + self.query('execute script s1()') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FALSE',)], rows) + self.query('truncate table fn2.t') + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/metadata.py b/test_container/tests/test/generic/python3/metadata.py new file mode 100755 index 000000000..1da7bb0bf --- /dev/null +++ b/test_container/tests/test/generic/python3/metadata.py @@ -0,0 +1,432 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _Python3UdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_char_length(text char(10)) + EMITS (len1 number, len2 number, dummy char(20)) AS + def run(ctx): + v = exa.meta.input_columns[0] + w = exa.meta.output_columns[2] + ctx.emit(v.length,w.length,'9876543210') + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_current_schema() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.current_schema + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_current_user() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.current_user + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_database_name() + RETURNS varchar(300) AS + def run(ctx): + return exa.meta.database_name + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_database_version() + RETURNS varchar(20) AS + def run(ctx): + return exa.meta.database_version + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_input_column_count_scalar(c1 double, c2 varchar(100)) + RETURNS number AS + def run(ctx): + return exa.meta.input_column_count + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT get_input_column_count_set(c1 double, c2 varchar(100)) + RETURNS number AS + def run(ctx): + return exa.meta.input_column_count + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_input_columns(c1 double, c2 varchar(200)) + EMITS (column_id number, column_name varchar(200), column_type varchar(20), + column_sql_type varchar(20), column_precision number, column_scale number, + column_length number) AS + def run(ctx): + cols = exa.meta.input_columns + for i in range(0, len(cols)): + name = cols[i].name + precision = cols[i].precision + thetype = repr(cols[i].type) + sql_type = cols[i].sql_type + scale = cols[i].scale + length = cols[i].length + if name == None: name = 'no-name' + if thetype == None: thetype = 'no-type' + if sql_type == None: sql_type = 'no-sql-type' + if precision == None: precision = 0 + if scale == None: scale = 0 + if length == None: length = 0 + ctx.emit(i+1, name, thetype, sql_type, precision, scale, length) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_input_type_scalar() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.input_type + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT get_input_type_set(a double) + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.input_type + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_node_id() + RETURNS number AS + def run(ctx): + return exa.meta.node_id + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_output_column_count_emit() + EMITS (x number, y number, z number) AS + def run(ctx): + ctx.emit(exa.meta.output_column_count,exa.meta.output_column_count,exa.meta.output_column_count) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_output_column_count_return() + RETURNS number AS + def run(ctx): + return exa.meta.output_column_count + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_output_columns() + EMITS (column_id number, column_name varchar(200), column_type varchar(20), + column_sql_type varchar(20), column_precision number, column_scale number, + column_length number) AS + def run(ctx): + cols = exa.meta.output_columns + for i in range(0, len(cols)): + name = cols[i].name + thetype = repr(cols[i].type) + sql_type = cols[i].sql_type + precision = cols[i].precision + scale = cols[i].scale + length = cols[i].length + if name == None: name = 'no-name' + if thetype == None: thetype = 'no-type' + if sql_type == None: sql_type = 'no-sql-type' + if precision == None: precision = 0 + if scale == None: scale = 0 + if length == None: length = 0 + ctx.emit(i+1, name, thetype, sql_type, precision, scale, length) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_output_type_emit() + EMITS (t varchar(200)) AS + def run(ctx): + ctx.emit(exa.meta.output_type) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_output_type_return() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.output_type + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_precision_scale_length(n decimal(6,3), v varchar(10)) + EMITS (precision1 number, scale1 number, length1 number, precision2 number, scale2 number, length2 number) AS + def run(ctx): + v = exa.meta.input_columns[0] + precision1 = v.precision + scale1 = v.scale + length1 = v.length + w = exa.meta.input_columns[1] + precision2 = w.precision + scale2 = w.scale + length2 = w.length + if precision1== None: precision1= 0 + if scale1== None: scale1= 0 + if length1== None: length1= 0 + if precision2== None: precision2= 0 + if scale2== None: scale2= 0 + if length2== None: length2= 0 + ctx.emit(precision1, scale1, length1, precision2, scale2, length2) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_scope_user() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.scope_user + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_script_code() + RETURNS varchar(2000) AS + def run(ctx): + return exa.meta.script_code + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_script_language() + EMITS (s1 varchar(300), s2 varchar(300)) AS + def run(ctx): + ctx.emit(exa.meta.script_language, "Python") + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_script_name() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.script_name + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_script_schema() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.script_schema + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_session_id() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.session_id + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_statement_id() + RETURNS number AS + def run(ctx): + return exa.meta.statement_id + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT get_vm_id() + RETURNS varchar(200) AS + def run(ctx): + return exa.meta.vm_id + / + ''')) + + # Close schema so test_current_schema_null passes (expects NULL) + self.query('CLOSE SCHEMA') + +class MetaDataTest(_Python3UdfSetup): + + def test_database_name(self): + rows = self.query('''SELECT fn1.get_database_name() FROM DUAL''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_database_version(self): + rows = self.query('''select fn1.get_database_version() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_script_language(self): + rows = self.query('''select fn1.get_script_language() from dual''') + self.assertTrue((rows[0][0]).upper().startswith((rows[0][1]).upper())) + + def test_script_name(self): + rows = self.query('''select fn1.get_script_name() from dual''') + self.assertRowEqual(('GET_SCRIPT_NAME',), rows[0]) + + def test_script_schema(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_script_schema() from dual''') + self.assertRowEqual(('FN1',), rows[0]) + + def test_script_user(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_current_user() from dual''') + self.assertRowEqual(('SYS',), rows[0]) + + def test_scope_user(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_scope_user() from dual''') + self.assertRowEqual(('SYS',), rows[0]) + + def test_current_schema_null(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_current_schema() from dual''') + self.assertRowEqual(('NULL',), rows[0]) + + def test_current_schema(self): + if (udf.opts.is_compat_mode != "true"): + self.query('''create schema test_schema''') + rows = self.query('''select fn1.get_current_schema() from dual''') + self.assertRowEqual(('TEST_SCHEMA',), rows[0]) + self.query('''drop schema test_schema cascade''') + + def test_script_code(self): + rows = self.query('''select fn1.get_script_code() from dual''') + self.assertTrue((rows[0][0]).upper().find('CTX') >= 0) + + def test_session_id(self): + rows = self.query('''select fn1.get_session_id() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_statement_id(self): + rows = self.query('''select fn1.get_statement_id() from dual''') + self.assertTrue(rows[0][0] >= 0) + + def test_node_id(self): + rows = self.query('''select fn1.get_node_id() from dual''') + self.assertTrue(rows[0][0] >= 0) + + def test_vm_id(self): + rows = self.query('''select fn1.get_vm_id() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_input_type_scalar(self): + rows = self.query('''select fn1.get_input_type_scalar() from dual''') + self.assertRowEqual(('SCALAR',), rows[0]) + + def test_input_type_set(self): + rows = self.query('''select fn1.get_input_type_set(x) from (values 1,2,3) as t(x)''') + self.assertRowEqual(('SET',), rows[0]) + + def test_input_column_count_scalar(self): + rows = self.query('''select fn1.get_input_column_count_scalar(12.3, 'hihihi') from dual''') + self.assertRowEqual((2,), rows[0]) + + + def test_input_column_count_set(self): + rows = self.query('''select fn1.get_input_column_count_set(x, y) from (values (12.3, 'hihihi')) as t(x,y)''') + self.assertRowEqual((2,), rows[0]) + + def test_input_columns(self): + rows = self.query('''select fn1.get_input_columns(1.2, '123') from dual order by column_id''') + r0 = rows[0] + r1 = rows[1] + self.assertTrue(r0[0] == 1) + self.assertTrue(r0[1].upper() == 'C1') + self.assertTrue(r0[2].upper() == 'NUMBER' or r0[2].upper() == "" or r0[2].upper() == "DOUBLE" or r0[2].upper() == "JAVA.LANG.DOUBLE" or r0[2].upper() == "") + self.assertTrue(r0[3].upper() == 'DOUBLE') + self.assertTrue(r1[0] == 2) + self.assertTrue(r1[1].upper() == 'C2') + self.assertTrue(r1[2].upper() == 'STRING' or r1[2].upper() == "" or r1[2].upper() == "CHARACTER" or r1[2].upper() == "JAVA.LANG.STRING" or r1[2].upper() == "") + self.assertTrue(r1[3].upper().startswith('VARCHAR(200)')) + self.assertTrue(r0[6] == 0) + self.assertTrue(r1[6] == 200) + + def test_output_type_return(self): + rows = self.query('''select fn1.get_output_type_return() from dual''') + self.assertTrue(rows[0][0] == 'RETURN') + + + def test_output_type_emit(self): + rows = self.query('''select fn1.get_output_type_emit() from dual''') + self.assertTrue(rows[0][0] == 'EMIT') + + + def test_output_column_count_return(self): + rows = self.query('''select fn1.get_output_column_count_return() from dual''') + self.assertRowEqual((1,),rows[0]) + + + def test_output_column_count_emit(self): + rows = self.query('''select fn1.get_output_column_count_emit() from dual''') + self.assertRowEqual((3,3,3),rows[0]) + + def test_output_columns(self): + rows = self.query('''select fn1.get_output_columns() from dual order by column_id''') + r0 = rows[0] + r1 = rows[1] + r2 = rows[2] + r3 = rows[3] + r4 = rows[4] + r5 = rows[5] + r6 = rows[6] + self.assertTrue(r0[0] == 1) + self.assertTrue(r1[0] == 2) + self.assertTrue(r2[0] == 3) + self.assertTrue(r3[0] == 4) + self.assertTrue(r4[0] == 5) + self.assertTrue(r5[0] == 6) + self.assertTrue(r6[0] == 7) + self.assertTrue(r0[1].upper() == 'COLUMN_ID') + self.assertTrue(r1[1].upper() == 'COLUMN_NAME') + self.assertTrue(r2[1].upper() == 'COLUMN_TYPE') + self.assertTrue(r3[1].upper() == 'COLUMN_SQL_TYPE') + self.assertTrue(r4[1].upper() == 'COLUMN_PRECISION') + self.assertTrue(r5[1].upper() == 'COLUMN_SCALE') + self.assertTrue(r6[1].upper() == 'COLUMN_LENGTH') + self.assertTrue(r0[2].upper() == 'NUMBER' or r0[2].upper() == "" or r0[2].upper() == "DOUBLE" or r0[2].upper() == "JAVA.LANG.DOUBLE" or r0[2].upper() == "") + self.assertTrue(r1[2].upper() == 'STRING' or r1[2].upper() == "" or r1[2].upper() == "CHARACTER" or r1[2].upper() == "JAVA.LANG.STRING" or r1[2].upper() == "") + self.assertTrue(r2[2].upper() == 'STRING' or r2[2].upper() == "" or r2[2].upper() == "CHARACTER" or r2[2].upper() == "JAVA.LANG.STRING" or r2[2].upper() == "") + self.assertTrue(r3[2].upper() == 'STRING' or r3[2].upper() == "" or r3[2].upper() == "CHARACTER" or r3[2].upper() == "JAVA.LANG.STRING" or r3[2].upper() == "") + self.assertTrue(r4[2].upper() == 'NUMBER' or r4[2].upper() == "" or r4[2].upper() == "DOUBLE" or r4[2].upper() == "JAVA.LANG.DOUBLE" or r4[2].upper() == "") + self.assertTrue(r5[2].upper() == 'NUMBER' or r5[2].upper() == "" or r5[2].upper() == "DOUBLE" or r5[2].upper() == "JAVA.LANG.DOUBLE" or r5[2].upper() == "") + self.assertTrue(r6[2].upper() == 'NUMBER' or r6[2].upper() == "" or r6[2].upper() == "DOUBLE" or r6[2].upper() == "JAVA.LANG.DOUBLE" or r6[2].upper() == "") + self.assertTrue(r0[3].upper() == 'DOUBLE') + self.assertTrue(r1[3].upper().startswith('VARCHAR(200)')) + self.assertTrue(r2[3].upper().startswith('VARCHAR(20)')) + self.assertTrue(r3[3].upper().startswith('VARCHAR(20)')) + self.assertTrue(r4[3].upper() == 'DOUBLE') + self.assertTrue(r5[3].upper() == 'DOUBLE') + self.assertTrue(r6[3].upper() == 'DOUBLE') + self.assertTrue(r1[6] == 200) + self.assertTrue(r2[6] == 20) + self.assertTrue(r3[6] == 20) + + + + def test_precision_scale_length(self): + rows = self.query('''select fn1.get_precision_scale_length(2.5, '0123456789') from dual''') + self.assertRowEqual((6,3,0,0,0,10), rows[0]) + + + def test_char_length(self): + rows = self.query('''select fn1.get_char_length('0123456789') from dual''') + self.assertRowEqual((10,20,'9876543210 '), (int(rows[0][0]), int(rows[0][1]), rows[0][2])) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/numeric_functions.py b/test_container/tests/test/generic/python3/numeric_functions.py new file mode 100755 index 000000000..35dffa5a7 --- /dev/null +++ b/test_container/tests/test/generic/python3/numeric_functions.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class _Python3UdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT add_three_doubles(x DOUBLE, y DOUBLE, z DOUBLE) + RETURNS DOUBLE AS + def run(ctx): + if ctx.x is not None and ctx.y is not None and ctx.z is not None: + return ctx.x + ctx.y + ctx.z; + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT add_two_doubles(x DOUBLE, y DOUBLE) + RETURNS DOUBLE AS + def run(ctx): + if ctx.x is not None and ctx.y is not None: + return ctx.x + ctx.y; + return None + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT double_mult("x" double, "y" double) + RETURNS double AS + def run(ctx): + if ctx.x is None or ctx.y is None: + return None + else: + return ctx.x * ctx.y + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT pi() + RETURNS double AS + import math + + def run(ctx): + return math.pi + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT split_integer_into_digits("x" INTEGER) + EMITS (y INTEGER) AS + import math + def run(ctx): + if ctx.x is not None: + y = abs(ctx.x) + while y > 0: + ctx.emit(y % 10) + y //= 10 + / + ''')) + +class Test(_Python3UdfSetup): + def test_pi(self): + rows = self.query(''' + SELECT fn1.pi() + FROM dual''') + result = rows[0][0] + self.assertAlmostEqual(3.1415926535, result) + + def test_select(self): + rows = self.query(''' + SELECT DISTINCT + FN1.double_mult(float1, float2) = float1 * float2 AS a + FROM test.enginetablebig1 + ORDER BY a + ''') + self.assertRowsEqual([(True,), (None,)], rows) + + def test_select_into(self): + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('CREATE TABLE FN2.t(diff double)') + self.query(''' + INSERT INTO FN2.t + SELECT + fn1.double_mult(float1, float2) - float1 * float2 AS a + FROM test.enginetable + ''') + self.query(''' + SELECT DISTINCT diff + FROM FN2.t + WHERE diff != 0 AND diff IS NOT NULL + ''') + self.assertEqual(0, self.rowcount()) + + def test_subselect(self): + rows = self.query(''' + SELECT i, a + FROM ( + SELECT int_index AS i, + (fn1.double_mult(float1, float2) - float1 * float2) AS a + FROM test.enginetable) + WHERE a IS NOT NULL + ORDER BY a + LIMIT 20''') + for row in rows: + rows2 = self.query(''' + SELECT + float1, + float2, + fn1.double_mult(float1, float2) - float1 * float2 AS a + FROM test.enginetable + WHERE int_index = ?''', + row.I) + self.assertEqual(row.A, rows2[0].A) + + def test_udf_with_two_doubles(self): + rows = self.query(''' + SELECT + fn1.add_two_doubles(NULL, NULL) IS NULL, + fn1.add_two_doubles(NULL, 0) IS NULL, + fn1.add_two_doubles( 0, NULL) IS NULL, + fn1.add_two_doubles(0,0) = 0, + fn1.add_two_doubles(1,0) = 1, + fn1.add_two_doubles(0,2) = 2, + fn1.add_two_doubles(2,3) = 5 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 7)], rows) + + def test_udf_with_three_doubles_part1(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles(NULL, NULL, NULL) is NULL, + fn1.add_three_doubles(NULL, NULL, 0) is NULL, + fn1.add_three_doubles(NULL, 0, NULL) is NULL, + fn1.add_three_doubles( 0, NULL, NULL) is NULL, + fn1.add_three_doubles(NULL, 0, 0) is NULL + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_udf_with_three_doubles_part2(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles( 0, NULL, 0) is NULL, + fn1.add_three_doubles( 0, 0, NULL) is NULL, + fn1.add_three_doubles(0, 0, 0) = 0, + fn1.add_three_doubles(1, 0, 0) = 1, + fn1.add_three_doubles(0, 2, 0) = 2 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_udf_with_three_doubles_part3(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles(0, 0, 3) = 3, + fn1.add_three_doubles(1, 2, 0) = 3, + fn1.add_three_doubles(1, 0, 3) = 4, + fn1.add_three_doubles(0, 2, 3) = 5, + fn1.add_three_doubles(1, 2, 3) = 6 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_right_number_of_emitted_rows(self): + rows = self.query(''' + SELECT fn1.split_integer_into_digits(123) + FROM DUAL''') + self.assertRowsEqual([(3,), (2,), (1,)], rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/pathological_functions.py b/test_container/tests/test/generic/python3/pathological_functions.py new file mode 100755 index 000000000..90b2ee43e --- /dev/null +++ b/test_container/tests/test/generic/python3/pathological_functions.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf + + +class Test(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT sleep("sec" double) + RETURNS double AS + import time + + def run(ctx): + time.sleep(ctx.sec) + return ctx.sec + / + ''')) + + def test_query_timeout(self): + self.query('ALTER SESSION SET QUERY_TIMEOUT = 10') + try: + with self.assertRaisesRegex(Exception, 'Successfully reconnected after query timeout'): + self.query('SELECT fn1.sleep(100) FROM dual') + finally: + self.query('ALTER SESSION SET QUERY_TIMEOUT = 0') + + +if __name__ == '__main__': + udf.main() + diff --git a/test_container/tests/test/generic/python3/performance.py b/test_container/tests/test/generic/python3/performance.py new file mode 100755 index 000000000..7e0826f41 --- /dev/null +++ b/test_container/tests/test/generic/python3/performance.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 + +import locale +import os +import subprocess + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import timer, SkipTest, skip + +locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') + + +class WordCount(udf.TestCase): + + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create Python3 UDFs for performance testing + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT PERFORMANCE_MAP_WORDS(w VARCHAR(1000)) + EMITS (w VARCHAR(1000), c INTEGER) AS + + import re + import string + + pattern = re.compile(r'''+'\'\'\'([]\w!"#$%&\\\'()*+,./:;<=>?@[\\\\^_`{|}~-]+)\'\'\''+''') + + def run(ctx): + if ctx.w is not None: + for w in re.findall(pattern, ctx.w): + ctx.emit(w, 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT PERFORMANCE_MAP_UNICODE_WORDS(w VARCHAR(1000)) + EMITS (w VARCHAR(1000), c INTEGER) AS + + import re + import string + + pattern = re.compile(r'''+'\'\'\'([]\w!"#$%&\\\'()*+,./:;<=>?@[\\\\^_`{|}~-]+)\'\'\''+''', re.UNICODE) + + def run(ctx): + if ctx.w is not None: + for w in re.findall(pattern, ctx.w): + ctx.emit(w, 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT PERFORMANCE_REDUCE_COUNTS(w VARCHAR(1000), c INTEGER) + EMITS (w VARCHAR(1000), c INTEGER) AS + + def run(ctx): + word = ctx.w + count = 0 + while True: + count += ctx.c + if not ctx.next(): break + ctx.emit(word, count) + / + ''')) + + def test_word_count(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + def test_word_unicode_count(self): + """Test Unicode word counting""" + sql = ''' + SELECT performance_reduce_counts(w, c) + FROM ( + SELECT performance_map_unicode_words(c3_varchar100) + FROM test.enginetablebigunicode + ) + GROUP BY w + ORDER BY 1 DESC''' + + with timer() as t: + self.query(sql) + self.assertLessEqual(t.duration, 11) + + +@skip('csv data for tables wiki_freq and wiki_names is currently not available') +class FrequencyAnalysis(udf.TestCase): + maxDiff = 1024 * 20 + + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Create Python3 UDFs for character frequency analysis + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT PERFORMANCE_MAP_CHARACTERS(text VARCHAR(1000)) + EMITS (w CHAR(1), c INTEGER) AS + def run(ctx): + if ctx.text is not None: + for c in ctx.text: + ctx.emit(c, 1) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SET SCRIPT PERFORMANCE_REDUCE_CHARACTERS(w CHAR(1), c INTEGER) + EMITS (w CHAR(1), c INTEGER) AS + + def run(ctx): + c = 0 + w = ctx.w + if w is not None: + while True: + c += 1 + if not ctx.next(): break + ctx.emit(w, c) + / + ''')) + + def compare(self, old, new): + self.log.info('compare new data with reference data') + n_old = len(list(old)) + n_new = len(list(new)) + self.log.info('old data has %d lines', n_old) + self.log.info('new data has %d lines', n_new) + if max(n_old, n_new) <= 50: + self.assertEqual(old, new) + else: + self.log.info('switching to compact comparison') + old_set = set(old) + new_set = set(new) + only_new = list(sorted(new_set.difference(old_set))) + only_old = list(sorted(old_set.difference(new_set))) + if max(len(only_new), len(only_old)) <= 200: + self.assertEqual(([], []), (only_old, only_new)) + else: + self.log.info('diff is still to big') + self.fail("difference: +%d/-%d elements" % + (len(only_new), len(only_old))) + + @classmethod + def setUpClass(cls): + sql = """ + DROP SCHEMA daten CASCADE; + CREATE SCHEMA daten; + + CREATE TABLE wiki_freq(w char(1), c INTEGER); + + IMPORT INTO wiki_freq + FROM LOCAL CSV FILE '/share/fs8/Databases/UDF/freebase-frequency_analysis.csv' + COLUMN SEPARATOR = 'TAB' + REJECT LIMIT 0; + + CREATE TABLE wiki_names(id INTEGER IDENTITY PRIMARY KEY, text VARCHAR(350)); + + IMPORT INTO wiki_names(text) + FROM LOCAL CSV FILE '/share/fs8/Databases/UDF/freebase-export.csv' + COLUMN SEPARATOR = 'TAB' + REJECT LIMIT 0;""" + + cmd = '''%(exaplus)s -c %(conn)s -u sys -P exasol + -no-config -autocommit ON -L -pipe -jdbcparam validateservercertificate=0''' % { + 'exaplus': os.environ.get('EXAPLUS', + '/usr/opt/EXASuite-4/EXASolution-4.2.9/bin/Console/exaplus'), + 'conn': udf.opts.server + } + env = os.environ.copy() + env['PATH'] = '/usr/opt/jdk1.8.0_latest/bin:' + env['PATH'] + exaplus = subprocess.Popen( + cmd.split(), + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + out, _err = exaplus.communicate(sql.encode('utf-8')) + if exaplus.returncode != 0: + cls.log.critical('EXAplus error: %d', exaplus.returncode) + cls.log.error(out) + else: + cls.log.debug(out) + + def test_frequency_analysis(self): + with timer() as t1: + rows1 = self.query(''' + SELECT fn1.performance_reduce_characters(w, c) + FROM ( + SELECT fn1.performance_map_characters(text) + FROM daten.wiki_names + ) + GROUP BY w + ORDER BY c DESC, w ASC''') + + with timer() as t2: + rows2 = self.query(''' + SELECT w, c + FROM daten.wiki_freq + ORDER BY c DESC, w ASC''') + + data = [tuple(x) for x in rows1] + reference = [tuple(x) for x in rows2] + print("test_frequency_analysis query:", t1.duration, t2.duration) + self.compare(reference, data) + + def test_frequency_analysis_light(self): + """Lighter version processing subset of data""" + self.query(''' + SELECT fn1.performance_reduce_characters(w, c) + FROM ( + SELECT fn1.performance_map_characters(text) + FROM daten.wiki_names + WHERE mod(length(daten.wiki_names.text), 50) = 2 + ) + GROUP BY w + ORDER BY c DESC, w ASC''') + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/python3/unicode.py b/test_container/tests/test/generic/python3/unicode.py new file mode 100755 index 000000000..807bb9b3e --- /dev/null +++ b/test_container/tests/test/generic/python3/unicode.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import useData + +class _Python3UdfSetup(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT unicode_count(word VARCHAR(1000), convert_ INT) + EMITS (uchar VARCHAR(1), count INT) AS + import collections + + def run(ctx): + if ctx.convert_ > 0: + word = ctx.word.upper() + elif ctx.convert_ < 0: + word = ctx.word.lower() + else: + word = ctx.word + + count = collections.Counter(word) + for k, v in count.items(): + ctx.emit(k, v) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT unicode_len(word VARCHAR(1000)) + RETURNS INT AS + def run(ctx): + if ctx.word is not None: + return len(ctx.word) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT unicode_upper(word VARCHAR(1000)) + RETURNS VARCHAR(1000) AS + def run(ctx): + if ctx.word is not None: + x = ctx.word.upper() + ex = x.encode('utf8') + p = ex.find(b'\xcc') + if p != -1: + x = (ex[:p]).decode('utf8') + return x + / + ''')) + +# coding: utf-8 + +import csv +import locale +import logging +import os +import subprocess +import sys +import tempfile +import unicodedata +import re +import argparse + +udf.pythonVersionInUdf = -1 + +from exasol_python_test_framework.exatest.testcase import skipIf + + +locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') + + +def getPythonVersionInUDFs(server, script_languages): + log = logging.getLogger('unicodedata') + log.info("trying to figure out python version of python in UDFs") + sql = udf.fixindent(''' + alter session set script_languages='%(sl)s'; + drop schema if exists pyversion_schema cascade; + create schema pyversion_schema; + create or replace python3 scalar script pyversion_schema.python_version() returns varchar(1000) as + import sys + def run(ctx): + return 'Python='+str(sys.version_info[0]) + / + select pyversion_schema.python_version(); + ''' % {'sl': script_languages}) + cmd = '''%(exaplus)s -c %(conn)s -u sys -P exasol + -no-config -autocommit ON -L -pipe -jdbcparam validateservercertificate=0''' % { + 'exaplus': os.environ.get('EXAPLUS', + '/usr/opt/EXASuite-4/EXASolution-4.2.9/bin/Console/exaplus'), + 'conn': server + } + env = os.environ.copy() + # env['PATH'] = '/usr/opt/jdk1.8.0_latest/bin:' + env['PATH'] + exaplus = subprocess.Popen( + cmd.split(), + env=env, + + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, _err = exaplus.communicate(sql.encode('utf-8')) + pythonVersionInUdf = -1 + for line in out.strip().decode('utf-8').split(sep="\n"): + m = re.search(r'Python=(\d)', line) + if m: + pythonVersionInUdf = int(m.group(1)) + continue + + if pythonVersionInUdf not in [2, 3]: + print('cannot set pythonVersionInUdf: %s' % pythonVersionInUdf) + sys.exit(1) + + return pythonVersionInUdf + + +def setUpModule(): + log = logging.getLogger('unicodedata') + + log.info('generating unicodedata CSV') + with tempfile.NamedTemporaryFile(prefix='unicode-', suffix='.csv', encoding='utf-8', mode='w+', + delete=False) as csvfile: + c = csv.writer(csvfile, quoting=csv.QUOTE_ALL) + for i in range(sys.maxunicode + 1): + if i >= 5024 and i <= 5119: + continue # the Unicode Cherokee-Block is broken in Python 2.7 and Python 3.4 (maybe also 3.5) + u = chr(i) + if unicodedata.category(u).startswith('C'): + # [Cc]Other, Control + # [Cf]Other, Format + # [Cn]Other, Not Assigned + # [Co]Other, Private Use + # [Cs]Other, Surrogate + continue + row = (i, # INT 0-1114111 + unicodedata.name(u, 'UNICODE U+%08X' % i), # VARCHAR(100) ASCII + u, # VARCHAR(1) UNICODE + u.upper(), # VARCHAR(1) UNICODE + u.lower(), # VARCHAR(1) UNICODE + unicodedata.decimal(u, None), # INT + unicodedata.numeric(u, None), # DOUBLE + unicodedata.category(u), # VARCHAR(3) ASCII + unicodedata.bidirectional(u), # VARCHAR(3) ASCII + unicodedata.combining(u), # VARCHAR(3) ASCII + unicodedata.east_asian_width(u), # VARCHAR(1) ASCII + bool(unicodedata.mirrored), # BOOLEAN + unicodedata.decomposition(u), # VARCHAR(10) ASCII + unicodedata.normalize('NFC', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFD', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFKC', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFKD', u), # VARCHAR(3) UNICODE + ) + c.writerow(row) + csvfile.flush() + + log.info('loading CSV') + sql = ''' + DROP SCHEMA utest CASCADE; + CREATE SCHEMA utest; + CREATE TABLE utest.unicodedata ( + codepoint INT NOT NULL, + name VARCHAR(100) ASCII, + uchar VARCHAR(1) UTF8, + to_upper VARCHAR(1) UTF8, + to_lower VARCHAR(1) UTF8, + decimal_value INT, + numeric_value INT, + category VARCHAR(3) ASCII, + bidirectional VARCHAR(3) ASCII, + combining VARCHAR(10) ASCII, + east_asian_width VARCHAR(2) ASCII, mirrored BOOLEAN, + decomposition VARCHAR(100) ASCII, + NFC VARCHAR(10) UTF8, + NFD VARCHAR(10) UTF8, + NFKC VARCHAR(20) UTF8, + NFKD VARCHAR(20) UTF8 + ); + IMPORT INTO utest.unicodedata + FROM LOCAL CSV FILE '%s' + ROW SEPARATOR = 'CRLF'; + ''' % os.path.join(os.getcwd(), csvfile.name) + cmd = '''%(exaplus)s -c %(conn)s -u sys -P exasol + -no-config -autocommit ON -L -pipe -jdbcparam validateservercertificate=0''' % { + 'exaplus': os.environ.get('EXAPLUS', + '/usr/opt/EXASuite-4/EXASolution-4.2.9/bin/Console/exaplus'), + 'conn': udf.opts.server + } + env = os.environ.copy() + env['PATH'] = '/usr/opt/jdk1.8.0_latest/bin:' + env['PATH'] + exaplus = subprocess.Popen( + cmd.split(), + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, _err = exaplus.communicate(sql.encode('utf-8')) + if exaplus.returncode != 0 or _err is not None: + log.critical('EXAplus error: %d', exaplus.returncode) + log.error(out) + else: + log.debug(out) + + +def add_uniname(data): + return [(n, unicodedata.name(chr(n), 'U+%04X' % n)) + for n in data] + + +class Unicode(_Python3UdfSetup): + + def query_unicode_char(self, u): + rows = self.query(''' + SELECT count, unicode(uchar) AS u + FROM ( + SELECT fn1.unicode_count(unicodechr(%d), 0) + FROM dual) + ''' % u) + self.assertEqual(1, self.rowcount()) + self.assertEqual(1, rows[0].COUNT) + self.assertEqual(u, rows[0].U) + + data = add_uniname(( + 65, + 255, + 382, + 65279, + 63882, + 65534, + 66432, + 173746, + 1114111, + )) + + @useData(data) + def test_unicode(self, codepoint, _name): + self.query_unicode_char(codepoint) + + def test_unicode_count(self): + self.maxDiff = 1024 + rows = self.query(''' + SELECT + c1_integer AS i, + len(c2_varchar100) AS len_exa, + fn1.unicode_len(c2_varchar100) AS len + FROM test.enginetablebigunicodevarchar + WHERE len(c2_varchar100) != fn1.unicode_len(c2_varchar100) + ORDER BY c1_integer + LIMIT 100 + ''') + self.assertRowsEqual([], rows) + + +class UnicodeData(_Python3UdfSetup): + + # @udf.TestCase.expectedFailureIfLang('lua') + def test_unicode_upper_is_subset_of_Unicode520_part2(self): + """DWA-13388 (Lua); DWA-13702 (Lua)""" + rows = self.query(''' + SELECT + codepoint, + name, + unicode(to_upper), + unicode(fn1.unicode_upper(uchar)) + FROM utest.unicodedata + WHERE codepoint in (181, 8126) + and (to_upper != fn1.unicode_upper(uchar)) + and (uchar != fn1.unicode_upper(uchar)) + ORDER BY codepoint + LIMIT 50 + ''') + self.assertRowsEqual([], rows) + + @udf.TestCase.expectedFailureIfLang('lua') + def test_unicode_upper_is_subset_of_Unicode520_part3(self): + """DWA-13388 (Lua); DWA-13702 (Lua); DWA-13782 (R)""" + rows = self.query(''' + SELECT + codepoint, + name, + unicode(to_upper), + unicode(fn1.unicode_upper(uchar)) + FROM utest.unicodedata + WHERE codepoint in (1010) + and (to_upper != fn1.unicode_upper(uchar)) + and (uchar != fn1.unicode_upper(uchar)) + ORDER BY codepoint + LIMIT 50 + ''') + self.assertRowsEqual([], rows) + + def test_unicode_len(self): + rows = self.query(''' + SELECT codepoint, name + FROM utest.unicodedata + WHERE codepoint not between 55296 and 57343 + and len(uchar) != fn1.unicode_len(uchar) + ORDER BY codepoint + LIMIT 100 + ''') + self.assertRowsEqual([], rows) + + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--server', help='connection string') + parser.add_argument('--script-languages', help='definition of the SCRIPT_LANGUAGES variable') + opts, _unknown = parser.parse_known_args() + setattr(udf, 'pythonVersionInUdf', getPythonVersionInUDFs(opts.server, opts.script_languages)) + udf.main() diff --git a/test_container/tests/test/generic/python3/vectorsize.py b/test_container/tests/test/generic/python3/vectorsize.py new file mode 100755 index 000000000..30c3456ba --- /dev/null +++ b/test_container/tests/test/generic/python3/vectorsize.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 + +import sys + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import useData + + +class _Python3UdfSetup(udf.TestCase): + LANG = 'python3' + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT basic_range(n INTEGER) + EMITS (n INTEGER) AS + def run(ctx): + if ctx.n is not None: + for i in range(ctx.n): + ctx.emit(i) + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT vectorsize(length INT, dummy DOUBLE) + RETURNS VARCHAR(2000000) AS + import gc + import sys + + if sys.version_info[0] == 3: xrange = range + + cache = {} + cache_size = 0 + cache_max = 1024*1024*64 + + def run(ctx): + global cache_size, cache + if ctx.length not in cache: + curstr = ''.join([str(i) for i in xrange(ctx.length)]) + if cache_size + len(curstr) > cache_max: + cache = {} + cache_size = 0 + gc.collect() + cache[ctx.length] = curstr + cache_size += len(curstr) + return cache[ctx.length] + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT vectorsize5000(A DOUBLE) + RETURNS VARCHAR(2000000) AS + retval = ''.join([str(i) for i in range(5000)]) + + def run(ctx): + return retval + / + ''')) + + self.query(udf.fixindent(''' + CREATE PYTHON3 SCALAR SCRIPT vectorsize_set(length INT, n INT, dummy DOUBLE) + EMITS (o VARCHAR(2000000)) AS + import gc + import sys + + if sys.version_info[0] == 3: xrange = range + + cache = {} + cache_size = 0 + cache_max = 1024*1024*64 + + def run(ctx): + global cache_size, cache + if ctx.length not in cache: + curstr = ''.join([str(i) for i in xrange(ctx.length)]) + if cache_size + len(curstr) > cache_max: + cache = {} + cache_size = 0 + gc.collect() + cache[ctx.length] = curstr + cache_size += len(curstr) + for i in xrange(ctx.n): + ctx.emit(cache[ctx.length]) + / + ''')) + +class Vectorsize(_Python3UdfSetup): + + def test_vectorsize_5000(self): + self.query(''' + SELECT max(fn1.vectorsize5000(float1)) + FROM TEST.ENGINETABLEBIG1''') + + data = [ + (10,), + (30,), + (100,), + (300,), + (1000,), + (3000,), + (10000,), + (30000,), + (100000,), + (200000,), + (351850,), + ] + + @useData(data) + def test_vectorsize(self, size): + limits = { + 'lua': 100000, + 'python3': 8000, + 'r': 3000, + 'java': 3000 + } + if size > limits.get(self.LANG, sys.maxsize): + raise udf.SkipTest('test is to slow') + + self.query(''' + SELECT max(fn1.vectorsize(%d, float1)) + FROM TEST.ENGINETABLEBIG1 + ''' % size) + + data = [ + (10, 10, 10), + (100, 100, 100), + (1000, 100, 100), + (10000, 100, 100), + (100000, 100, 100), + (351850, 100, 100), + (100, 10, 100000), + (100, 100, 10000), + (100, 1000, 1000), + (100, 10000, 100), + (100, 100000, 10), + ] + + @useData(data) + def test_vectorsize_set(self, a, b, c): + q = ''' + SELECT max(o) + FROM ( + SELECT fn1.vectorsize_set(%d, %d, n) + FROM ( + SELECT fn1.basic_range(%d) + FROM DUAL + ) + ) + ''' % (a, b, c) + self.query(q) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/basic.py b/test_container/tests/test/generic/r/basic.py new file mode 100755 index 000000000..17af93d83 --- /dev/null +++ b/test_container/tests/test/generic/r/basic.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +import re + + +class BasicTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'performance.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + def test_basic_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_range(3) + FROM DUAL + ''') + self.assertRowsEqual([(x,) for x in range(3)], sorted(rows)) + + def test_basic_set_returns(self): + rows = self.query(''' + SELECT fn1.basic_sum(3) + FROM DUAL + ''') + self.assertRowsEqual([(3,)], rows) + + def test_emit_two_ints(self): + rows = self.query(''' + SELECT fn1.basic_emit_two_ints() + FROM DUAL''') + self.assertRowsEqual([(1, 2)], rows) + + def test_simple_combination(self): + rows = self.query(''' + SELECT fn1.basic_sum(psum) + FROM ( + SELECT fn1.basic_nth_partial_sum(n) AS PSUM + FROM ( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(165,)], rows) + + def test_simple_combination_grouping(self): + rows = self.query(''' + SELECT fn1.BASIC_SUM_GRP(psum) + FROM ( + SELECT MOD(N, 3) AS n, + fn1.basic_nth_partial_sum(n) AS psum + FROM ( + SELECT fn1.basic_range(10) + FROM DUAL + ) + ) + GROUP BY n + ORDER BY 1''') + self.assertRowsEqual([(39.0,), (54.0,), (72.0,)], rows) + + def test_reset(self): + rows = self.query(''' + SELECT fn1.basic_test_reset(i, j) + FROM (SELECT fn1.basic_emit_several_groups(16, 8) FROM DUAL) + GROUP BY i + ORDER BY 1''') + self.assertRowsEqual([(0.0,), (0.0,), (0.0,), (0.0,), (1.0,), (1.0,), (1.0,), (1.0,), (2.0,)], rows[:9]) + + def test_order_by_clause(self): + rows = self.query(''' + SELECT fn1.performance_reduce_characters(w, c) + FROM ( + SELECT fn1.performance_map_characters('hello hello hello abc') + FROM DUAL + ) + GROUP BY w + ORDER BY c DESC''') + + unsorted_list = [tuple(x) for x in rows] + sorted_list = sorted(unsorted_list, key=lambda x: x[1], reverse=True) + #for x in zip(unsorted_list, sorted_list): + # print x + + self.assertEqual(sorted_list, unsorted_list) + + +class SetWithEmptyInput(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'basic.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('CREATE TABLE FN2.empty_table(c int)') + + def test_set_returns_has_empty_input_group_by(self): + self.query("""select FN1.set_returns_has_empty_input(c) from empty_table group by 'X'""") + self.assertEqual(0, self.rowcount()) + + def test_set_returns_has_empty_input_no_group_by(self): + rows = self.query('''select FN1.set_returns_has_empty_input(c) from empty_table''') + self.assertRowsEqual([(None,)], rows) + + + def test_set_emits_has_empty_input_group_by(self): + self.query("""select FN1.set_emits_has_empty_input(c) from empty_table group by 'X'""") + self.assertEqual(0, self.rowcount()) + + def test_set_emits_has_empty_input_no_group_by(self): + rows = self.query('''select FN1.set_emits_has_empty_input(c) from empty_table''') + self.assertRowsEqual([(None,None)], rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/combinations.py b/test_container/tests/test/generic/r/combinations.py new file mode 100755 index 000000000..eb00c3dea --- /dev/null +++ b/test_container/tests/test/generic/r/combinations.py @@ -0,0 +1,536 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +from exasol_python_test_framework.udf import ( + SkipTest, + useData, +) +import re + + +class Combinations_1_ary(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_RANGE, combinations.sql for test functions) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'combinations.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO small VALUES (0.1, 0.2), (0.2, 0.1)') + + def test_set_returns(self): + rows = self.query(''' + SELECT fn1.SET_RETURNS(x,y) + FROM small''') + self.assertEqual(round(0.6 / 2), round(rows[0][0] / 2)) + + + def test_scalar_returns(self): + rows = self.query(''' + SELECT round(fn1.scalar_returns(x,y) / 2) + FROM small''') + self.assertRowsEqual([(round(0.3 / 2),), (round(0.3 / 2),)], rows) + + def test_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10 ,y * 10) + FROM small''') + self.assertRowsEqual([(1, 1,), (2, 4,)], rows) + + def test_set_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x * 10 ,y * 10) + FROM small''') + self.assertRowsEqual([(2.0, 1.0,), (1.0, 2.0,)], rows) + + def test_two_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.scalar_returns(fn1.scalar_returns(x * 10 ,y * 10), + fn1.scalar_returns(y * 10 ,x * 10)) + FROM small''') + self.assertRowsEqual([(6,), (6,)], rows) + + +class Combinations_2_ary_scalar_returns(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_RANGE, combinations.sql for test functions) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'combinations.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO small VALUES (0.1, 0.2), (0.2, 0.1)') + + def test_scalar_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_returns(x * 10 ,y * 10 ) + FROM ( + SELECT fn1.scalar_emits(x * 10 ,y * 10) + FROM small + )''') + self.assertRowsEqual([(20,), (60,)], rows) + + def test_scalar_returns_set_returns_inline(self): + rows = self.query(''' + SELECT + fn1.scalar_returns( + fn1.set_returns(x * 10, y * 10), + fn1.set_returns(x * 10, y * 10) + ) + FROM small''') + self.assertRowsEqual([(12,)], rows) + + def test_scalar_returns_set_returns_1(self): + rows = self.query(''' + SELECT fn1.scalar_returns(a, 5) + FROM ( + SELECT fn1.set_returns(x * 10, y * 10) AS a + FROM SMALL + ) + ''') + self.assertRowsEqual([(11,)], rows) + + def test_scalar_returns_set_returns_2(self): + rows = self.query(''' + SELECT fn1.scalar_returns(aa.a, bb.a) + FROM ( + SELECT fn1.set_returns(x * 10, y * 20) AS a + FROM SMALL + ) AS aa, + ( + SELECT fn1.set_returns(x * 20, y * 10) AS a + FROM SMALL + ) AS bb + ''') + self.assertRowsEqual([(18,)], rows) + + def test_scalar_returns_set_emits_1(self): + rows = self.query(''' + SELECT fn1.scalar_returns(x * 10, y * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM small + )''') + self.assertRowsEqual([(30,), (30,)], rows) + + def test_scalar_returns_set_emits_2(self): + rows = self.query(''' + SELECT fn1.scalar_returns(aa.x * 10, bb.x * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM small + ) AS aa, + ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM small + ) AS bb + WHERE aa.x = bb.y and aa.y = bb.x + ''') + self.assertRowsEqual([(30,), (30,)], rows) + + +class Combinations_2_ary_scalar_emits(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_RANGE, combinations.sql for test functions) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'combinations.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO small VALUES (0.1, 0.2), (0.2, 0.1)') + + def test_scalar_emits_scalar_returns_inline(self): + rows = self.query(''' + SELECT + fn1.scalar_emits( + fn1.scalar_returns(x * 10, y * 10), + fn1.scalar_returns(y * 10, x * 10) + ) + FROM small''') + self.assertRowsEqual([(3, 9,), (3, 9,)], rows) + + def test_scalar_emits_scalar_returns(self): + rows = self.query(''' + SELECT fn1.scalar_emits(a, b) + FROM ( + SELECT + fn1.scalar_returns(x * 10 ,y * 10) AS A, + fn1.scalar_returns(y * 10 ,x * 10) AS B + FROM small + ) + ''') + self.assertRowsEqual([(3, 9,), (3, 9,)], rows) + + def test_scalar_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10,y * 10) + FROM ( + SELECT fn1.scalar_emits(x * 10,y * 10) + FROM small + ) + ORDER by x,y''') + r = [(10.0, 100.0)] + r.extend([(i, i * i) for i in range(20, 41)]) + self.assertRowsEqual(r, rows) + + def test_scalar_emits_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.scalar_emits( + fn1.set_returns(x * 10, y * 10), + fn1.set_returns(x * 10, y * 10) + ) + FROM small''') + + def test_scalar_emits_set_returns(self): + rows = self.query(''' + SELECT fn1.scalar_emits(a, b) + FROM ( + SELECT fn1.set_returns(x * 10, y * 10) AS a + FROM small + ), + ( + SELECT fn1.set_returns(x * 10, y *10) AS b + FROM small + ) + ''') + self.assertRowsEqual([(6, 36)], rows) + + def test_scalar_emits_set_emits(self): + rows = self.query(''' + SELECT fn1.scalar_emits(x * 10, y * 10) + FROM ( + SELECT fn1.set_emits(x * 10, y * 10) + FROM small + )''') + r = ([(i, i * i) for i in range(10, 21)]) + self.assertRowsEqual(r, rows) + + +class Combinations_2_ary_set_returns(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_RANGE, combinations.sql for test functions) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'combinations.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO small VALUES (0.1, 0.2), (0.2, 0.1)') + + def test_set_returns_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.set_returns( + fn1.scalar_returns(x * 10, y * 10), + fn1.scalar_returns(y * 10, x * 10) + ) + FROM small''') + self.assertRowsEqual([(12,)], rows) + + def test_set_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.set_returns(x*10, y*10) + FROM ( + SELECT fn1.scalar_emits(x*10, y*10) + FROM small + )''') + self.assertRowsEqual([(80,)], rows) + + def test_set_returns_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.set_returns( + fn1.set_returns(x*10, y*10), + fn1.set_returns(x*10, y*10) + ) + FROM small''') + + def test_set_returns_set_returns(self): + rows = self.query(''' + SELECT fn1.set_returns(a, b) + FROM( + SELECT fn1.set_returns(x*20, y*30) AS a + FROM small + ), + ( + SELECT fn1.set_returns(x*50, y*70) AS b + FROM small + )''') + self.assertRowsEqual([(51,)], rows) + + def test_set_returns_set_emits(self): + rows = self.query(''' + SELECT fn1.set_returns(x*10, y*10) + FROM ( + SELECT fn1.set_emits(x*10, y*10) + FROM small + )''') + self.assertRowsEqual([(60,)], rows) + + +class Combinations_2_ary_set_emits(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_RANGE, combinations.sql for test functions) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'combinations.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO small VALUES (0.1, 0.2), (0.2, 0.1)') + + def test_set_emits_scalar_returns(self): + rows = self.query(''' + SELECT + fn1.set_emits( + fn1.scalar_returns(x*10, y*10), + fn1.scalar_returns(y*10, x*10) + ) + FROM small''') + self.assertRowsEqual([(3, 3,), (3, 3,)], rows) + + def test_set_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x*10, y*10) + FROM ( + SELECT fn1.scalar_emits(x*10, 10*y) + FROM small + ) + ORDER BY x, y;''') + self.assertRowsEqual([(10, 10,), (40, 20,)], rows) + + def test_set_emits_set_returns_inline(self): + with self.assertRaisesRegex(Exception, 'encapsulated set function'): + self.query(''' + SELECT + fn1.set_emits( + fn1.set_returns(x*10, 10*y), + fn1.set_returns(10*x, y*10) + ) + FROM small''') + + def test_set_emits_set_returns(self): + rows = self.query(''' + SELECT fn1.set_emits(a, b) + FROM ( + SELECT fn1.set_returns(x*20, 30*y) AS a + FROM small + ), + ( + SELECT fn1.set_returns(50*x, y*70) AS b + FROM small + ) + ''') + self.assertRowsEqual([(36, 15,)], rows) + + def test_set_emits_set_emits(self): + rows = self.query(''' + SELECT fn1.set_emits(x * 10 , 10 * y) + FROM ( + SELECT fn1.set_emits(x * 10, y *10) + FROM small + ) + ORDER BY x, y;''') + self.assertRowsEqual([(10, 20,), (20, 10,)], rows) + + +class Combinations_3_ary(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_RANGE, combinations.sql for test functions) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'combinations.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO small VALUES (0.1, 0.2), (0.2, 0.1)') + + def test_set_returns_set_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(s) + FROM ( + SELECT fn1.basic_sum_grp(n) + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(45,)], rows) + + def test_set_returns_scalar_emits_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(x) + FROM ( + SELECT fn1.scalar_emits(n, n+2) + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(165,)], rows) + + def test_set_returns_scalar_returns_scalar_emits(self): + rows = self.query(''' + SELECT fn1.basic_sum(x) + FROM ( + SELECT fn1.scalar_returns(n, 2) AS x + FROM( + SELECT fn1.basic_range(10) + FROM DUAL + ) + )''') + self.assertRowsEqual([(65,)], rows) + + +class Combinations_n_ary(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_RANGE, combinations.sql for test functions) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'combinations.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA combinations CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA combinations') + self.query('CREATE TABLE small(x DOUBLE, y DOUBLE)') + self.query('INSERT INTO small VALUES (0.1, 0.2), (0.2, 0.1)') + + def partial_sum(n, degree): + def basic_range(n, d): + if d == 0: + return list(range(n)) + else: + return sum([list(range(x)) for x in basic_range(n + 1, d - 1)], []) + + return len(basic_range(n, degree)) + + @useData((i,) for i in range(10)) + def test_n_scalar_emits(self, n): + if 'BASIC_RANGE' not in udf.capabilities: + raise SkipTest('requires: BASIC_RANGE') + + self.query( + 'SELECT fn1.basic_range(n+1) FROM (\n' * n + + 'SELECT fn1.basic_range(5) FROM DUAL\n' + + ')' * n) + self.assertEqual(self.partial_sum(5, n), self.rowcount()) + + @useData((i,) for i in range(10)) + def test_set_returns_n_scalar_emits(self, n): + if 'BASIC_RANGE' not in udf.capabilities: + raise SkipTest('requires: BASIC_RANGE') + + rows = self.query( + 'SELECT max(n) FROM (' + + 'SELECT fn1.basic_range(n+1) FROM (\n' * n + + 'SELECT fn1.basic_range(5) FROM DUAL\n' + + ')' * (n + 1)) + self.assertEqual(4, rows[0][0]) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/dynamic_input.py b/test_container/tests/test/generic/r/dynamic_input.py new file mode 100755 index 000000000..79f877b46 --- /dev/null +++ b/test_container/tests/test/generic/r/dynamic_input.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +import re + + +class DynamicMetadataTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_* functions, dynamic_input.sql for dynamic input tests) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'dynamic_input.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA dynamic_input CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_input') + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + + def test_meta_scalar_return(self): + rows = self.query(''' + SELECT fn1.metadata_scalar_return('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertRowEqual(('2',), rows[0]) + + def test_meta_scalar_emit(self): + rows = self.query(''' + SELECT fn1.metadata_scalar_emit('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertRowEqual(('2',), rows[0]) + self.assertRowEqual(('0',), rows[1]) + self.assertTrue(rows[2][0] == "string" or rows[2][0] == "" or rows[2][0] == "character" or rows[2][0] == "java.lang.String" or rows[2][0] == "") + self.assertRowEqual(('CHAR(3) ASCII',), rows[3]) + self.assertRowEqual(('3',), rows[6]) + self.assertRowEqual(('1',), rows[7]) + self.assertTrue(rows[8][0] == 'number' or rows[8][0] == "" or rows[8][0] == "double" or rows[8][0] == "java.lang.Double" or rows[8][0] == "") + self.assertRowEqual(('DOUBLE',), rows[9]) + + +class DynamicInputBasic(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_* functions, dynamic_input.sql for dynamic input tests) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'dynamic_input.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA dynamic_input CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_input') + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + + def test_basic_scalar_emit_constants(self): + rows = self.query(''' + SELECT fn1.basic_scalar_emit('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertTrue(rows[0][0] == 'abc' or rows[0][0] == "u'abc'" or rows[0][0] == "'abc'") + self.assertTrue(rows[1][0] == '99' or rows[1][0] == "99.0") + + def test_basic_scalar_emit(self): + rows = self.query(''' + SELECT fn1.basic_scalar_emit(x, y) + FROM small + ''') + self.assertTrue(rows[0][0] == 'Some string ... and some more' or rows[0][0] == "u'Some string ... and some more'" or rows[0][0] == "'Some string ... and some more'") + self.assertRowEqual(('2.2',), rows[1]) + + def test_basic_scalar_return_constants(self): + rows = self.query(''' + SELECT fn1.basic_scalar_return('abc', cast(99 as double)) + FROM DUAL + ''') + self.assertTrue(rows[0][0] == '99' or rows[0][0] == "99.0") + + def test_basic_scalar_return(self): + rows = self.query(''' + SELECT fn1.basic_scalar_return(x, y, x, y, x, y, x, y, x, y, x, y, x, y, x, y) + FROM small + ''') + self.assertRowEqual(('2.2',), rows[0]) + + def test_basic_set_emit_constants(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(cast(99 as double),'77','aaaa') + FROM DUAL + ''') + print("0---:"+str(rows[3][0])) + self.assertTrue(rows[0][0] == '99' or rows[0][0] == "99.0") + self.assertTrue(rows[1][0] == '77' or rows[1][0] == "u'77'" or rows[1][0] == "'77'") + self.assertTrue(rows[2][0] == 'aaaa' or rows[2][0] == "u'aaaa'" or rows[2][0] == "'aaaa'") + self.assertTrue(rows[3][0] == 'result: , 99 , 77 , aaaa' or rows[3][0] == "result: 99.0 , u'77' , u'aaaa' , " or rows[3][0] == "result: 99 , 77 , aaaa , " or rows[3][0] == "result: 99.0 , 77 , aaaa , " or rows[3][0] == "result: 99.0 , '77' , 'aaaa' , ") + + def test_basic_set_emit(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(n, v) + FROM groupt GROUP BY id ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == '1' or rows[0][0] == "1.0" or rows[0][0] == "'aa'") + self.assertTrue(rows[1][0] == '2' or rows[1][0] == "2.0" or rows[1][0] == "'ab'") + self.assertTrue(rows[2][0] == '2' or rows[2][0] == "2.0" or rows[2][0] == "'ba'") + self.assertTrue(rows[3][0] == 'aa' or rows[3][0] == "result: 1.0 , u'aa' , 2.0 , u'ab' , " or rows[3][0] == "1.0") + self.assertTrue(rows[4][0] == 'ab' or rows[4][0] == "result: 2.0 , u'ba' , " or rows[4][0] == "2.0") + self.assertTrue(rows[5][0] == 'ba' or rows[5][0] == "u'aa'" or rows[5][0] == "2.0") + self.assertTrue(rows[6][0] == 'result: , 1 , aa , 2 , ab' or rows[6][0] == "u'ab'" or rows[6][0] == "result: 1 , aa , 2 , ab , " or rows[6][0] == "result: 1.0 , aa , 2.0 , ab , " or rows[6][0] == "result: 1.0 , 'aa' , 2.0 , 'ab' , ") + self.assertTrue(rows[7][0] == 'result: , 2 , ba' or rows[7][0] == "u'ba'" or rows[7][0] == "result: 2 , ba , " or rows[7][0] == "result: 2.0 , ba , " or rows[7][0] == "result: 2.0 , 'ba' , ") + + def test_basic_set_emit_one_group(self): + rows = self.query(''' + SELECT fn1.basic_set_emit(cast(id as double), n, v) + FROM groupt ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == '1' or rows[0][0] == "1.0" or rows[0][0] == "'aa'") + self.assertTrue(rows[1][0] == '1' or rows[1][0] == "1.0" or rows[1][0] == "'ab'") + self.assertTrue(rows[2][0] == '1' or rows[2][0] == "1.0" or rows[2][0] == "'ba'") + self.assertTrue(rows[3][0] == '2' or rows[3][0] == "2.0" or rows[3][0] == "1.0") + self.assertTrue(rows[4][0] == '2' or rows[4][0] == "2.0" or rows[4][0] == "1.0") + self.assertTrue(rows[5][0] == '2' or rows[5][0] == "2.0" or rows[5][0] == "1.0") + self.assertTrue(rows[6][0] == 'aa' or rows[7][0] == "u'aa'" or rows[6][0] == "2.0") + self.assertTrue(rows[7][0] == 'ab' or rows[8][0] == "u'ab'" or rows[7][0] == "2.0") + self.assertTrue(rows[8][0] == 'ba' or rows[9][0] == "u'ba'" or rows[8][0] == "2.0") + self.assertTrue(rows[9][0] == 'result: , 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab' \ + or rows[6][0] == "result: 1.0 , 1.0 , u'aa' , 2.0 , 2.0 , u'ba' , 1.0 , 2.0 , u'ab' , " \ + or rows[9][0] == "result: 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab , " \ + or rows[9][0] == "result: 1.0 , 1.0 , aa , 2.0 , 2.0 , ba , 1.0 , 2.0 , ab , " or rows[9][0] == "result: 1.0 , 1.0 , 'aa' , 2.0 , 2.0 , 'ba' , 1.0 , 2.0 , 'ab' , ") + + def test_basic_set_return_constants(self): + rows = self.query(''' + SELECT fn1.basic_set_return(cast(99 as double),'77','aaaa') + FROM DUAL + ''') + self.assertTrue(rows[0][0] == 'result: , 99 , 77 , aaaa ' \ + or rows[0][0] == "result: 99.0 , u'77' , u'aaaa' , " \ + or rows[0][0] == "result: 99 , 77 , aaaa , " \ + or rows[0][0] == "result: 99.0 , 77 , aaaa , " or rows[0][0] == "result: 99.0 , '77' , 'aaaa' , ") + + def test_basic_set_return(self): + rows = self.query(''' + SELECT fn1.basic_set_return(n, v) + FROM groupt GROUP BY id ORDER BY 1 + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , aa , 2 , ab ' \ + or rows[0][0] == "result: 1.0 , u'aa' , 2.0 , u'ab' , " \ + or rows[0][0] == "result: 1 , aa , 2 , ab , " \ + or rows[0][0] == "result: 1.0 , aa , 2.0 , ab , " or rows[0][0] == "result: 1.0 , 'aa' , 2.0 , 'ab' , ") + self.assertTrue(rows[1][0] == 'result: , 2 , ba ' \ + or rows[1][0] == "result: 2.0 , u'ba' , " \ + or rows[1][0] == "result: 2 , ba , " \ + or rows [1][0] == "result: 2.0 , ba , " or rows [1][0] == "result: 2.0 , 'ba' , ") + + def test_basic_set_return_one_group(self): + rows = self.query(''' + SELECT fn1.basic_set_return(cast(id as double), n, v) + FROM groupt + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab ' \ + or rows[0][0] == "result: 1.0 , 1.0 , u'aa' , 2.0 , 2.0 , u'ba' , 1.0 , 2.0 , u'ab' , " \ + or rows[0][0] == "result: 1 , 1 , aa , 2 , 2 , ba , 1 , 2 , ab , " \ + or rows[0][0] == "result: 1.0 , 1.0 , aa , 2.0 , 2.0 , ba , 1.0 , 2.0 , ab , " or rows[0][0] == "result: 1.0 , 1.0 , 'aa' , 2.0 , 2.0 , 'ba' , 1.0 , 2.0 , 'ab' , ") + + +class DynamicInputDatatypeSpecific(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'dynamic_input.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA dynamic_input CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_input') + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + + def test_type_specific_add_string(self): + rows = self.query(''' + SELECT fn1.type_specific_add(v, v, v) + FROM groupt + ''') + self.assertTrue('result: , aa , aa , aa , ba , ba , ba , ab , ab , ab' == rows[0][0] or 'result: aa , aa , aa , ba , ba , ba , ab , ab , ab , ' == rows[0][0]) + + def test_type_specific_add_number(self): + rows = self.query(''' + SELECT fn1.type_specific_add(n,n,n,n,n,n,n,n,n,n) + FROM groupt + ''') + self.assertTrue(rows[0][0] == 'result: 50' or rows[0][0] == "result: 50.0" or rows[0][0] == 'result: 50') + + +class DynamicInputErrors(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'dynamic_input.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA dynamic_input CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_input') + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + + def test_exception_wrong_arg(self): + if udf.opts.lang == 'r' or udf.opts.lang is None: + raise udf.SkipTest('does not work with R currently') + err_text = { + 'lua': 'out of range', + 'python3': 'does not exist', + 'java': 'does not exist', + } + with self.assertRaisesRegex(Exception, err_text[udf.opts.lang]): + self.query('''select fn1.wrong_arg('a') from dual''') + + def test_exception_wrong_operation(self): + err_text = { + 'lua': 'attempt to perform arithmetic on field', + 'r': 'non-numeric argument to binary operator', + 'python3': 'multiply sequence by non-int of type', + 'java': 'bad operand types for binary operator', + } + # Default to 'r' if lang is None (running generic R tests) + lang = udf.opts.lang if udf.opts.lang is not None else 'r' + with self.assertRaisesRegex(Exception, err_text[lang]): + self.query('''select fn1.wrong_operation('a','b') from dual''') + + def test_exception_empty_set_returns(self): + with self.assertRaisesRegex(Exception, 'data exception - missing input parameters for SET UDF script'): + self.query('''select fn1.empty_set_returns() from groupt''') + + def test_exception_empty_set_emits(self): + with self.assertRaisesRegex(Exception, 'data exception - missing input parameters for SET UDF script'): + self.query('''select fn1.empty_set_emits() from groupt''') + +class DynamicInputOptimizations(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'dynamic_input.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA dynamic_input CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA dynamic_input') + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + + def test_mapreduce_optimization(self): + rows = self.query(''' + select fn1.basic_set_return("v") from ( select fn1.basic_scalar_emit(n,n,n,n,n,n,n,n,n,n) from groupt) + ''') + self.assertTrue(rows[0][0] == 'result: , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ' \ + or rows[0][0] == "result: u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'1.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , u'2.0' , " \ + or rows[0][0] == "result: 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , " \ + or rows[0][0] == "result: 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 1.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , 2.0 , " or rows[0][0] == "result: '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '1.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , '2.0' , ") + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/dynamic_output.py b/test_container/tests/test/generic/r/dynamic_output.py new file mode 100755 index 000000000..fb30057be --- /dev/null +++ b/test_container/tests/test/generic/r/dynamic_output.py @@ -0,0 +1,2713 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework import exatest +import pathlib +import re + + +class DynamicOutputCreateScript(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + def test_create_script_set(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='VAREMIT_SIMPLE_SET' and SCRIPT_TEXT LIKE 'CREATE % SET SCRIPT "VAREMIT_SIMPLE_SET" ("a" DOUBLE) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_scalar(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='VAREMIT_SIMPLE_SCALAR' and SCRIPT_TEXT LIKE 'CREATE % SCALAR SCRIPT "VAREMIT_SIMPLE_SCALAR" ("a" DOUBLE) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_all_dyn(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='VAREMIT_SIMPLE_ALL_DYN' and SCRIPT_TEXT LIKE 'CREATE % SCALAR SCRIPT "VAREMIT_SIMPLE_ALL_DYN" (...) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_syntax_var(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='VAREMIT_SIMPLE_SYNTAX_VAR' and SCRIPT_TEXT LIKE 'CREATE % SET SCRIPT "VAREMIT_SIMPLE_SYNTAX_VAR" (...) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + +class DynamicOutputTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_generic_emit(self): + rows = self.query(''' + SELECT fn1.VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100)); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + + def test_all_generic(self): + rows = self.query(''' + SELECT fn1.VAREMIT_ALL_GENERIC('SUPERDYNAMIC') EMITS (a varchar(100)); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + + def test_correctness_emits_subquery(self): + rows = self.query(''' + SELECT "A" || 'x' || "B" FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100), b varchar(100))); + ''') + self.assertRowEqual(('SUPERDYNAMICxSUPERDYNAMIC',), rows[0]) + + def test_correctness_emits_with_grouping(self): + rows = self.query(''' + SELECT 'X' || count(a) || 'X' FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100)) + FROM groupt GROUP BY id + ) where a = 'SUPERDYNAMIC'; + ''') + self.assertRowEqual(('X2X',), rows[0]) + + + def test_correctness_nested(self): + rows = self.query(''' + SELECT fn1.VAREMIT_GENERIC_EMIT(c || 'D') EMITS (d varchar(100)) FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT(b || 'C') EMITS (c varchar(100)) FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT(a || 'B') EMITS(b varchar(100)) FROM ( + SELECT fn1.VAREMIT_GENERIC_EMIT('A') EMITS (a varchar(100)) + ) + ) + ); + ''') + self.assertRowEqual(('ABCD',), rows[0]) + + + def test_metadata_correctness(self): + rows = self.query(''' + SELECT fn1.VAREMIT_METADATA_SET_EMIT(1) EMITS (a varchar(123), b double) + FROM DUAL + ''') + stringType = { + 'python3': ["", ""], + 'r': ["character"], + 'lua': ["string"], + 'java': ["java.lang.String"] + } + numType = { + 'python3': ["", ""], + 'r': ["double"], + 'lua': ["number"], + 'java': ["java.lang.Double"] + } + # Default to 'r' if lang is None (running generic R tests) + lang = udf.opts.lang if udf.opts.lang is not None else 'r' + self.assertRowEqual(('2',1.0), rows[0]) + self.assertRowEqual(('A',1.0), rows[1]) + self.assertTrue(rows[2][0] in stringType.get(lang)) + self.assertRowEqual(('VARCHAR(123) UTF8',1), rows[3]) + self.assertRowEqual(('123',1.0), rows[6]) + self.assertRowEqual(('B',1.0), rows[7]) + self.assertTrue(rows[8][0] in numType.get(lang)) + self.assertRowEqual(('DOUBLE',1.0), rows[9]) + + +class DynamicOutputWrongUsage(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_error_emit_missing(self): + #with self.assertRaisesRegex(Exception, 'The script has dynamic return arguments. Either specify the return arguments in the query via EMITS or implement the method (default_output_columns|getDefaultOutputColumns|defaultOutputColumns) in the UDF'): + with self.assertRaisesRegex(Exception, 'The script has dynamic return arguments. Either specify the return arguments in the query via EMITS or implement the method'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1)''') + + def test_error_empty_emit(self): + with self.assertRaisesRegex(Exception, 'Empty return argument definition is not allowed'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1) EMITS ();''') + + def test_error_empty_emit_2(self): + with self.assertRaisesRegex(Exception, 'syntax error'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1) EMITS (a);''') + + def test_error_wrong_emit(self): + with self.assertRaisesRegex(Exception, 'syntax error'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1) EMITS (int);''') + + def test_error_redundant_name(self): + with self.assertRaisesRegex(Exception, 'Return argument A is declared more than once'): + self.query('''SELECT fn1.VAREMIT_GENERIC_EMIT(1) EMITS (a int, b int, a int);''') + + def test_error_non_var_emit(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''SELECT fn1.VAREMIT_NON_VAR_EMIT(1) EMITS (a double);''') + + def test_error_non_var_emit_2(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''SELECT fn1.VAREMIT_NON_VAR_EMIT(1) EMITS ();''') + + def test_error_returns_not_supported(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''select fn1.VAREMIT_SIMPLE_RETURNS(1) EMITS (a INT);''') + + def test_error_built_in_set_not_supported(self): + with self.assertRaisesRegex(Exception, 'emits specification is not allowed for built-in functions'): + self.query('''SELECT AVG(a) EMITS(a int) FROM VAREMITS;''') + + def test_error_built_in_scalar_not_supported(self): + with self.assertRaisesRegex(Exception, 'emits specification is not allowed for built-in functions'): + self.query('''SELECT -ABS(a) EMITS(a int) FROM VAREMITS;''') + + + +class DynamicOutputInsertInto(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_insert_basic(self): + self.query('''delete from target;''') + self.query(''' + insert into target select fn1.VAREMIT_EMIT_INPUT(1, CAST (1.1 AS DOUBLE), 'a'); + ''') + rows = self.query(''' + select * from target; + ''') + self.assertRowEqual((1, 1.1, 'a'), rows[0]) + self.query('''delete from target;''') + + def test_insert_metadata_correctness(self): + self.query(''' + insert into target select fn1.VAREMIT_EMIT_INPUT_WITH_META_CHECK(cast (2 as int), CAST (2.2 AS DOUBLE), cast ('b' as varchar(100))); + ''') + rows = self.query(''' + select * from target; + ''') + self.assertRowEqual((2, 2.2, 'b'), rows[0]) + self.query('''delete from target;''') + + def test_insert_target_columns_change_order(self): + self.query(''' + insert into target (c, b, a) select fn1.VAREMIT_EMIT_INPUT('c', CAST (3.3 AS DOUBLE), 3); + ''') + rows = self.query(''' + select * from target; + ''') + self.assertRowEqual((3, 3.3, 'c'), rows[0]) + self.query('''delete from target;''') + + def test_insert_target_columns_subset(self): + self.query(''' + insert into target (b) select fn1.VAREMIT_EMIT_INPUT(CAST (4.4 AS DOUBLE)); + ''') + rows = self.query(''' + select * from target; + ''') + self.assertRowEqual((None, 4.4, None), rows[0]) + self.query('''delete from target;''') + + def test_insert_emits_not_allowed(self): + with self.assertRaisesRegex(Exception, 'The return arguments for EMITS functions are inferred from the table to insert into. Specification of EMITS is not allowed in this case.'): + self.query('''insert into target select FN1.VAREMIT_EMIT_INPUT(1) emits (a int);''') + + +class DynamicOutputCreateTableAs(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_insert_basic(self): + self.query('''drop table if exists targetcreated;''') + self.query(''' + create table targetcreated as select fn1.VAREMIT_EMIT_INPUT(1, CAST (1.1 AS DOUBLE), 'a') emits (a decimal(20,0), b double, c varchar(100)); + ''') + rows = self.query(''' + select * from targetcreated; + ''') + self.assertRowEqual((1, 1.1, 'a'), rows[0]) + rows = self.query(''' + describe targetcreated; + ''') + self.assertRowEqual(('A', 'DECIMAL(20,0)'), rows[0][0:2]) + self.assertRowEqual(('B', 'DOUBLE'), rows[1][0:2]) + self.assertRowEqual(('C', 'VARCHAR(100) UTF8'), rows[2][0:2]) + +## ##################################################### +## The same as above but now with default output columns +## ##################################################### + +class DefaultDynamicOutputCreateScript(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_create_script_set(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='DEFAULT_VAREMIT_SIMPLE_SET' and SCRIPT_TEXT LIKE 'CREATE % SET SCRIPT "DEFAULT_VAREMIT_SIMPLE_SET" ("a" DOUBLE) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_scalar(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='DEFAULT_VAREMIT_SIMPLE_SCALAR' and SCRIPT_TEXT LIKE 'CREATE % SCALAR SCRIPT "DEFAULT_VAREMIT_SIMPLE_SCALAR" ("a" DOUBLE) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_all_dyn(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='DEFAULT_VAREMIT_SIMPLE_ALL_DYN' and SCRIPT_TEXT LIKE 'CREATE % SCALAR SCRIPT "DEFAULT_VAREMIT_SIMPLE_ALL_DYN" (...) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + def test_create_script_syntax_var(self): + rows = self.query(''' + select count(*) from exa_all_scripts where script_name='DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR' and SCRIPT_TEXT LIKE 'CREATE % SET SCRIPT "DEFAULT_VAREMIT_SIMPLE_SYNTAX_VAR" (...) EMITS (...) AS%'; + ''') + self.assertRowEqual((1,), rows[0]) + + +class DefaultDynamicOutputTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_generic_emit(self): + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100)); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC'); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + + def test_all_generic(self): + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_ALL_GENERIC('SUPERDYNAMIC') EMITS (a varchar(100)); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_ALL_GENERIC('SUPERDYNAMIC'); + ''') + self.assertRowEqual(('SUPERDYNAMIC',), rows[0]) + + + def test_correctness_emits_subquery(self): + rows = self.query(''' + SELECT "A" || 'x' || "B" FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100), b varchar(100))); + ''') + self.assertRowEqual(('SUPERDYNAMICxSUPERDYNAMIC',), rows[0]) + rows = self.query(''' + SELECT "A" || 'x' || "A" FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC')); + ''') + self.assertRowEqual(('SUPERDYNAMICxSUPERDYNAMIC',), rows[0]) + + def test_correctness_emits_with_grouping(self): + rows = self.query(''' + SELECT 'X' || count(a) || 'X' FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') EMITS (a varchar(100)) + FROM groupt GROUP BY id + ) where a = 'SUPERDYNAMIC'; + ''') + self.assertRowEqual(('X2X',), rows[0]) + rows = self.query(''' + SELECT 'X' || count(a) || 'X' FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('SUPERDYNAMIC') + FROM groupt GROUP BY id + ) where a = 'SUPERDYNAMIC'; + ''') + self.assertRowEqual(('X2X',), rows[0]) + + + def test_correctness_nested(self): + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(c || 'D') EMITS (d varchar(100)) FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(b || 'C') EMITS (c varchar(100)) FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(a || 'B') EMITS(b varchar(100)) FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('A') EMITS (a varchar(100)) + ) + ) + ); + ''') + self.assertRowEqual(('ABCD',), rows[0]) + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(a || 'D') FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(a || 'C') FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(a || 'B') FROM ( + SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT('A') + ) + ) + ); + ''') + self.assertRowEqual(('ABCD',), rows[0]) + + + def test_metadata_correctness(self): + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_METADATA_SET_EMIT(1) EMITS (a varchar(123), b double) + FROM DUAL + ''') + stringType = { + 'python3': ["", ""], + 'r': ["character"], + 'lua': ["string"], + 'java': ["java.lang.String"] + } + numType = { + 'python3': ["", ""], + 'r': ["double"], + 'lua': ["number"], + 'java': ["java.lang.Double"] + } + # Default to 'r' if lang is None (running generic R tests) + lang = udf.opts.lang if udf.opts.lang is not None else 'r' + self.assertRowEqual(('2',1.0), rows[0]) + self.assertRowEqual(('A',1.0), rows[1]) + self.assertTrue(rows[2][0] in stringType.get(lang)) + self.assertRowEqual(('VARCHAR(123) UTF8',1), rows[3]) + self.assertRowEqual(('123',1.0), rows[6]) + self.assertRowEqual(('B',1.0), rows[7]) + self.assertTrue(rows[8][0] in numType.get(lang)) + self.assertRowEqual(('DOUBLE',1.0), rows[9]) + # now again with intrinsic emits clause + rows = self.query(''' + SELECT fn1.DEFAULT_VAREMIT_METADATA_SET_EMIT(1) + FROM DUAL + ''') + self.assertRowEqual(('2',1.0), rows[0]) + self.assertRowEqual(('A',1.0), rows[1]) + self.assertTrue(rows[2][0] in stringType.get(lang)) + self.assertRowEqual(('VARCHAR(123) UTF8',1), rows[3]) + self.assertRowEqual(('123',1.0), rows[6]) + self.assertRowEqual(('B',1.0), rows[7]) + self.assertTrue(rows[8][0] in numType.get(lang)) + self.assertRowEqual(('DOUBLE',1.0), rows[9]) + + +class DefaultDynamicOutputWrongUsage(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + ## def test_error_emit_missing(self): + ## with self.assertRaisesRegex(Exception, 'The script has dynamic return args, but EMITS specification is missing in the query'): + ## self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1)''') + + def test_error_empty_emit(self): + with self.assertRaisesRegex(Exception, 'Empty return argument definition is not allowed'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1) EMITS ();''') + + def test_error_empty_emit_2(self): + with self.assertRaisesRegex(Exception, 'syntax error'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1) EMITS (a);''') + + def test_error_wrong_emit(self): + with self.assertRaisesRegex(Exception, 'syntax error'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1) EMITS (int);''') + + def test_error_redundant_name(self): + with self.assertRaisesRegex(Exception, 'Return argument A is declared more than once'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_GENERIC_EMIT(1) EMITS (a int, b int, a int);''') + + def test_error_non_var_emit(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_NON_VAR_EMIT(1) EMITS (a double);''') + + def test_error_non_var_emit_2(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''SELECT fn1.DEFAULT_VAREMIT_NON_VAR_EMIT(1) EMITS ();''') + + def test_error_returns_not_supported(self): + with self.assertRaisesRegex(Exception, 'The script has a static return argument definition. Dynamic return arguments are not supported in this case'): + self.query('''select fn1.DEFAULT_VAREMIT_SIMPLE_RETURNS(1) EMITS (a INT);''') + + + + +class DefaultDynamicOutputInsertInto(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_insert_basic(self): + self.query('''delete from target;''') + self.query(''' + insert into target select fn1.DEFAULT_VAREMIT_EMIT_INPUT(1, CAST (1.1 AS DOUBLE), 'a'); + ''') + rows = self.query(''' + select * from target; + ''') + self.assertRowEqual((1, 1.1, 'a'), rows[0]) + self.query('''delete from target;''') + + def test_insert_metadata_correctness(self): + self.query(''' + insert into target select fn1.DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK(cast (2 as int), CAST (2.2 AS DOUBLE), cast ('b' as varchar(100))); + ''') + rows = self.query(''' + select * from target; + ''') + self.assertRowEqual((2, 2.2, 'b'), rows[0]) + self.query('''delete from target;''') + + def test_insert_target_columns_change_order(self): + self.query(''' + insert into target (c, b, a) select fn1.DEFAULT_VAREMIT_EMIT_INPUT('c', CAST (3.3 AS DOUBLE), 3); + ''') + rows = self.query(''' + select * from target; + ''') + self.assertRowEqual((3, 3.3, 'c'), rows[0]) + self.query('''delete from target;''') + + def test_insert_target_columns_subset(self): + self.query(''' + insert into target (b) select fn1.DEFAULT_VAREMIT_EMIT_INPUT(CAST (4.4 AS DOUBLE)); + ''') + rows = self.query(''' + select * from target; + ''') + self.assertRowEqual((None, 4.4, None), rows[0]) + self.query('''delete from target;''') + + def test_insert_emits_not_allowed(self): + with self.assertRaisesRegex(Exception, 'The return arguments for EMITS functions are inferred from the table to insert into. Specification of EMITS is not allowed in this case.'): + self.query('''insert into target select FN1.DEFAULT_VAREMIT_EMIT_INPUT(1) emits (a int);''') + + +class DefaultDynamicOutputCreateTableAs(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_insert_basic(self): + self.query('''drop table if exists targetcreated;''') + self.query(''' + create table targetcreated as select fn1.DEFAULT_VAREMIT_EMIT_INPUT(1, CAST (1.1 AS DOUBLE), 'a') emits (a decimal(20,0), b double, c varchar(100)); + ''') + rows = self.query(''' + select * from targetcreated; + ''') + self.assertRowEqual((1, 1.1, 'a'), rows[0]) + rows = self.query(''' + describe targetcreated; + ''') + self.assertRowEqual(('A', 'DECIMAL(20,0)'), rows[0][0:2]) + self.assertRowEqual(('B', 'DOUBLE'), rows[1][0:2]) + self.assertRowEqual(('C', 'VARCHAR(100) UTF8'), rows[2][0:2]) + + +class DefaultDynamicOutputEmptyStringResult(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_empty_string_error(self): + with self.assertRaisesRegex(Exception, 'Empty default output columns'): + self.query('''select fn1.DEFAULT_VAREMIT_EMPTY_DEF(42.42);''') + rows = self.query('''select fn1.DEFAULT_VAREMIT_EMPTY_DEF(42.42) emits (x double);''') + self.assertRowEqual((1.4,), rows[0]) + + + +class DefaultDynamicOutputFromInputMeta(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def test_copy_relation(self): + rows = self.query(''' + select fn1.copy_relation(1,2,3); + ''') + self.assertRowEqual((1, 2, 3), rows[0]) + + + + +class DynamicOutFromConnectionsAndViews(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'dynamic_output.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # Add R-specific test scripts inline + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_GENERIC_EMIT (a varchar(100)) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_ALL_GENERIC (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[1]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_METADATA_SET_EMIT (...) EMITS(...) AS +run <- function(ctx) { + ctx$emit(paste(exa$meta$output_column_count), 1) + for(i in seq(exa$meta$output_column_count)) { + ctx$emit(paste(exa$meta$output_columns[[i]]$name), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$sql_type), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$precision), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$scale), 1) + ctx$emit(paste(exa$meta$output_columns[[i]]$length), 1) + } +} +defaultOutputColumns <- function() { + return ("a varchar(123), b double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_NON_VAR_EMIT (...) EMITS (a double) AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a double") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_SIMPLE_RETURNS (a int) RETURNS int AS +run <- function(ctx) { + ctx$emit(1) +} +defaultOutputColumns <- function() { + return ("a int") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT DEFAULT_VAREMIT_EMIT_INPUT_WITH_META_CHECK (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$output_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + return ("a int, b double, c varchar(100)") +} +''') + self.query(''' +CREATE R SET SCRIPT COPY_RELATION (...) EMITS (...) AS +run <- function(ctx) { + outRec <- list() + for (i in 1:exa$meta$input_column_count) { + outRec[i] <- ctx[[i]]() + } + do.call(ctx$emit, outRec) +} +defaultOutputColumns <- function() { + res <- c() + for (i in 0:(exa$meta$input_column_count-1)) { + col <- exa$meta$input_columns[[i]] + col_name <- col$name + tryCatch({ + col_name <- paste0("col_", as.integer(col_name)) + }, error = function(e) {}) + res <- c(res, paste(col_name, col$sql_type)) + } + return(paste(res, collapse=", ")) +} +''') + self.query(''' +CREATE R SCALAR SCRIPT OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 (a double) EMITS (...) AS +run <- function(ctx) { + ctx$emit(1.0, 2.2, 3.3, 4.4) +} +defaultOutputColumns <- function() { + return("PASSWORD double, A double, B double, C double") +} +''') + + self.query('''drop connection SPOT4245''', ignore_errors=True) + self.query('CREATE TABLE small(x VARCHAR(2000), y DOUBLE)') + self.query('''INSERT INTO small VALUES ('Some string ... and some more', 2.2)''') + self.query('create table groupt(id int, n double, v varchar(999))') + self.query('''insert into groupt values (1,1,'aa'), + (1,2,'ab'), + (2,2,'ba') + ''') + self.query('create table target (a int, b double, c varchar(100));') + self.query('''create connection SPOT4245 to 'a' user 'b' identified by 'c' ''') + + + def getConnection(self, username, password): + client = exatest.ODBCClient('exatest') + self.log.debug('connecting to DSN "exa" for user {username}'.format(username=username)) + client.connect(uid = username, pwd = password) + return client + + def createUser(self, username, password): + self.query('DROP USER IF EXISTS {username} CASCADE'.format(username = username)) + self.query('CREATE USER {username} IDENTIFIED BY "{password}"'.format(username = username, password = password)) + self.query('GRANT CREATE SESSION TO {username}'.format(username=username)) + + + def checkColumnNamesOfQuery(self,query,expected_rows): + self.query('''drop table if exists targetcreated''') + self.query('''create schema spot4245_tmp''') + self.query('''create table targetcreated as ''' + str(query)) + rows = self.query('''describe targetcreated''') + for i in range(len(expected_rows)): + self.assertRowEqual(expected_rows[i][0:2], rows[i][0:2]) + self.query('''drop schema spot4245_tmp cascade''') + #('A', 'DECIMAL(20,0)', 'TRUE', 'FALSE'), rows[0]) + #self.assertRowEqual(('B', 'DOUBLE', 'TRUE', 'FALSE'), rows[1]) + #self.assertRowEqual(('C', 'VARCHAR(100) UTF8', 'TRUE', 'FALSE'), rows[2]) + + + + def test_dynamic_out_from_connection_SPOT4245(self): + expected_rows = [('PASSWORD', 'DOUBLE', 'TRUE', 'FALSE'), ('A', 'DOUBLE', 'TRUE', 'FALSE'), ('B', 'DOUBLE', 'TRUE', 'FALSE'), ('C', 'DOUBLE', 'TRUE', 'FALSE')] + self.checkColumnNamesOfQuery('''select fn1.OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245(1.0)''', expected_rows) + + def test_dynamic_out_from_connection_SPOT4245_fails_for_user_foo(self): + self.createUser("foo","foo") + self.query('grant execute on fn1.OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245 to foo') + self.commit() + foo_conn = self.getConnection('foo','foo') + expected_rows = [('PASSWORD', 'DOUBLE', 'TRUE', 'FALSE'), ('A', 'DOUBLE', 'TRUE', 'FALSE'), ('B', 'DOUBLE', 'TRUE', 'FALSE'), ('C', 'DOUBLE', 'TRUE', 'FALSE')] + with self.assertRaisesRegex(Exception, 'insufficient privileges for using connection SPOT4245 in script OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245'): + foo_conn.query('''select fn1.OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245(1.0)''') + self.assertEqual(["PASSWORD","A","B","C"], foo_conn.columnNames()) + self.query("drop user foo cascade") + + + def test_dynamic_out_from_connection_SPOT4245_for_user_foo_with_view(self): + self.query('create view fn1.OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245_view as select fn1.OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245(1.0)') + self.createUser("foo","foo") + self.query('grant select on fn1.OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245_view to foo') + self.commit() + foo_conn = self.getConnection('foo','foo') + foo_conn.query('''select * from fn1.OUTPUT_COLUMNS_AS_IN_CONNECTION_SPOT4245_view''') + self.assertEqual(["PASSWORD","A","B","C"], [colDescription[0] for colDescription in foo_conn.cursorDescription()]) + self.query("drop user foo cascade") + + + +## ########################################################################## +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/emit.py b/test_container/tests/test/generic/r/emit.py new file mode 100644 index 000000000..5fafce37b --- /dev/null +++ b/test_container/tests/test/generic/r/emit.py @@ -0,0 +1,524 @@ +#!/usr/bin/env python3 + +import datetime + +from exasol_python_test_framework import udf +import pathlib +import re + + +class InputOutputMatchingTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'emit.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create table t(id double, x double)') + self.query('insert into t values (100,1),(100,2),(200,3)') + + def test_iomatch_1i_1o(self): + rows = self.query(''' + select x*2, fn1.line_1i_1o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,3,), (6,3,9,), (4,2,6,)]), sorted(rows)) + + def test_iomatch_1i_2o(self): + rows = self.query(''' + select x*2, fn1.line_1i_2o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,1,3,), (6,3,3,9,), (4,2,2,6,)]), sorted(rows)) + + def test_iomatch_2i_1o(self): + rows = self.query(''' + select x*2, fn1.line_2i_1o(x,id), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,101,3,), (6,203,9,), (4,102,6,)]), sorted(rows)) + + def test_iomatch_3i_2o(self): + rows = self.query(''' + select x*2, fn1.line_3i_2o(x,id,id), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,101,3000,3,), (6,203,3000,9,), (4,102,3000,6,)]), sorted(rows)) + + def test_iomatch_dob_1i_1o(self): + rows = self.query(''' + select x*2, fn1.dob_1i_1o(x), x*3 + FROM fn2.t + ''') + self.assertRowsEqual(sorted([(2,1,3,), (2,1,3,), (6,3,9,), (6,3,9,), (4,2,6,), (4,2,6,)]), sorted(rows)) + + +class ColumnNamesTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'emit.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create table t(id double, x double)') + self.query('insert into t values (100,1),(100,2),(200,3)') + + def test_col_names(self): + self.query(''' + create or replace table fn2.foo as select x*2 a, fn1.line_3i_2o(x,id,id), x*3 b + FROM fn2.t + ''') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('A',rows[0][0]) + self.assertEqual('Z1',rows[1][0]) + self.assertEqual('Z2',rows[2][0]) + self.assertEqual('B',rows[3][0]) + + +class DatatypesTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'emit.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + + def test_boolean(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x BOOLEAN)') + self.query('insert into fn2.dt values false') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(False,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('BOOLEAN', rows[0][1]) + + def test_double(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DOUBLE)') + self.query('insert into fn2.dt values 32768e100') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(3.2768e+104,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DOUBLE', rows[0][1]) + + def test_dec_32bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(9,0)', rows[0][1]) + + def test_dec_64bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(18,0)', rows[0][1]) + + def test_dec_128bit(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,0))') + self.query('insert into fn2.dt values 32768') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(32768,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(36,0)', rows[0][1]) + + def test_dec_32bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,1))') + self.query('insert into fn2.dt values 99999999.1') + rows = self.query(''' + select x = 99999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(9,1)', rows[0][1]) + + def test_dec_64bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,1))') + self.query('insert into fn2.dt values 9999999999999999.1') + rows = self.query(''' + select x = 9999999999999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(18,1)', rows[0][1]) + + def test_dec_128bit_with_scale(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,1))') + self.query('insert into fn2.dt values 999999999999999999999999999999999.1') + rows = self.query(''' + select x = 999999999999999999999999999999999.1 from (select x, fn1.line_1i_1o(0) + FROM fn2.dt) + ''') + self.assertRowsEqual([(True,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DECIMAL(36,1)', rows[0][1]) + + def test_timestamp(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x TIMESTAMP)') + self.query(''' + insert into fn2.dt values '2010-01-01 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.datetime(2010, 1, 1, 23, 33, 33),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('TIMESTAMP(3)', rows[0][1]) + + def test_timestamp_with_timezone(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x TIMESTAMP WITH LOCAL TIME ZONE)') + self.query(''' + insert into fn2.dt values '2010-01-01 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.datetime(2010, 1, 1, 23, 33, 33),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('TIMESTAMP(3) WITH LOCAL TIME ZONE', rows[0][1]) + + def test_date(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DATE)') + self.query(''' + insert into fn2.dt values '2010-01-01' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(datetime.date(2010, 1, 1),0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('DATE', rows[0][1]) + + def test_varchar_utf8(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x VARCHAR(3000) UTF8)') + self.query('insert into fn2.dt values repeat(5,300)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*300,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('VARCHAR(3000) UTF8', rows[0][1]) + + def test_varchar_ascii(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x VARCHAR(3000) ASCII)') + self.query('insert into fn2.dt values repeat(5,300)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*300,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('VARCHAR(3000) ASCII', rows[0][1]) + + def test_char_utf8(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x CHAR(2000) UTF8)') + self.query('insert into fn2.dt values repeat(5,2000)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*2000,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('CHAR(2000) UTF8', rows[0][1]) + + def test_char_ascii(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x CHAR(2000) ASCII)') + self.query('insert into fn2.dt values repeat(5,2000)') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('5'*2000,0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('CHAR(2000) ASCII', rows[0][1]) + + def test_interval_ym(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x INTERVAL YEAR TO MONTH)') + self.query(''' + insert into fn2.dt values '23-11' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('+23-11',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('INTERVAL YEAR(2) TO MONTH', rows[0][1]) + + def test_interval_ds(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x INTERVAL DAY TO SECOND)') + self.query(''' + insert into fn2.dt values '30 23:33:33' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('+30 23:33:33.000',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('INTERVAL DAY(2) TO SECOND(3)', rows[0][1]) + + def test_geometry(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x GEOMETRY)') + self.query(''' + insert into fn2.dt values 'POINT(1 1)' + ''') + rows = self.query(''' + select x, fn1.line_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([('POINT (1 1)',0,)], rows) + self.query('create or replace table fn2.foo as select x, fn1.line_1i_1o(0) from fn2.dt') + rows = self.query(''' + describe fn2.foo + ''') + self.assertEqual('GEOMETRY', rows[0][1]) + + +class NullTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'emit.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + + def test_boolean_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x BOOLEAN)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,True,),(None,0,True,)], rows) + + def test_double_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DOUBLE)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(x), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,None,1,),(None,None,1,)], rows) + + def test_int32_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(9,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_int64_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(18,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_int128_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x DECIMAL(36,0))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0), NVL(x,1) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,1,),(None,0,1,)], rows) + + def test_timestamp_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x timestamp)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_date_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x date)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_intervalym_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x interval year to month)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_intervalds_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x interval day to second)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_geo_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x geometry)') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_varchar_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x varchar(2000))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + def test_char_null(self): + self.query('CREATE OR REPLACE TABLE fn2.DT(x char(2000))') + self.query('insert into fn2.dt values NULL') + rows = self.query(''' + select x, fn1.dob_1i_1o(0) + FROM fn2.dt + ''') + self.assertRowsEqual([(None,0,),(None,0,)], rows) + + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/export_alias.py b/test_container/tests/test/generic/r/export_alias.py new file mode 100644 index 000000000..b7c164294 --- /dev/null +++ b/test_container/tests/test/generic/r/export_alias.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +import re + +# ATTENTION! +# The logic for the tests had to be put in the export_alias.sql files for each language. +# This was required because EXPORT INTO SCRIPT can only return a single integer. + + +class ExportAliasTest(udf.TestCase): + result_unknown = 0 + result_ok = 1 + result_failed = 2 + result_test_error = 3 + + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'export_alias.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create or replace table t(a int, z varchar(3000))') + self.query("insert into t values (1, 'x')") + self.query('create or replace table "tl"(a int, "z" varchar(3000))') + self.query("insert into \"tl\" values (1, 'x')") + self.query("create connection FOOCONN to 'a' user 'b' identified by 'c'", ignore_errors=True) + self.query('OPEN SCHEMA FN1') + + def test_export_use_params(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_param_foo_bar with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_connection_name(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_connection_name AT FOOCONN with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_connection_info(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_connection_info AT 'a' USER 'b' IDENTIFIED BY 'c' with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_has_truncate(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_has_truncate with foo='bar' bar='foo' truncate") + self.assertEqual(self.result_ok, rows) + + def test_export_use_replace_created_by(self): + rows = self.executeStatement("EXPORT fn2.t INTO SCRIPT fn1.expal_use_replace_created_by with foo='bar' bar='foo' replace created by 'create table t(a int, z varchar(3000))'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_column_name_lower_case(self): + rows = self.executeStatement("EXPORT fn2.\"tl\" INTO SCRIPT fn1.expal_use_column_name_lower_case with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_column_selection(self): + rows = self.executeStatement("EXPORT fn2.\"tl\"(a, \"z\") INTO SCRIPT fn1.expal_use_column_selection with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + def test_export_use_query(self): + rows = self.executeStatement("EXPORT (select a as 'col1', \"z\" as 'col2' from fn2.\"tl\") INTO SCRIPT fn1.expal_use_query with foo='bar' bar='foo'") + self.assertEqual(self.result_ok, rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/generic_types.py b/test_container/tests/test/generic/r/generic_types.py new file mode 100755 index 000000000..e1ff0183a --- /dev/null +++ b/test_container/tests/test/generic/r/generic_types.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +import re + + +class TestEcho(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'types.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + def test_echo_boolean(self): + rows = self.query(''' + SELECT + fn1.echo_boolean(Null) is NULL, + fn1.echo_boolean(True) = True, + fn1.echo_boolean(False) = False + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + def test_echo_char1(self): + rows = self.query(''' + SELECT + fn1.echo_char1(NULL) is NULL, + fn1.echo_char1('a') = 'a' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_char10(self): + rows = self.query(''' + SELECT + fn1.echo_char10(NULL) is NULL, + fn1.echo_char10('ab') = 'ab ' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_date(self): + rows = self.query(''' + SELECT + fn1.echo_date(NULL) is NULL, + fn1.echo_date(current_date()) = current_date() + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_integer_basic(self): + rows = self.query(''' + SELECT + fn1.echo_integer(NULL) is NULL, + fn1.echo_integer(-1) = -1, + fn1.echo_integer(0) = 0, + fn1.echo_integer(1) = 1 + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_integer_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_integer(-(1e18 - 1)) = -(1e18 - 1), + fn1.echo_integer( 1e18 - 1) = 1e18 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_double(self): + rows = self.query(''' + SELECT + fn1.echo_double(NULL) is NULL, + fn1.echo_double(CAST(1.5 AS DOUBLE)) = CAST(1.5 AS DOUBLE), + fn1.echo_double(0) = 0.0, + fn1.echo_double(0.0) = 0.0, + fn1.echo_double(-1.7e-308) = -1.7e-308, + fn1.echo_double(+1.7e-308) = +1.7e-308 + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True, True, True)], rows) + + def test_echo_decimal_36_0_basic(self): + rows = self.query(''' + SELECT + fn1.echo_decimal_36_0(NULL) is NULL, + fn1.echo_decimal_36_0(0) = 0, + fn1.echo_decimal_36_0(0.0) = 0.0 + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_decimal_36_0_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_decimal_36_0(-(1e35 - 1)) = -(1e35 - 1), + fn1.echo_decimal_36_0( 1e35 - 1) = 1e35 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_decimal_36_36_basic(self): + rows = self.query(''' + SELECT + fn1.echo_decimal_36_36(NULL) is NULL, + fn1.echo_decimal_36_36(0) = 0, + fn1.echo_decimal_36_36(0.0) = 0.0 + FROM DUAL''') + self.assertRowsEqual([(True, True, True)], rows) + + @udf.TestCase.expectedFailureIfLang('r') + def test_echo_decimal_36_36_limits(self): + """DWA-13784 (R)""" + rows = self.query(''' + SELECT + fn1.echo_decimal_36_36(-(1e-35 - 1)) = -(1e-35 - 1), + fn1.echo_decimal_36_36( 1e-35 - 1) = 1e-35 - 1 + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + def test_echo_varchar10(self): + rows = self.query(''' + SELECT + fn1.echo_varchar10(NULL) is NULL, + fn1.echo_varchar10('') is NULL, + fn1.echo_varchar10(' ') = ' ', + fn1.echo_varchar10('a') = 'a', + fn1.echo_varchar10('a ') = 'a ', + fn1.echo_varchar10(' a ') = ' a ' + FROM DUAL''') + self.assertRowsEqual([(True, True, True, True, True, True)], rows) + + def test_echo_timestamp(self): + rows = self.query(''' + SELECT fn1.echo_timestamp(NULL) is NULL + FROM DUAL''') + self.assertRowsEqual([(True,)], rows) + + rows = self.query(''' + SELECT fn1.echo_timestamp('2017-08-01 13:13:50.910') = '2017-08-01 13:13:50.910', + fn1.echo_timestamp('2017-08-01 13:13:50.983') = '2017-08-01 13:13:50.983' + FROM DUAL''') + self.assertRowsEqual([(True, True)], rows) + + self.query('DROP SCHEMA ECHO_TEST CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA ECHO_TEST') + self.query('''CREATE OR REPLACE TABLE N1 (now VARCHAR(255))''') + self.query('''INSERT INTO N1 VALUES (SELECT now() AS x FROM DUAL)''') + self.query(''' + SELECT x1 = x2, x1, x2, x3 + FROM (SELECT fn1.echo_timestamp(x) AS x1, x AS x2, x AS x3 + FROM (SELECT now AS x FROM N1)) + ''') + self.assertEqual(True, rows[0][0], str(rows)) + self.query('''DROP SCHEMA ECHO_TEST CASCADE''') + + +class EmptyTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'types.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + def test_run_func_is_empty(self): + rows = self.query(''' + SELECT + fn1.run_func_is_empty() IS NULL + FROM DUAL''') + self.assertRowsEqual([(True,)], rows) + + +class BottleneckTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'types.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + def test_varchar10(self): + for i in 0, 1, 5, 10: + rows = self.query(''' + SELECT fn1.bottleneck_varchar10('%s') + FROM DUAL''' % ('x' * i)) + self.assertEqual('x' * i if i > 0 else None, rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_varchar10('%s') + FROM DUAL''' % ('x' * 11)) + + def test_char10(self): + for i in 0, 1, 5, 10: + rows = self.query(''' + SELECT fn1.bottleneck_char10('%s') + FROM DUAL''' % ('x' * i)) + self.assertEqual( + ('x' * i + ' ' * 10)[:10] if i > 0 else None, + rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_char10('%s') + FROM DUAL''' % ('x' * 11)) + + def test_decimal5(self): + for i in 3, 4: + rows = self.query(''' + SELECT fn1.bottleneck_decimal5(%d) + FROM DUAL''' % (10 ** i)) + self.assertEqual(10 ** i, rows[0][0]) + with self.assertRaises(Exception): + self.query(''' + SELECT fn1.bottleneck_decimal5(%d) + FROM DUAL''' % (10 ** 5)) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/get_connection.py b/test_container/tests/test/generic/r/get_connection.py new file mode 100644 index 000000000..5ce8ff85b --- /dev/null +++ b/test_container/tests/test/generic/r/get_connection.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +from exasol_python_test_framework import exatest +import re + + +class GetConnectionMemoryBug(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'get_connection.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query( + '''CREATE OR REPLACE CONNECTION test_get_connection_bug_connection TO '' USER 'ialjksdhfalskdjhflaskdjfhalskdjhflaksjdhflaksdjfhalksjdfhlaksjdhflaksjdhfalskjdfhalskdjhflaksjdhflaksjdfhlaksjsadajksdhfaksjdfhalksdjfhalksdjfhalksjdfhqwiueryqw;er;lkjqwe;rdhflaksjdfhlaksdjfhaabcdefghijklmnopqrstuvwxyz' IDENTIFIED BY 'abcdeoqsdfgsdjfglksjdfhglskjdfhglskdjfglskjdfghuietyewlrkjthertrewerlkjhqwelrkjhqwerlkjnwqerlkjhqwerkjlhqwerlkjhqwerlkhqwerkljhqwerlkjhqwerfghijklmnopqrstuvwxyz';''') + + def test_get_connection(self): + for x in range(10): + row = self.query( + '''with ten as (values 0,1,2,3,4,5,6,7,8,9 as p(x)) select count(*) from (select fn1.print_connection_set_emits('test_get_connection_bug_connection') from (select a.x from ten a, ten, ten, ten, ten) v group by mod(v.rownum,4019))''')[ + 0] + self.assertEqual(4019, row[0]) + + +class AccessConnectionSysPriv(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'get_connection.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + def testSysPrivExists(self): + sys_priv = self.query("SELECT * FROM EXA_DBA_SYS_PRIVS WHERE PRIVILEGE = 'ACCESS ANY CONNECTION'") + self.assertRowsEqual([("DBA", "ACCESS ANY CONNECTION", True)], sys_priv) + + +class GetConnectionTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'get_connection.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query(''' + create connection FOOCONN to 'a' user 'b' identified by 'c' + ''') + + def tearDown(self): + self.query('drop connection FOOCONN') + + def test_print_existing_connection(self): + rows = self.query(''' + SELECT fn1.print_connection('FOOCONN') + ''') + self.assertRowsEqual([('password', 'a', 'b', 'c')], rows) + + def test_connection_not_found(self): + with self.assertRaisesRegex(Exception, 'connection FOO does not exist'): + self.query(''' + SELECT fn1.print_connection('FOO') + ''') + + def test_print_existing_connection_v2(self): + rows = self.query(''' + SELECT fn1.print_connection_v2('FOOCONN') + ''') + self.assertRowsEqual([('password', 'a', 'b', 'c')], rows) + +class GetConnectionAccessControlTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'get_connection.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query(''' + create connection AC_FOOCONN to 'a' user 'b' identified by 'c' + ''', ignore_errors=True) + + def getConnection(self, username, password): + client = exatest.ODBCClient('exatest') + self.log.debug('connecting to DSN "exa" for user {username}'.format(username=username)) + client.connect(uid=username, pwd=password) + return client + + def createUser(self, username, password): + self.query('DROP USER IF EXISTS {username} CASCADE'.format(username=username)) + self.query('CREATE USER {username} IDENTIFIED BY "{password}"'.format(username=username, password=password)) + self.query('GRANT CREATE SESSION TO {username}'.format(username=username)) + + def testUseConnectionWithoutRights(self): + self.createUser("foo", "foo") + self.query('grant create schema to foo') + self.query('grant create script to foo') + self.query('grant execute on script fn1.print_connection to foo') + self.commit() + foo_conn = self.getConnection('foo', 'foo') + with self.assertRaisesRegex(Exception, + 'insufficient privileges for using connection AC_FOOCONN in script PRINT_CONNECTION'): + foo_conn.query(''' + SELECT fn1.print_connection('AC_FOOCONN') + ''') + foo_conn.commit() + self.query('drop user foo cascade') + self.commit() + + def testUseConnectionWithOldRight(self): + self.createUser("foo", "foo") + self.query('grant create schema to foo') + self.query('grant create script to foo') + self.query('grant connection ac_fooconn to foo') + self.query('grant execute on script fn1.print_connection to foo') + self.commit() + foo_conn = self.getConnection('foo', 'foo') + with self.assertRaisesRegex(Exception, + 'insufficient privileges for using connection AC_FOOCONN in script PRINT_CONNECTION'): + foo_conn.query(''' + select fn1.print_connection('AC_FOOCONN') + ''') + foo_conn.commit() + self.query('drop user foo cascade') + self.commit() + + def testUseConnectionWithNewRight(self): + self.createUser("foo", "foo") + self.query('grant create schema to foo') + self.query('grant create script to foo') + self.query('grant execute on script fn1.print_connection to foo') + self.query('GRANT ACCESS ON CONNECTION ac_fooconn to foo') + self.commit() + foo_conn = self.getConnection('foo', 'foo') + rows = foo_conn.query(''' + select fn1.print_connection('AC_FOOCONN') + ''') + self.assertRowsEqual([('password', 'a', 'b', 'c')], rows) + foo_conn.commit() + self.query('drop user foo cascade') + self.commit() + + def testUseConnectionInOldImportWithNewRight(self): + self.createUser("foo", "foo") + self.query('grant insert any table to foo') + self.query('grant import to foo', ignore_errors=True) # only supported and needed since 6.1 + self.query('GRANT ACCESS ON CONNECTION ac_fooconn to foo') + self.commit() + foo_conn = self.getConnection('foo', 'foo') + with self.assertRaisesRegex(Exception, 'insufficient privileges for using connection'): + foo_conn.query(''' + import from fbv at ac_fooconn file 'foo' + ''') + foo_conn.commit() + self.query('drop user foo cascade') + self.commit() + + +class BigConnectionTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'get_connection.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + # Should be max. size 2.000.000, but this will cause our odbc driver to crash (sigsegv) during logging (DWA-20290). + # Will be increased to max size when bug is fixed + address = "a" * 2 * 1000 * 100 + user = "u" * 2 * 1000 * 100 + password = "p" * 2 * 1000 * 100 + + def setUp(self): + self.query(''' + create connection LARGEST_CONN to '{address}' user '{user}' identified by '{password}' + '''.format(address=self.address, user=self.user, password=self.password)) + + def tearDown(self): + self.query("DROP CONNECTION LARGEST_CONN") + + def testGetBigConnection(self): + rows = self.query(''' + SELECT fn1.print_connection('LARGEST_CONN') + ''') + self.assertRowsEqual([('password', self.address, self.user, self.password)], rows) + + +class ConnectionTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'get_connection.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + def testAccessConnectionInAdapter(self): + self.query("CREATE SCHEMA IF NOT EXISTS ADAPTER") + self.query("CREATE CONNECTION my_conn TO 'MYADDRESS' USER 'MYUSER' IDENTIFIED BY 'MYPASSWORD'") + self.query(udf.fixindent(''' + CREATE OR REPLACE PYTHON3 ADAPTER SCRIPT adapter.fast_adapter_conn AS + import json + import string +import re + encodeUTF8 = lambda x: x + + def adapter_call(request): + root = json.loads(request) + if root["type"] == "createVirtualSchema": + c = exa.get_connection("MY_CONN") + res = { + "type": "createVirtualSchema", + "schemaMetadata": { + "tables": [ + { + "name": "T1", + "columns": [{ + "name": c.address, + "dataType": {"type": "VARCHAR", "size": 2000000} + },{ + "name": c.user, + "dataType": {"type": "VARCHAR", "size": 2000000} + },{ + "name": c.password, + "dataType": {"type": "VARCHAR", "size": 2000000} + }] + }] + } + } + return encodeUTF8(json.dumps(res)) + elif root["type"] == "dropVirtualSchema": + return encodeUTF8(json.dumps({"type": "dropVirtualSchema"})) + else: + raise ValueError('Unsupported callback') + / + ''')) + self.query("CREATE VIRTUAL SCHEMA VS USING ADAPTER.FAST_ADAPTER_CONN") + rows = self.query("SELECT COLUMN_NAME FROM EXA_ALL_COLUMNS WHERE COLUMN_TABLE='T1' ORDER BY COLUMN_NAME") + self.assertRowsEqual([('MYADDRESS',), ('MYPASSWORD',), ('MYUSER',)], rows) + self.query("DROP FORCE VIRTUAL SCHEMA VS CASCADE") + + +class OptionalUSERandIDENTIFIEDBYTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'get_connection.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + def testNoUSERandNoIDENTIFIEDBY(self): + self.query("CREATE or replace CONNECTION my_conn1 TO 'MYADDRESS'") + rows = self.query('''SELECT fn1.print_connection('MY_CONN1')''') + self.assertRowsEqual([('password', 'MYADDRESS', None, None)], rows) + self.query("drop CONNECTION my_conn1") + + def testNoUSER(self): + self.query("CREATE or replace CONNECTION my_conn2 TO 'MYADDRESS' identified by 'MYPASSWORD'") + rows = self.query('''SELECT fn1.print_connection('MY_CONN2')''') + self.assertRowsEqual([('password', 'MYADDRESS', None, 'MYPASSWORD')], rows) + self.query("drop CONNECTION my_conn2") + + def testNoIDENIFIEDBY(self): + self.query("CREATE or replace CONNECTION my_conn3 TO 'MYADDRESS' USER 'MYUSER'") + rows = self.query('''SELECT fn1.print_connection('MY_CONN3')''') + self.assertRowsEqual([('password', 'MYADDRESS', "MYUSER", None)], rows) + self.query("drop CONNECTION my_conn3") + + +## +## +## +## + +class GetConnectionAccessControlWithViewsTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'get_connection.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query(''' + create connection AC_FOOCONN to 'a' user 'b' identified by 'c' + ''', ignore_errors=True) + + def getConnection(self, username, password): + client = exatest.ODBCClient('exatest') + self.log.debug('connecting to DSN "exa" for user {username}'.format(username=username)) + client.connect(uid=username, pwd=password) + return client + + def createUser(self, username, password): + self.query('DROP USER IF EXISTS {username} CASCADE'.format(username=username)) + self.query('CREATE USER {username} IDENTIFIED BY "{password}"'.format(username=username, password=password)) + self.query('GRANT CREATE SESSION TO {username}'.format(username=username)) + + def testUseConnectionUDFsInView(self): + self.createUser("foo", "foo") + self.query('create schema if not exists spot42542') + self.query( + "create or replace view spot42542.print_connection_wrapper as select fn1.print_connection('AC_FOOCONN')") + self.query("grant select on spot42542.print_connection_wrapper to foo") + self.commit() + foo_conn = self.getConnection('foo', 'foo') + rows = foo_conn.query('''select * from spot42542.print_connection_wrapper''') + foo_conn.commit() + self.assertRowsEqual([('password', 'a', 'b', 'c')], rows) + foo_conn.commit() + self.query('drop schema spot42542 cascade') + self.commit() + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/import_alias.py b/test_container/tests/test/generic/r/import_alias.py new file mode 100644 index 000000000..15f8f2f66 --- /dev/null +++ b/test_container/tests/test/generic/r/import_alias.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +from exasol_python_test_framework.udf import skip +from exasol_python_test_framework import exatest +import pathlib +import re + + +class ImportAliasTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'import_alias.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('create or replace table t(z varchar(3000))') + self.query('create or replace table t2(y varchar(2000), z varchar(3000))') + self.query(''' + create connection FOOCONN to 'a' user 'b' identified by 'c' + ''', ignore_errors=True) + self.query('OPEN SCHEMA FN1') + + def test_import_use_is_subselect(self): + self.query(''' + IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_is_subselect + ''') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FALSE',)], rows) + self.query('truncate table fn2.t') + + def test_import_use_is_subselect_subselect(self): + rows = self.query(''' + SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_is_subselect) + ''') + self.assertRowsEqual([(True,)], rows) + rows = self.query(''' + IMPORT FROM SCRIPT fn1.impal_use_is_subselect + ''') + self.assertRowsEqual([(True,)], rows) + + def test_import_use_params(self): + self.query("IMPORT INTO fn2.t2 FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo'") + rows = self.query('select * from fn2.t2') + self.assertRowsEqual([('bar','foo')], rows) + self.query('truncate table fn2.t2') + + def test_import_use_params_subselect(self): + rows = self.query("SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo')") + self.assertRowsEqual([('bar','foo')], rows) + rows = self.query("IMPORT FROM SCRIPT fn1.impal_use_param_foo_bar with foo='bar' bar='foo'") + self.assertRowsEqual([('bar','foo')], rows) + + def test_import_use_connection_name(self): + self.query('IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_connection_name at fooconn') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FOOCONN',)], rows) + self.query('truncate table fn2.t') + + + def test_import_use_connection_fooconn(self): + rows = self.query('IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.assertRowsEqual([('abc',)], rows) + self.query('truncate table fn2.t') + + def getConnection(self, username, password): + client = exatest.ODBCClient('exatest') + self.log.debug('connecting to DSN "exa" for user {username}'.format(username=username)) + client.connect(uid = username, pwd = password) + return client + + def createUser(self, username, password): + self.query('DROP USER IF EXISTS {username} CASCADE'.format(username = username)) + self.query('CREATE USER {username} IDENTIFIED BY "{password}"'.format(username = username, password = password)) + self.query('GRANT CREATE SESSION TO {username}'.format(username=username)) + + + def test_import_use_connection_fooconn_fails_for_user_foo(self): + self.createUser('foo','foo') + self.commit() + foo_conn = self.getConnection('foo','foo') + with self.assertRaisesRegex(Exception, 'aslkfhalsjfdhsa'): + foo_conn.query('IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.query('drop user foo cascade') + + @skip("IMPORT with views is not supported") + def test_import_use_connection_fooconn_for_user_foo_and_view(self): + self.query('create view fn2.fooconn_import_view as IMPORT FROM SCRIPT fn1.impal_use_connection_fooconn') + self.createUser('foo','foo') + self.commit() + foo_conn = self.getConnection('foo','foo') + rows = foo_conn.query('select * from fn2.foo_conn_import_view') + self.assertRowsEqual([('abc',)], rows) + self.query('drop user foo cascade') + self.query('drop view fn2.foo_conn_import_view') + + + def test_import_use_connection_name_subselect(self): + rows = self.query('SELECT * FROM (IMPORT FROM SCRIPT fn1.impal_use_connection_name at fooconn)') + self.assertRowsEqual([('FOOCONN',)], rows) + rows = self.query('IMPORT FROM SCRIPT fn1.impal_use_connection_name at fooconn') + self.assertRowsEqual([('FOOCONN',)], rows) + + def test_import_use_connection(self): + self.query(''' + IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser' + ''') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + self.query('truncate table fn2.t') + + def test_import_use_connection_subselect(self): + rows = self.query(''' SELECT * FROM ( + IMPORT FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser') + ''') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + rows = self.query(''' + IMPORT FROM SCRIPT fn1.impal_use_connection + at 'fooconn' user 'hans' identified by 'meiser' + ''') + self.assertRowsEqual([('hansmeiserfooconnpassword',)], rows) + + def test_import_use_all(self): + self.query(''' + IMPORT INTO fn2.t2 FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value' + ''') + rows = self.query('select * from fn2.t2') + self.assertRowsEqual([('1','FALSE_Y_hansmeiserfooconnpassword_a value_T_N')], rows) + self.query('truncate table fn2.t2') + + def test_import_use_all_subselect(self): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value') + ''') + self.assertRowsEqual([(1, 'TRUE_Y_hansmeiserfooconnpassword_a value_TDOUBLEVARCHAR(3000) UTF8_NAB')], rows) + rows = self.query(''' + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo='a value' + ''') + self.assertRowsEqual([(1, 'TRUE_Y_hansmeiserfooconnpassword_a value_TDOUBLEVARCHAR(3000) UTF8_NAB')], rows) + + def test_prepared_statement_params(self): + with self.assertRaisesRegex(Exception, 'syntax error, unexpected \'?\''): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at 'fooconn' user 'hans' identified by 'meiser' with foo=?) + ''', 'bar') + + def test_prepared_statement_conn(self): + with self.assertRaisesRegex(Exception, 'syntax error, unexpected \'?\''): + rows = self.query(''' SELECT * FROM ( + IMPORT INTO (a double, b varchar(3000)) FROM SCRIPT fn1.impal_use_all + at ? user ? identified by ? with foo='bar') + ''', 'fooconn', 'hans', 'meiser', 'bar') + + def test_import_in_lua_scripting(self): + self.query(''' + create or replace script s1() as + res = pquery [[ IMPORT INTO fn2.t FROM SCRIPT fn1.impal_use_is_subselect ]] + ''') + self.query('execute script s1()') + rows = self.query('select * from fn2.t') + self.assertRowsEqual([('FALSE',)], rows) + self.query('truncate table fn2.t') + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/metadata.py b/test_container/tests/test/generic/r/metadata.py new file mode 100755 index 000000000..4587a7f06 --- /dev/null +++ b/test_container/tests/test/generic/r/metadata.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +import re + + +class MetaDataTest(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'metadata.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + def test_database_name(self): + rows = self.query('''SELECT fn1.get_database_name() FROM DUAL''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_database_version(self): + rows = self.query('''select fn1.get_database_version() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_script_language(self): + rows = self.query('''select fn1.get_script_language() from dual''') + self.assertTrue((rows[0][0]).upper().startswith((rows[0][1]).upper())) + + def test_script_name(self): + rows = self.query('''select fn1.get_script_name() from dual''') + self.assertRowEqual(('GET_SCRIPT_NAME',), rows[0]) + + def test_script_schema(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_script_schema() from dual''') + self.assertRowEqual(('FN1',), rows[0]) + + def test_script_user(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_current_user() from dual''') + self.assertRowEqual(('SYS',), rows[0]) + + def test_scope_user(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_scope_user() from dual''') + self.assertRowEqual(('SYS',), rows[0]) + + def test_current_schema_null(self): + if (udf.opts.is_compat_mode != "true"): + rows = self.query('''select fn1.get_current_schema() from dual''') + self.assertRowEqual(('NULL',), rows[0]) + + def test_current_schema(self): + if (udf.opts.is_compat_mode != "true"): + self.query('''create schema test_schema''') + rows = self.query('''select fn1.get_current_schema() from dual''') + self.assertRowEqual(('TEST_SCHEMA',), rows[0]) + self.query('''drop schema test_schema cascade''') + + def test_script_code(self): + rows = self.query('''select fn1.get_script_code() from dual''') + self.assertTrue((rows[0][0]).upper().find('CTX') >= 0) + + def test_session_id(self): + rows = self.query('''select fn1.get_session_id() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_statement_id(self): + rows = self.query('''select fn1.get_statement_id() from dual''') + self.assertTrue(rows[0][0] >= 0) + + def test_node_id(self): + rows = self.query('''select fn1.get_node_id() from dual''') + self.assertTrue(rows[0][0] >= 0) + + def test_vm_id(self): + rows = self.query('''select fn1.get_vm_id() from dual''') + self.assertTrue(len(rows[0][0]) > 0) + + def test_input_type_scalar(self): + rows = self.query('''select fn1.get_input_type_scalar() from dual''') + self.assertRowEqual(('SCALAR',), rows[0]) + + def test_input_type_set(self): + rows = self.query('''select fn1.get_input_type_set(x) from (values 1,2,3) as t(x)''') + self.assertRowEqual(('SET',), rows[0]) + + def test_input_column_count_scalar(self): + rows = self.query('''select fn1.get_input_column_count_scalar(12.3, 'hihihi') from dual''') + self.assertRowEqual((2,), rows[0]) + + + def test_input_column_count_set(self): + rows = self.query('''select fn1.get_input_column_count_set(x, y) from (values (12.3, 'hihihi')) as t(x,y)''') + self.assertRowEqual((2,), rows[0]) + + def test_input_columns(self): + rows = self.query('''select fn1.get_input_columns(1.2, '123') from dual order by column_id''') + r0 = rows[0] + r1 = rows[1] + self.assertTrue(r0[0] == 1) + self.assertTrue(r0[1].upper() == 'C1') + self.assertTrue(r0[2].upper() == 'NUMBER' or r0[2].upper() == "" or r0[2].upper() == "DOUBLE" or r0[2].upper() == "JAVA.LANG.DOUBLE" or r0[2].upper() == "") + self.assertTrue(r0[3].upper() == 'DOUBLE') + self.assertTrue(r1[0] == 2) + self.assertTrue(r1[1].upper() == 'C2') + self.assertTrue(r1[2].upper() == 'STRING' or r1[2].upper() == "" or r1[2].upper() == "CHARACTER" or r1[2].upper() == "JAVA.LANG.STRING" or r1[2].upper() == "") + self.assertTrue(r1[3].upper().startswith('VARCHAR(200)')) + self.assertTrue(r0[6] == 0) + self.assertTrue(r1[6] == 200) + + def test_output_type_return(self): + rows = self.query('''select fn1.get_output_type_return() from dual''') + self.assertTrue(rows[0][0] == 'RETURN') + + + def test_output_type_emit(self): + rows = self.query('''select fn1.get_output_type_emit() from dual''') + self.assertTrue(rows[0][0] == 'EMIT') + + + def test_output_column_count_return(self): + rows = self.query('''select fn1.get_output_column_count_return() from dual''') + self.assertRowEqual((1,),rows[0]) + + + def test_output_column_count_emit(self): + rows = self.query('''select fn1.get_output_column_count_emit() from dual''') + self.assertRowEqual((3,3,3),rows[0]) + + def test_output_columns(self): + rows = self.query('''select fn1.get_output_columns() from dual order by column_id''') + r0 = rows[0] + r1 = rows[1] + r2 = rows[2] + r3 = rows[3] + r4 = rows[4] + r5 = rows[5] + r6 = rows[6] + self.assertTrue(r0[0] == 1) + self.assertTrue(r1[0] == 2) + self.assertTrue(r2[0] == 3) + self.assertTrue(r3[0] == 4) + self.assertTrue(r4[0] == 5) + self.assertTrue(r5[0] == 6) + self.assertTrue(r6[0] == 7) + self.assertTrue(r0[1].upper() == 'COLUMN_ID') + self.assertTrue(r1[1].upper() == 'COLUMN_NAME') + self.assertTrue(r2[1].upper() == 'COLUMN_TYPE') + self.assertTrue(r3[1].upper() == 'COLUMN_SQL_TYPE') + self.assertTrue(r4[1].upper() == 'COLUMN_PRECISION') + self.assertTrue(r5[1].upper() == 'COLUMN_SCALE') + self.assertTrue(r6[1].upper() == 'COLUMN_LENGTH') + self.assertTrue(r0[2].upper() == 'NUMBER' or r0[2].upper() == "" or r0[2].upper() == "DOUBLE" or r0[2].upper() == "JAVA.LANG.DOUBLE" or r0[2].upper() == "") + self.assertTrue(r1[2].upper() == 'STRING' or r1[2].upper() == "" or r1[2].upper() == "CHARACTER" or r1[2].upper() == "JAVA.LANG.STRING" or r1[2].upper() == "") + self.assertTrue(r2[2].upper() == 'STRING' or r2[2].upper() == "" or r2[2].upper() == "CHARACTER" or r2[2].upper() == "JAVA.LANG.STRING" or r2[2].upper() == "") + self.assertTrue(r3[2].upper() == 'STRING' or r3[2].upper() == "" or r3[2].upper() == "CHARACTER" or r3[2].upper() == "JAVA.LANG.STRING" or r3[2].upper() == "") + self.assertTrue(r4[2].upper() == 'NUMBER' or r4[2].upper() == "" or r4[2].upper() == "DOUBLE" or r4[2].upper() == "JAVA.LANG.DOUBLE" or r4[2].upper() == "") + self.assertTrue(r5[2].upper() == 'NUMBER' or r5[2].upper() == "" or r5[2].upper() == "DOUBLE" or r5[2].upper() == "JAVA.LANG.DOUBLE" or r5[2].upper() == "") + self.assertTrue(r6[2].upper() == 'NUMBER' or r6[2].upper() == "" or r6[2].upper() == "DOUBLE" or r6[2].upper() == "JAVA.LANG.DOUBLE" or r6[2].upper() == "") + self.assertTrue(r0[3].upper() == 'DOUBLE') + self.assertTrue(r1[3].upper().startswith('VARCHAR(200)')) + self.assertTrue(r2[3].upper().startswith('VARCHAR(20)')) + self.assertTrue(r3[3].upper().startswith('VARCHAR(20)')) + self.assertTrue(r4[3].upper() == 'DOUBLE') + self.assertTrue(r5[3].upper() == 'DOUBLE') + self.assertTrue(r6[3].upper() == 'DOUBLE') + self.assertTrue(r1[6] == 200) + self.assertTrue(r2[6] == 20) + self.assertTrue(r3[6] == 20) + + + + def test_precision_scale_length(self): + rows = self.query('''select fn1.get_precision_scale_length(2.5, '0123456789') from dual''') + self.assertRowEqual((6,3,0,0,0,10), rows[0]) + + + def test_char_length(self): + rows = self.query('''select fn1.get_char_length('0123456789') from dual''') + self.assertRowEqual((10,20,'9876543210 '), (int(rows[0][0]), int(rows[0][1]), rows[0][2])) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/numeric_functions.py b/test_container/tests/test/generic/r/numeric_functions.py new file mode 100755 index 000000000..3241f26eb --- /dev/null +++ b/test_container/tests/test/generic/r/numeric_functions.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +import re + + +class Test(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'numeric_functions.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + def test_pi(self): + rows = self.query(''' + SELECT fn1.pi() + FROM dual''') + result = rows[0][0] + self.assertAlmostEqual(3.1415926535, result) + + def test_select(self): + rows = self.query(''' + SELECT DISTINCT + FN1.double_mult(float1, float2) = float1 * float2 AS a + FROM test.enginetablebig1 + ORDER BY a + ''') + self.assertRowsEqual([(True,), (None,)], rows) + + def test_select_into(self): + self.query('DROP SCHEMA FN2 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN2') + self.query('CREATE TABLE FN2.t(diff double)') + self.query(''' + INSERT INTO FN2.t + SELECT + fn1.double_mult(float1, float2) - float1 * float2 AS a + FROM test.enginetable + ''') + self.query(''' + SELECT DISTINCT diff + FROM FN2.t + WHERE diff != 0 AND diff IS NOT NULL + ''') + self.assertEqual(0, self.rowcount()) + + def test_subselect(self): + rows = self.query(''' + SELECT i, a + FROM ( + SELECT int_index AS i, + (fn1.double_mult(float1, float2) - float1 * float2) AS a + FROM test.enginetable) + WHERE a IS NOT NULL + ORDER BY a + LIMIT 20''') + for row in rows: + rows2 = self.query(''' + SELECT + float1, + float2, + fn1.double_mult(float1, float2) - float1 * float2 AS a + FROM test.enginetable + WHERE int_index = ?''', + row.I) + self.assertEqual(row.A, rows2[0].A) + + def test_udf_with_two_doubles(self): + rows = self.query(''' + SELECT + fn1.add_two_doubles(NULL, NULL) IS NULL, + fn1.add_two_doubles(NULL, 0) IS NULL, + fn1.add_two_doubles( 0, NULL) IS NULL, + fn1.add_two_doubles(0,0) = 0, + fn1.add_two_doubles(1,0) = 1, + fn1.add_two_doubles(0,2) = 2, + fn1.add_two_doubles(2,3) = 5 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 7)], rows) + + def test_udf_with_three_doubles_part1(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles(NULL, NULL, NULL) is NULL, + fn1.add_three_doubles(NULL, NULL, 0) is NULL, + fn1.add_three_doubles(NULL, 0, NULL) is NULL, + fn1.add_three_doubles( 0, NULL, NULL) is NULL, + fn1.add_three_doubles(NULL, 0, 0) is NULL + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_udf_with_three_doubles_part2(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles( 0, NULL, 0) is NULL, + fn1.add_three_doubles( 0, 0, NULL) is NULL, + fn1.add_three_doubles(0, 0, 0) = 0, + fn1.add_three_doubles(1, 0, 0) = 1, + fn1.add_three_doubles(0, 2, 0) = 2 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_udf_with_three_doubles_part3(self): + rows = self.query(''' + SELECT + fn1.add_three_doubles(0, 0, 3) = 3, + fn1.add_three_doubles(1, 2, 0) = 3, + fn1.add_three_doubles(1, 0, 3) = 4, + fn1.add_three_doubles(0, 2, 3) = 5, + fn1.add_three_doubles(1, 2, 3) = 6 + FROM DUAL''') + self.assertRowsEqual([tuple([True] * 5)], rows) + + def test_right_number_of_emitted_rows(self): + rows = self.query(''' + SELECT fn1.split_integer_into_digits(123) + FROM DUAL''') + self.assertRowsEqual([(3,), (2,), (1,)], rows) + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/pathological_functions.py b/test_container/tests/test/generic/r/pathological_functions.py new file mode 100755 index 000000000..3f79e3f00 --- /dev/null +++ b/test_container/tests/test/generic/r/pathological_functions.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +from exasol_python_test_framework import udf +import pathlib +import re + + +class Test(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'pathological_functions.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + def test_query_timeout(self): + self.query('ALTER SESSION SET QUERY_TIMEOUT = 10') + try: + with self.assertRaisesRegex(Exception, 'Successfully reconnected after query timeout'): + self.query('SELECT fn1.sleep(100) FROM dual') + finally: + self.query('ALTER SESSION SET QUERY_TIMEOUT = 0') + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/performance.py b/test_container/tests/test/generic/r/performance.py new file mode 100755 index 000000000..1f421b364 --- /dev/null +++ b/test_container/tests/test/generic/r/performance.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python3 + +import locale +import os +import subprocess + +from exasol_python_test_framework import udf +import pathlib +from exasol_python_test_framework.udf import ( + SkipTest, + timer, + skip, +) +import re + +locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') + + +class WordCount(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'performance.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + def setUp(self): + self.query('OPEN SCHEMA FN1') + + def test_word_count(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + def test_word_count_fast0(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts_fast0(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count_fast0 query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + def test_word_count_fast7(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts_fast7(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count_fast7 query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + def test_word_count_fast77(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts_fast77(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count_fast77 query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + def test_word_count_fast777(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts_fast777(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count_fast777 query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + def test_word_count_fast7777(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts_fast7777(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count_fast7777 query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + def test_word_count_fast777777(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts_fast777777(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count_fast777777 query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + def test_word_count_fast77777777(self): + sql = ''' + SELECT COUNT(*) FROM ( + SELECT performance_reduce_counts_fast77777777(w, c) + FROM ( + SELECT performance_map_words(varchar02) + FROM test.enginetablebig1 + ) + GROUP BY w + ORDER BY 1 DESC)''' + + with timer() as t: + ret = self.query(sql) + print("test_word_count_fast77777777 query:", t.duration, repr(ret)) + self.assertLessEqual(t.duration, 160) + + @udf.TestCase.expectedFailureIfLang('lua') + def test_word_unicode_count(self): + """DWA-13860 (lua)""" + sql = ''' + SELECT performance_reduce_counts(w, c) + FROM ( + SELECT performance_map_unicode_words(c3_varchar100) + FROM test.enginetablebigunicode + ) + GROUP BY w + ORDER BY 1 DESC''' + + with timer() as t: + self.query(sql) + self.assertLessEqual(t.duration, 11) + + +@skip('csv data for tables wiki_freq and wiki_names is currently not available') +class FrequencyAnalysis(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'performance.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + maxDiff = 1024 * 20 + + def compare(self, old, new): + self.log.info('compare new data with reference data') + n_old = len(list(old)) + n_new = len(list(new)) + self.log.info('old data has %d lines', n_old) + self.log.info('new data has %d lines', n_new) + if max(n_old, n_new) <= 50: + self.assertEqual(old, new) + else: + self.log.info('switching to compact comparison') + old_set = set(old) + new_set = set(new) + only_new = list(sorted(new_set.difference(old_set))) + only_old = list(sorted(old_set.difference(new_set))) + if max(len(only_new), len(only_old)) <= 200: + self.assertEqual(([], []), (only_old, only_new)) + else: + self.log.info('diff is still to big') + self.fail("difference: +%d/-%d elements" % + (len(only_new), len(only_old))) + + @classmethod + def setUpClass(cls): + sql = """ + DROP SCHEMA daten CASCADE; + CREATE SCHEMA daten; + + CREATE TABLE wiki_freq(w char(1), c INTEGER); + + IMPORT INTO wiki_freq + FROM LOCAL CSV FILE '/share/fs8/Databases/UDF/freebase-frequency_analysis.csv' + COLUMN SEPARATOR = 'TAB' + REJECT LIMIT 0; + + CREATE TABLE wiki_names(id INTEGER IDENTITY PRIMARY KEY, text VARCHAR(350)); + + IMPORT INTO wiki_names(text) + FROM LOCAL CSV FILE '/share/fs8/Databases/UDF/freebase-export.csv' + COLUMN SEPARATOR = 'TAB' + REJECT LIMIT 0;""" + + cmd = '''%(exaplus)s -c %(conn)s -u sys -P exasol + -no-config -autocommit ON -L -pipe -jdbcparam validateservercertificate=0''' % { + 'exaplus': os.environ.get('EXAPLUS', + '/usr/opt/EXASuite-4/EXASolution-4.2.9/bin/Console/exaplus'), + 'conn': udf.opts.server + } + env = os.environ.copy() + env['PATH'] = '/usr/opt/jdk1.8.0_latest/bin:' + env['PATH'] + exaplus = subprocess.Popen( + cmd.split(), + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + out, _err = exaplus.communicate(sql.encode('utf-8')) + if exaplus.returncode != 0: + cls.log.critical('EXAplus error: %d', exaplus.returncode) + cls.log.error(out) + else: + cls.log.debug(out) + + def test_frequency_analysis(self): + if udf.opts.lang == 'r': + raise SkipTest('this R implementation is too slow') + with timer() as t1: + rows1 = self.query(''' + SELECT fn1.performance_reduce_characters(w, c) + FROM ( + SELECT fn1.performance_map_characters(text) + FROM daten.wiki_names + ) + GROUP BY w + ORDER BY c DESC, w ASC''') + + with timer() as t2: + rows2 = self.query(''' + SELECT w, c + FROM daten.wiki_freq + ORDER BY c DESC, w ASC''') + + data = [tuple(x) for x in rows1] + reference = [tuple(x) for x in rows2] + print("test_frequency_analysis query:", t1.duration, t2.duration) + self.compare(reference, data) + + def test_frequency_analysis_fast(self): + with timer() as t1: + rows1 = self.query(''' + SELECT fn1.performance_reduce_characters_fast(w, c) + FROM ( + SELECT fn1.performance_map_characters_fast(text) + FROM daten.wiki_names + WHERE text IS NOT NULL + ) + GROUP BY w + ORDER BY c DESC, w ASC''') + + with timer() as t2: + rows2 = self.query(''' + SELECT w, c + FROM daten.wiki_freq + ORDER BY c DESC, w ASC''') + + data = [tuple(x) for x in rows1] + reference = [tuple(x) for x in rows2] + print("test_frequency_analysis_fast query:", t1.duration, t2.duration) + self.compare(reference, data) + + def test_frequency_analysis_fast0(self): + with timer() as t1: + rows1 = self.query(''' + SELECT fn1.performance_reduce_characters_fast(w, c) + FROM ( + SELECT fn1.performance_map_characters_fast0(text) + FROM daten.wiki_names + WHERE text IS NOT NULL + ) + GROUP BY w + ORDER BY c DESC, w ASC''') + + with timer() as t2: + rows2 = self.query(''' + SELECT w, c + FROM daten.wiki_freq + ORDER BY c DESC, w ASC''') + + data = [tuple(x) for x in rows1] + reference = [tuple(x) for x in rows2] + print("test_frequency_analysis_fast0 query:", t1.duration, t2.duration) + self.compare(reference, data) + + def test_frequency_analysis_light(self): + if udf.opts.lang != 'r': + raise SkipTest('light test for R only') + self.query(''' + SELECT fn1.performance_reduce_characters(w, c) + FROM ( + SELECT fn1.performance_map_characters(text) + FROM daten.wiki_names + WHERE mod(length(daten.wiki_names.text), 50) = 2 + ) + GROUP BY w + ORDER BY c DESC, w ASC''') + + +if __name__ == '__main__': + udf.main() diff --git a/test_container/tests/test/generic/r/unicode.py b/test_container/tests/test/generic/r/unicode.py new file mode 100755 index 000000000..2dfbd82ef --- /dev/null +++ b/test_container/tests/test/generic/r/unicode.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +import csv +import locale +import logging +import os +import subprocess +import sys +import tempfile +import unicodedata +import re +import argparse + +from exasol_python_test_framework import udf +import pathlib + +udf.pythonVersionInUdf = -1 +from exasol_python_test_framework.udf import ( + useData +) + +from exasol_python_test_framework.exatest.testcase import skipIf + + +locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') + + +def getPythonVersionInUDFs(server, script_languages): + log = logging.getLogger('unicodedata') + log.info("trying to figure out python version of python in UDFs") + sql = udf.fixindent(''' + alter session set script_languages='%(sl)s'; + drop schema if exists pyversion_schema cascade; + create schema pyversion_schema; + create or replace python3 scalar script pyversion_schema.python_version() returns varchar(1000) as + import sys + def run(ctx): + return 'Python='+str(sys.version_info[0]) + / + select pyversion_schema.python_version(); + ''' % {'sl': script_languages}) + cmd = '''%(exaplus)s -c %(conn)s -u sys -P exasol + -no-config -autocommit ON -L -pipe -jdbcparam validateservercertificate=0''' % { + 'exaplus': os.environ.get('EXAPLUS', + '/usr/opt/EXASuite-4/EXASolution-4.2.9/bin/Console/exaplus'), + 'conn': server + } + env = os.environ.copy() + # env['PATH'] = '/usr/opt/jdk1.8.0_latest/bin:' + env['PATH'] + exaplus = subprocess.Popen( + cmd.split(), + env=env, + + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, _err = exaplus.communicate(sql.encode('utf-8')) + pythonVersionInUdf = -1 + for line in out.strip().decode('utf-8').split(sep="\n"): + m = re.search(r'Python=(\d)', line) + if m: + pythonVersionInUdf = int(m.group(1)) + continue + + if pythonVersionInUdf not in [2, 3]: + print('cannot set pythonVersionInUdf: %s' % pythonVersionInUdf) + sys.exit(1) + + return pythonVersionInUdf + + +def setUpModule(): + log = logging.getLogger('unicodedata') + + log.info('generating unicodedata CSV') + with tempfile.NamedTemporaryFile(prefix='unicode-', suffix='.csv', encoding='utf-8', mode='w+', + delete=False) as csvfile: + c = csv.writer(csvfile, quoting=csv.QUOTE_ALL) + for i in range(sys.maxunicode + 1): + if i >= 5024 and i <= 5119: + continue # the Unicode Cherokee-Block is broken in Python 2.7 and Python 3.4 (maybe also 3.5) + u = chr(i) + if unicodedata.category(u).startswith('C'): + # [Cc]Other, Control + # [Cf]Other, Format + # [Cn]Other, Not Assigned + # [Co]Other, Private Use + # [Cs]Other, Surrogate + continue + row = (i, # INT 0-1114111 + unicodedata.name(u, 'UNICODE U+%08X' % i), # VARCHAR(100) ASCII + u, # VARCHAR(1) UNICODE + u.upper(), # VARCHAR(1) UNICODE + u.lower(), # VARCHAR(1) UNICODE + unicodedata.decimal(u, None), # INT + unicodedata.numeric(u, None), # DOUBLE + unicodedata.category(u), # VARCHAR(3) ASCII + unicodedata.bidirectional(u), # VARCHAR(3) ASCII + unicodedata.combining(u), # VARCHAR(3) ASCII + unicodedata.east_asian_width(u), # VARCHAR(1) ASCII + bool(unicodedata.mirrored), # BOOLEAN + unicodedata.decomposition(u), # VARCHAR(10) ASCII + unicodedata.normalize('NFC', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFD', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFKC', u), # VARCHAR(3) UNICODE + unicodedata.normalize('NFKD', u), # VARCHAR(3) UNICODE + ) + c.writerow(row) + csvfile.flush() + + log.info('loading CSV') + sql = ''' + DROP SCHEMA utest CASCADE; + CREATE SCHEMA utest; + CREATE TABLE unicodedata ( + codepoint INT NOT NULL, + name VARCHAR(100) ASCII, + uchar VARCHAR(1) UTF8, + to_upper VARCHAR(1) UTF8, + to_lower VARCHAR(1) UTF8, + decimal_value INT, + numeric_value INT, + category VARCHAR(3) ASCII, + bidirectional VARCHAR(3) ASCII, + combining VARCHAR(10) ASCII, + east_asian_width VARCHAR(2) ASCII, mirrored BOOLEAN, + decomposition VARCHAR(100) ASCII, + NFC VARCHAR(10) UTF8, + NFD VARCHAR(10) UTF8, + NFKC VARCHAR(20) UTF8, + NFKD VARCHAR(20) UTF8 + ); + IMPORT INTO unicodedata + FROM LOCAL CSV FILE '%s' + ROW SEPARATOR = 'CRLF'; + ''' % os.path.join(os.getcwd(), csvfile.name) + cmd = '''%(exaplus)s -c %(conn)s -u sys -P exasol + -no-config -autocommit ON -L -pipe -jdbcparam validateservercertificate=0''' % { + 'exaplus': os.environ.get('EXAPLUS', + '/usr/opt/EXASuite-4/EXASolution-4.2.9/bin/Console/exaplus'), + 'conn': udf.opts.server + } + env = os.environ.copy() + env['PATH'] = '/usr/opt/jdk1.8.0_latest/bin:' + env['PATH'] + exaplus = subprocess.Popen( + cmd.split(), + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, _err = exaplus.communicate(sql.encode('utf-8')) + if exaplus.returncode != 0 or _err is not None: + log.critical('EXAplus error: %d', exaplus.returncode) + log.error(out) + else: + log.debug(out) + + +def add_uniname(data): + return [(n, unicodedata.name(chr(n), 'U+%04X' % n)) + for n in data] + + +class Unicode(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'unicode.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + def query_unicode_char(self, u): + rows = self.query(''' + SELECT count, unicode(uchar) AS u + FROM ( + SELECT fn1.unicode_count(unicodechr(%d), 0) + FROM dual) + ''' % u) + self.assertEqual(1, self.rowcount()) + self.assertEqual(1, rows[0].COUNT) + self.assertEqual(u, rows[0].U) + + data = add_uniname(( + 65, + 255, + 382, + 65279, + 63882, + 65534, + 66432, + 173746, + 1114111, + )) + + @useData(data) + def test_unicode(self, codepoint, _name): + self.query_unicode_char(codepoint) + + def test_unicode_count(self): + self.maxDiff = 1024 + rows = self.query(''' + SELECT + c1_integer AS i, + len(c2_varchar100) AS len_exa, + fn1.unicode_len(c2_varchar100) AS len + FROM test.enginetablebigunicodevarchar + WHERE len(c2_varchar100) != fn1.unicode_len(c2_varchar100) + ORDER BY c1_integer + LIMIT 100 + ''') + self.assertRowsEqual([], rows) + + +class UnicodeData(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL file + sql_file = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' / 'unicode.sql' + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + # @udf.TestCase.expectedFailureIfLang('lua') + def test_unicode_upper_is_subset_of_Unicode520_part2(self): + """DWA-13388 (Lua); DWA-13702 (Lua)""" + rows = self.query(''' + SELECT + codepoint, + name, + unicode(to_upper), + unicode(fn1.unicode_upper(uchar)) + FROM utest.unicodedata + WHERE codepoint in (181, 8126) + and (to_upper != fn1.unicode_upper(uchar)) + and (uchar != fn1.unicode_upper(uchar)) + ORDER BY codepoint + LIMIT 50 + ''') + self.assertRowsEqual([], rows) + + @udf.TestCase.expectedFailureIfLang('lua') + def test_unicode_upper_is_subset_of_Unicode520_part3(self): + """DWA-13388 (Lua); DWA-13702 (Lua); DWA-13782 (R)""" + rows = self.query(''' + SELECT + codepoint, + name, + unicode(to_upper), + unicode(fn1.unicode_upper(uchar)) + FROM utest.unicodedata + WHERE codepoint in (1010) + and (to_upper != fn1.unicode_upper(uchar)) + and (uchar != fn1.unicode_upper(uchar)) + ORDER BY codepoint + LIMIT 50 + ''') + self.assertRowsEqual([], rows) + + def test_unicode_len(self): + rows = self.query(''' + SELECT codepoint, name + FROM utest.unicodedata + WHERE codepoint not between 55296 and 57343 + and len(uchar) != fn1.unicode_len(uchar) + ORDER BY codepoint + LIMIT 100 + ''') + self.assertRowsEqual([], rows) + + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--server', help='connection string') + parser.add_argument('--script-languages', help='definition of the SCRIPT_LANGUAGES variable') + opts, _unknown = parser.parse_known_args() + setattr(udf, 'pythonVersionInUdf', getPythonVersionInUDFs(opts.server, opts.script_languages)) + udf.main() diff --git a/test_container/tests/test/generic/r/vectorsize.py b/test_container/tests/test/generic/r/vectorsize.py new file mode 100755 index 000000000..67cb01a06 --- /dev/null +++ b/test_container/tests/test/generic/r/vectorsize.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +import sys + +from exasol_python_test_framework import udf +import pathlib +from exasol_python_test_framework.udf import useData, SkipTest +import re + + +class Vectorsize(udf.TestCase): + def setUp(self): + self.query('DROP SCHEMA FN1 CASCADE', ignore_errors=True) + self.query('CREATE SCHEMA FN1') + self.query('OPEN SCHEMA FN1') + + # Load R scripts from SQL files (basic.sql for BASIC_RANGE, vectorsize.sql for test functions) + lang_path = pathlib.Path(__file__).parent.parent.parent.parent / 'lang' / 'r' + for sql_filename in ['basic.sql', 'vectorsize.sql']: + sql_file = lang_path / sql_filename + with open(sql_file, 'r') as f: + sql_content = f.read() + + # Execute each CREATE SCRIPT statement + statements = re.split(r'^\s*/\s*$', sql_content, flags=re.MULTILINE) + for stmt in statements: + stmt = stmt.strip() + if stmt and 'CREATE' in stmt.upper(): + self.query(stmt) + + + def test_vectorsize_5000(self): + self.query(''' + SELECT max(fn1.vectorsize5000(float1)) + FROM TEST.ENGINETABLEBIG1''') + + data = [ + (10,), + (30,), + (100,), + (300,), + (1000,), + (3000,), + (10000,), + (30000,), + (100000,), + (200000,), + (351850,), + ] + + @useData(data) + def test_vectorsize(self, size): + limits = { + 'lua': 100000, + 'python3': 8000, + 'r': 3000, + 'java': 3000 + } + if size > limits.get(udf.opts.lang, sys.maxsize): + raise SkipTest('test is to slow') + + self.query(''' + SELECT max(fn1.vectorsize(%d, float1)) + FROM TEST.ENGINETABLEBIG1 + ''' % size) + + data = [ + (10, 10, 10), + (100, 100, 100), + (1000, 100, 100), + (10000, 100, 100), + (100000, 100, 100), + (351850, 100, 100), + (100, 10, 100000), + (100, 100, 10000), + (100, 1000, 1000), + (100, 10000, 100), + (100, 100000, 10), + ] + + @useData(data) + def test_vectorsize_set(self, a, b, c): + q = ''' + SELECT max(o) + FROM ( + SELECT fn1.vectorsize_set(%d, %d, n) + FROM ( + SELECT fn1.basic_range(%d) + FROM DUAL + ) + ) + ''' % (a, b, c) + self.query(q) + + +if __name__ == '__main__': + udf.main()