mariadb-syntax-procedures-functions
Installation
SKILL.md
MariaDB Stored Procedures and Stored Functions
Deterministic guidance for writing stored procedures, stored functions, control flow, cursors, error handlers (DECLARE HANDLER, SIGNAL, RESIGNAL), and choosing DETERMINISTIC vs NOT DETERMINISTIC. This skill covers the routine body itself. For triggers, events, and views, see the sibling skill mariadb-syntax-triggers-events-views.
Quick Reference
- ALWAYS use
DELIMITER //(or any non-semicolon) beforeCREATE PROCEDURE/CREATE FUNCTIONin client tools so the;inside the body is not interpreted as end-of-statement. Reset withDELIMITER ;afterwards. This is a client-side directive, NOT SQL. Drivers that send one statement per call (PDO, JDBCexecuteUpdate) do not need DELIMITER ; the multi-statement client (mariadbCLI, MySQL Workbench) does. Verified : KBcreate-procedure/. - ALWAYS write
DETERMINISTIConly when the function truly is deterministic. A function is deterministic only when "it can produce only one result for a given list of parameters". Functions that callNOW(),CURRENT_TIMESTAMP(),RAND(),UUID(),CONNECTION_ID(),LAST_INSERT_ID(), or that read session variables (@var) or non-immutable table data are NOT deterministic. Mis-declaring as DETERMINISTIC lets the optimizer cache or hoist the call, producing wrong results. Verified : KBcreate-function/. - NEVER call a NOT DETERMINISTIC function in a WHERE clause on a large table. The optimizer cannot fold the call to a constant, so it is re-evaluated per row, no index on the function-result can be used, and the plan degrades to a full scan with a per-row procedure call. ALWAYS push the predicate to a constant-folded expression in the application, or store the result in a column.
- ALWAYS declare local variables and cursors and handlers in the correct order inside
BEGIN ... END: (1) localDECLARE var TYPE, then (2)DECLARE condition_name CONDITION, then (3)DECLARE cursor_name CURSOR, then (4)DECLARE ... HANDLER. Any other order is a syntax error. Verified : KBdeclare-cursor/. - ALWAYS attach
DECLARE CONTINUE HANDLER FOR NOT FOUNDto detect end-of-cursor. The handler sets a flag the LOOP body checks ; without it, FETCH past the last row raises SQLSTATE02000and the procedure either silently exits (no handler) or hangs forever (handler badly written). Pattern :DECLARE done INT DEFAULT 0 ; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1 ;. - ALWAYS use
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '...'for user-defined errors.45000is the documented class for application-raised conditions ; it surfaces as an error (not a warning) when unhandled. Optionally setMYSQL_ERRNO = <integer>for a custom numeric code. Verified : KBsignal/. - ALWAYS use
RESIGNAL(notSIGNAL) inside a handler to re-raise the caught error.RESIGNALwith no arguments re-throws the original condition unchanged ;RESIGNAL SET MESSAGE_TEXT = '...'re-throws with an overridden message but preserves the original SQLSTATE and MYSQL_ERRNO. UsingSIGNALinstead loses the original error context. Verified : KBresignal/. - ALWAYS raise
max_sp_recursion_depthbefore calling a recursive procedure. Default is0, which forbids recursion ; max is255. Set per-session :SET @@SESSION.max_sp_recursion_depth = 32 ;. Without this, the second call into the same procedure errors withRecursive limit 0 (as set by the max_sp_recursion_depth variable) was exceeded. Verified : KBcreate-procedure/. - ALWAYS pick the right characteristic clause. Functions default to
CONTAINS SQLandNOT DETERMINISTIC. SetREADS SQL DATAwhen the body hasSELECT,MODIFIES SQL DATAwhen it hasINSERT/UPDATE/DELETE/ DDL. These clauses are required to be accurate whenlog_bin_trust_function_creators = OFF(the secure default) ; otherwiseCREATE FUNCTIONis refused for binlog-safety. Verified : KBcreate-function/. - ALWAYS prefer
SQL SECURITY INVOKERfor routines that touch user data. TheDEFINERdefault runs the body with the creator's privileges, which is convenient for exposing a subset of sensitive tables but is a privilege-escalation vector if the routine is misused.INVOKERdefers privilege checks to the caller. AuditSHOW CREATE PROCEDUREregularly for unexpectedDEFINERaccounts. - NEVER use
INVISIBLE,BEFOREtriggers, or trigger-style transaction control inside a function. Triggers and views are not in this skill (see siblingmariadb-syntax-triggers-events-views) but the rule that "functions cannot contain transaction control" (COMMIT,ROLLBACK,SAVEPOINT,START TRANSACTION) is enforced at runtime withExplicit or implicit commit is not allowed in stored function or trigger. Procedures CAN contain transaction control. OUT/INOUT/IN OUTon a function (10.8+) can ONLY be called viaSETandCALL, NEVER insideSELECT. AttemptingSELECT myfunc(@x)on a function with OUT/INOUT parameters raises error 4186. For most cases : if you need multi-value return, write a PROCEDURE with OUT parameters, not a FUNCTION. Verified : KBcreate-function/.- MariaDB 11.8+ adds Oracle-mode
IN OUTand per-parameterDEFAULT. On 11.8 and later,CREATE PROCEDURE p(IN OUT a INT, IN b INT DEFAULT 0)is legal. On 10.6 through 11.7, onlyIN | OUT | INOUTmodes exist and DEFAULT is a syntax error. Verified : KBcreate-procedure/.