Project: YugabyteDB (App Definition and Development / Database)
File: exception-section.md

title: PL/pgSQL exception section [YSQL] headerTitle: The PL/pgSQL exception section linkTitle: Exception section description: Describes the syntax and semantics of the PL/pgSQL exception section. [YSQL]. menu: stable: identifier: exception-section parent: plpgsql-syntax-and-semantics weight: 30 type: docs showRightNav: true {{%ebnf%}} plpgsqlexceptionsection, plpgsql_handler, plpgsqlhandlercondition {{%/ebnf%}} The plpgsqlexceptionsection (or "exception section" in prose) contains one or more so-called handlers. Briefly, when an error occurs in PL/pgSQL code, or in a SQL statement that it issues dynamically, this manifests as an exception. Then the point of execution immediately moves to a handler that matches the exception in the exception section of the most-tightly-enclosing . (When this happens, the exception is said to have been caught.) See , below, for the full account. The term of art error is used to characterize the outcome when the attempt to execute a SQL statement fails. Any putative SQL statement can, of course, cause a syntax error. And almost all syntactically correct SQL statements have the capacity to cause a semantic error. (Only a few degenerate special cases, like "select 1" cannot cause an error.) When you use psql (or ysqlsh) a SQL error is reported, on stderr, in general using several lines of textthe first of which starts with the word ERROR. Various facts about the error are available and you can control the volume of the report with the psql variable VERBOSITY. The legal values are default, terse, and verbose and you specify your choice with the \\set meta-command. You can see the effect of the VERBOSITY choice with a trivial example that provokes a run-time error: ```plpgsql \set VERBOSITY default select 1.0/0.0; ``` It causes this output: ```output ERROR: division by zero ``` When you attempt the same statement with the verbose choice, you get this output: ```output ERROR: 22012: division by zero LOCATION: int4div, int.c:820 ``` Various client-side tools (for example, Python with ) can be used to connect to a database in a PostgreSQL cluster or in a YugabyteDB cluster. Each has its own way of reporting when a SQL statement causes an error and, then, giving access to the error code and to other information about the context of the error. PL/pgSQL also has corresponding mechanisms. (But language sql subprograms have no such mechanism.) When a SQL error occurs during the execution of a PL/pgSQL program, it manifests as a so-called exception. Notice that every PL/pgSQL expression is evaluated by using an under-the-covers select statement. See the section . It explains that, for example, the simple assignment a := b + c;" is executed by using "select $1 + $2", binding in the current values of a and b. This means that the 22012 error illustrated above, and countless other errors can occur, and manifest as an exception, while a is executing. This subsection explains how errors can occur and how they can be handled by PL/pgSQL code. This discussion concerns only run-time errors. PL/pgSQL syntax errors most commonly occur when one of these SQL statements is issued at top level: the statement the statement the statement (during its syntax analysis phase) However, PL/pgSQL supports the execute statement (see the rule)which can be used only in the or the . This allows the text of any SQL statement to be defined dynamically and executed from PL/pgSQL

