WL#2265: Stored Procedures: Implement RESIGNAL

Affects: Server-5.5   —   Status: Complete   —   Priority: Low

MySQL will implement the standard SQL (SQL:2008) RESIGNAL
statement, which is used to pass forward an error or warning 
condition which is trapped in a handler of a compound
statement.

This is a subtask needed for stored procedures. It was 
decided at the Malta conference that this feature does 
not need to be implemented for the MySQL 5.0 release. 
For a while we said "it is now due for 5.1". Now we say
it is on the roadmap as a must-have item for 6.x.

Assume that WL#2110 (SIGNAL) will happen first.

Rationale
---------

Standard requirement:
RESIGNAL is part of SQL:2008 non-core feature P002
Computational Completeness. Additionally the standard
requires support of "implicit RESIGNAL" but that is
not one of the requirements for this task.

Needed functionality:
Right now there's no way to both handle an error
and return the error message -- by executing an SQL
statement within the handler, we destroy the
information that caused the handler's activation.
Also, it would make some procedures shorter if one
could handle part of a situation, then pass the
condition 'up the line' to another handler.

Compatibility:
IBM DB2 supports RESIGNAL in the standard-SQL manner.
Oracle doesn't support RESIGNAL syntax, but does provide 
similar functionality: use RAISE without an exception
name to "reraise an exception".
SQL Server does not support an equivalent functionality
other than the RAISERROR <error-number> statement, which
is closer to SIGNAL than to RESIGNAL.

In the Santa Cruz Dev-Mt Offsite meeting (2006-08), Brian
Aker and Monty Widenius made some time estimates and statements about
dependencies (see "Progress Reports" for details).
Marc Alff may have different estimates.
RESIGNAL passes on the condition (error or warning) information
that is available during execution of a condition handler
inside a compound statement inside a stored procedure, function,
trigger, or event. RESIGNAL may change some information
before passing it on.

Syntax
------

The succinct explanation is:
RESIGNAL [ <condition name> | <sqlstate value> ] [ SET <signal information> ];
which causes three possibilities that we will treat in separate sections.

RESIGNAL alone:
RESIGNAL;

RESIGNAL with new signal information:
RESIGNAL SET <signal information>;

RESIGNAL with condition name or sqlstate value, and possibly new signal information:
RESIGNAL { <condition name> | <sqlstate value> } [ SET <signal information> ];

There are explanations of <condition name>, <sqlstate value>,
<signal information>, and <condition information item> in
WL#2110 Stored Procedures: Implement SIGNAL. Unless otherwise
stated, the rules for those items are the same as in WL#2110.

Diagnostics areas
-----------------

A diagnostics area contains one or more condition areas.
A condition area contains condition information items,
such as MYSQL_ERRNO or MESSAGE_TEXT.

In standard SQL there is a stack of diagnostics areas.
When a handler takes control, it pushes the top of the stack.
So during handler execution there are two diagnostics areas:
1. The current diagnostics area, which starts as a copy of the
last diagnostics area, but will be overwritten by the first
procedure statement in the handler.
2. The last diagnostics area, which has the condition areas that
were set up before the handler took control.

How many diagnostics areas can be in the diagnostics area stack?
The implementor will decide. A fixed limit is okay,
provided that it's documented or discoverable.

How many condition areas can be in one diagnostics area?
We answer this question by saying that, in principle, we
want the condition area list to be analogous to existing
MySQL structures. Therefore the maximum is @@max_error_count.

RESIGNAL alone
--------------

The simple statement
RESIGNAL;
means "pass on with no change".
Specifically: restore the last diagnostics area and make it
the current diagnostics area, that is, "pop" the diagnostics
area stack.

For example:
DELIMITER //
SET @error_count = 0//
SET @a = 0//
DROP TABLE IF EXISTS xx//
DROP PROCEDURE IF EXISTS p//
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL; END IF;
    END;
  DROP TABLE xx;
  END//
CALL p()//
The "DROP TABLE xx;" statement fails.
The diagnostics area stack looks like this:
"1. ERROR 1051 (42S02): Unknown table 'xx'".
Now we enter the exit handler.
It starts by pushing the top of the stack.
The diagnostics area stack now looks like this:
"1. ERROR 1051 (42S02): Unknown table 'xx'
 2. ERROR 1051 (42S02): Unknown table 'xx'".
Usually a procedure statement clears the first diagnostics
area (which is also called the "current" diagnostics area).
BEGIN is an exception, it does not clear, it does nothing.
SET is not an exception, it clears, performs the operation,
and then, since it inevitably succeeds, says "success".
The diagnostics area stack now looks like this:
"1. ERROR 0000 (00000): Successful operation
 2. ERROR 1051 (42S02): Unknown table 'xx'".
