8. Stored Functions
Stored functions share many similarities with stored procedures and some
similarities with triggers. Similar to both stored procedures and
triggers, stored functions have a DEFINER clause that is
normally (but not always) used when the CREATE FUNCTION
statement is written to the binary log.
In contrast to stored procedures, stored routines can return values and you can therefore
embed them in various places in SQL statements. For example, consider
the definition of a stored routine in Example 11, which extracts
the email address of an employee given the employee’s name. The function
is a little contrived—it is significantly more efficient to just execute
statements directly—but it suits our purposes well.
Example 11. A stored function to fetch the name of an employee
delimiter $$
CREATE FUNCTION employee_email(p_name CHAR(64))
RETURNS CHAR(64)
DETERMINISTIC
BEGIN
DECLARE l_email CHAR(64);
SELECT email INTO l_email FROM employee WHERE name = p_name;
RETURN l_email;
END $$
delimiter ;
|
This stored function can be used conveniently in other statements,
as shown in Example 12. In
contrast to stored procedures, stored functions have to specify a characteristic—such as DETERMINISTIC, NO
SQL, or READS SQL DATA—if
they are to be written to the binary log.
Example 12. Examples of using the stored function
master> INSERT INTO collected(name, email) ('mats', employee_email('mats'));
Query OK, 1 row affected (0.01 sec)
master> SELECT employee_email('mats');
+------------------------+
| employee_email('mats') |
+------------------------+
| mats@example.com |
+------------------------+
1 row in set (0.00 sec)
|
When it comes to calls, stored functions are replicated in the
same manner as triggers: as part of the statement that executes the
function. For instance, the binary log doesn’t need any events preceding
the INSERT statement in Example 3-12, but it will contain
the context events necessary to replicate the stored function inside
the INSERT.
What about SELECT? Normally,
SELECT statements are not written to the binary log, since they
don’t change any data, but a SELECT
containing a stored function is an exception. When executing the stored
function, the server notices that it adds a row to the
log table and marks the statement as an “updating”
statement, which means that it will be written to the binary log.
The CREATE ROUTINE privilege
is required to define a stored procedure or stored
function. Strictly speaking, no other privileges are needed to create
a stored routine, but since it normally executes under the privileges
of the definer, defining a stored routine would not make much sense if
the definer of the procedure didn’t have the necessary privileges to
read to or write from tables referenced by the stored
procedure.
But replication threads on the slave execute without privilege checks. This leaves a serious
security hole allowing any user with the CREATE ROUTINE privilege to elevate her
privileges and execute any statement on the slave.
In MySQL versions earlier than 5.0, this does not cause
problems, since all paths of a statement are explored when the
statement is executed on the master. A privilege violation on the
master will prevent a statement from being written to the binary log,
so users cannot access objects on the slave that were out of bounds on
the master. However, with the introduction of stored routines, it is
possible to create conditional execution paths, and the server does
not explore all paths when executing a stored routine.
Since stored procedures are unrolled, the exact statements
executed on the master are also executed on the slave, and since the
statement is only logged if it was successfully executed on the
master, it is not possible to get access to other objects. Not so with
stored functions.
If a stored function is defined with SQL SECURITY
INVOKER, a malicious user can craft a function that will
execute differently on the master and the slave. The security breach
can then be buried in the branch executed on the slave. This is
demonstrated in the following example:
CREATE FUNCTION magic()
RETURNS CHAR(64)
SQL SECURITY INVOKER
BEGIN
DECLARE result CHAR(64);
IF @@server_id <> 1 THEN
SELECT what INTO result FROM secret.agents LIMIT 1;
RETURN result;
ELSE
RETURN 'I am magic!';
END IF;
END $$
One piece of code executes on the master (the ELSE branch), whereas a separate piece of
code (the IF branch) executes on
the slave where the privilege checks are disabled. The effect is to
elevate the user’s privileges from CREATE ROUTINE to
the equivalent of SUPER.
Notice that this problem doesn’t occur if the function is
defined with SQL SECURITY
DEFINER, because the function executes with the user’s
privileges and will be blocked on the slave.
To prevent privilege escalation on a slave, MySQL requires
SUPER privileges by default to
define stored functions. But since stored functions are very useful,
and some database administrators trust their users with creating
proper functions, this check can be disabled with the log-bin-trust-function-creators
option.