In such a case, a PL/pgSQL syntax error that a dynamically executed create procedure, create function or do statement causes manifests as a run-time error occurring in the plpgsqlexecutablesection, or plpgsqlexceptionsection, from which the DDL is submitted. An error can therefore occur in any one of these sections: the declaration sectionin the evaluation of an expression or in the attempt to install the value that the expression computes into the declared variable the executable sectionduring the execution of any the exception sectionduring the execution of any It helps, first, to define the term top-level PL/pgSQL block statement. It's this that defines the implementation for a user-defined PL/pgSQL subprogram and for a do statement. Because the is a kind of , the top-level PL/pgSQL block statement may contain arbitrarily many child PL/pgSQL block statements, to arbitrary depth, in a hierarchy. When an error occurs, it manifests as an exception. An exception is identified (under the covers) by the error code of the error that caused it. The current execution flow is aborted and the search begins for a so-called matching handler (see the rule). Here "matching" means that one (or more) of the handler's conditions specifies the error code of the to-be-handled exception. The condition might specify the error code explicitly by using the keyword sqlstate. Or it might specify it indirectly by using the identifier for the name of the exception. See the section in the PostgreSQL documentation. It shows pairs of mappings between an error code and its corresponding name. Condition names are interpreted case-insensitively. The special catch-all condition, denoted by the bare others keyword, matches every possible error except error 'P0004' (assertfailure) and error '57014' (querycanceled). Notice that it is possible, but generally unwise, to catch these two errors explicitly. See the section . When an error occurs in the executable section of a PL/pgSQL block statement, the search starts in that block's exception section (if present). If no match is found, then the search continues in the exception section (if present) of the containing PL/pgSQL block statement (if this exists). When an error occurs in the declaration section or the exception section of a PL/pgSQL block statement, the search starts in the exception section (if present) of the containing PL/pgSQL block statement (if this exists). Again, if no match is found, then the search continues in the exception section (if present) of the containing PL/pgSQL block statement (if this exists). If the search fails all the way up through the (possible) block hierarchy and fails in the top-level PL/pgSQL block statement, then the exception remains unhandled and escapes to the calling environment as the SQL error to which it corresponds. In the case that the calling environment is a PL/pgSQL block statement in a separate PL/pgSQL subprogram, or in a do statement, the in-flight exception continues as such and the search starts anew in the PL/pgSQL block statement from which the PL/pgSQL subprogram, or do statement, where the original error occurred was invoked. Once a matching handler is found, the exception is defined to be handled (or caught) and the entire effect of the present block statement's executable section is rolled back. See the section . The scope of the rollback includes all statically nested block statements, together with the effect of all block statements that might have been called

If the search for a handler fails even up through the top-level PL/pgSQL block statement of the subprogram or do statement that the client code invoked, then the error escapes to the client code. The executable statements that define the handler are executed in the normal way and then normal execution continues after the end of the in whose exception section the handler is foundjust as it would as if that block's executable section had ended normally. Here's a trivial example. Use any convenient sandbox database and a role that has no special attributes and that has create and connect on that database. Do this first: ```plpgsql \set db <the sandbox database> \set u <the ordinary role> ``` Ensure a clean start: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; ``` Create a trivial function: ```plpgsql create function s.f() returns text set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare a int; begin a := 1.0/0.0; return a::text; exception when sqlstate '22012' then return 'Caught "Error 22012".'; end; $body$; ``` Execute it: ```plpgsql select s.f(); ``` The create function statement completes without error. And then select s.f() completes without error, returning this result: ```output Caught "Error 22012". ``` Notice that this handler: ```plpgsql when divisionbyzero or sqlstate '2200F' or others then... ``` Because the others condition will handle any exception, the two preceding conditions, divisionbyzero and sqlstate '2200F', are redundantand therefore will confuse the reader about the code author's intention. Here's a more elaborate example: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; create type s.rec as(outcome text, a numeric, b text); ``` Create a function that will be called by a second function: ```plpgsql create function s.callee(mode in text) returns s.rec set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare r s.rec := ('OK', -1, '-')::s.rec; x numeric := 1.0; y varchar(4) := 'abcd'; z varchar(4); begin begin case mode when 'divisionbyzero' then x := x/0.0; when 'stringdataright_truncation' then z := y||'e'::varchar(4); else x := x/2.0; z := 'Nice'; end case; exception when divisionbyzero then r.outcome := 'Caught "divisionbyzero" in "s.callee()".'; return r; end; r.a := x::text; r.b := z::text; return r; end; $body$; ``` Create a function to call the callee function: ```plpgsql create function s.caller(mode in text) returns s.rec set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare r s.rec := ('OK', -1, '-')::s.rec; begin r := s.callee(mode); return r; exception when stringdataright_truncation then r.outcome := 'Caught "stringdataright_truncation" in "s.caller()".'; return r; end; $body$; ``` Invoke the caller without provoking an error: ```plpgsql with c(rec) as (select s.caller('benign')) select (c.rec).outcome, (c.rec).a::text, (c.rec).b from c; ``` This is the result: ```output outcome | a | b ++ OK | 0.50000000000000000000 | Nice ``` Now provoke an error that will be handled in the callee: ```plpgsql with c(rec) as (select s.caller('divisionbyzero')) select (c.rec).outcome, (c.rec).a::text, (c.rec).b from c; ``` This is the result: ```output outcome | a | b --+-+ Caught "divisionbyzero" in "s.callee()". | -1 | - ``` And now provoke an error for which there's no handler in the callee function and which escapes to the caller to be handled: ```plpgsql with c(rec) as (select s.caller('stringdataright_truncation')) select (c.rec).outcome, (c.rec).a::text, (c.rec).b from c; ``` This is the result: ```output outcome | a | b --+-+ Caught "stringdataright_truncation" in