At this point, if @a = 0, RESIGNAL pops the stack.
The diagnostics area stack now looks like this:
"1. ERROR 1051 (42S02): Unknown table 'xx'".
And that is what the caller sees.
Otherwise, the handler simply ends, which means that
there is no more use for the last diagnostics area
(it has been "handled"), so it can be thrown away.
The diagnostics area stack now looks like this:
"1. ERROR 0000 (00000): Successful operation".

The details make it look complex, but the end users should
like the end result: handlers can execute without
destroying information about the condition that caused
activation of the handler.

RESIGNAL with new signal information
------------------------------------

The statement
RESIGNAL SET <signal information>;
means "pass on with change".

As with RESIGNAL alone, the idea is to pop the diagnostics
area stack so that the original information (the information
that existed before entry into the handler) will go out.
With this difference: anything specified in <signal information>
changes.

For example:
DELIMITER //
SET @error_count = 0//
DROP TABLE IF EXISTS xx//
DROP PROCEDURE IF EXISTS p//
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL SET MESSAGE_TEXT = '!'; END IF;
    END;
  DROP TABLE xx;
  END//
CALL p()//

Remember from the previous section that RESIGNAL alone causes
"1. ERROR 1051 (42S02): Unknown table 'xx'".
But "RESIGNAL SET MESSAGE_TEXT = '!';" causes
"1. ERROR 1051 (42S02): !".
In other words, it changes the message text, and nothing else.

A user could change all the signal information items,
making the first condition area of the diagnostics area
look quite different.

RESIGNAL with condition name or sqlstate value, and possibly new signal information
-----------------------------------------------------------------------------------

The complex statement
RESIGNAL { <condition name> | <sqlstate value> } [ SET <signal information> ]
means "push a condition and pass on with possible change".
Specifically: restore the last diagnostics area and make it
the current diagnostics area, that is, "pop" the diagnostics
area stack. So far, it's the same as what a simple RESIGNAL;
would do: But also: change the diagnostics area depending on
the condition name, sqlstate value, or signal information.

DELIMITER //
SET @error_count = 0//
SET @a = 0//
SET @@max_error_count = 2//
DROP TABLE IF EXISTS xx//
DROP PROCEDURE IF EXISTS p//
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    SET @error_count = @error_count + 1;
    IF @a = 0 THEN RESIGNAL SQLSTATE '45000' SET MYSQL_ERRNO=5; END IF;
    END;
  DROP TABLE xx;
  END//
CALL p()//
SHOW ERRORS//

This is the same as the example in section "RESIGNAL without argument",
and the effects are the same, except that if RESIGNAL happens
the current condition area looks different at the end.

In standard SQL, RESIGNAL "pushes" a new condition area.
So at the end the diagnostics area looks like this:
"1. (condition 1) ERROR 5 (45000) Unknown table 'xx'
    (condition 2) ERROR 1051 (42S02): Unknown table 'xx'".

So the result of "CALL p()//" and "SHOW ERRORS//" for this example is:
mysql> CALL p()//
ERROR 5 (45000): Unknown table 'xx'
mysql> show errors//
+-------+------+----------------------------------+
| Level | Code | Message                          |
+-------+------+----------------------------------+
| Error |    5 | Unknown table 'xx'               |
| Error | 1051 | Unknown table 'xx'               |
+-------+------+----------------------------------+
2 rows in set (0.00 sec)

(As discussed elsewhere, the order is reversed in
the current MySQL implementation.)

Note that here we have the old error and a new error in the list.
This deviates from the standard, where a message number should've been
changed in *both* errors, but sqlstate - only in the second one.

Neither Mimer nor DB2 support this exactly as specified in the standard.

Peter believes that our behavior is more logical and more useful, and
suspects an error in the standard. As a Sun delegate in the ANSI
committee he hopes we can suggest a correction.

Handler not active
------------------

RESIGNAL is illegal if a handler is not active.
The appropriate error message is:
0K000 "resignal when handler not active".

For example:
CREATE PROCEDURE p () RESIGNAL; /* no error */
CALL p();                       /* error: 0K000 */

But here is a more difficult example:
DELIMITER //
CREATE FUNCTION f () RETURNS INT
BEGIN
  RESIGNAL;
  RETURN 5;
  END//
CREATE PROCEDURE p ()
BEGIN
  DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @a=f();
  SIGNAL SQLSTATE '55555';
  END//
At the time the RESIGNAL happens, there is a handler,
even though the RESIGNAL is not inside the handler.

Implicit RESIGNAL
-----------------

This section is informative, it does not affect implementation.

