From 15a74f42002b79a320a66293df822443956889e7 Mon Sep 17 00:00:00 2001 From: Drew Kimball Date: Tue, 26 Mar 2024 04:06:12 -0600 Subject: [PATCH] sql: add tests for tail-call property This commit adds tests for the `ExtractTailCalls` function from the previous commit, and adds a `tail-call` field to `UDFCall` expressions that are in tail-call position in another routine. It also adds a regression test for #120916. Informs #120916 Release note: None --- .../testdata/logic_test/explain_call_plpgsql | 1 + .../testdata/logic_test/nested_routines | 27 + .../tests/3node-tenant/generated_test.go | 7 + .../tests/fakedist-disk/BUILD.bazel | 2 +- .../tests/fakedist-disk/generated_test.go | 7 + .../tests/fakedist-vec-off/BUILD.bazel | 2 +- .../tests/fakedist-vec-off/generated_test.go | 7 + .../logictestccl/tests/fakedist/BUILD.bazel | 2 +- .../tests/fakedist/generated_test.go | 7 + .../local-legacy-schema-changer/BUILD.bazel | 2 +- .../generated_test.go | 7 + .../tests/local-read-committed/BUILD.bazel | 2 +- .../local-read-committed/generated_test.go | 7 + .../tests/local-vec-off/BUILD.bazel | 2 +- .../tests/local-vec-off/generated_test.go | 7 + pkg/ccl/logictestccl/tests/local/BUILD.bazel | 2 +- .../tests/local/generated_test.go | 7 + pkg/sql/opt/memo/expr_format.go | 21 +- pkg/sql/opt/memo/testdata/logprops/tail-calls | 1053 +++++++++++++++++ pkg/sql/opt/norm/testdata/rules/routine | 2 + .../opt/optbuilder/testdata/procedure_plpgsql | 13 + pkg/sql/opt/optbuilder/testdata/udf_plpgsql | 235 ++++ 22 files changed, 1411 insertions(+), 11 deletions(-) create mode 100644 pkg/ccl/logictestccl/testdata/logic_test/nested_routines create mode 100644 pkg/sql/opt/memo/testdata/logprops/tail-calls diff --git a/pkg/ccl/logictestccl/testdata/logic_test/explain_call_plpgsql b/pkg/ccl/logictestccl/testdata/logic_test/explain_call_plpgsql index 5ea1be3c5785..4ee7658021cb 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/explain_call_plpgsql +++ b/pkg/ccl/logictestccl/testdata/logic_test/explain_call_plpgsql @@ -85,6 +85,7 @@ call ├── fd: ()-->(5) └── tuple [type=tuple{void}] └── udf: _stmt_raise_1 [type=void] + ├── tail-call ├── args │ └── variable: x:1 [type=int] ├── params: x:2(int) diff --git a/pkg/ccl/logictestccl/testdata/logic_test/nested_routines b/pkg/ccl/logictestccl/testdata/logic_test/nested_routines new file mode 100644 index 000000000000..01a8d86bf100 --- /dev/null +++ b/pkg/ccl/logictestccl/testdata/logic_test/nested_routines @@ -0,0 +1,27 @@ +# LogicTest: !local-mixed-23.1 !local-mixed-23.2 + +# Regression test for #120916 - the nested routine is not in tail-call position, +# and so cannot be a target for TCO. +statement ok +CREATE FUNCTION f_nested(x INT) RETURNS INT AS $$ + BEGIN + x := x * 2; + RETURN x; + END +$$ LANGUAGE PLpgSQL; + +statement ok +CREATE FUNCTION f() RETURNS RECORD AS $$ + DECLARE + a INT := -2; + BEGIN + a := f_nested(a); + RAISE NOTICE 'here'; + RETURN (a, -a); + END +$$ LANGUAGE PLpgSQL; + +query II +SELECT * FROM f() AS g(x INT, y INT); +---- +-4 4 diff --git a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go index ea18c1bdb19d..d1625e0835e2 100644 --- a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go +++ b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go @@ -2608,6 +2608,13 @@ func TestTenantLogicCCL_hash_sharded_index_read_committed( runCCLLogicTest(t, "hash_sharded_index_read_committed") } +func TestTenantLogicCCL_nested_routines( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "nested_routines") +} + func TestTenantLogicCCL_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel index 9222a647bb1b..2348d79ae0d5 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel @@ -12,7 +12,7 @@ go_test( "//build/toolchains:is_heavy": {"test.Pool": "heavy"}, "//conditions:default": {"test.Pool": "large"}, }), - shard_count = 26, + shard_count = 27, tags = [ "ccl_test", "cpu:2", diff --git a/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go index efb8333f643f..6efe18f36e0e 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go @@ -99,6 +99,13 @@ func TestCCLLogic_hash_sharded_index_read_committed( runCCLLogicTest(t, "hash_sharded_index_read_committed") } +func TestCCLLogic_nested_routines( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "nested_routines") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel index 474815813dbd..d20bad5b2b00 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel @@ -12,7 +12,7 @@ go_test( "//build/toolchains:is_heavy": {"test.Pool": "heavy"}, "//conditions:default": {"test.Pool": "large"}, }), - shard_count = 26, + shard_count = 27, tags = [ "ccl_test", "cpu:2", diff --git a/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go index b490824c5ef2..db8eb2e17bd8 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go @@ -99,6 +99,13 @@ func TestCCLLogic_hash_sharded_index_read_committed( runCCLLogicTest(t, "hash_sharded_index_read_committed") } +func TestCCLLogic_nested_routines( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "nested_routines") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel index cf89a1265ecd..940d98f323c5 100644 --- a/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel @@ -12,7 +12,7 @@ go_test( "//build/toolchains:is_heavy": {"test.Pool": "heavy"}, "//conditions:default": {"test.Pool": "large"}, }), - shard_count = 27, + shard_count = 28, tags = [ "ccl_test", "cpu:2", diff --git a/pkg/ccl/logictestccl/tests/fakedist/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist/generated_test.go index 5d7bc7fc5cda..d752e9deffbb 100644 --- a/pkg/ccl/logictestccl/tests/fakedist/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist/generated_test.go @@ -99,6 +99,13 @@ func TestCCLLogic_hash_sharded_index_read_committed( runCCLLogicTest(t, "hash_sharded_index_read_committed") } +func TestCCLLogic_nested_routines( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "nested_routines") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel index 23d00a43d2b4..b10d882d5978 100644 --- a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel @@ -9,7 +9,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 26, + shard_count = 27, tags = [ "ccl_test", "cpu:1", diff --git a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go index 7f6fc7dad431..98c042c77ea7 100644 --- a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go @@ -99,6 +99,13 @@ func TestCCLLogic_hash_sharded_index_read_committed( runCCLLogicTest(t, "hash_sharded_index_read_committed") } +func TestCCLLogic_nested_routines( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "nested_routines") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-read-committed/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-read-committed/BUILD.bazel index 4798e6cd5b8e..6f0f26ec3a11 100644 --- a/pkg/ccl/logictestccl/tests/local-read-committed/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-read-committed/BUILD.bazel @@ -10,7 +10,7 @@ go_test( "//pkg/sql/opt/exec/execbuilder:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 33, + shard_count = 34, tags = [ "ccl_test", "cpu:1", diff --git a/pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go b/pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go index 914ad76fe16d..3c4d8e428c8e 100644 --- a/pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-read-committed/generated_test.go @@ -126,6 +126,13 @@ func TestReadCommittedLogicCCL_hash_sharded_index_read_committed( runCCLLogicTest(t, "hash_sharded_index_read_committed") } +func TestReadCommittedLogicCCL_nested_routines( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "nested_routines") +} + func TestReadCommittedLogicCCL_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel index d7c874c807b5..1087a077a893 100644 --- a/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel @@ -9,7 +9,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 26, + shard_count = 27, tags = [ "ccl_test", "cpu:1", diff --git a/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go b/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go index 39736076be8b..9f2a448b36b3 100644 --- a/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go @@ -99,6 +99,13 @@ func TestCCLLogic_hash_sharded_index_read_committed( runCCLLogicTest(t, "hash_sharded_index_read_committed") } +func TestCCLLogic_nested_routines( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "nested_routines") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local/BUILD.bazel b/pkg/ccl/logictestccl/tests/local/BUILD.bazel index 3524b606c516..54c58be6bcbc 100644 --- a/pkg/ccl/logictestccl/tests/local/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local/BUILD.bazel @@ -9,7 +9,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"test.Pool": "large"}, - shard_count = 42, + shard_count = 43, tags = [ "ccl_test", "cpu:1", diff --git a/pkg/ccl/logictestccl/tests/local/generated_test.go b/pkg/ccl/logictestccl/tests/local/generated_test.go index 0bc32da7f3f0..6635dee8eb4a 100644 --- a/pkg/ccl/logictestccl/tests/local/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local/generated_test.go @@ -155,6 +155,13 @@ func TestCCLLogic_hash_sharded_index_read_committed( runCCLLogicTest(t, "hash_sharded_index_read_committed") } +func TestCCLLogic_nested_routines( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "nested_routines") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/sql/opt/memo/expr_format.go b/pkg/sql/opt/memo/expr_format.go index 83122d85b35e..6a88236a2f45 100644 --- a/pkg/sql/opt/memo/expr_format.go +++ b/pkg/sql/opt/memo/expr_format.go @@ -171,6 +171,10 @@ type ExprFmtCtx struct { // seenUDFs is used to ensure that formatting of recursive UDFs does not // infinitely recurse. seenUDFs map[*UDFDefinition]struct{} + + // tailCalls allows for quick lookup of all the routines in tail-call position + // when the last body statement of a routine is formatted. + tailCalls map[*UDFCallExpr]struct{} } // makeExprFmtCtxForString creates an expression formatting context from a new @@ -957,13 +961,18 @@ func (f *ExprFmtCtx) formatScalarWithLabel( } n := tp.Child("body") for i := range def.Body { + stmtNode := n if i == 0 && def.CursorDeclaration != nil { // The first statement is opening a cursor. - cur := n.Child("open-cursor") - f.formatExpr(def.Body[i], cur) - continue + stmtNode = n.Child("open-cursor") + } + prevTailCalls := f.tailCalls + if i == len(def.Body)-1 { + f.tailCalls = make(map[*UDFCallExpr]struct{}) + ExtractTailCalls(def.Body[i], f.tailCalls) } - f.formatExpr(def.Body[i], n) + f.formatExpr(def.Body[i], stmtNode) + f.tailCalls = prevTailCalls } delete(f.seenUDFs, def) } else { @@ -998,6 +1007,10 @@ func (f *ExprFmtCtx) formatScalarWithLabel( if !udf.Def.CalledOnNullInput { tp.Child("strict") } + if _, tailCall := f.tailCalls[udf]; tailCall { + // This routine is in tail-call position in the parent routine. + tp.Child("tail-call") + } formatRoutineArgs(udf.Args, tp) formatUDFDefinition(udf.Def, tp) } diff --git a/pkg/sql/opt/memo/testdata/logprops/tail-calls b/pkg/sql/opt/memo/testdata/logprops/tail-calls new file mode 100644 index 000000000000..14adda5af6f2 --- /dev/null +++ b/pkg/sql/opt/memo/testdata/logprops/tail-calls @@ -0,0 +1,1053 @@ +exec-ddl +CREATE FUNCTION nested() RETURNS INT AS $$ + SELECT 1; +$$ LANGUAGE SQL; +---- + +exec-ddl +CREATE FUNCTION nested_arg(x INT) RETURNS INT AS $$ + SELECT x; +$$ LANGUAGE SQL; +---- + +exec-ddl +CREATE FUNCTION generator() RETURNS SETOF INT AS $$ + VALUES (1), (2), (3); +$$ LANGUAGE SQL; +---- + +exec-ddl +CREATE TABLE t (a INT); +---- + +# ============================================================================== +# Test explicit tail-calls with a SQL routine as the parent. +# ============================================================================== + +# Basic tail call. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + SELECT * FROM t; + SELECT nested(); +$$ LANGUAGE SQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + ├── scan t + └── values + └── tuple + └── udf: nested + ├── tail-call + └── body + └── values + └── tuple + └── const: 1 + +# Not a tail-call because it isn't the last body statement. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + SELECT * FROM t; + SELECT nested(); + SELECT 1; +$$ LANGUAGE SQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + ├── scan t + ├── values + │ └── tuple + │ └── udf: nested + │ └── body + │ └── values + │ └── tuple + │ └── const: 1 + └── values + └── tuple + └── const: 1 + +# Tail-call with a named result column. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + SELECT * FROM t; + SELECT nested() AS foo; +$$ LANGUAGE SQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + ├── scan t + └── values + └── tuple + └── udf: nested + ├── tail-call + └── body + └── values + └── tuple + └── const: 1 + +# Nested routine cannot be a tail-call because it's a data source. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + SELECT * FROM t; + SELECT * FROM nested(); +$$ LANGUAGE SQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + ├── scan t + └── limit + ├── project-set + │ ├── values + │ │ └── tuple + │ └── zip + │ └── udf: nested + │ └── body + │ └── values + │ └── tuple + │ └── const: 1 + └── const: 1 + +# Case with a nonempty input with one row. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + SELECT * FROM t; + SELECT nested() FROM (VALUES (1)); +$$ LANGUAGE SQL; +---- + +norm format=(hide-all,show-scalars) disable=(MergeProjectWithValues,PruneValuesCols) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + ├── scan t + └── project + ├── values + │ └── tuple + │ └── const: 1 + └── projections + └── udf: nested + ├── tail-call + └── body + └── project + ├── values + │ └── tuple + └── projections + └── const: 1 + +# Case with a nonempty input with more than one row. The nested routine can +# still be considered a tail-call because the UDF enforces a LIMIT 1. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + SELECT * FROM t; + SELECT nested() FROM (VALUES (1), (2)); +$$ LANGUAGE SQL; +---- + +norm format=(hide-all,show-scalars) disable=PruneValuesCols +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + ├── scan t + └── project + ├── limit + │ ├── values + │ │ ├── tuple + │ │ │ └── const: 1 + │ │ └── tuple + │ │ └── const: 2 + │ └── const: 1 + └── projections + └── udf: nested + ├── tail-call + └── body + └── values + └── tuple + └── const: 1 + +# Case with a nonempty input with more than one row, which disqualifies the +# routine from being a tail-call. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS SETOF INT AS $$ + SELECT * FROM t; + SELECT nested() FROM (VALUES (1), (2)); +$$ LANGUAGE SQL; +---- + +norm format=(hide-all,show-scalars) disable=PruneValuesCols +SELECT f(); +---- +project-set + ├── values + │ └── tuple + └── zip + └── udf: f + └── body + ├── scan t + └── project + ├── values + │ ├── tuple + │ │ └── const: 1 + │ └── tuple + │ └── const: 2 + └── projections + └── udf: nested + └── body + └── values + └── tuple + └── const: 1 + +# A generator function cannot be considered a tail-call. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS SETOF INT AS $$ + SELECT * FROM t; + SELECT generator(); +$$ LANGUAGE SQL; +---- + +norm format=(hide-all,show-scalars) disable=PruneValuesCols +SELECT f(); +---- +project-set + ├── values + │ └── tuple + └── zip + └── udf: f + └── body + ├── scan t + └── project-set + ├── values + │ └── tuple + └── zip + └── udf: generator + └── body + └── values + ├── tuple + │ └── const: 1 + ├── tuple + │ └── const: 2 + └── tuple + └── const: 3 + +# ============================================================================== +# Test explicit tail-calls with a PL/pgSQL routine as the parent. These tests +# also demonstrate that PL/pgSQL sub-routines are tail-calls, as do the +# optbuilder tests. +# ============================================================================== + +# Basic tail call. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE NOTICE 'foo'; + RETURN nested(); + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── values + └── tuple + └── udf: _stmt_raise_1 + ├── tail-call + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'foo' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── values + └── tuple + └── udf: nested + ├── tail-call + └── body + └── values + └── tuple + └── const: 1 + +# Not a tail-call because the result is not used by the parent function. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE NOTICE 'foo'; + SELECT nested(); + RETURN 0; + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── values + └── tuple + └── udf: _stmt_raise_1 + ├── tail-call + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'foo' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── values + └── tuple + └── udf: _stmt_exec_3 + ├── tail-call + └── body + ├── values + │ └── tuple + │ └── udf: nested + │ └── body + │ └── values + │ └── tuple + │ └── const: 1 + └── values + └── tuple + └── const: 0 + +# Tail-call mediated through a variable assignment. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + x INT; + BEGIN + RAISE NOTICE 'foo'; + x := nested(); + RETURN x; + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── null + └── projections + └── udf: _stmt_raise_1 + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'foo' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── values + └── tuple + └── udf: nested + ├── tail-call + └── body + └── values + └── tuple + └── const: 1 + +# Not a tail-call because of the second RAISE statement. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + x INT; + BEGIN + RAISE NOTICE 'foo'; + x := nested(); + RAISE NOTICE 'bar'; + RETURN x; + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── null + └── projections + └── udf: _stmt_raise_1 + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'foo' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── udf: nested + │ └── body + │ └── values + │ └── tuple + │ └── const: 1 + └── projections + └── udf: _stmt_raise_3 + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'bar' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── values + └── tuple + └── variable: x + +# Tail-call with an argument. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + x INT; + BEGIN + RAISE NOTICE 'foo'; + SELECT a INTO x FROM t LIMIT 1; + RETURN nested_arg(x); + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── null + └── projections + └── udf: _stmt_raise_1 + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'foo' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── values + └── tuple + └── udf: _stmt_exec_3 + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + └── project + ├── barrier + │ └── project + │ ├── left-join (cross) + │ │ ├── values + │ │ │ └── tuple + │ │ ├── limit + │ │ │ ├── scan t + │ │ │ └── const: 1 + │ │ └── filters (true) + │ └── projections + │ └── variable: a + └── projections + └── udf: _stmt_exec_ret_4 + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + └── values + └── tuple + └── udf: nested_arg + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + └── values + └── tuple + └── variable: x + +# Tail-calls within an IF statement. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + x INT := 10; + BEGIN + IF random() > 0.5 THEN + RETURN nested(); + ELSE + RETURN nested_arg(x); + END IF; + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── const: 10 + └── projections + └── case + ├── true + ├── when + │ ├── gt + │ │ ├── function: random + │ │ └── const: 0.5 + │ └── subquery + │ └── values + │ └── tuple + │ └── udf: nested + │ ├── tail-call + │ └── body + │ └── values + │ └── tuple + │ └── const: 1 + └── subquery + └── values + └── tuple + └── udf: nested_arg + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + └── values + └── tuple + └── variable: x + +# Tail-call reachable from both branches of an IF statement. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + x INT; + BEGIN + IF random() > 0.5 THEN + x := 100; + ELSE + x := 200; + END IF; + RETURN nested_arg(x); + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── null + └── projections + └── case + ├── true + ├── when + │ ├── gt + │ │ ├── function: random + │ │ └── const: 0.5 + │ └── subquery + │ └── project + │ ├── barrier + │ │ └── values + │ │ └── tuple + │ │ └── const: 100 + │ └── projections + │ └── udf: stmt_if_1 + │ ├── tail-call + │ ├── args + │ │ └── variable: x + │ ├── params: x + │ └── body + │ └── values + │ └── tuple + │ └── udf: nested_arg + │ ├── tail-call + │ ├── args + │ │ └── variable: x + │ ├── params: x + │ └── body + │ └── values + │ └── tuple + │ └── variable: x + └── subquery + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── const: 200 + └── projections + └── udf: stmt_if_1 + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + └── values + └── tuple + └── udf: nested_arg + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + └── values + └── tuple + └── variable: x + +# Tail-call within a loop. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + i INT := 0; + BEGIN + WHILE i < 10 LOOP + IF i = 5 THEN + RETURN nested(); + END IF; + i := i + 1; + END LOOP; + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── const: 0 + └── projections + └── udf: stmt_loop_5 + ├── tail-call + ├── args + │ └── variable: i + ├── params: i + └── body + └── values + └── tuple + └── case + ├── true + ├── when + │ ├── lt + │ │ ├── variable: i + │ │ └── const: 10 + │ └── subquery + │ └── values + │ └── tuple + │ └── case + │ ├── true + │ ├── when + │ │ ├── eq + │ │ │ ├── variable: i + │ │ │ └── const: 5 + │ │ └── subquery + │ │ └── values + │ │ └── tuple + │ │ └── udf: nested + │ │ ├── tail-call + │ │ └── body + │ │ └── values + │ │ └── tuple + │ │ └── const: 1 + │ └── subquery + │ └── values + │ └── tuple + │ └── subquery + │ └── project + │ ├── values + │ │ └── tuple + │ │ └── plus + │ │ ├── variable: i + │ │ └── const: 1 + │ └── projections + │ └── subquery + │ └── values + │ └── tuple + │ └── udf: stmt_loop_5 + │ ├── tail-call + │ ├── args + │ │ └── variable: i + │ └── recursive-call + └── subquery + └── values + └── tuple + └── udf: loop_exit_1 + ├── tail-call + ├── args + │ └── variable: i + ├── params: i + └── body + └── values + └── tuple + └── udf: _end_of_function_2 + ├── tail-call + ├── args + │ └── variable: i + ├── params: i + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'ERROR' + │ ├── const: 'control reached end of function without RETURN' + │ ├── const: '' + │ ├── const: '' + │ └── const: '2F005' + └── values + └── tuple + └── null + +# Tail-call within nested PL/pgSQL blocks. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + BEGIN + RAISE NOTICE 'foo'; + If random() < 0.5 THEN + RETURN nested(); + END IF; + BEGIN + RAISE NOTICE 'bar'; + RETURN nested_arg(100); + END; + END; + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── values + └── tuple + └── udf: _stmt_raise_5 + ├── tail-call + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'foo' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── values + └── tuple + └── case + ├── true + ├── when + │ ├── lt + │ │ ├── function: random + │ │ └── const: 0.5 + │ └── subquery + │ └── values + │ └── tuple + │ └── udf: nested + │ ├── tail-call + │ └── body + │ └── values + │ └── tuple + │ └── const: 1 + └── subquery + └── values + └── tuple + └── udf: stmt_if_7 + ├── tail-call + └── body + └── values + └── tuple + └── udf: _stmt_raise_9 + ├── tail-call + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'bar' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── values + └── tuple + └── udf: nested_arg + ├── tail-call + ├── args + │ └── const: 100 + ├── params: x + └── body + └── values + └── tuple + └── variable: x + +# Tail-call within an exception handler. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RETURN nested_arg(50); + EXCEPTION WHEN division_by_zero THEN + RAISE NOTICE 'oops'; + RETURN nested(); + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── values + └── tuple + └── udf: exception_block_5 + ├── tail-call + ├── body + │ └── values + │ └── tuple + │ └── udf: nested_arg + │ ├── tail-call + │ ├── args + │ │ └── const: 50 + │ ├── params: x + │ └── body + │ └── values + │ └── tuple + │ └── variable: x + └── exception-handler + └── SQLSTATE '22012' + └── values + └── tuple + └── udf: _stmt_raise_2 + └── body + ├── values + │ └── tuple + │ └── function: crdb_internal.plpgsql_raise + │ ├── const: 'NOTICE' + │ ├── const: 'oops' + │ ├── const: '' + │ ├── const: '' + │ └── const: '00000' + └── values + └── tuple + └── udf: nested + ├── tail-call + └── body + └── values + └── tuple + └── const: 1 + +# Nested routine cannot be a tail-call because it's a data source. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + x INT; + BEGIN + SELECT * INTO x FROM nested(); + RETURN x; + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) +VALUES (f()); +---- +values + └── tuple + └── udf: f + └── body + └── project + ├── barrier + │ └── values + │ └── tuple + │ └── null + └── projections + └── udf: _stmt_exec_1 + ├── tail-call + ├── args + │ └── variable: x + ├── params: x + └── body + └── project + ├── left-join (cross) + │ ├── values + │ │ └── tuple + │ ├── limit + │ │ ├── project-set + │ │ │ ├── values + │ │ │ │ └── tuple + │ │ │ └── zip + │ │ │ └── udf: nested + │ │ │ └── body + │ │ │ └── values + │ │ │ └── tuple + │ │ │ └── const: 1 + │ │ └── const: 1 + │ └── filters (true) + └── projections + └── variable: nested + +# A generator function cannot be considered a tail-call. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RETURN (SELECT generator()); + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) disable=PruneValuesCols +SELECT f(); +---- +values + └── tuple + └── udf: f + └── body + └── values + └── tuple + └── subquery + └── max1-row + └── project-set + ├── values + │ └── tuple + └── zip + └── udf: generator + └── body + └── values + ├── tuple + │ └── const: 1 + ├── tuple + │ └── const: 2 + └── tuple + └── const: 3 + +# TODO(121105): the set-returning UDF call should be built into a project-set. +# Until then, just make sure it isn't considered a tail-call. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RETURN generator(); + END +$$ LANGUAGE PLpgSQL; +---- + +norm format=(hide-all,show-scalars) disable=PruneValuesCols +SELECT f(); +---- +values + └── tuple + └── udf: f + └── body + └── values + └── tuple + └── udf: generator + └── body + └── values + ├── tuple + │ └── const: 1 + ├── tuple + │ └── const: 2 + └── tuple + └── const: 3 diff --git a/pkg/sql/opt/norm/testdata/rules/routine b/pkg/sql/opt/norm/testdata/rules/routine index fbd3986f9f44..ba00dd625b81 100644 --- a/pkg/sql/opt/norm/testdata/rules/routine +++ b/pkg/sql/opt/norm/testdata/rules/routine @@ -49,6 +49,7 @@ call │ ├── fd: ()-->(7) │ └── tuple │ └── udf: _stmt_raise_2 + │ ├── tail-call │ ├── args │ │ └── variable: x:1 │ ├── params: x:4 @@ -97,6 +98,7 @@ call ├── fd: ()-->(11) └── tuple └── udf: _stmt_exec_4 + ├── tail-call ├── args │ └── variable: x:1 ├── params: x:8 diff --git a/pkg/sql/opt/optbuilder/testdata/procedure_plpgsql b/pkg/sql/opt/optbuilder/testdata/procedure_plpgsql index af2d758664a9..4cc850497c90 100644 --- a/pkg/sql/opt/optbuilder/testdata/procedure_plpgsql +++ b/pkg/sql/opt/optbuilder/testdata/procedure_plpgsql @@ -85,6 +85,7 @@ call │ │ └── variable: count_rows:14 [as=c:57] │ └── projections │ └── udf: _stmt_exec_ret_2 [as="_stmt_exec_ret_2":58] + │ ├── tail-call │ ├── args │ │ ├── variable: arg_k:5 │ │ ├── variable: new_i:6 @@ -110,6 +111,7 @@ call │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_exec_4 [as="_stmt_exec_4":41] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: arg_k:15 │ │ │ ├── variable: new_i:16 @@ -142,6 +144,7 @@ call │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_if_3 [as=stmt_if_3:40] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: arg_k:24 │ │ │ ├── variable: new_i:25 @@ -163,6 +166,7 @@ call │ │ └── tuple │ └── projections │ └── udf: _stmt_exec_5 [as="_stmt_exec_5":55] + │ ├── tail-call │ ├── args │ │ ├── variable: arg_k:15 │ │ ├── variable: new_i:16 @@ -188,6 +192,7 @@ call │ │ └── tuple │ └── projections │ └── udf: stmt_if_3 [as=stmt_if_3:54] + │ ├── tail-call │ ├── args │ │ ├── variable: arg_k:42 │ │ ├── variable: new_i:43 @@ -323,6 +328,7 @@ call │ │ └── const: 1 │ └── projections │ └── udf: _stmt_raise_6 [as="_stmt_raise_6":26] + │ ├── tail-call │ ├── args │ │ ├── variable: x:18 │ │ ├── variable: y:19 @@ -367,6 +373,7 @@ call │ │ └── tuple │ └── projections │ └── udf: nested_block_3 [as=nested_block_3:25] + │ ├── tail-call │ ├── args │ │ ├── variable: x:21 │ │ └── variable: y:22 @@ -392,6 +399,7 @@ call │ │ └── const: 1 │ └── projections │ └── udf: _stmt_raise_4 [as="_stmt_raise_4":16] + │ ├── tail-call │ ├── args │ │ ├── variable: x:10 │ │ └── variable: y:11 @@ -499,6 +507,7 @@ call │ │ └── tuple │ └── projections │ └── udf: exception_block_7 [as=exception_block_7:17] + │ ├── tail-call │ ├── args │ │ └── variable: x:2 │ ├── params: x:12 @@ -509,6 +518,7 @@ call │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_exec_8 [as="_stmt_exec_8":16] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: x:12 │ │ ├── params: x:13 @@ -527,6 +537,7 @@ call │ │ │ └── tuple │ │ └── projections │ │ └── udf: nested_block_3 [as=nested_block_3:15] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: x:13 │ │ ├── params: x:4 @@ -537,6 +548,7 @@ call │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_raise_4 [as="_stmt_raise_4":8] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: x:4 │ │ ├── params: x:5 @@ -590,6 +602,7 @@ call │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_4 [as="_stmt_raise_4":8] + │ ├── tail-call │ ├── args │ │ └── variable: x:4 │ ├── params: x:5 diff --git a/pkg/sql/opt/optbuilder/testdata/udf_plpgsql b/pkg/sql/opt/optbuilder/testdata/udf_plpgsql index 8f82dd8570bc..934149db89a9 100644 --- a/pkg/sql/opt/optbuilder/testdata/udf_plpgsql +++ b/pkg/sql/opt/optbuilder/testdata/udf_plpgsql @@ -725,6 +725,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_6 [as=stmt_if_6:21] + │ ├── tail-call │ ├── args │ │ ├── variable: a:13 │ │ ├── variable: b:14 @@ -804,6 +805,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: loop_exit_1 [as=loop_exit_1:22] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:8 │ │ │ ├── variable: b:9 @@ -823,6 +825,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_4 [as=stmt_if_4:23] + │ ├── tail-call │ ├── args │ │ ├── variable: a:8 │ │ ├── variable: b:9 @@ -854,6 +857,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_5 [as=stmt_if_5:20] + │ ├── tail-call │ ├── args │ │ ├── variable: a:11 │ │ ├── variable: b:12 @@ -872,6 +876,7 @@ project │ │ └── const: 1 │ └── projections │ └── udf: stmt_loop_3 [as=stmt_loop_3:18] + │ ├── tail-call │ ├── args │ │ ├── variable: a:14 │ │ ├── variable: b:15 @@ -943,6 +948,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: loop_exit_1 [as=loop_exit_1:16] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:8 │ │ │ ├── variable: b:9 @@ -962,6 +968,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_4 [as=stmt_if_4:17] + │ ├── tail-call │ ├── args │ │ ├── variable: a:8 │ │ ├── variable: b:9 @@ -980,6 +987,7 @@ project │ │ └── const: 1 │ └── projections │ └── udf: stmt_loop_3 [as=stmt_loop_3:15] + │ ├── tail-call │ ├── args │ │ ├── variable: a:11 │ │ ├── variable: b:12 @@ -1076,6 +1084,7 @@ project │ │ │ │ └── tuple │ │ │ └── projections │ │ │ └── udf: loop_exit_3 [as=loop_exit_3:26] + │ │ │ ├── tail-call │ │ │ ├── args │ │ │ │ ├── variable: a:15 │ │ │ │ ├── variable: b:16 @@ -1089,6 +1098,7 @@ project │ │ │ │ └── tuple │ │ │ └── projections │ │ │ └── udf: stmt_if_1 [as=stmt_if_1:14] + │ │ │ ├── tail-call │ │ │ ├── args │ │ │ │ ├── variable: a:10 │ │ │ │ ├── variable: b:11 @@ -1109,6 +1119,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_if_5 [as=stmt_if_5:27] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:15 │ │ │ ├── variable: b:16 @@ -1134,6 +1145,7 @@ project │ │ │ └── const: 1 │ │ └── projections │ │ └── udf: stmt_loop_4 [as=stmt_loop_4:25] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:19 │ │ │ ├── variable: b:20 @@ -1237,6 +1249,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: loop_exit_1 [as=loop_exit_1:29] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:10 │ │ │ ├── variable: b:11 @@ -1257,6 +1270,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_4 [as=stmt_if_4:30] + │ ├── tail-call │ ├── args │ │ ├── variable: a:10 │ │ ├── variable: b:11 @@ -1288,6 +1302,7 @@ project │ │ │ └── const: 1 │ │ └── projections │ │ └── udf: stmt_loop_3 [as=stmt_loop_3:26] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:14 │ │ │ ├── variable: b:15 @@ -1301,6 +1316,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_5 [as=stmt_if_5:27] + │ ├── tail-call │ ├── args │ │ ├── variable: a:14 │ │ ├── variable: b:15 @@ -1326,6 +1342,7 @@ project │ │ └── const: 1 │ └── projections │ └── udf: stmt_loop_3 [as=stmt_loop_3:24] + │ ├── tail-call │ ├── args │ │ ├── variable: a:18 │ │ ├── variable: b:19 @@ -1417,6 +1434,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: loop_exit_1 [as=loop_exit_1:47] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:12 │ │ │ ├── variable: b:13 @@ -1438,6 +1456,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_4 [as=stmt_if_4:48] + │ ├── tail-call │ ├── args │ │ ├── variable: a:12 │ │ ├── variable: b:13 @@ -1456,6 +1475,7 @@ project │ │ └── const: 0 [as=j:22] │ └── projections │ └── udf: stmt_loop_6 [as=stmt_loop_6:46] + │ ├── tail-call │ ├── args │ │ ├── variable: a:17 │ │ ├── variable: b:18 @@ -1482,6 +1502,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: loop_exit_5 [as=loop_exit_5:43] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:30 │ │ │ ├── variable: b:31 @@ -1502,6 +1523,7 @@ project │ │ │ └── const: 1 │ │ └── projections │ │ └── udf: stmt_loop_3 [as=stmt_loop_3:29] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: a:23 │ │ │ ├── variable: b:24 @@ -1516,6 +1538,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_7 [as=stmt_if_7:44] + │ ├── tail-call │ ├── args │ │ ├── variable: a:30 │ │ ├── variable: b:31 @@ -1542,6 +1565,7 @@ project │ │ └── const: 1 │ └── projections │ └── udf: stmt_loop_6 [as=stmt_loop_6:42] + │ ├── tail-call │ ├── args │ │ ├── variable: a:35 │ │ ├── variable: b:36 @@ -1631,6 +1655,7 @@ project │ │ │ └── const: 1 │ │ └── projections │ │ └── udf: stmt_if_4 [as=stmt_if_4:17] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:8 │ │ │ ├── variable: sum:15 @@ -1643,6 +1668,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_loop_3 [as=stmt_loop_3:14] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:11 │ │ │ ├── variable: sum:12 @@ -1655,6 +1681,7 @@ project │ │ └── tuple │ └── projections │ └── udf: loop_exit_1 [as=loop_exit_1:18] + │ ├── tail-call │ ├── args │ │ ├── variable: n:8 │ │ ├── variable: sum:9 @@ -1738,6 +1765,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: loop_exit_1 [as=loop_exit_1:17] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:8 │ │ │ ├── variable: sum:9 @@ -1757,6 +1785,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_4 [as=stmt_if_4:18] + │ ├── tail-call │ ├── args │ │ ├── variable: n:8 │ │ ├── variable: sum:9 @@ -1781,6 +1810,7 @@ project │ │ └── const: 1 │ └── projections │ └── udf: stmt_loop_3 [as=stmt_loop_3:16] + │ ├── tail-call │ ├── args │ │ ├── variable: n:11 │ │ ├── variable: sum:14 @@ -1859,6 +1889,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_loop_3 [as=stmt_loop_3:17] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:8 │ │ │ ├── variable: sum:9 @@ -1871,6 +1902,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_4 [as=stmt_if_4:18] + │ ├── tail-call │ ├── args │ │ ├── variable: n:8 │ │ ├── variable: sum:9 @@ -1895,6 +1927,7 @@ project │ │ └── const: 1 │ └── projections │ └── udf: stmt_loop_3 [as=stmt_loop_3:16] + │ ├── tail-call │ ├── args │ │ ├── variable: n:11 │ │ ├── variable: sum:14 @@ -1952,6 +1985,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":10] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_4:2 @@ -1970,6 +2004,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":9] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_6:3 @@ -1988,6 +2023,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":8] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_8:4 @@ -2006,6 +2042,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":7] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_10:5 @@ -2082,6 +2119,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":10] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_4:2 @@ -2121,6 +2159,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":9] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_6:3 @@ -2143,6 +2182,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":8] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_8:4 @@ -2172,6 +2212,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":7] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_10:5 @@ -2293,6 +2334,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":16] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_4:2 @@ -2311,6 +2353,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":15] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_6:3 @@ -2329,6 +2372,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":14] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_8:4 @@ -2347,6 +2391,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":13] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_10:5 @@ -2365,6 +2410,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_11 [as="_stmt_raise_11":12] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_12:6 @@ -2383,6 +2429,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_13 [as="_stmt_raise_13":11] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_14:7 @@ -2401,6 +2448,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_15 [as="_stmt_raise_15":10] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_16:8 @@ -2472,6 +2520,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":12] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_4:2 @@ -2493,6 +2542,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":11] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_6:3 @@ -2511,6 +2561,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":10] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_8:4 @@ -2529,6 +2580,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":9] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_10:5 @@ -2547,6 +2599,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_11 [as="_stmt_raise_11":8] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_12:6 @@ -2641,6 +2694,7 @@ project │ │ └── const: 100 [as=i:4] │ └── projections │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":27] + │ ├── tail-call │ ├── args │ │ └── variable: i:4 │ ├── params: i:5 @@ -2684,6 +2738,7 @@ project │ │ └── count-rows [as=count_rows:12] │ └── projections │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":26] + │ ├── tail-call │ ├── args │ │ └── variable: i:13 │ ├── params: i:14 @@ -2712,6 +2767,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":25] + │ ├── tail-call │ ├── args │ │ └── variable: i:14 │ ├── params: i:16 @@ -2815,6 +2871,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: loop_exit_1 [as=loop_exit_1:14] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:7 │ │ ├── params: i:2 @@ -2825,6 +2882,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_raise_2 [as="_stmt_raise_2":6] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:2 │ │ ├── params: i:3 @@ -2860,6 +2918,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_6 [as=stmt_if_6:15] + │ ├── tail-call │ ├── args │ │ └── variable: i:7 │ ├── params: i:8 @@ -2870,6 +2929,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":13] + │ ├── tail-call │ ├── args │ │ └── variable: i:8 │ ├── params: i:9 @@ -2904,6 +2964,7 @@ project │ │ └── const: 1 │ └── projections │ └── udf: stmt_loop_5 [as=stmt_loop_5:12] + │ ├── tail-call │ ├── args │ │ └── variable: i:11 │ └── recursive-call @@ -3116,6 +3177,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: loop_exit_1 [as=loop_exit_1:21] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:7 │ │ ├── params: i:2 @@ -3126,6 +3188,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_raise_2 [as="_stmt_raise_2":6] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:2 │ │ ├── params: i:3 @@ -3161,6 +3224,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_6 [as=stmt_if_6:22] + │ ├── tail-call │ ├── args │ │ └── variable: i:7 │ ├── params: i:8 @@ -3183,6 +3247,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_raise_10 [as="_stmt_raise_10":18] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:8 │ │ ├── params: i:15 @@ -3211,6 +3276,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_if_7 [as=stmt_if_7:17] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:15 │ │ ├── params: i:9 @@ -3221,6 +3287,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_raise_8 [as="_stmt_raise_8":14] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:9 │ │ ├── params: i:10 @@ -3255,6 +3322,7 @@ project │ │ │ └── const: 1 │ │ └── projections │ │ └── udf: stmt_loop_5 [as=stmt_loop_5:13] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:12 │ │ └── recursive-call @@ -3265,6 +3333,7 @@ project │ │ └── tuple │ └── projections │ └── udf: stmt_if_7 [as=stmt_if_7:19] + │ ├── tail-call │ ├── args │ │ └── variable: i:8 │ ├── params: i:9 @@ -3275,6 +3344,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_8 [as="_stmt_raise_8":14] + │ ├── tail-call │ ├── args │ │ └── variable: i:9 │ ├── params: i:10 @@ -3309,6 +3379,7 @@ project │ │ └── const: 1 │ └── projections │ └── udf: stmt_loop_5 [as=stmt_loop_5:13] + │ ├── tail-call │ ├── args │ │ └── variable: i:12 │ └── recursive-call @@ -3903,6 +3974,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_raise_8 [as="_stmt_raise_8":6] + │ │ ├── tail-call │ │ └── body │ │ ├── project │ │ │ ├── columns: stmt_raise_9:4 @@ -4076,6 +4148,7 @@ project │ │ │ └── variable: i:10 │ │ └── projections │ │ └── udf: assign_exception_block_4 [as=assign_exception_block_4:32] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: i:10 │ │ │ ├── variable: j:11 @@ -4097,6 +4170,7 @@ project │ │ │ └── variable: j:16 │ │ └── projections │ │ └── udf: assign_exception_block_5 [as=assign_exception_block_5:31] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: i:15 │ │ │ ├── variable: j:16 @@ -4118,6 +4192,7 @@ project │ │ │ └── variable: k:22 │ │ └── projections │ │ └── udf: assign_exception_block_6 [as=assign_exception_block_6:30] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: i:20 │ │ │ ├── variable: j:21 @@ -4218,6 +4293,7 @@ project │ │ │ │ └── const: 100 [as=x:11] │ │ │ └── projections │ │ │ └── udf: assign_exception_block_6 [as=assign_exception_block_6:19] + │ │ │ ├── tail-call │ │ │ ├── args │ │ │ │ ├── variable: i:6 │ │ │ │ └── variable: x:11 @@ -4229,6 +4305,7 @@ project │ │ │ │ └── tuple │ │ │ └── projections │ │ │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":18] + │ │ │ ├── tail-call │ │ │ ├── args │ │ │ │ ├── variable: i:12 │ │ │ │ └── variable: x:13 @@ -4251,6 +4328,7 @@ project │ │ │ │ └── tuple │ │ │ └── projections │ │ │ └── udf: stmt_if_4 [as=stmt_if_4:17] + │ │ │ ├── tail-call │ │ │ ├── args │ │ │ │ ├── variable: i:14 │ │ │ │ └── variable: x:15 @@ -4275,6 +4353,7 @@ project │ │ │ └── const: 200 [as=x:20] │ │ └── projections │ │ └── udf: assign_exception_block_9 [as=assign_exception_block_9:28] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: i:6 │ │ │ └── variable: x:20 @@ -4286,6 +4365,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: _stmt_raise_10 [as="_stmt_raise_10":27] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: i:21 │ │ │ └── variable: x:22 @@ -4308,6 +4388,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_if_4 [as=stmt_if_4:26] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: i:23 │ │ │ └── variable: x:24 @@ -4393,6 +4474,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_loop_6 [as=stmt_loop_6:43] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:10 │ │ │ ├── variable: a:11 @@ -4418,6 +4500,7 @@ project │ │ │ │ └── tuple │ │ │ └── projections │ │ │ └── udf: loop_exit_4 [as=loop_exit_4:40] + │ │ │ ├── tail-call │ │ │ ├── args │ │ │ │ ├── variable: n:19 │ │ │ │ ├── variable: a:20 @@ -4438,6 +4521,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_if_7 [as=stmt_if_7:41] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:19 │ │ │ ├── variable: a:20 @@ -4461,6 +4545,7 @@ project │ │ │ └── variable: a:24 │ │ └── projections │ │ └── udf: assign_exception_block_8 [as=assign_exception_block_8:39] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:23 │ │ │ ├── variable: a:24 @@ -4482,6 +4567,7 @@ project │ │ │ └── const: 1 │ │ └── projections │ │ └── udf: assign_exception_block_9 [as=assign_exception_block_9:38] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:28 │ │ │ ├── variable: a:29 @@ -4495,6 +4581,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: stmt_loop_6 [as=stmt_loop_6:37] + │ │ ├── tail-call │ │ ├── args │ │ │ ├── variable: n:33 │ │ │ ├── variable: a:34 @@ -4560,6 +4647,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_exec_2 [as="_stmt_exec_2":27] + │ ├── tail-call │ └── body │ ├── delete kv │ │ ├── columns: @@ -4578,6 +4666,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_exec_3 [as="_stmt_exec_3":26] + │ ├── tail-call │ └── body │ ├── insert kv │ │ ├── columns: @@ -4677,6 +4766,7 @@ project │ │ └── variable: y:6 [as=j:14] │ └── projections │ └── udf: _stmt_exec_ret_2 [as="_stmt_exec_ret_2":15] + │ ├── tail-call │ ├── args │ │ ├── variable: i:13 │ │ └── variable: j:14 @@ -4760,6 +4850,7 @@ project │ │ └── variable: x:3 [as=i:26] │ └── projections │ └── udf: _stmt_exec_ret_2 [as="_stmt_exec_ret_2":27] + │ ├── tail-call │ ├── args │ │ └── variable: i:26 │ ├── params: i:8 @@ -4770,6 +4861,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":25] + │ ├── tail-call │ ├── args │ │ └── variable: i:8 │ ├── params: i:9 @@ -4798,6 +4890,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_exec_5 [as="_stmt_exec_5":24] + │ ├── tail-call │ ├── args │ │ └── variable: i:9 │ ├── params: i:11 @@ -4831,6 +4924,7 @@ project │ │ └── variable: x:12 [as=i:22] │ └── projections │ └── udf: _stmt_exec_ret_6 [as="_stmt_exec_ret_6":23] + │ ├── tail-call │ ├── args │ │ └── variable: i:22 │ ├── params: i:17 @@ -4841,6 +4935,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":21] + │ ├── tail-call │ ├── args │ │ └── variable: i:17 │ ├── params: i:18 @@ -4924,6 +5019,7 @@ project │ │ └── variable: curs:5 │ └── projections │ └── udf: _stmt_open_1 [as="_stmt_open_1":7] + │ ├── tail-call │ ├── args │ │ └── variable: curs:6 │ ├── params: curs:2 @@ -5001,6 +5097,7 @@ project │ │ └── variable: curs:12 │ └── projections │ └── udf: _stmt_open_1 [as="_stmt_open_1":14] + │ ├── tail-call │ ├── args │ │ ├── variable: i:11 │ │ └── variable: curs:13 @@ -5091,6 +5188,7 @@ project │ │ └── variable: curs:29 │ └── projections │ └── udf: _stmt_open_1 [as="_stmt_open_1":33] + │ ├── tail-call │ ├── args │ │ ├── variable: curs:32 │ │ ├── variable: curs2:30 @@ -5110,6 +5208,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _gen_cursor_name_6 [as="_gen_cursor_name_6":28] + │ ├── tail-call │ ├── args │ │ ├── variable: curs:4 │ │ ├── variable: curs2:5 @@ -5129,6 +5228,7 @@ project │ │ └── variable: curs2:24 │ └── projections │ └── udf: _stmt_open_2 [as="_stmt_open_2":27] + │ ├── tail-call │ ├── args │ │ ├── variable: curs:23 │ │ ├── variable: curs2:26 @@ -5148,6 +5248,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _gen_cursor_name_5 [as="_gen_cursor_name_5":22] + │ ├── tail-call │ ├── args │ │ ├── variable: curs:8 │ │ ├── variable: curs2:9 @@ -5167,6 +5268,7 @@ project │ │ └── variable: curs3:19 │ └── projections │ └── udf: _stmt_open_3 [as="_stmt_open_3":21] + │ ├── tail-call │ ├── args │ │ ├── variable: curs:17 │ │ ├── variable: curs2:18 @@ -5295,6 +5397,7 @@ project │ │ └── variable: curs:8 [type=refcursor] │ └── projections │ └── udf: _stmt_open_1 [as="_stmt_open_1":10, type=int, outer=(9), volatile, udf] + │ ├── tail-call │ ├── args │ │ └── variable: curs:9 [type=refcursor] │ ├── params: curs:2(refcursor) @@ -5328,6 +5431,7 @@ project │ │ └── tuple [type=tuple] │ └── projections │ └── udf: _stmt_close_2 [as="_stmt_close_2":7, type=int, outer=(2), volatile, udf] + │ ├── tail-call │ ├── args │ │ └── variable: curs:2 [type=refcursor] │ ├── params: curs:4(refcursor) @@ -5413,6 +5517,7 @@ project │ │ │ └── tuple │ │ └── projections │ │ └── udf: _gen_cursor_name_8 [as="_gen_cursor_name_8":17] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: curs:10 │ │ ├── params: curs:14 @@ -5430,6 +5535,7 @@ project │ │ │ └── variable: curs:14 │ │ └── projections │ │ └── udf: _stmt_open_6 [as="_stmt_open_6":16] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: curs:15 │ │ ├── params: curs:11 @@ -5474,6 +5580,7 @@ project │ │ └── variable: curs:6 │ └── projections │ └── udf: _stmt_open_2 [as="_stmt_open_2":8] + │ ├── tail-call │ ├── args │ │ └── variable: curs:7 │ ├── params: curs:3 @@ -5614,6 +5721,7 @@ project │ │ └── variable: curs:19 [type=refcursor] │ └── projections │ └── udf: _stmt_open_1 [as="_stmt_open_1":22, type=int, outer=(20,21), volatile, udf] + │ ├── tail-call │ ├── args │ │ ├── variable: curs:21 [type=refcursor] │ │ └── variable: x:20 [type=int] @@ -5644,6 +5752,7 @@ project │ │ └── tuple [type=tuple] │ └── projections │ └── udf: _stmt_fetch_2 [as="_stmt_fetch_2":18, type=int, outer=(3,4), volatile, udf] + │ ├── tail-call │ ├── args │ │ ├── variable: curs:3 [type=refcursor] │ │ └── variable: x:4 [type=int] @@ -5692,6 +5801,7 @@ project │ │ └── variable: stmt_fetch_3:12 [type=tuple{int}] │ └── projections │ └── udf: _stmt_exec_ret_4 [as="_stmt_exec_ret_4":17, type=int, outer=(10,13), udf] + │ ├── tail-call │ ├── args │ │ ├── variable: curs:10 [type=refcursor] │ │ └── variable: x:13 [type=int] @@ -5766,6 +5876,7 @@ project │ │ └── variable: curs:12 │ └── projections │ └── udf: _stmt_open_1 [as="_stmt_open_1":14] + │ ├── tail-call │ ├── args │ │ └── variable: curs:13 │ ├── params: curs:2 @@ -5781,6 +5892,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_fetch_2 [as="_stmt_fetch_2":11] + │ ├── tail-call │ ├── args │ │ └── variable: curs:2 │ ├── params: curs:8 @@ -5980,6 +6092,7 @@ project │ │ │ └── const: 1 [type=int] │ │ └── projections │ │ └── udf: stmt_if_6 [as=stmt_if_6:11, type=int] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:10 [type=int] │ │ ├── params: i:8(int) @@ -5990,6 +6103,7 @@ project │ │ │ └── tuple [type=tuple] │ │ └── projections │ │ └── udf: stmt_loop_5 [as=stmt_loop_5:9, type=int] + │ │ ├── tail-call │ │ ├── args │ │ │ └── variable: i:8 [type=int] │ │ └── recursive-call @@ -6000,6 +6114,7 @@ project │ │ └── tuple [type=tuple] │ └── projections │ └── udf: loop_exit_1 [as=loop_exit_1:12, type=int] + │ ├── tail-call │ ├── args │ │ └── variable: i:7 [type=int] │ ├── params: i:2(int) @@ -6010,6 +6125,7 @@ project │ │ └── tuple [type=tuple] │ └── projections │ └── udf: _end_of_function_2 [as="_end_of_function_2":6, type=int] + │ ├── tail-call │ ├── args │ │ └── variable: i:2 [type=int] │ ├── params: i:3(int) @@ -6130,6 +6246,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_exec_3 [as="_stmt_exec_3":33] + │ ├── tail-call │ ├── args │ │ └── variable: found:2 │ ├── params: found:4 @@ -6162,6 +6279,7 @@ project │ │ └── variable: a:5 [as=found:31] │ └── projections │ └── udf: _stmt_exec_ret_4 [as="_stmt_exec_ret_4":32] + │ ├── tail-call │ ├── args │ │ └── variable: found:31 │ ├── params: found:9 @@ -6172,6 +6290,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":30] + │ ├── tail-call │ ├── args │ │ └── variable: found:9 │ ├── params: found:10 @@ -6193,6 +6312,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_exec_7 [as="_stmt_exec_7":29] + │ ├── tail-call │ ├── args │ │ └── variable: found:10 │ ├── params: found:12 @@ -6233,6 +6353,7 @@ project │ │ └── variable: a:13 [as=found:27] │ └── projections │ └── udf: _stmt_exec_ret_8 [as="_stmt_exec_ret_8":28] + │ ├── tail-call │ ├── args │ │ └── variable: found:27 │ ├── params: found:22 @@ -6243,6 +6364,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":26] + │ ├── tail-call │ ├── args │ │ └── variable: found:22 │ ├── params: found:23 @@ -6354,6 +6476,7 @@ project │ │ └── const: 80 [as=inner_quantity:10] │ └── projections │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":19] + │ ├── tail-call │ ├── args │ │ ├── variable: outer_quantity:4 │ │ └── variable: inner_quantity:10 @@ -6383,6 +6506,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":18] + │ ├── tail-call │ ├── args │ │ ├── variable: outer_quantity:11 │ │ └── variable: inner_quantity:12 @@ -6412,6 +6536,7 @@ project │ │ └── tuple │ └── projections │ └── udf: nested_block_3 [as=nested_block_3:17] + │ ├── tail-call │ ├── args │ │ └── variable: outer_quantity:14 │ ├── params: outer_quantity:5 @@ -6422,6 +6547,7 @@ project │ │ └── tuple │ └── projections │ └── udf: _stmt_raise_4 [as="_stmt_raise_4":9] + │ ├── tail-call │ ├── args │ │ └── variable: outer_quantity:5 │ ├── params: outer_quantity:6 @@ -6508,6 +6634,7 @@ call │ │ └── tuple │ └── projections │ └── udf: _stmt_exec_3 [as="_stmt_exec_3":22] + │ ├── tail-call │ └── body │ ├── insert txn_timestamps │ │ ├── columns: @@ -6536,6 +6663,7 @@ call │ │ └── tuple │ └── projections │ └── udf: _stmt_exec_5 [as="_stmt_exec_5":20] + │ ├── tail-call │ └── body │ ├── insert txn_timestamps │ │ ├── columns: @@ -6635,6 +6763,7 @@ project │ │ └── tuple [type=tuple] │ └── projections │ └── udf: stmt_if_1 [as=stmt_if_1:5, type=tuple{int, unknown, decimal}] + │ ├── tail-call │ └── body │ └── project │ ├── columns: "_end_of_function_2":3(tuple{int, unknown, decimal}) @@ -6642,6 +6771,7 @@ project │ │ └── tuple [type=tuple] │ └── projections │ └── udf: _end_of_function_2 [as="_end_of_function_2":3, type=tuple{int, unknown, decimal}] + │ ├── tail-call │ └── body │ ├── project │ │ ├── columns: stmt_raise_3:1(int) @@ -6661,3 +6791,108 @@ project │ └── projections │ └── null [as=end_of_function_4:2, type=tuple{int, unknown, decimal}] └── const: 1 [type=int] + +# Regression test for #120916 - the nested call should not have the "tail-call" +# property, because it isn't a terminal statement. +exec-ddl +CREATE OR REPLACE FUNCTION f_nested(x INT) RETURNS INT AS $$ + BEGIN + x := x * 2; + RETURN x; + END +$$ LANGUAGE PLpgSQL; +---- + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS RECORD AS $$ + DECLARE + a INT := -2; + BEGIN + a := f_nested(a); + RAISE NOTICE 'here'; + RETURN (a, -a); + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT * FROM f() AS g(x INT, y INT); +---- +project-set + ├── columns: x:12 y:13 + ├── values + │ └── tuple + └── zip + └── udf: f + └── body + └── project + ├── columns: column10:10 column11:11 + ├── limit + │ ├── columns: "_stmt_raise_1":9 + │ ├── project + │ │ ├── columns: "_stmt_raise_1":9 + │ │ ├── barrier + │ │ │ ├── columns: a:5 + │ │ │ └── project + │ │ │ ├── columns: a:5 + │ │ │ ├── barrier + │ │ │ │ ├── columns: a:1!null + │ │ │ │ └── project + │ │ │ │ ├── columns: a:1!null + │ │ │ │ ├── values + │ │ │ │ │ └── tuple + │ │ │ │ └── projections + │ │ │ │ └── const: -2 [as=a:1] + │ │ │ └── projections + │ │ │ └── udf: f_nested [as=a:5] + │ │ │ ├── args + │ │ │ │ └── variable: a:1 + │ │ │ ├── params: x:2 + │ │ │ └── body + │ │ │ └── limit + │ │ │ ├── columns: stmt_return_1:4 + │ │ │ ├── project + │ │ │ │ ├── columns: stmt_return_1:4 + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: x:3 + │ │ │ │ │ ├── values + │ │ │ │ │ │ └── tuple + │ │ │ │ │ └── projections + │ │ │ │ │ └── mult [as=x:3] + │ │ │ │ │ ├── variable: x:2 + │ │ │ │ │ └── const: 2 + │ │ │ │ └── projections + │ │ │ │ └── variable: x:3 [as=stmt_return_1:4] + │ │ │ └── const: 1 + │ │ └── projections + │ │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":9] + │ │ ├── args + │ │ │ └── variable: a:5 + │ │ ├── params: a:6 + │ │ └── body + │ │ ├── project + │ │ │ ├── columns: stmt_raise_2:7 + │ │ │ ├── values + │ │ │ │ └── tuple + │ │ │ └── projections + │ │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:7] + │ │ │ ├── const: 'NOTICE' + │ │ │ ├── const: 'here' + │ │ │ ├── const: '' + │ │ │ ├── const: '' + │ │ │ └── const: '00000' + │ │ └── project + │ │ ├── columns: stmt_return_3:8 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── tuple [as=stmt_return_3:8] + │ │ ├── variable: a:6 + │ │ └── unary-minus + │ │ └── variable: a:6 + │ └── const: 1 + └── projections + ├── column-access: 0 [as=column10:10] + │ └── variable: "_stmt_raise_1":9 + └── column-access: 1 [as=column11:11] + └── variable: "_stmt_raise_1":9