| -1 | - ``` When the point of execution enters a PL/pgSQL block statement, at any level of nesting, the runtime system detects whether or not it has an exception sectionand if it does (and only in that case), then it automatically creates an internally named savepoint at that moment. If an error occurs in the block's executable section, or if an unhandled error escapes from a subprogram or a do statement that it invokes, so that the point of execution enters the exception section, and if a matching handler is found, then the runtime system issues a rollback to that internally named savepointand the handler code is executed. Then normal execution continues after the present block statement's end. But if no error occurs in the block's executable section, or if an error occurs but no matching handler is found, then the runtime system releases that internally named savepointand the search for a handler continues as described in the previous section. Of course, if the search for a handler fails all the way up through the top-level PL/pgSQL block statement that defines the subprogram or do statement that was the object of the top-level server call, then the entire effect of the server call is rolled back and the error escapes to the calling client program. {{< note title="Performance implications for a block statement that has an exception section" >}} The PostgreSQL documentation, in the subsection of the section within the , includes this note (slightly paraphrased): A block statement that has an exception section is significantly more expensive to enter and exit than a block without one. Therefore, don't write an exception section unless you need it. The putative cost is due to the creation of the savepoint and the subsequent release of, or rollback to, the savepoint. There has been some discussion about this advice on the pgsql-general email list. See, for example, and . This, from acknowledged expert Tom Lane, best summarises the responses: The doc note is just there to let people know that it isn't free; not to scare people away from using it at all. It helps to distinguish between two kinds of error: Expected (but regrettable) errors. Consider a graphical interface that invites the user to register to use an application. Very commonly, the user has to invent a nickname. This might well collide with an existing nickname; and the usual response is a friendly message that explains the problem, in end-user terminology, followed by an invitation to choose a different nickname. The collision is detected, under the covers, by the violation of a unique constraint on the nickname column. The standard good practice is to write the SQL statement that inserts the row with the potentially colliding nickname in a tightly enclosing block-statement that has an exception section with a handler for the when uniqueviolation_ error. This handler recognizes the problem as exactly the expected regrettable outcome that it is and then signals the client code accordingly so that it can usefully inform the user and suggest trying again. The rationale is that it's most reliable to handle the error exactly where it occurs, when all the context information is

If, instead, the error is allowed to bubble up and to escape to the client code (or even to an exception handler at the end of the top-level PL/pgSQL block statement that implements the subprogram or do statement that the client code invoked), then the analysis task is harder and less reliable because there might be many other cases, during the execution of the entire server call, where a uniqueviolation_ might occur. You can read Tom Lane's advice to mean that this pattern of exception handler use is perfectly fine. Unexpected errors: An example of such an error would occur if an attempt is made to change the content of a table that an administrator has carelessly (and recently) set to be read-only. Some diagnostic information about an erroring SQL statement is available only in the tightly-enclosing local context from which the statement is issued. For example, actual values that are bound to the statement for execution are no longer available where the block in which they are declared goes out of scope. A maximally defensive approach would be to issue every single SQL statement from its own dedicated tightly enclosing block statement and to provide a when others handler that assembles all available context information into, say, a jsonb value. This implies that every single subprogram must have an out argument to return this value to the caller. The handler must also use the bare raise statement (see the section ) when the error report value has been created to stop execution simply blundering on. However, this approach would lead to verbose, cluttered, code to the extent that it would be very difficult to read and maintain. And, with so very many savepoints being created and then released during normal, error free, execution, there would be noticeable performance penalty. The best, and simplest, way to handle such unexpected errors is in a single when others handler at the end of the top-level PL/pgSQL block statement that implements the subprogram or do statement that the client code invoked. This handler can marshal as many facts as are available. These facts include the context which specifies the call stack at the time of the exception. See the account of the get stacked diagnostics statement in the next section. These facts can then be recorded in an incidents table with an auto-generated ticketnumber primary key. When this exception section finishes, control returns to the client. This means that there is no need, here, for a bare raise_ statement to stop execution blundering on. Rather, it's enough to return the fact that such an unexpected error occurred together with the ticket number. The client can then report this outcome in end-user terminology. You can see a self-contained working code example that illustrates this paradigm in the GitHub repo. Look at the case-study and within that at the exception section in the source code for the procedure . {{< /note >}} The dedicated (one of the kinds of ), gives information about the error that the current handler caught. {{%ebnf%}} plpgsqlgetstackeddiagnosticsstmt, plpgsqlstackeddiagnostics_item, plpgsqlstackeddiagnosticsitemname {{%/ebnf%}} The attempt to invoke it in a block statement's executable section causes the 0Z002 run-time error: ```output GET STACKED DIAGNOSTICS cannot be used outside an exception handler ``` Each diagnostics item is identified by a keyword that specifies a status value that will be assigned to the specified target variable (or subprogram argument). The available items are listed in the following table. Each status value has the data type

<a name="diagnostics-item-names"></a> | Diagnostics item name | Description | | - | - | | returned_sqlstate | The code of the error that caused the exception. | | message_text | The text of the exception's primary message. | | pgexceptiondetail | The text of the exception's detail message. | | pgexceptionhint | The text of the exception's hint message. | | schema_name | The name of the schema related to exception. | | table_name | The name of the table related to exception. | | column_name | The name of the column related to exception. | | pgdatatypename | The name of the data type related to exception. | | constraint_name | The name of the constraint related to exception. | | pgexceptioncontext | Line(s) of text that specify the call stack at the time of the exception. | Different errors define values for different subsets of the available items. When a value for a particular item isn't defined, the get stacked diagnostics statement assigns the empty string (rather than null) to the specified target variable. This means that you can declare the target variables with a not null constraint. Here is an example. Ensure a clean start and create some supporting objects: ```plpgsql \c :db :u drop schema if exists s cascade; create schema s; create domain s.d as int constraint d_ok check(value between 1 and 5); create table s.t( k int primary key, c1 varchar(4) constraint tc1nn not null, c2 s.d, constraint tc2nn check(c2 is not null)); insert into s.t(k, c1, c2) values(1, 'dog', 3); create type s.diagnostics as( returned_sqlstate text, message_text text, pgexceptiondetail text, pgexceptionhint text, schema_name text, table_name text, column_name text, pgdatatypename text, constraint_name text, pgexceptioncontext text ); create function s.show(t in text) returns text set searchpath = pgcatalog, pg_temp language plpgsql as $body$ begin return case t when '' then '-' else t end; end; $body$; ``` Now create a function s.f() that will provoke one of a few different contrived errors according to what its input actual argument specifies. One of these is a user-defined error that is caused by the . Notice that the exception section defines just a single others handler. This will populate a value of the composite type s.diagnostics that has an attribute for every single available diagnostics item. The function returns the s.diagnostics value so that a caller table function can display each value that get stacked diagnostics populated. ```plpgsql create function s.f(mode in text) returns s.diagnostics set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare diags s.diagnostics; err text not null := ''; begin case mode when 'unique_violation' then insert into s.t(k, c1, c2) values(1, 'cat', 4); when 'notnullviolation' then update s.t set c1 = null where k = 1; when 'stringdataright_truncation' then update s.t set c1 = 'mouse' where k = 1; when 'check_violation 1' then update s.t set c2 = 7 where k = 1; when 'check_violation 2' then update s.t set c2 = null where k = 1; when 'raise_exception' then raise exception using errcode := 'YB573', message := 'It went wrong', detail := 'Caused by "raise exception".', hint := 'Do better next time.', schema := 'n/a', table := 'n/a', column := 'n/a', datatype := 'n/a', constraint := 'n/a' ; else -- No error update s.t set c1 = 'cat', c2 = 2 where k = 1; end case; diags.returned_sqlstate := 'No error'; return diags; exception when others then get stacked diagnostics diags.returnedsqlstate := returnedsqlstate, diags.messagetext := messagetext, diags.pgexceptiondetail := pgexceptiondetail, diags.pgexceptionhint := pgexceptionhint,