In standard SQL, technically, most statements cause raising of
conditions, but not activation of handlers. RESIGNAL causes
activation of handlers. For example, the statement
INSERT INTO no_such_table VALUES (no_such_variable);
has two errors: nonexistent table and nonexistent variable.
DBMSs can "raise" both these errors, that is, they don't
have to stop after seeing no_such_table, they can keep
filling up condition areas. But when it's all over, there
is an "implicit RESIGNAL". This is a short way to indicate
that the rules for handler activation take place.

In fact MySQL does not have to execute an implicit RESIGNAL.
It merely has to accomplish the same effect as if there was
an implicit RESIGNAL.

Walk through the SQL Standard
-----------------------------
The attached file walkthrough_wl2265.txt (Private, MySQL internal
document) is an explanatory appendix for MySQL people who care how
well MySQL's RESIGNAL statement complies with SQL:2008.
The implementor should consider it to be part of the specification.

With Marc assenting and nobody dissenting, Peter decided that
MySQL must depart from the SQL:2008 standard description thus:
For 9075-4 17.3 <resignal statement>, do General Rule 5)
before General Rule 4). Thus if both <set signal information>
and <signal value> are specified, the new signal information will
end up in condition area #1.

Other apparent differences between MySQL and other
possible implementations are easy to explain:
* Peter Gulutzan believes the errors are in the wrong
order but there was a decision not to change SHOW
WARNINGS for the sake of SIGNAL/RESIGNAL work.
* Peter maintains that when we 'push' the condition area
for General Rule 5), we do not reset MESSAGE_TEXT.
As he's noted, it depends how one interprets the
words "All occupied condition areas in DA are
stacked such that the i-th condition area is placed
at the position of the i+1-st condition area in DA."
Peter is sure that either interpretation is correct.
There was no objection to using the interpretation
that we do not clear.

Privileges
----------

No privileges required.

Reserved words
--------------

RESIGNAL

Example
-------

DELIMITER //
CREATE PROCEDURE p ()
BEGIN
  DECLARE CONTINUE HANDLER FOR SQLWARNING
  BEGIN
    SELECT 'MAXIMUM SMALLINT VALUE is 32767';
    RESIGNAL;
    END;
  CREATE TABLE t (s1 SMALLINT);
  INSERT INTO t VALUES (32769); /* "out of range" */
  END//
CALL p()//
Result: warning 1264 "Out of range value for column 's1' at row 1"
is caught by the handler, which displays a message.
Assuming that SELECT does not return an error, the caller sees:
mysql> call p()//
+---------------------------------+
| MAXIMUM SMALLINT VALUE is 32767 |
+---------------------------------+
| MAXIMUM SMALLINT VALUE is 32767 |
+---------------------------------+
Query OK, 1 row affected, 1 warning (0.00 sec)
and SHOW WARNINGS will show:
mysql> show warnings//
+---------+------+---------------------------------------------+
| Level   | Code | Message                                     |
+---------+------+---------------------------------------------+
| Warning | 1264 | Out of range value for column 's1' at row 1 |
+---------+------+---------------------------------------------+
1 row in set (0.00 sec)

Example
-------

DELIMITER //
CREATE PROCEDURE p1 ()
BEGIN
  DECLARE x CONDITION FOR SQLSTATE '42000';
  DECLARE EXIT HANDLER FOR x
  BEGIN
    SELECT '2';
    RESIGNAL x SET MYSQL_ERRNO = 999;
    END;
  BEGIN
    DECLARE EXIT HANDLER FOR x
    BEGIN
      SELECT '1';
      RESIGNAL x SET SCHEMA_NAME = 'test';
      END;
    SET @@sql_mode=NULL;
    END;
  END//
CREATE PROCEDURE p2 ()
BEGIN
  DECLARE x CONDITION FOR SQLSTATE '42000';
  DECLARE EXIT HANDLER FOR x
  BEGIN
    SELECT '3';
    RESIGNAL x SET MESSAGE_TEXT = 'Hi, I am a useless error message';
    END;
  CALL p1();
  END//
CALL p2()//
Result: the user sees '1', then '2', then '3', then
ERROR 999 (42000): Hi, I am a useless error message
(The actual error number will not be '999' but will be fixed when
the feature is in the main mysql-6.0 tree.)
Notice the effects of handlers within handlers, of
changes in scope for condition names, and how the
condition information keeps going up the line until
eventually it's seen by the caller. The original
error message, "Variable 'sql_mode' can't be set to
the value of 'NULL'", is lost.

References
----------

WL#2110 Stored Procedures: Implement SIGNAL

DB2 manual for RESIGNAL:
http://publib.boulder.ibm.com/infocenter/db2luw/v9/index.jsp?topic=/com.ibm.db2.udb.admin.doc/doc/r0005654.htm