Details
-
Bug
-
Status: Confirmed (View Workflow)
-
Major
-
Resolution: Unresolved
-
10.2.7, 10.0(EOL), 10.1(EOL)
-
Operating System: CentOS Linux release 7.3.1611 (Core)
Originally found on an Amazon EC2 Instance, with Plesk as the control panel for the server. MariaDB had been upgraded twice (from the CentOS's own version to 10.1 and then to 10.2)
Description
Note: This affects MySQL 5.7.19 as well (bug report 87442).
When you have a function (e.g. f()) that uses SIGNAL SQLSTATE '01xxx' (where xxx is any three digits), then running SELECT f(); will return a success message (and the return value) or an error message, but in both cases will not return any warnings.
Should you modify the function so it is a procedure (only changing CREATE FUNCTION to CREATE PROCEDURE and removing RETURNS <TYPE> and RETURN <return value>), then run CALL F(); the procedure will return a warning ONLY if the SIGNAL is the last line. (In a function it is impossible for the SIGNAL to be the last line, as you are required to have a RETURN.)
Please see attached file for example function/procedures, and the results.
Attachments
Issue Links
Activity
I agree there is inconsistency which the current documentation in KB doesn't seem to explain. In the test case below I got rid of SIGNAL, to make sure it's not the cause of the problem. Instead, I use SELECT 1/0 INTO @a to generate a warning.
With procedure, the warning is generated, not cleared up, and is returned after the procedure call:
Procedure |
CREATE PROCEDURE p() |
BEGIN
|
SELECT 1/0 INTO @a; |
SELECT 1; |
END $$ |
Results with the procedure |
MariaDB [test]> CALL p();
|
+---+ |
| 1 |
|
+---+ |
| 1 |
|
+---+ |
1 row in set (0.00 sec) |
|
Query OK, 0 rows affected, 1 warning (0.00 sec) |
|
MariaDB [test]> SHOW WARNINGS;
|
+---------+------+---------------+ |
| Level | Code | Message | |
+---------+------+---------------+ |
| Warning | 1365 | Division by 0 | |
+---------+------+---------------+ |
1 row in set (0.00 sec) |
Function |
CREATE FUNCTION f() RETURNS INT |
BEGIN
|
SELECT 1/0 INTO @a; |
RETURN 1; |
END $$ |
Results with the function |
MariaDB [test]> SELECT f(); |
+------+ |
| f() |
|
+------+ |
| 1 |
|
+------+ |
1 row in set (0.00 sec) |
However, neither the function call itself nor RETURN inside the function clear the condition if it's already set. The previously set diagnostics area is still visible inside the function, and it is still there after the function execution:
DROP FUNCTION IF EXISTS f2; |
|
delimiter $$
|
CREATE FUNCTION f2() RETURNS INT |
BEGIN
|
GET DIAGNOSTICS @num_conditions_inside_function = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno_inside_function = MYSQL_ERRNO, @message_text_inside_function = MESSAGE_TEXT;
|
RETURN 1; |
END $$ |
delimiter ;
|
|
# Let us clear the diagnostics area
|
CREATE OR REPLACE TABLE new_table (i INT); |
|
GET DIAGNOSTICS @num_conditions = NUMBER;
|
SELECT @num_conditions; |
+-----------------+ |
| @num_conditions |
|
+-----------------+ |
| 0 |
|
+-----------------+ |
1 row in set (0.00 sec) |
# Now generate a new condition
|
SELECT 1/0 INTO @b; |
|
GET DIAGNOSTICS @num_conditions = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT;
|
SELECT @num_conditions, @errno, @message_text; |
+-----------------+--------+---------------+ |
| @num_conditions | @errno | @message_text |
|
+-----------------+--------+---------------+ |
| 1 | 1365 | Division by 0 | |
+-----------------+--------+---------------+ |
1 row in set (0.00 sec) |
# Check if f2 can see this condition |
SELECT f2(); |
+------+ |
| f2() |
|
+------+ |
| 1 |
|
+------+ |
1 row in set (0.00 sec) |
|
SELECT @num_conditions_inside_function, @errno_inside_function, @message_text_inside_function; |
+---------------------------------+------------------------+-------------------------------+ |
| @num_conditions_inside_function | @errno_inside_function | @message_text_inside_function |
|
+---------------------------------+------------------------+-------------------------------+ |
| 1 | 1365 | Division by 0 | |
+---------------------------------+------------------------+-------------------------------+ |
1 row in set (0.00 sec) |
# Check that the condition is still there after the call |
|
GET DIAGNOSTICS @num_conditions_new = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno_new = MYSQL_ERRNO, @message_text_new = MESSAGE_TEXT;
|
SELECT @num_conditions_new, @errno_new, @message_text_new; |
|
+---------------------+------------+-------------------+ |
| @num_conditions_new | @errno_new | @message_text_new |
|
+---------------------+------------+-------------------+ |
| 1 | 1365 | Division by 0 | |
+---------------------+------------+-------------------+ |
1 row in set (0.00 sec) |
Finally, if a condition is generated inside the function (via SIGNAL or otherwise), it does reset the previous diagnostics area, but the new condition is not visible outside the function. The test below first generates ER_BAD_TABLE_ERROR, then calls a function which generates ER_DIVISION_BY_ZERO. After the function call, neither the old nor the new conditions are visible anymore.
DROP FUNCTION IF EXISTS f3; |
|
delimiter $$
|
CREATE FUNCTION f3() RETURNS INT |
BEGIN
|
SELECT 1/0 INTO @c; |
GET DIAGNOSTICS @num_conditions_inside_function = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno_inside_function = MYSQL_ERRNO, @message_text_inside_function = MESSAGE_TEXT;
|
RETURN 1; |
END $$ |
delimiter ;
|
DROP TABLE IF EXISTS non_existing_table; |
Query OK, 0 rows affected, 1 warning (0.00 sec) |
|
GET DIAGNOSTICS @num_conditions = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT;
|
SELECT @num_conditions, @errno, @message_text; |
+-----------------+--------+-----------------------------------------+ |
| @num_conditions | @errno | @message_text |
|
+-----------------+--------+-----------------------------------------+ |
| 1 | 1051 | Unknown table 'test.non_existing_table' | |
+-----------------+--------+-----------------------------------------+ |
1 row in set (0.00 sec) |
SELECT f3(); |
+------+ |
| f3() |
|
+------+ |
| 1 |
|
+------+ |
1 row in set (0.00 sec) |
|
SELECT @num_conditions_inside_function, @errno_inside_function, @message_text_inside_function; |
+---------------------------------+------------------------+-------------------------------+ |
| @num_conditions_inside_function | @errno_inside_function | @message_text_inside_function |
|
+---------------------------------+------------------------+-------------------------------+ |
| 1 | 1365 | Division by 0 | |
+---------------------------------+------------------------+-------------------------------+ |
1 row in set (0.00 sec) |
# Check whether any of the conditions are there after the call |
|
GET DIAGNOSTICS @num_conditions_new = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno_new = MYSQL_ERRNO, @message_text_new = MESSAGE_TEXT;
|
SELECT @num_conditions_new, @errno_new, @message_text_new; |
+---------------------+------------+-------------------+ |
| @num_conditions_new | @errno_new | @message_text_new |
|
+---------------------+------------+-------------------+ |
| 0 | NULL | NULL | |
+---------------------+------------+-------------------+ |
1 row in set (0.00 sec) |
I'll leave it to serg to decide whether this difference between functions and procedures is expected, and if it is, it should be clarified somewhere here: https://mariadb.com/kb/en/library/diagnostics-area/ , in "How the diagnostics area is populated and cleared".
Note: the examples below work only with sql_mode=ERROR_FOR_DIVISION_BY_ZERO.
In regard to MariaDB versions, for 5.5 does not seem to be affected, 10.x are.
It's intentional.
According to the SQL standard, any statement (excluding GET DIAGNOSTICS) should empty the diagnostics area. SQL:2016, Part 2, subclause 4.36 Diagnostics area:
> At the beginning of the execution of any <SQL procedure statement> that is not an <SQL diagnostics statement>, the first diagnostics area is emptied.
In MariaDB (and in historically in MySQL, although apparently not anymore) additionally any statement that does not use tables and does not generate warnings also preserves the diagnostics area. Otherwise every
SELECT @num_conditions_new, @errno_new, @message_text_new; |
in your test above would've reset all warnings.
Compare with:
drop table if exists foobar;
|
Warnings:
|
Note 1051 Unknown table 'test.foobar'
|
GET DIAGNOSTICS @num_conditions = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT;
|
SELECT @num_conditions, @errno, @message_text;
|
@num_conditions @errno @message_text
|
1 1051 Unknown table 'test.foobar'
|
select 1;
|
1
|
1
|
GET DIAGNOSTICS @num_conditions = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT;
|
SELECT @num_conditions, @errno, @message_text;
|
@num_conditions @errno @message_text
|
1 1051 Unknown table 'test.foobar'
|
select 1/0;
|
1/0
|
NULL
|
Warnings:
|
Warning 1365 Division by 0
|
GET DIAGNOSTICS @num_conditions = NUMBER;
|
GET DIAGNOSTICS CONDITION 1 @errno = MYSQL_ERRNO, @message_text = MESSAGE_TEXT;
|
SELECT @num_conditions, @errno, @message_text;
|
@num_conditions @errno @message_text
|
1 1365 Division by 0
|
Note that select 1 preserves the warning (because it doesn't use tables), and so does SELECT @num_conditions, @errno, @message_text itself. But select 1/0 empties the diagnostics area first, it does not append a warning to the existing list.
Hi Sergei,
Would you mind just confirming why there is the difference between Stored Procedures and Functions, in MariaDB specifically?
As far as I can tell:
- The diagnostic area is not cleared upon a statement-less return, as a stored procedure where the last statement produces a warning, will return the warning.
- The RETURN statement does however clear the diagnostic area. I understand this is inline with the SQL standard, but isn't this inconsistent with MariaDB's behaviour, in that the RETURN statement doesn't use tables or generate any warnings/errors of its own?
Thanks,
Robert
I have exchanged a couple of messages with one of the MySQL developers within the linked bug report, and they have come to conclusion that it is a documentation issue, and that the behaviour is within the expect design of MySQL.
I still feel that the behaviour I was expecting makes more sense, however that is just my opinion.
Regardless, the MariaDB documentation does not contain the same example in the SIGNAL section, and the diagnostics area works differently in MariaDB to MySQL.
Importantly, then in the Diagnostics Area Manual Page for MariaDB:
"When a table-based statement (like INSERT) is executed, the old data in the diagnostics area is cleared even if the statement doesn't produce any condition. However, statements which don't access any table (like SET, or a SELECT with no FROM clause) is executed and produces no warnings, the diagnostics area remains unchanged."
This does actually hold true for within a stored procedure (my original test case was in a stored function on MariaDB, I then replicated on MySQL and expanded) - in my test case I have attached, both CALL p() and CALL p2() return the expected warning*. However, f() still does not return the warning, and I can see no reason in the manual why the stored function is behaving differently, as I would expect "RETURN 5;" to be treated as a 'statement which don't access any table'.
*A further function, p3 (where `affiliates` is a table that exists):
CREATE PROCEDURE `p3`()
BEGIN
SIGNAL SQLSTATE '01234';
SELECT RAND() INTO @unused FROM `affiliates` LIMIT 1;
END$$
Will not return a the warning - but then that is behaviour that is expected according to the MariaDB manual (but the behaviour is not expected according to the current MySQL manual).
In light of the information above, I am not sure what to do in regards to this bug report. The upstream issue no longer applies to MariaDB (as the documentation issue is not present in the MariaDB, and even if MySQL re-classes the bug, it appears this is now separate due to the different handling of diagnostic areas). Is the fact the stored function isn't returning the signalled warning a bug, or have I missed something in the manual? If it is a bug, is it best to close this bug report and open a new report, or continue within this bug report? Sorry I have made a bit of a mess with this report!