:= schemaname, diags.tablename := tablename, diags.columnname := columnname, diags.pgdatatypename := pgdatatypename, diags.constraintname := constraintname, diags.pgexceptioncontext := pgexceptioncontext; err := case diags.returned_sqlstate when '23505' then 'unique_violation' when '23502' then 'notnullviolation' when '22001' then 'stringdataright_truncation' when '23514' then 'check_violation' else diags.returned_sqlstate end; diags.returned_sqlstate := err; return diags; end; $body$; ``` Now create the table function s.foutcome() to invoke s.f() and to report the value of each of the attributes of the s.diagnostics_ value that it returns: ```plpgsql create function s.f_outcome(mode in text) returns table(z text) set searchpath = pgcatalog, pg_temp language plpgsql as $body$ declare diags s.diagnostics := s.f(mode); begin z := rpad('sqlstate:', 13)||s.show(diags.returned_sqlstate); return next; if diags.returned_sqlstate <> 'No error' then z := rpad('message:', 13)||s.show(diags.message_text); return next; z := rpad('detail:', 13)||s.show(diags.pgexceptiondetail); return next; z := rpad('hint:', 13)||s.show(diags.pgexceptionhint); return next; z := rpad('schema:', 13)||s.show(diags.schema_name); return next; z := rpad('table:', 13)||s.show(diags.table_name); return next; z := rpad('column:', 13)||s.show(diags.column_name); return next; z := rpad('datatype:', 13)||s.show(diags.pgdatatypename); return next; z := rpad('constraint:', 13)||s.show(diags.constraint_name); return next; z := ''; return next; z := 'context:'; return next; z := s.show(diags.pgexceptioncontext); return next; end if; end; $body$; ``` Now invoke it with, in turn, each of the actual values of the mode formal argument that it was designed to respond to. First, cause no error: ```plpgsql select s.f_outcome('benign'); ``` It executes this statement: ```plpgsql update s.t set c1 = 'cat', c2 = 2 where k = 1; ``` This is the result: ```output sqlstate: No error ``` Next, cause an error: ```plpgsql select s.foutcome('uniqueviolation'); ``` It executes this statement: ```plpgsql insert into s.t(k, c1, c2) values(1, 'cat', 4); ``` This attempts to insert a row with the same value for the primary key column, k that the existing row has. This is the result: ```output sqlstate: unique_violation message: duplicate key value violates unique constraint "t_pkey" detail: - hint: - schema: - table: - column: - datatype: - constraint: - context: PL/pgSQL function s.f(text) line 5 at statement block + PL/pgSQL function s.f_outcome(text) line 4 during statement block local variable initialization ``` Notice the empty values. The error definitely occurs in the context of a specific table in a specific schema. And it's due to the violation of a specific constraint. So the implementation (inherited directly from PostgreSQL) could have filled in these values. However, because a primary key constraint is, in general, defined for a column list rather than for a single column, there can be no general way to fill in the column name and the data type. You'll notice that, often, fields, that might have been filled in, are not. Such cases could, presumably, be the subject of enhancement requests for vanilla PostgreSQL. Next, cause another error: ```plpgsql select s.foutcome('notnull_violation'); ``` It executes this statement: ```plpgsql update s.t set c1 = null where k = 1; ``` This attempts to update the existing row to set c1, which is declared with a not null column constraint to null. This is the result: ```output sqlstate: notnullviolation message: null value in column "c1" violates not-null constraint detail: Failing row contains (1, null, 2). hint: - schema: s table: t column: c1 datatype: - constraint: - context: SQL statement "update s.t set c1 = null where k = 1" + PL/pgSQL function s.f(text) line 10 at SQL statement + PL/pgSQL function

