From 85b76174fbef47aaa07572f4410620e96ec7bc5c Mon Sep 17 00:00:00 2001 From: Drew Kimball Date: Wed, 5 Jul 2023 18:00:10 -0600 Subject: [PATCH] plpgsql: implement RAISE statement This patch adds support for the PLpgSQL `RAISE` statement. The `RAISE` statement can send messages back to the client during execution, as well as raise a user-specified error. There are a few variations on the syntax, but in general `RAISE` statements have a log level (default `EXCEPTION`), a message (if not specified, the code string is used), and various options: `DETAIL`, `HINT`, `ERRCODE` etc. With log level `EXCEPTION` the error is returned just like any other error, but for other levels it is sent as a notice to the client and flushed synchronously before execution continues. This feature is often used to track progress, since the notices are sent before execution finishes. Fixes #105251 Release note (sql change): Added support for the PLpgSQL `RAISE` statement, which allows sending notices to the client and raising errors. Currently the notice is only sent to the client; support for logging notices is left for future work. --- .../logictest/testdata/logic_test/udf_plpgsql | 272 +++ pkg/sql/opt/memo/expr.go | 12 +- pkg/sql/opt/optbuilder/plpgsql.go | 217 ++- pkg/sql/opt/optbuilder/testdata/udf_plpgsql | 1541 +++++++++++++++++ 4 files changed, 2016 insertions(+), 26 deletions(-) diff --git a/pkg/sql/logictest/testdata/logic_test/udf_plpgsql b/pkg/sql/logictest/testdata/logic_test/udf_plpgsql index e78987fba0e2..9bb544467d3f 100644 --- a/pkg/sql/logictest/testdata/logic_test/udf_plpgsql +++ b/pkg/sql/logictest/testdata/logic_test/udf_plpgsql @@ -1,3 +1,7 @@ +statement ok +CREATE TABLE xy (x INT, y INT); +INSERT INTO xy VALUES (1, 2), (3, 4); + statement ok CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$ BEGIN @@ -559,3 +563,271 @@ CREATE FUNCTION f_err(p1 RECORD) RETURNS RECORD AS $$ RETURN p1; END $$ LANGUAGE PLpgSQL; + +# Testing RAISE statements. +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE DEBUG 'foo'; + RAISE LOG 'foo'; + RAISE INFO 'foo'; + RAISE NOTICE 'foo'; + RAISE WARNING 'foo'; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query T noticetrace +SELECT f(); +---- +INFO: foo +NOTICE: foo +WARNING: foo + +statement ok +SET client_min_messages = 'debug1'; + +query T noticetrace +SELECT f(); +---- +DEBUG1: foo +LOG: foo +INFO: foo +NOTICE: foo +WARNING: foo + +statement ok +RESET client_min_messages; + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE NOTICE '%', 1; + RAISE NOTICE 'foo: %, %, %', 1, 2, 3; + RAISE NOTICE '%%'; + RAISE NOTICE '%%%', 1; + RAISE NOTICE '%%%foo%% bar%%%% %% %%%% ba%z%', 1, 2, 3; + RETURN 0; + END +$$ LANGUAGE PLpgSQL; + +query T noticetrace +SELECT f(); +---- +NOTICE: 1 +NOTICE: foo: 1, 2, 3 +NOTICE: % +NOTICE: %1 +NOTICE: %1foo% bar%% % %% ba2z3 + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE NOTICE division_by_zero; + RAISE NOTICE null_value_not_allowed; + RAISE NOTICE reading_sql_data_not_permitted; + RAISE NOTICE SQLSTATE '22012'; + RAISE NOTICE SQLSTATE '22004'; + RAISE NOTICE SQLSTATE '39004'; + RAISE NOTICE SQLSTATE '2F004'; + RAISE NOTICE SQLSTATE '38004'; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query T noticetrace +SELECT f(); +---- +NOTICE: division_by_zero +SQLSTATE: 22012 +NOTICE: null_value_not_allowed +SQLSTATE: 22004 +NOTICE: reading_sql_data_not_permitted +SQLSTATE: 2F004 +NOTICE: 22012 +SQLSTATE: 22012 +NOTICE: 22004 +SQLSTATE: 22004 +NOTICE: 39004 +SQLSTATE: 39004 +NOTICE: 2F004 +SQLSTATE: 2F004 +NOTICE: 38004 +SQLSTATE: 38004 + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE NOTICE USING MESSAGE = 'foo'; + RAISE NOTICE USING MESSAGE = format('%s %s!','Hello','World'); + RAISE NOTICE USING MESSAGE = 'foo', DETAIL = 'bar', HINT = 'baz'; + RAISE NOTICE 'foo' USING ERRCODE = 'division_by_zero'; + RAISE NOTICE 'foo' USING ERRCODE = '22012'; + -- If no message is specified, the error code is used. + RAISE NOTICE USING ERRCODE = 'division_by_zero'; + RETURN 0; + END +$$ LANGUAGE PLpgSQL; + +query T noticetrace +SELECT f(); +---- +NOTICE: foo +NOTICE: Hello World! +NOTICE: foo +DETAIL: bar +HINT: baz +NOTICE: foo +SQLSTATE: 22012 +NOTICE: foo +SQLSTATE: 22012 +NOTICE: division_by_zero +SQLSTATE: 22012 + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + i INT := 0; + BEGIN + RAISE NOTICE '1: i = %', i; + i := 100; + RAISE NOTICE '2: i = %', i; + i := (SELECT count(*) FROM xy); + RAISE NOTICE '3: i = %', i; + RAISE NOTICE 'max_x: %', (SELECT max(x) FROM xy); + return i; + END +$$ LANGUAGE PLpgSQL; + +query T noticetrace +SELECT f(); +---- +NOTICE: 1: i = 0 +NOTICE: 2: i = 100 +NOTICE: 3: i = 2 +NOTICE: max_x: 3 + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + i INT := 0; + BEGIN + LOOP + IF i >= 5 THEN EXIT; END IF; + RAISE NOTICE 'i = %', i; + i := i + 1; + END LOOP; + RAISE NOTICE 'finished with i = %', i; + RETURN 0; + END +$$ LANGUAGE PLpgSQL; + +query T noticetrace +SELECT f(); +---- +NOTICE: i = 0 +NOTICE: i = 1 +NOTICE: i = 2 +NOTICE: i = 3 +NOTICE: i = 4 +NOTICE: finished with i = 5 + +# Testing RAISE statement with EXCEPTION log level. +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION 'foo'; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query error pgcode P0001 pq: foo +SELECT f(); + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION division_by_zero; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query error pgcode 22012 pq: division_by_zero +SELECT f(); + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION SQLSTATE '22012'; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query error pgcode 22012 pq: 22012 +SELECT f(); + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + i INT := 0; + BEGIN + LOOP + IF i >= 5 THEN EXIT; END IF; + IF i = 3 THEN + RAISE EXCEPTION 'i = %', i; + END IF; + RAISE NOTICE 'i = %', i; + i := i + 1; + END LOOP; + RAISE NOTICE 'finished with i = %', i; + RETURN 0; + END +$$ LANGUAGE PLpgSQL; + +query error pgcode P0001 pq: i = 3 +SELECT f(); + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION USING ERRCODE = 'division_by_zero'; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query error pgcode 22012 pq: division_by_zero +SELECT f(); + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION USING ERRCODE = '22012'; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query error pgcode 22012 pq: 22012 +SELECT f(); + +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION USING DETAIL = 'use default errcode for the code and message'; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query error pgcode P0001 pq: P0001\nDETAIL: use default errcode for the code and message +SELECT f(); + +# The default level is ERROR. +statement ok +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE 'foo'; + return 0; + END +$$ LANGUAGE PLpgSQL; + +query error pgcode P0001 pq: foo +SELECT f(); diff --git a/pkg/sql/opt/memo/expr.go b/pkg/sql/opt/memo/expr.go index 94fce5aa2e1a..9731810c5be8 100644 --- a/pkg/sql/opt/memo/expr.go +++ b/pkg/sql/opt/memo/expr.go @@ -706,6 +706,12 @@ type UDFDefinition struct { // applies to direct as well as indirect recursive calls (mutual recursion). IsRecursive bool + // Params is the list of columns representing parameters of the function. The + // i-th column in the list corresponds to the i-th parameter of the function. + // During execution of the UDF, these columns are replaced with the arguments + // of the function invocation. + Params opt.ColList + // Body contains a relational expression for each statement in the function // body. It is unset during construction of a recursive UDF. Body []RelExpr @@ -714,12 +720,6 @@ type UDFDefinition struct { // should be optimized if it is rebuilt. Each props corresponds to the RelExpr // at the same position in Body. BodyProps []*physical.Required - - // Params is the list of columns representing parameters of the function. The - // i-th column in the list corresponds to the i-th parameter of the function. - // During execution of the UDF, these columns are replaced with the arguments - // of the function invocation. - Params opt.ColList } // WindowFrame denotes the definition of a window frame for an individual diff --git a/pkg/sql/opt/optbuilder/plpgsql.go b/pkg/sql/opt/optbuilder/plpgsql.go index 907dfc69ec10..006f965b97b0 100644 --- a/pkg/sql/opt/optbuilder/plpgsql.go +++ b/pkg/sql/opt/optbuilder/plpgsql.go @@ -12,12 +12,14 @@ package optbuilder import ( "fmt" + "strings" "github.com/cockroachdb/cockroach/pkg/sql/opt" "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" "github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" + "github.com/cockroachdb/cockroach/pkg/sql/sem/builtins/builtinsregistry" "github.com/cockroachdb/cockroach/pkg/sql/sem/plpgsqltree" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sem/volatility" @@ -345,6 +347,40 @@ func (b *plpgsqlBuilder) buildPLpgSQLStatements( } else { panic(pgerror.New(pgcode.Syntax, "CONTINUE cannot be used outside a loop")) } + case *plpgsqltree.PLpgSQLStmtRaise: + // RAISE statements allow the PLpgSQL function to send an error or a + // notice to the client. We handle these side effects by building them + // into a separate body statement that is only executed for its side + // effects. The remaining PLpgSQL statements then become the last body + // statement, which returns the actual result of evaluation. + // + // The synchronous notice sending behavior is implemented in the + // crdb_internal.plpgsql_raise builtin function. The side-effecting body + // statement just makes a call into crdb_internal.plpgsql_raise using the + // RAISE statement options as parameters. + con := b.makeContinuation("_stmt_raise") + const raiseFnName = "crdb_internal.plpgsql_raise" + props, overloads := builtinsregistry.GetBuiltinProperties(raiseFnName) + if len(overloads) != 1 { + panic(errors.AssertionFailedf("expected one overload for %s", raiseFnName)) + } + raiseCall := b.ob.factory.ConstructFunction( + b.getRaiseArgs(con.s, t), + &memo.FunctionPrivate{ + Name: raiseFnName, + Typ: types.Int, + Properties: props, + Overload: &overloads[0], + }, + ) + raiseColName := scopeColName("").WithMetadataName(b.makeIdentifier("stmt_raise")) + raiseScope := con.s.push() + b.ob.synthesizeColumn(raiseScope, raiseColName, types.Int, nil /* expr */, raiseCall) + b.ob.constructProjectForScope(con.s, raiseScope) + con.def.Body = []memo.RelExpr{raiseScope.expr} + con.def.BodyProps = []*physical.Required{raiseScope.makePhysicalProps()} + b.finishContinuation(stmts[i+1:], &con, false /* recursive */) + return b.callContinuation(&con, s) default: panic(unimplemented.New( "unimplemented PL/pgSQL statement", @@ -386,25 +422,138 @@ func (b *plpgsqlBuilder) addPLpgSQLAssign( return assignScope } -// makeContinuation allocates a new continuation function with an uninitialized -// definition. -func (b *plpgsqlBuilder) makeContinuation(name string) continuation { - return continuation{ - def: &memo.UDFDefinition{ - Name: b.makeIdentifier(name), - Typ: b.returnType, - CalledOnNullInput: true, - }, +// getRaiseArgs validates the options attached to the given PLpgSQL RAISE +// statement and returns the arguments to be used for a call to the +// crdb_internal.plpgsql_raise builtin function. +func (b *plpgsqlBuilder) getRaiseArgs( + s *scope, raise *plpgsqltree.PLpgSQLStmtRaise, +) memo.ScalarListExpr { + var severity, message, detail, hint, code opt.ScalarExpr + makeConstStr := func(str string) opt.ScalarExpr { + return b.ob.factory.ConstructConstVal(tree.NewDString(str), types.String) + } + // Retrieve the error/notice severity. + logLevel := strings.ToUpper(raise.LogLevel) + if logLevel == "" { + // EXCEPTION is the default log level. + logLevel = "EXCEPTION" + } + switch logLevel { + case "EXCEPTION": + // ERROR is the equivalent severity to log-level EXCEPTION. + severity = makeConstStr("ERROR") + case "LOG", "INFO", "NOTICE", "WARNING": + severity = makeConstStr(logLevel) + case "DEBUG": + // DEBUG log-level maps to severity DEBUG1. + severity = makeConstStr("DEBUG1") + default: + panic(unimplemented.Newf( + "unimplemented log level", "RAISE log level %s is not yet supported", raise.LogLevel, + )) + } + // Retrieve the message, if it was set with the format syntax. + if raise.Message != "" { + message = b.makeRaiseFormatMessage(s, raise.Message, raise.Params) + } + if raise.Code != "" { + code = makeConstStr(raise.Code) + } else if raise.CodeName != "" { + code = makeConstStr(raise.CodeName) } + // Retrieve the RAISE options, if any. + buildOptionExpr := func(name string, expr plpgsqltree.PLpgSQLExpr, isDup bool) opt.ScalarExpr { + if isDup { + panic(pgerror.Newf(pgcode.Syntax, "RAISE option already specified: %s", name)) + } + return b.buildPLpgSQLExpr(expr, types.String, s) + } + for _, option := range raise.Options { + optName := strings.ToUpper(option.OptType) + switch optName { + case "MESSAGE": + message = buildOptionExpr(optName, option.Expr, message != nil) + case "DETAIL": + detail = buildOptionExpr(optName, option.Expr, detail != nil) + case "HINT": + hint = buildOptionExpr(optName, option.Expr, hint != nil) + case "ERRCODE": + code = buildOptionExpr(optName, option.Expr, code != nil) + case "COLUMN", "CONSTRAINT", "DATATYPE", "TABLE", "SCHEMA": + panic(unimplemented.NewWithIssuef(106237, "RAISE option %s is not yet implemented", optName)) + default: + panic(errors.AssertionFailedf("unrecognized RAISE option: %s", option.OptType)) + } + } + if code == nil { + if logLevel == "EXCEPTION" { + // The default error code for EXCEPTION is ERRCODE_RAISE_EXCEPTION. + code = makeConstStr(pgcode.RaiseException.String()) + } else { + code = makeConstStr(pgcode.SuccessfulCompletion.String()) + } + } + // If no message text is supplied, use the error code or condition name. + if message == nil { + message = code + } + args := memo.ScalarListExpr{severity, message, detail, hint, code} + for i := range args { + if args[i] == nil { + args[i] = makeConstStr("") + } + } + return args } -// finishContinuation initializes the definition of a continuation function with -// the function body. It is separate from makeContinuation to allow recursive -// function definitions, which need to push the continuation before it is -// finished. -func (b *plpgsqlBuilder) finishContinuation( - stmts []plpgsqltree.PLpgSQLStatement, con *continuation, recursive bool, -) { +// A PLpgSQL RAISE statement can specify a format string, where supplied +// expressions replace instances of '%' in the string. A literal '%' character +// is specified by doubling it: '%%'. The formatting arguments can be arbitrary +// SQL expressions. +func (b *plpgsqlBuilder) makeRaiseFormatMessage( + s *scope, format string, args []plpgsqltree.PLpgSQLExpr, +) (result opt.ScalarExpr) { + makeConstStr := func(str string) opt.ScalarExpr { + return b.ob.factory.ConstructConstVal(tree.NewDString(str), types.String) + } + addToResult := func(expr opt.ScalarExpr) { + if result == nil { + result = expr + } else { + // Concatenate the previously built string with the current one. + result = b.ob.factory.ConstructConcat(result, expr) + } + } + // Split the format string on each pair of '%' characters; any '%' characters + // in the substrings are formatting parameters. + var argIdx int + for i, literalSubstr := range strings.Split(format, "%%") { + if i > 0 { + // Add the literal '%' character in place of the matched '%%'. + addToResult(makeConstStr("%")) + } + // Split on the parameter characters '%'. + for j, paramSubstr := range strings.Split(literalSubstr, "%") { + if j > 0 { + // Add the next argument at the location of this parameter. + if argIdx >= len(args) { + panic(pgerror.Newf(pgcode.PLpgSQL, "too few parameters specified for RAISE")) + } + addToResult(b.buildPLpgSQLExpr(args[argIdx], types.String, s)) + argIdx++ + } + addToResult(makeConstStr(paramSubstr)) + } + } + if argIdx < len(args) { + panic(pgerror.Newf(pgcode.PLpgSQL, "too many parameters specified for RAISE")) + } + return result +} + +// makeContinuation allocates a new continuation function with an uninitialized +// definition. +func (b *plpgsqlBuilder) makeContinuation(name string) continuation { s := b.ob.allocScope() b.ensureScopeHasExpr(s) params := make(opt.ColList, 0, len(b.decls)+len(b.params)) @@ -422,18 +571,42 @@ func (b *plpgsqlBuilder) finishContinuation( for _, param := range b.params { addParam(tree.Name(param.Name), param.Typ) } + return continuation{ + def: &memo.UDFDefinition{ + Params: params, + Name: b.makeIdentifier(name), + Typ: b.returnType, + CalledOnNullInput: true, + }, + s: s, + } +} + +// finishContinuation adds the final body statement to the definition of a +// continuation function. This statement returns the result of executing the +// given PLpgSQL statements. There may be other statements that are executed +// before this final statement for their side effects (e.g. RAISE statement). +// +// finishContinuation is separate from makeContinuation to allow recursive +// function definitions, which need to push the continuation before it is +// finished. +func (b *plpgsqlBuilder) finishContinuation( + stmts []plpgsqltree.PLpgSQLStatement, con *continuation, recursive bool, +) { // Make sure to push s before constructing the continuation scope to ensure // that the parameter columns are not projected. - continuationScope := b.buildPLpgSQLStatements(stmts, s.push()) + continuationScope := b.buildPLpgSQLStatements(stmts, con.s.push()) if continuationScope == nil { // One or more branches did not terminate with a RETURN statement. con.reachedEndOfFunction = true return } + // Append to the body statements because some PLpgSQL statements will make a + // continuation routine with more than one body statement in order to handle + // side effects (see the RAISE case in buildPLpgSQLStatements). + con.def.Body = append(con.def.Body, continuationScope.expr) + con.def.BodyProps = append(con.def.BodyProps, continuationScope.makePhysicalProps()) con.def.IsRecursive = recursive - con.def.Body = []memo.RelExpr{continuationScope.expr} - con.def.BodyProps = []*physical.Required{continuationScope.makePhysicalProps()} - con.def.Params = params // Set the volatility of the continuation routine to the least restrictive // volatility level in the expression's Relational properties. vol := continuationScope.expr.Relational().VolatilitySet @@ -513,6 +686,10 @@ type continuation struct { // from a branch in the control flow. def *memo.UDFDefinition + // s is a scope initialized with the parameters of the routine. It should be + // used to construct the routine body statement. + s *scope + // isLoopContinuation indicates that this continuation was constructed for the // body statements of a loop. isLoopContinuation bool diff --git a/pkg/sql/opt/optbuilder/testdata/udf_plpgsql b/pkg/sql/opt/optbuilder/testdata/udf_plpgsql index 0aa9bb808e01..c86e57693c3a 100644 --- a/pkg/sql/opt/optbuilder/testdata/udf_plpgsql +++ b/pkg/sql/opt/optbuilder/testdata/udf_plpgsql @@ -1,3 +1,7 @@ +exec-ddl +CREATE TABLE xy (x INT, y INT); +---- + exec-ddl CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$ BEGIN @@ -1538,3 +1542,1540 @@ project │ │ └── variable: b:39 │ └── recursive-call └── const: 1 + +# TODO(drewk): consider adding a norm rules to fold nested UDFs. +# Testing RAISE statements. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE DEBUG 'foo'; + RAISE LOG 'foo'; + RAISE INFO 'foo'; + RAISE NOTICE 'foo'; + RAISE WARNING 'foo'; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:12 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:12] + └── body + └── limit + ├── columns: "_stmt_raise_1":11 + ├── project + │ ├── columns: "_stmt_raise_1":11 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":11] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'DEBUG1' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_3":10 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":10] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_4:2 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_4:2] + │ │ ├── const: 'LOG' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_5":9 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":9] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_6:3 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_6:3] + │ │ ├── const: 'INFO' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_7":8 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":8] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_8:4 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_8:4] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_9":7 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":7] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_10:5 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_10:5] + │ │ ├── const: 'WARNING' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: stmt_return_11:6!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_11:6] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE NOTICE '%', 1; + RAISE NOTICE 'foo: %, %, %', 1, 2, 3; + RAISE NOTICE '%%'; + RAISE NOTICE '%%%', 1; + RAISE NOTICE '%%%foo%% bar%%%% %% %%%% ba%z%', 1, 2, 3; + RETURN 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:12 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:12] + └── body + └── limit + ├── columns: "_stmt_raise_1":11 + ├── project + │ ├── columns: "_stmt_raise_1":11 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":11] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── const: '' + │ │ │ │ └── const: 1 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_3":10 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":10] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_4:2 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_4:2] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── concat + │ │ │ │ │ ├── concat + │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ ├── const: 'foo: ' + │ │ │ │ │ │ │ │ └── const: 1 + │ │ │ │ │ │ │ └── const: ', ' + │ │ │ │ │ │ └── const: 2 + │ │ │ │ │ └── const: ', ' + │ │ │ │ └── const: 3 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_5":9 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":9] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_6:3 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_6:3] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── const: '' + │ │ │ │ └── const: '%' + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_7":8 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":8] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_8:4 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_8:4] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── concat + │ │ │ │ │ ├── concat + │ │ │ │ │ │ ├── const: '' + │ │ │ │ │ │ └── const: '%' + │ │ │ │ │ └── const: '' + │ │ │ │ └── const: 1 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_9":7 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":7] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_10:5 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_10:5] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── concat + │ │ │ │ │ ├── concat + │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── concat + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── const: '' + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: '%' + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: '' + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: 1 + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: 'foo' + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: '%' + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: ' bar' + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: '%' + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: '' + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: '%' + │ │ │ │ │ │ │ │ │ │ │ │ │ └── const: ' ' + │ │ │ │ │ │ │ │ │ │ │ │ └── const: '%' + │ │ │ │ │ │ │ │ │ │ │ └── const: ' ' + │ │ │ │ │ │ │ │ │ │ └── const: '%' + │ │ │ │ │ │ │ │ │ └── const: '' + │ │ │ │ │ │ │ │ └── const: '%' + │ │ │ │ │ │ │ └── const: ' ba' + │ │ │ │ │ │ └── const: 2 + │ │ │ │ │ └── const: 'z' + │ │ │ │ └── const: 3 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: stmt_return_11:6!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_11:6] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE NOTICE division_by_zero; + RAISE NOTICE null_value_not_allowed; + RAISE NOTICE reading_sql_data_not_permitted; + RAISE NOTICE SQLSTATE '22012'; + RAISE NOTICE SQLSTATE '22004'; + RAISE NOTICE SQLSTATE '39004'; + RAISE NOTICE SQLSTATE '2F004'; + RAISE NOTICE SQLSTATE '38004'; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:18 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:18] + └── body + └── limit + ├── columns: "_stmt_raise_1":17 + ├── project + │ ├── columns: "_stmt_raise_1":17 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":17] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'division_by_zero' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'division_by_zero' + │ └── project + │ ├── columns: "_stmt_raise_3":16 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":16] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_4:2 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_4:2] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'null_value_not_allowed' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'null_value_not_allowed' + │ └── project + │ ├── columns: "_stmt_raise_5":15 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":15] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_6:3 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_6:3] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'reading_sql_data_not_permitted' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'reading_sql_data_not_permitted' + │ └── project + │ ├── columns: "_stmt_raise_7":14 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":14] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_8:4 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_8:4] + │ │ ├── const: 'NOTICE' + │ │ ├── const: '22012' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '22012' + │ └── project + │ ├── columns: "_stmt_raise_9":13 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":13] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_10:5 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_10:5] + │ │ ├── const: 'NOTICE' + │ │ ├── const: '22004' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '22004' + │ └── project + │ ├── columns: "_stmt_raise_11":12 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_11 [as="_stmt_raise_11":12] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_12:6 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_12:6] + │ │ ├── const: 'NOTICE' + │ │ ├── const: '39004' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '39004' + │ └── project + │ ├── columns: "_stmt_raise_13":11 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_13 [as="_stmt_raise_13":11] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_14:7 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_14:7] + │ │ ├── const: 'NOTICE' + │ │ ├── const: '2F004' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '2F004' + │ └── project + │ ├── columns: "_stmt_raise_15":10 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_15 [as="_stmt_raise_15":10] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_16:8 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_16:8] + │ │ ├── const: 'NOTICE' + │ │ ├── const: '38004' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '38004' + │ └── project + │ ├── columns: stmt_return_17:9!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_17:9] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE NOTICE USING MESSAGE = 'foo'; + RAISE NOTICE USING MESSAGE = format('%s %s!','Hello','World'); + RAISE NOTICE USING MESSAGE = 'foo', DETAIL = 'bar', HINT = 'baz'; + RAISE NOTICE 'foo' USING ERRCODE = 'division_by_zero'; + RAISE NOTICE 'foo' USING ERRCODE = '22012'; + -- If no message is specified, the error code is used. + RAISE NOTICE USING ERRCODE = 'division_by_zero'; + RETURN 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:14 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:14] + └── body + └── limit + ├── columns: "_stmt_raise_1":13 + ├── project + │ ├── columns: "_stmt_raise_1":13 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":13] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_3":12 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":12] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_4:2 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_4:2] + │ │ ├── const: 'NOTICE' + │ │ ├── function: format + │ │ │ ├── const: '%s %s!' + │ │ │ ├── const: 'Hello' + │ │ │ └── const: 'World' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_5":11 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":11] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_6:3 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_6:3] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'foo' + │ │ ├── const: 'bar' + │ │ ├── const: 'baz' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_7":10 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":10] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_8:4 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_8:4] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'division_by_zero' + │ └── project + │ ├── columns: "_stmt_raise_9":9 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_9 [as="_stmt_raise_9":9] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_10:5 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_10:5] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '22012' + │ └── project + │ ├── columns: "_stmt_raise_11":8 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_11 [as="_stmt_raise_11":8] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_12:6 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_12:6] + │ │ ├── const: 'NOTICE' + │ │ ├── const: 'division_by_zero' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'division_by_zero' + │ └── project + │ ├── columns: stmt_return_13:7!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_13:7] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + i INT := 0; + BEGIN + RAISE NOTICE '1: i = %', i; + i := 100; + RAISE NOTICE '2: i = %', i; + i := (SELECT count(*) FROM xy); + RAISE NOTICE '3: i = %', i; + RAISE NOTICE 'max_x: %', (SELECT max(x) FROM xy); + return i; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:29 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:29] + └── body + └── limit + ├── columns: "_stmt_raise_1":28 + ├── project + │ ├── columns: "_stmt_raise_1":28 + │ ├── project + │ │ ├── columns: i:1!null + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── const: 0 [as=i:1] + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":28] + │ ├── args + │ │ └── variable: i:1 + │ ├── params: i:2 + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:3 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:3] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── const: '1: i = ' + │ │ │ │ └── variable: i:2 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_3":27 + │ ├── project + │ │ ├── columns: i:4!null + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── const: 100 [as=i:4] + │ └── projections + │ └── udf: _stmt_raise_3 [as="_stmt_raise_3":27] + │ ├── args + │ │ └── variable: i:4 + │ ├── params: i:5 + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_4:6 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_4:6] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── const: '2: i = ' + │ │ │ │ └── variable: i:5 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_5":26 + │ ├── project + │ │ ├── columns: i:13 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── subquery [as=i:13] + │ │ └── max1-row + │ │ ├── columns: count_rows:12!null + │ │ └── scalar-group-by + │ │ ├── columns: count_rows:12!null + │ │ ├── project + │ │ │ └── scan xy + │ │ │ └── columns: x:7 y:8 rowid:9!null crdb_internal_mvcc_timestamp:10 tableoid:11 + │ │ └── aggregations + │ │ └── count-rows [as=count_rows:12] + │ └── projections + │ └── udf: _stmt_raise_5 [as="_stmt_raise_5":26] + │ ├── args + │ │ └── variable: i:13 + │ ├── params: i:14 + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_6:15 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_6:15] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── const: '3: i = ' + │ │ │ │ └── variable: i:14 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: "_stmt_raise_7":25 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":25] + │ ├── args + │ │ └── variable: i:14 + │ ├── params: i:16 + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_8:23 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_8:23] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── const: 'max_x: ' + │ │ │ │ └── subquery + │ │ │ │ └── max1-row + │ │ │ │ ├── columns: max:22 + │ │ │ │ └── scalar-group-by + │ │ │ │ ├── columns: max:22 + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: x:17 + │ │ │ │ │ └── scan xy + │ │ │ │ │ └── columns: x:17 y:18 rowid:19!null crdb_internal_mvcc_timestamp:20 tableoid:21 + │ │ │ │ └── aggregations + │ │ │ │ └── max [as=max:22] + │ │ │ │ └── variable: x:17 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: stmt_return_9:24 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── variable: i:16 [as=stmt_return_9:24] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + i INT := 0; + BEGIN + LOOP + IF i >= 5 THEN EXIT; END IF; + RAISE NOTICE 'i = %', i; + i := i + 1; + END LOOP; + RAISE NOTICE 'finished with i = %', i; + RETURN 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:18 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:18] + └── body + └── limit + ├── columns: stmt_loop_5:17 + ├── project + │ ├── columns: stmt_loop_5:17 + │ ├── project + │ │ ├── columns: i:1!null + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── const: 0 [as=i:1] + │ └── projections + │ └── udf: stmt_loop_5 [as=stmt_loop_5:17] + │ ├── args + │ │ └── variable: i:1 + │ ├── params: i:7 + │ └── body + │ └── project + │ ├── columns: stmt_if_9:16 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── case [as=stmt_if_9:16] + │ ├── true + │ ├── when + │ │ ├── ge + │ │ │ ├── variable: i:7 + │ │ │ └── const: 5 + │ │ └── subquery + │ │ └── project + │ │ ├── columns: loop_exit_1:14 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── udf: loop_exit_1 [as=loop_exit_1:14] + │ │ ├── args + │ │ │ └── variable: i:7 + │ │ ├── params: i:2 + │ │ └── body + │ │ └── project + │ │ ├── columns: "_stmt_raise_2":6 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── udf: _stmt_raise_2 [as="_stmt_raise_2":6] + │ │ ├── args + │ │ │ └── variable: i:2 + │ │ ├── params: i:3 + │ │ └── body + │ │ ├── project + │ │ │ ├── columns: stmt_raise_3:4 + │ │ │ ├── values + │ │ │ │ └── tuple + │ │ │ └── projections + │ │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_3:4] + │ │ │ ├── const: 'NOTICE' + │ │ │ ├── concat + │ │ │ │ ├── concat + │ │ │ │ │ ├── const: 'finished with i = ' + │ │ │ │ │ └── variable: i:3 + │ │ │ │ └── const: '' + │ │ │ ├── const: '' + │ │ │ ├── const: '' + │ │ │ └── const: '00000' + │ │ └── project + │ │ ├── columns: stmt_return_4:5!null + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── const: 0 [as=stmt_return_4:5] + │ └── subquery + │ └── project + │ ├── columns: stmt_if_6:15 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: stmt_if_6 [as=stmt_if_6:15] + │ ├── args + │ │ └── variable: i:7 + │ ├── params: i:8 + │ └── body + │ └── project + │ ├── columns: "_stmt_raise_7":13 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_7 [as="_stmt_raise_7":13] + │ ├── args + │ │ └── variable: i:8 + │ ├── params: i:9 + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_8:10 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_8:10] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── const: 'i = ' + │ │ │ │ └── variable: i:9 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: stmt_loop_5:12 + │ ├── project + │ │ ├── columns: i:11 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── plus [as=i:11] + │ │ ├── variable: i:9 + │ │ └── const: 1 + │ └── projections + │ └── udf: stmt_loop_5 [as=stmt_loop_5:12] + │ ├── args + │ │ └── variable: i:11 + │ └── recursive-call + └── const: 1 + +# Testing RAISE statement with EXCEPTION log level. +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION 'foo'; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:4 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:4] + └── body + └── limit + ├── columns: "_stmt_raise_1":3 + ├── project + │ ├── columns: "_stmt_raise_1":3 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":3] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'ERROR' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'P0001' + │ └── project + │ ├── columns: stmt_return_3:2!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_3:2] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION division_by_zero; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:4 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:4] + └── body + └── limit + ├── columns: "_stmt_raise_1":3 + ├── project + │ ├── columns: "_stmt_raise_1":3 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":3] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'ERROR' + │ │ ├── const: 'division_by_zero' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'division_by_zero' + │ └── project + │ ├── columns: stmt_return_3:2!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_3:2] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION SQLSTATE '22012'; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:4 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:4] + └── body + └── limit + ├── columns: "_stmt_raise_1":3 + ├── project + │ ├── columns: "_stmt_raise_1":3 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":3] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'ERROR' + │ │ ├── const: '22012' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '22012' + │ └── project + │ ├── columns: stmt_return_3:2!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_3:2] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + DECLARE + i INT := 0; + BEGIN + LOOP + IF i >= 5 THEN EXIT; END IF; + IF i = 3 THEN + RAISE EXCEPTION 'i = %', i; + END IF; + RAISE NOTICE 'i = %', i; + i := i + 1; + END LOOP; + RAISE NOTICE 'finished with i = %', i; + RETURN 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:25 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:25] + └── body + └── limit + ├── columns: stmt_loop_5:24 + ├── project + │ ├── columns: stmt_loop_5:24 + │ ├── project + │ │ ├── columns: i:1!null + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── const: 0 [as=i:1] + │ └── projections + │ └── udf: stmt_loop_5 [as=stmt_loop_5:24] + │ ├── args + │ │ └── variable: i:1 + │ ├── params: i:7 + │ └── body + │ └── project + │ ├── columns: stmt_if_13:23 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── case [as=stmt_if_13:23] + │ ├── true + │ ├── when + │ │ ├── ge + │ │ │ ├── variable: i:7 + │ │ │ └── const: 5 + │ │ └── subquery + │ │ └── project + │ │ ├── columns: loop_exit_1:21 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── udf: loop_exit_1 [as=loop_exit_1:21] + │ │ ├── args + │ │ │ └── variable: i:7 + │ │ ├── params: i:2 + │ │ └── body + │ │ └── project + │ │ ├── columns: "_stmt_raise_2":6 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── udf: _stmt_raise_2 [as="_stmt_raise_2":6] + │ │ ├── args + │ │ │ └── variable: i:2 + │ │ ├── params: i:3 + │ │ └── body + │ │ ├── project + │ │ │ ├── columns: stmt_raise_3:4 + │ │ │ ├── values + │ │ │ │ └── tuple + │ │ │ └── projections + │ │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_3:4] + │ │ │ ├── const: 'NOTICE' + │ │ │ ├── concat + │ │ │ │ ├── concat + │ │ │ │ │ ├── const: 'finished with i = ' + │ │ │ │ │ └── variable: i:3 + │ │ │ │ └── const: '' + │ │ │ ├── const: '' + │ │ │ ├── const: '' + │ │ │ └── const: '00000' + │ │ └── project + │ │ ├── columns: stmt_return_4:5!null + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── const: 0 [as=stmt_return_4:5] + │ └── subquery + │ └── project + │ ├── columns: stmt_if_6:22 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: stmt_if_6 [as=stmt_if_6:22] + │ ├── args + │ │ └── variable: i:7 + │ ├── params: i:8 + │ └── body + │ └── project + │ ├── columns: stmt_if_12:20 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── case [as=stmt_if_12:20] + │ ├── true + │ ├── when + │ │ ├── eq + │ │ │ ├── variable: i:8 + │ │ │ └── const: 3 + │ │ └── subquery + │ │ └── project + │ │ ├── columns: "_stmt_raise_10":18 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── udf: _stmt_raise_10 [as="_stmt_raise_10":18] + │ │ ├── args + │ │ │ └── variable: i:8 + │ │ ├── params: i:15 + │ │ └── body + │ │ ├── project + │ │ │ ├── columns: stmt_raise_11:16 + │ │ │ ├── values + │ │ │ │ └── tuple + │ │ │ └── projections + │ │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_11:16] + │ │ │ ├── const: 'ERROR' + │ │ │ ├── concat + │ │ │ │ ├── concat + │ │ │ │ │ ├── const: 'i = ' + │ │ │ │ │ └── variable: i:15 + │ │ │ │ └── const: '' + │ │ │ ├── const: '' + │ │ │ ├── const: '' + │ │ │ └── const: 'P0001' + │ │ └── project + │ │ ├── columns: stmt_if_7:17 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── udf: stmt_if_7 [as=stmt_if_7:17] + │ │ ├── args + │ │ │ └── variable: i:15 + │ │ ├── params: i:9 + │ │ └── body + │ │ └── project + │ │ ├── columns: "_stmt_raise_8":14 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── udf: _stmt_raise_8 [as="_stmt_raise_8":14] + │ │ ├── args + │ │ │ └── variable: i:9 + │ │ ├── params: i:10 + │ │ └── body + │ │ ├── project + │ │ │ ├── columns: stmt_raise_9:11 + │ │ │ ├── values + │ │ │ │ └── tuple + │ │ │ └── projections + │ │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_9:11] + │ │ │ ├── const: 'NOTICE' + │ │ │ ├── concat + │ │ │ │ ├── concat + │ │ │ │ │ ├── const: 'i = ' + │ │ │ │ │ └── variable: i:10 + │ │ │ │ └── const: '' + │ │ │ ├── const: '' + │ │ │ ├── const: '' + │ │ │ └── const: '00000' + │ │ └── project + │ │ ├── columns: stmt_loop_5:13 + │ │ ├── project + │ │ │ ├── columns: i:12 + │ │ │ ├── values + │ │ │ │ └── tuple + │ │ │ └── projections + │ │ │ └── plus [as=i:12] + │ │ │ ├── variable: i:10 + │ │ │ └── const: 1 + │ │ └── projections + │ │ └── udf: stmt_loop_5 [as=stmt_loop_5:13] + │ │ ├── args + │ │ │ └── variable: i:12 + │ │ └── recursive-call + │ └── subquery + │ └── project + │ ├── columns: stmt_if_7:19 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: stmt_if_7 [as=stmt_if_7:19] + │ ├── args + │ │ └── variable: i:8 + │ ├── params: i:9 + │ └── body + │ └── project + │ ├── columns: "_stmt_raise_8":14 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_8 [as="_stmt_raise_8":14] + │ ├── args + │ │ └── variable: i:9 + │ ├── params: i:10 + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_9:11 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_9:11] + │ │ ├── const: 'NOTICE' + │ │ ├── concat + │ │ │ ├── concat + │ │ │ │ ├── const: 'i = ' + │ │ │ │ └── variable: i:10 + │ │ │ └── const: '' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '00000' + │ └── project + │ ├── columns: stmt_loop_5:13 + │ ├── project + │ │ ├── columns: i:12 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── plus [as=i:12] + │ │ ├── variable: i:10 + │ │ └── const: 1 + │ └── projections + │ └── udf: stmt_loop_5 [as=stmt_loop_5:13] + │ ├── args + │ │ └── variable: i:12 + │ └── recursive-call + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION USING ERRCODE = 'division_by_zero'; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:4 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:4] + └── body + └── limit + ├── columns: "_stmt_raise_1":3 + ├── project + │ ├── columns: "_stmt_raise_1":3 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":3] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'ERROR' + │ │ ├── const: 'division_by_zero' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'division_by_zero' + │ └── project + │ ├── columns: stmt_return_3:2!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_3:2] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION USING ERRCODE = '22012'; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:4 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:4] + └── body + └── limit + ├── columns: "_stmt_raise_1":3 + ├── project + │ ├── columns: "_stmt_raise_1":3 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":3] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'ERROR' + │ │ ├── const: '22012' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: '22012' + │ └── project + │ ├── columns: stmt_return_3:2!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_3:2] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE EXCEPTION USING DETAIL = 'use default errcode for the code and message'; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:4 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:4] + └── body + └── limit + ├── columns: "_stmt_raise_1":3 + ├── project + │ ├── columns: "_stmt_raise_1":3 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":3] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'ERROR' + │ │ ├── const: 'P0001' + │ │ ├── const: 'use default errcode for the code and message' + │ │ ├── const: '' + │ │ └── const: 'P0001' + │ └── project + │ ├── columns: stmt_return_3:2!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_3:2] + └── const: 1 + +exec-ddl +CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$ + BEGIN + RAISE 'foo'; + return 0; + END +$$ LANGUAGE PLpgSQL; +---- + +build format=show-scalars +SELECT f(); +---- +project + ├── columns: f:4 + ├── values + │ └── tuple + └── projections + └── udf: f [as=f:4] + └── body + └── limit + ├── columns: "_stmt_raise_1":3 + ├── project + │ ├── columns: "_stmt_raise_1":3 + │ ├── values + │ │ └── tuple + │ └── projections + │ └── udf: _stmt_raise_1 [as="_stmt_raise_1":3] + │ └── body + │ ├── project + │ │ ├── columns: stmt_raise_2:1 + │ │ ├── values + │ │ │ └── tuple + │ │ └── projections + │ │ └── function: crdb_internal.plpgsql_raise [as=stmt_raise_2:1] + │ │ ├── const: 'ERROR' + │ │ ├── const: 'foo' + │ │ ├── const: '' + │ │ ├── const: '' + │ │ └── const: 'P0001' + │ └── project + │ ├── columns: stmt_return_3:2!null + │ ├── values + │ │ └── tuple + │ └── projections + │ └── const: 0 [as=stmt_return_3:2] + └── const: 1