line 4 during statement block local variable initialization ``` Notice that here the names of the schema, the table, and the column are filled in here; but the data type is not (even though it is known). Strangely, the not null column constraint tc1nn is not filled in either. This is probably because it isn't listed in the pgconstraint_ catalog table. Try this: ```plpgsql select conname from pg_constraint where connamespace = (select oid from pg_namespace where nspname = 's'); ``` This is the result: ```output conname d_ok tc2nn t_pkey ``` Notice that tc2nn, which is listed, is defined as a table constraint rather than as a column constraint. Next, cause another error: ```plpgsql select s.foutcome('stringdatarighttruncation'); ``` It executes this statement: ```plpgsql update s.t set c1 = 'mouse' where k = 1; ``` This attempts to update the existing row to set c1, which is declared as varchar(4) with a five-character value. This is the result: ```output sqlstate: stringdataright_truncation message: value too long for type character varying(4) detail: - hint: - schema: - table: - column: - datatype: - constraint: - context: SQL statement "update s.t set c1 = 'mouse' where k = 1" + PL/pgSQL function s.f(text) line 12 at SQL statement + PL/pgSQL function s.f_outcome(text) line 4 during statement block local variable initialization ``` Here, too, the names of the schema, the table, the column, and its data type are left blank. (The implied constraint that the varchar(4) declaration imposes is anonymous.) Next, cause another error: ```plpgsql select s.foutcome('checkviolation 1'); ``` It executes this statement: ```plpgsql update s.t set c2 = 7 where k = 1; ``` This attempts to update the existing row to set c2, which is declared using the s.d domainand this has an explicitly defined constraint s.dok_ that checks that the value lies between 1 and 5. This is the result: ```output sqlstate: check_violation message: value for domain s.d violates check constraint "d_ok" detail: - hint: - schema: s table: - column: - datatype: d constraint: d_ok context: SQL statement "update s.t set c2 = 7 where k = 1" + PL/pgSQL function s.f(text) line 14 at SQL statement + PL/pgSQL function s.f_outcome(text) line 4 during statement block local variable initialization ``` Here, the names of the data type (i.e. the domain name and the schema in which it lives) and the name of the domain's constraint are filled out. But the names of table and the column are left blank. Finally, cause another error: ```plpgsql select s.foutcome('checkviolation 2'); ``` It executes this statement: ```plpgsql update s.t set c2 = null where k = 1; ``` This attempts to update the existing row to set c2 to null. But the table s.t has a table-level constraint tc2nn check(c2 is not null). This is the result: ```output sqlstate: check_violation message: new row for relation "t" violates check constraint "tc2nn" detail: Failing row contains (1, cat, null). hint: - schema: s table: t column: - datatype: - constraint: tc2nn context: SQL statement "update s.t set c2 = null where k = 1" + PL/pgSQL function s.f(text) line 16 at SQL statement + PL/pgSQL function s.f_outcome(text) line 4 during statement block local variable initialization ``` Here, the names of the schema, the table, and the table-level constraint are filled out. But the names of the column and the data type are left blankpresumably because, in general, a table level constraint is not limited to constrain the values for just a single column.