Commit 0963c705 authored by kroki@mysql.com's avatar kroki@mysql.com

Bug#14635: Accept NEW.x as INOUT parameters to stored procedures

from within triggers

Add support for passing NEW.x as INOUT and OUT parameters to stored
procedures.  Passing NEW.x as INOUT parameter requires SELECT and
UPDATE privileges on that column, and passing it as OUT parameter
requires only UPDATE privilege.
parent 65b87b86
...@@ -282,9 +282,9 @@ select @tmp_x, @tmp_y, @tmp_z| ...@@ -282,9 +282,9 @@ select @tmp_x, @tmp_y, @tmp_z|
@tmp_x @tmp_y @tmp_z @tmp_x @tmp_y @tmp_z
42 45 87 42 45 87
call p(42, 43, @tmp_z)| call p(42, 43, @tmp_z)|
ERROR 42000: OUT or INOUT argument 2 for routine test.p is not a variable ERROR 42000: OUT or INOUT argument 2 for routine test.p is not a variable or NEW pseudo-variable in BEFORE trigger
call p(42, @tmp_y, 43)| call p(42, @tmp_y, 43)|
ERROR 42000: OUT or INOUT argument 3 for routine test.p is not a variable ERROR 42000: OUT or INOUT argument 3 for routine test.p is not a variable or NEW pseudo-variable in BEFORE trigger
drop procedure p| drop procedure p|
create procedure p() begin end| create procedure p() begin end|
lock table t1 read| lock table t1 read|
......
...@@ -310,3 +310,87 @@ SELECT @mysqltest_var; ...@@ -310,3 +310,87 @@ SELECT @mysqltest_var;
Hello, world! Hello, world!
DROP USER mysqltest_u1@localhost; DROP USER mysqltest_u1@localhost;
DROP DATABASE mysqltest_db1; DROP DATABASE mysqltest_db1;
DELETE FROM mysql.user WHERE User LIKE 'mysqltest_%';
DELETE FROM mysql.db WHERE User LIKE 'mysqltest_%';
DELETE FROM mysql.tables_priv WHERE User LIKE 'mysqltest_%';
DELETE FROM mysql.columns_priv WHERE User LIKE 'mysqltest_%';
FLUSH PRIVILEGES;
DROP DATABASE IF EXISTS mysqltest_db1;
CREATE DATABASE mysqltest_db1;
USE mysqltest_db1;
CREATE TABLE t1 (i1 INT);
CREATE TABLE t2 (i1 INT);
CREATE USER mysqltest_dfn@localhost;
CREATE USER mysqltest_inv@localhost;
GRANT EXECUTE, CREATE ROUTINE, SUPER ON *.* TO mysqltest_dfn@localhost;
GRANT INSERT ON mysqltest_db1.* TO mysqltest_inv@localhost;
CREATE PROCEDURE p1(OUT i INT) DETERMINISTIC NO SQL SET i = 3;
CREATE PROCEDURE p2(INOUT i INT) DETERMINISTIC NO SQL SET i = i * 5;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW
CALL p2(NEW.i1);
INSERT INTO t1 VALUES (7);
ERROR 42000: UPDATE command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't1'
INSERT INTO t2 VALUES (11);
ERROR 42000: SELECT,UPDATE command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't2'
DROP TRIGGER t2_bi;
DROP TRIGGER t1_bi;
GRANT SELECT ON mysqltest_db1.* TO mysqltest_dfn@localhost;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW
CALL p2(NEW.i1);
INSERT INTO t1 VALUES (13);
ERROR 42000: UPDATE command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't1'
INSERT INTO t2 VALUES (17);
ERROR 42000: UPDATE command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't2'
REVOKE SELECT ON mysqltest_db1.* FROM mysqltest_dfn@localhost;
DROP TRIGGER t2_bi;
DROP TRIGGER t1_bi;
GRANT UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW
CALL p2(NEW.i1);
INSERT INTO t1 VALUES (19);
INSERT INTO t2 VALUES (23);
ERROR 42000: SELECT command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't2'
REVOKE UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost;
DROP TRIGGER t2_bi;
DROP TRIGGER t1_bi;
GRANT SELECT, UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW
CALL p2(NEW.i1);
INSERT INTO t1 VALUES (29);
INSERT INTO t2 VALUES (31);
REVOKE SELECT, UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost;
DROP TRIGGER t2_bi;
DROP TRIGGER t1_bi;
DROP PROCEDURE p2;
DROP PROCEDURE p1;
GRANT UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost;
CREATE PROCEDURE p1(OUT i INT) DETERMINISTIC NO SQL SET i = 37;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
INSERT INTO t1 VALUES (41);
DROP PROCEDURE p1;
CREATE PROCEDURE p1(IN i INT) DETERMINISTIC NO SQL SET @v1 = i + 43;
INSERT INTO t1 VALUES (47);
ERROR 42000: SELECT command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't1'
DROP PROCEDURE p1;
CREATE PROCEDURE p1(INOUT i INT) DETERMINISTIC NO SQL SET i = i + 51;
INSERT INTO t1 VALUES (53);
ERROR 42000: SELECT command denied to user 'mysqltest_dfn'@'localhost' for column 'i1' in table 't1'
DROP PROCEDURE p1;
REVOKE UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost;
DROP TRIGGER t1_bi;
DROP USER mysqltest_inv@localhost;
DROP USER mysqltest_dfn@localhost;
DROP TABLE t2;
DROP TABLE t1;
DROP DATABASE mysqltest_db1;
USE test;
End of 5.0 tests.
...@@ -1034,3 +1034,59 @@ SET @@sql_mode=@save_sql_mode; ...@@ -1034,3 +1034,59 @@ SET @@sql_mode=@save_sql_mode;
DROP TRIGGER t1_ai; DROP TRIGGER t1_ai;
DROP TRIGGER t1_au; DROP TRIGGER t1_au;
DROP TABLE t1; DROP TABLE t1;
DROP TABLE IF EXISTS t1;
DROP PROCEDURE IF EXISTS p1;
DROP PROCEDURE IF EXISTS p2;
CREATE TABLE t1 (i1 INT);
INSERT INTO t1 VALUES (3);
CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET i1 = 5;
CREATE PROCEDURE p2(INOUT i1 INT) DETERMINISTIC NO SQL SET i1 = i1 * 7;
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW
BEGIN
CALL p1(NEW.i1);
CALL p2(NEW.i1);
END//
UPDATE t1 SET i1 = 11 WHERE i1 = 3;
DROP TRIGGER t1_bu;
DROP PROCEDURE p2;
DROP PROCEDURE p1;
INSERT INTO t1 VALUES (13);
CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET @a = 17;
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW
CALL p1(OLD.i1);
UPDATE t1 SET i1 = 19 WHERE i1 = 13;
ERROR 42000: OUT or INOUT argument 1 for routine test.p1 is not a variable or NEW pseudo-variable in BEFORE trigger
DROP TRIGGER t1_bu;
DROP PROCEDURE p1;
INSERT INTO t1 VALUES (23);
CREATE PROCEDURE p1(INOUT i1 INT) DETERMINISTIC NO SQL SET @a = i1 * 29;
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW
CALL p1(OLD.i1);
UPDATE t1 SET i1 = 31 WHERE i1 = 23;
ERROR 42000: OUT or INOUT argument 1 for routine test.p1 is not a variable or NEW pseudo-variable in BEFORE trigger
DROP TRIGGER t1_bu;
DROP PROCEDURE p1;
INSERT INTO t1 VALUES (37);
CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET @a = 41;
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW
CALL p1(NEW.i1);
UPDATE t1 SET i1 = 43 WHERE i1 = 37;
ERROR 42000: OUT or INOUT argument 1 for routine test.p1 is not a variable or NEW pseudo-variable in BEFORE trigger
DROP TRIGGER t1_au;
DROP PROCEDURE p1;
INSERT INTO t1 VALUES (47);
CREATE PROCEDURE p1(INOUT i1 INT) DETERMINISTIC NO SQL SET @a = i1 * 49;
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW
CALL p1(NEW.i1);
UPDATE t1 SET i1 = 51 WHERE i1 = 47;
ERROR 42000: OUT or INOUT argument 1 for routine test.p1 is not a variable or NEW pseudo-variable in BEFORE trigger
DROP TRIGGER t1_au;
DROP PROCEDURE p1;
SELECT * FROM t1;
i1
35
13
23
43
51
DROP TABLE t1;
...@@ -564,3 +564,176 @@ SELECT @mysqltest_var; ...@@ -564,3 +564,176 @@ SELECT @mysqltest_var;
DROP USER mysqltest_u1@localhost; DROP USER mysqltest_u1@localhost;
DROP DATABASE mysqltest_db1; DROP DATABASE mysqltest_db1;
#
# Test for bug #14635 Accept NEW.x as INOUT parameters to stored
# procedures from within triggers
#
# We require UPDATE privilege when NEW.x passed as OUT parameter, and
# SELECT and UPDATE when NEW.x passed as INOUT parameter.
#
DELETE FROM mysql.user WHERE User LIKE 'mysqltest_%';
DELETE FROM mysql.db WHERE User LIKE 'mysqltest_%';
DELETE FROM mysql.tables_priv WHERE User LIKE 'mysqltest_%';
DELETE FROM mysql.columns_priv WHERE User LIKE 'mysqltest_%';
FLUSH PRIVILEGES;
--disable_warnings
DROP DATABASE IF EXISTS mysqltest_db1;
--enable_warnings
CREATE DATABASE mysqltest_db1;
USE mysqltest_db1;
CREATE TABLE t1 (i1 INT);
CREATE TABLE t2 (i1 INT);
CREATE USER mysqltest_dfn@localhost;
CREATE USER mysqltest_inv@localhost;
GRANT EXECUTE, CREATE ROUTINE, SUPER ON *.* TO mysqltest_dfn@localhost;
GRANT INSERT ON mysqltest_db1.* TO mysqltest_inv@localhost;
connect (definer,localhost,mysqltest_dfn,,mysqltest_db1);
connect (invoker,localhost,mysqltest_inv,,mysqltest_db1);
connection definer;
CREATE PROCEDURE p1(OUT i INT) DETERMINISTIC NO SQL SET i = 3;
CREATE PROCEDURE p2(INOUT i INT) DETERMINISTIC NO SQL SET i = i * 5;
# Check that having no privilege won't work.
connection definer;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW
CALL p2(NEW.i1);
connection invoker;
--error ER_COLUMNACCESS_DENIED_ERROR
INSERT INTO t1 VALUES (7);
--error ER_COLUMNACCESS_DENIED_ERROR
INSERT INTO t2 VALUES (11);
connection definer;
DROP TRIGGER t2_bi;
DROP TRIGGER t1_bi;
# Check that having only SELECT privilege is not enough.
connection default;
GRANT SELECT ON mysqltest_db1.* TO mysqltest_dfn@localhost;
connection definer;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW
CALL p2(NEW.i1);
connection invoker;
--error ER_COLUMNACCESS_DENIED_ERROR
INSERT INTO t1 VALUES (13);
--error ER_COLUMNACCESS_DENIED_ERROR
INSERT INTO t2 VALUES (17);
connection default;
REVOKE SELECT ON mysqltest_db1.* FROM mysqltest_dfn@localhost;
connection definer;
DROP TRIGGER t2_bi;
DROP TRIGGER t1_bi;
# Check that having only UPDATE privilege is enough for OUT parameter,
# but not for INOUT parameter.
connection default;
GRANT UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost;
connection definer;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW
CALL p2(NEW.i1);
connection invoker;
INSERT INTO t1 VALUES (19);
--error ER_COLUMNACCESS_DENIED_ERROR
INSERT INTO t2 VALUES (23);
connection default;
REVOKE UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost;
connection definer;
DROP TRIGGER t2_bi;
DROP TRIGGER t1_bi;
# Check that having SELECT and UPDATE privileges is enough.
connection default;
GRANT SELECT, UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost;
connection definer;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
CREATE TRIGGER t2_bi BEFORE INSERT ON t2 FOR EACH ROW
CALL p2(NEW.i1);
connection invoker;
INSERT INTO t1 VALUES (29);
INSERT INTO t2 VALUES (31);
connection default;
REVOKE SELECT, UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost;
connection definer;
DROP TRIGGER t2_bi;
DROP TRIGGER t1_bi;
connection default;
DROP PROCEDURE p2;
DROP PROCEDURE p1;
# Check that late procedure redefining won't open a security hole.
connection default;
GRANT UPDATE ON mysqltest_db1.* TO mysqltest_dfn@localhost;
connection definer;
CREATE PROCEDURE p1(OUT i INT) DETERMINISTIC NO SQL SET i = 37;
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
CALL p1(NEW.i1);
connection invoker;
INSERT INTO t1 VALUES (41);
connection definer;
DROP PROCEDURE p1;
CREATE PROCEDURE p1(IN i INT) DETERMINISTIC NO SQL SET @v1 = i + 43;
connection invoker;
--error ER_COLUMNACCESS_DENIED_ERROR
INSERT INTO t1 VALUES (47);
connection definer;
DROP PROCEDURE p1;
CREATE PROCEDURE p1(INOUT i INT) DETERMINISTIC NO SQL SET i = i + 51;
connection invoker;
--error ER_COLUMNACCESS_DENIED_ERROR
INSERT INTO t1 VALUES (53);
connection default;
DROP PROCEDURE p1;
REVOKE UPDATE ON mysqltest_db1.* FROM mysqltest_dfn@localhost;
connection definer;
DROP TRIGGER t1_bi;
# Cleanup.
disconnect definer;
disconnect invoker;
connection default;
DROP USER mysqltest_inv@localhost;
DROP USER mysqltest_dfn@localhost;
DROP TABLE t2;
DROP TABLE t1;
DROP DATABASE mysqltest_db1;
USE test;
--echo End of 5.0 tests.
...@@ -1213,4 +1213,78 @@ DROP TRIGGER t1_ai; ...@@ -1213,4 +1213,78 @@ DROP TRIGGER t1_ai;
DROP TRIGGER t1_au; DROP TRIGGER t1_au;
DROP TABLE t1; DROP TABLE t1;
#
# Test for bug #14635 Accept NEW.x as INOUT parameters to stored
# procedures from within triggers
#
--disable_warnings
DROP TABLE IF EXISTS t1;
DROP PROCEDURE IF EXISTS p1;
DROP PROCEDURE IF EXISTS p2;
--enable_warnings
CREATE TABLE t1 (i1 INT);
# Check that NEW.x pseudo variable is accepted as INOUT and OUT
# parameter to stored routine.
INSERT INTO t1 VALUES (3);
CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET i1 = 5;
CREATE PROCEDURE p2(INOUT i1 INT) DETERMINISTIC NO SQL SET i1 = i1 * 7;
delimiter //;
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW
BEGIN
CALL p1(NEW.i1);
CALL p2(NEW.i1);
END//
delimiter ;//
UPDATE t1 SET i1 = 11 WHERE i1 = 3;
DROP TRIGGER t1_bu;
DROP PROCEDURE p2;
DROP PROCEDURE p1;
# Check that OLD.x pseudo variable is not accepted as INOUT and OUT
# parameter to stored routine.
INSERT INTO t1 VALUES (13);
CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET @a = 17;
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW
CALL p1(OLD.i1);
--error ER_SP_NOT_VAR_ARG
UPDATE t1 SET i1 = 19 WHERE i1 = 13;
DROP TRIGGER t1_bu;
DROP PROCEDURE p1;
INSERT INTO t1 VALUES (23);
CREATE PROCEDURE p1(INOUT i1 INT) DETERMINISTIC NO SQL SET @a = i1 * 29;
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW
CALL p1(OLD.i1);
--error ER_SP_NOT_VAR_ARG
UPDATE t1 SET i1 = 31 WHERE i1 = 23;
DROP TRIGGER t1_bu;
DROP PROCEDURE p1;
# Check that NEW.x pseudo variable is read-only in the AFTER TRIGGER.
INSERT INTO t1 VALUES (37);
CREATE PROCEDURE p1(OUT i1 INT) DETERMINISTIC NO SQL SET @a = 41;
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW
CALL p1(NEW.i1);
--error ER_SP_NOT_VAR_ARG
UPDATE t1 SET i1 = 43 WHERE i1 = 37;
DROP TRIGGER t1_au;
DROP PROCEDURE p1;
INSERT INTO t1 VALUES (47);
CREATE PROCEDURE p1(INOUT i1 INT) DETERMINISTIC NO SQL SET @a = i1 * 49;
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW
CALL p1(NEW.i1);
--error ER_SP_NOT_VAR_ARG
UPDATE t1 SET i1 = 51 WHERE i1 = 47;
DROP TRIGGER t1_au;
DROP PROCEDURE p1;
# Post requisite.
SELECT * FROM t1;
DROP TABLE t1;
# End of 5.0 tests # End of 5.0 tests
...@@ -958,6 +958,12 @@ void Item_splocal::print(String *str) ...@@ -958,6 +958,12 @@ void Item_splocal::print(String *str)
} }
bool Item_splocal::set_value(THD *thd, sp_rcontext *ctx, Item *it)
{
return ctx->set_variable(thd, get_var_idx(), it);
}
/***************************************************************************** /*****************************************************************************
Item_case_expr methods Item_case_expr methods
*****************************************************************************/ *****************************************************************************/
...@@ -5365,6 +5371,25 @@ bool Item_trigger_field::eq(const Item *item, bool binary_cmp) const ...@@ -5365,6 +5371,25 @@ bool Item_trigger_field::eq(const Item *item, bool binary_cmp) const
} }
void Item_trigger_field::set_required_privilege(const bool rw)
{
/*
Require SELECT and UPDATE privilege if this field will be read and
set, and only UPDATE privilege for setting the field.
*/
want_privilege= (rw ? SELECT_ACL | UPDATE_ACL : UPDATE_ACL);
}
bool Item_trigger_field::set_value(THD *thd, sp_rcontext */*ctx*/, Item *it)
{
Item *item= sp_prepare_func_item(thd, &it);
return (!item || (!fixed && fix_fields(thd, 0)) ||
(item->save_in_field(field, 0) < 0));
}
bool Item_trigger_field::fix_fields(THD *thd, Item **items) bool Item_trigger_field::fix_fields(THD *thd, Item **items)
{ {
/* /*
...@@ -5387,8 +5412,7 @@ bool Item_trigger_field::fix_fields(THD *thd, Item **items) ...@@ -5387,8 +5412,7 @@ bool Item_trigger_field::fix_fields(THD *thd, Item **items)
if (table_grants) if (table_grants)
{ {
table_grants->want_privilege= table_grants->want_privilege= want_privilege;
access_type == AT_READ ? SELECT_ACL : UPDATE_ACL;
if (check_grant_column(thd, table_grants, triggers->table->s->db, if (check_grant_column(thd, table_grants, triggers->table->s->db,
triggers->table->s->table_name, field_name, triggers->table->s->table_name, field_name,
...@@ -5420,6 +5444,7 @@ void Item_trigger_field::print(String *str) ...@@ -5420,6 +5444,7 @@ void Item_trigger_field::print(String *str)
void Item_trigger_field::cleanup() void Item_trigger_field::cleanup()
{ {
want_privilege= original_privilege;
/* /*
Since special nature of Item_trigger_field we should not do most of Since special nature of Item_trigger_field we should not do most of
things from Item_field::cleanup() or Item_ident::cleanup() here. things from Item_field::cleanup() or Item_ident::cleanup() here.
......
...@@ -372,6 +372,42 @@ public: ...@@ -372,6 +372,42 @@ public:
/*************************************************************************/ /*************************************************************************/
class sp_rcontext;
class Settable_routine_parameter
{
public:
/*
Set required privileges for accessing the parameter.
SYNOPSIS
set_required_privilege()
rw if 'rw' is true then we are going to read and set the
parameter, so SELECT and UPDATE privileges might be
required, otherwise we only reading it and SELECT
privilege might be required.
*/
virtual void set_required_privilege(bool rw) {};
/*
Set parameter value.
SYNOPSIS
set_value()
thd thread handle
ctx context to which parameter belongs (if it is local
variable).
it item which represents new value
RETURN
FALSE if parameter value has been set,
TRUE if error has occured.
*/
virtual bool set_value(THD *thd, sp_rcontext *ctx, Item *it)= 0;
};
typedef bool (Item::*Item_processor)(byte *arg); typedef bool (Item::*Item_processor)(byte *arg);
typedef Item* (Item::*Item_transformer) (byte *arg); typedef Item* (Item::*Item_transformer) (byte *arg);
typedef void (*Cond_traverser) (const Item *item, void *arg); typedef void (*Cond_traverser) (const Item *item, void *arg);
...@@ -744,6 +780,15 @@ public: ...@@ -744,6 +780,15 @@ public:
} }
virtual bool is_splocal() { return 0; } /* Needed for error checking */ virtual bool is_splocal() { return 0; } /* Needed for error checking */
/*
Return Settable_routine_parameter interface of the Item. Return 0
if this Item is not Settable_routine_parameter.
*/
virtual Settable_routine_parameter *get_settable_routine_parameter()
{
return 0;
}
}; };
...@@ -842,7 +887,8 @@ inline bool Item_sp_variable::send(Protocol *protocol, String *str) ...@@ -842,7 +887,8 @@ inline bool Item_sp_variable::send(Protocol *protocol, String *str)
runtime. runtime.
*****************************************************************************/ *****************************************************************************/
class Item_splocal :public Item_sp_variable class Item_splocal :public Item_sp_variable,
private Settable_routine_parameter
{ {
uint m_var_idx; uint m_var_idx;
...@@ -880,6 +926,15 @@ public: ...@@ -880,6 +926,15 @@ public:
inline enum Type type() const; inline enum Type type() const;
inline Item_result result_type() const; inline Item_result result_type() const;
private:
bool set_value(THD *thd, sp_rcontext *ctx, Item *it);
public:
Settable_routine_parameter *get_settable_routine_parameter()
{
return this;
}
}; };
/***************************************************************************** /*****************************************************************************
...@@ -2100,14 +2155,13 @@ class Table_triggers_list; ...@@ -2100,14 +2155,13 @@ class Table_triggers_list;
two Field instances representing either OLD or NEW version of this two Field instances representing either OLD or NEW version of this
field. field.
*/ */
class Item_trigger_field : public Item_field class Item_trigger_field : public Item_field,
private Settable_routine_parameter
{ {
public: public:
/* Is this item represents row from NEW or OLD row ? */ /* Is this item represents row from NEW or OLD row ? */
enum row_version_type {OLD_ROW, NEW_ROW}; enum row_version_type {OLD_ROW, NEW_ROW};
row_version_type row_version; row_version_type row_version;
/* Is this item used for reading or updating the value? */
enum access_types { AT_READ = 0x1, AT_UPDATE = 0x2 };
/* Next in list of all Item_trigger_field's in trigger */ /* Next in list of all Item_trigger_field's in trigger */
Item_trigger_field *next_trg_field; Item_trigger_field *next_trg_field;
/* Index of the field in the TABLE::field array */ /* Index of the field in the TABLE::field array */
...@@ -2118,11 +2172,11 @@ public: ...@@ -2118,11 +2172,11 @@ public:
Item_trigger_field(Name_resolution_context *context_arg, Item_trigger_field(Name_resolution_context *context_arg,
row_version_type row_ver_arg, row_version_type row_ver_arg,
const char *field_name_arg, const char *field_name_arg,
access_types access_type_arg) ulong priv, const bool ro)
:Item_field(context_arg, :Item_field(context_arg,
(const char *)NULL, (const char *)NULL, field_name_arg), (const char *)NULL, (const char *)NULL, field_name_arg),
row_version(row_ver_arg), field_idx((uint)-1), row_version(row_ver_arg), field_idx((uint)-1), original_privilege(priv),
access_type(access_type_arg), table_grants(NULL) want_privilege(priv), table_grants(NULL), read_only (ro)
{} {}
void setup_field(THD *thd, TABLE *table, GRANT_INFO *table_grant_info); void setup_field(THD *thd, TABLE *table, GRANT_INFO *table_grant_info);
enum Type type() const { return TRIGGER_FIELD_ITEM; } enum Type type() const { return TRIGGER_FIELD_ITEM; }
...@@ -2133,8 +2187,39 @@ public: ...@@ -2133,8 +2187,39 @@ public:
void cleanup(); void cleanup();
private: private:
access_types access_type; void set_required_privilege(const bool rw);
bool set_value(THD *thd, sp_rcontext *ctx, Item *it);
public:
Settable_routine_parameter *get_settable_routine_parameter()
{
return (read_only ? 0 : this);
}
bool set_value(THD *thd, Item *it)
{
return set_value(thd, NULL, it);
}
private:
/*
'want_privilege' holds privileges required to perform operation on
this trigger field (SELECT_ACL if we are going to read it and
UPDATE_ACL if we are going to update it). It is initialized at
parse time but can be updated later if this trigger field is used
as OUT or INOUT parameter of stored routine (in this case
set_required_privilege() is called to appropriately update
want_privilege and cleanup() is responsible for restoring of
original want_privilege once parameter's value is updated).
*/
ulong original_privilege;
ulong want_privilege;
GRANT_INFO *table_grants; GRANT_INFO *table_grants;
/*
Trigger field is read-only unless it belongs to the NEW row in a
BEFORE INSERT of BEFORE UPDATE trigger.
*/
bool read_only;
}; };
......
...@@ -4103,6 +4103,18 @@ bool Item_func_get_user_var::eq(const Item *item, bool binary_cmp) const ...@@ -4103,6 +4103,18 @@ bool Item_func_get_user_var::eq(const Item *item, bool binary_cmp) const
} }
bool Item_func_get_user_var::set_value(THD *thd,
sp_rcontext */*ctx*/, Item *it)
{
Item_func_set_user_var *suv= new Item_func_set_user_var(get_name(), it);
/*
Item_func_set_user_var is not fixed after construction, call
fix_fields().
*/
return (!suv || suv->fix_fields(thd, &it) || suv->check() || suv->update());
}
bool Item_user_var_as_out_param::fix_fields(THD *thd, Item **ref) bool Item_user_var_as_out_param::fix_fields(THD *thd, Item **ref)
{ {
DBUG_ASSERT(fixed == 0); DBUG_ASSERT(fixed == 0);
......
...@@ -1178,7 +1178,8 @@ public: ...@@ -1178,7 +1178,8 @@ public:
}; };
class Item_func_get_user_var :public Item_func class Item_func_get_user_var :public Item_func,
private Settable_routine_parameter
{ {
user_var_entry *var_entry; user_var_entry *var_entry;
...@@ -1205,6 +1206,15 @@ public: ...@@ -1205,6 +1206,15 @@ public:
table_map used_tables() const table_map used_tables() const
{ return const_item() ? 0 : RAND_TABLE_BIT; } { return const_item() ? 0 : RAND_TABLE_BIT; }
bool eq(const Item *item, bool binary_cmp) const; bool eq(const Item *item, bool binary_cmp) const;
private:
bool set_value(THD *thd, sp_rcontext *ctx, Item *it);
public:
Settable_routine_parameter *get_settable_routine_parameter()
{
return this;
}
}; };
......
...@@ -5475,7 +5475,7 @@ ER_SP_DUP_HANDLER 42000 ...@@ -5475,7 +5475,7 @@ ER_SP_DUP_HANDLER 42000
eng "Duplicate handler declared in the same block" eng "Duplicate handler declared in the same block"
ger "Doppelter Handler im selben Block deklariert" ger "Doppelter Handler im selben Block deklariert"
ER_SP_NOT_VAR_ARG 42000 ER_SP_NOT_VAR_ARG 42000
eng "OUT or INOUT argument %d for routine %s is not a variable" eng "OUT or INOUT argument %d for routine %s is not a variable or NEW pseudo-variable in BEFORE trigger"
ger "OUT- oder INOUT-Argument %d fr Routine %s ist keine Variable" ger "OUT- oder INOUT-Argument %d fr Routine %s ist keine Variable"
ER_SP_NO_RETSET 0A000 ER_SP_NO_RETSET 0A000
eng "Not allowed to return a result set from a %s" eng "Not allowed to return a result set from a %s"
......
...@@ -1380,19 +1380,6 @@ err_with_cleanup: ...@@ -1380,19 +1380,6 @@ err_with_cleanup:
} }
static Item_func_get_user_var *item_is_user_var(Item *it)
{
if (it->type() == Item::FUNC_ITEM)
{
Item_func *fi= static_cast<Item_func*>(it);
if (fi->functype() == Item_func::GUSERVAR_FUNC)
return static_cast<Item_func_get_user_var*>(fi);
}
return NULL;
}
/* /*
Execute a procedure. Execute a procedure.
SYNOPSIS SYNOPSIS
...@@ -1468,22 +1455,28 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) ...@@ -1468,22 +1455,28 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
for (uint i= 0 ; i < params ; i++) for (uint i= 0 ; i < params ; i++)
{ {
Item *arg_item= it_args++; Item *arg_item= it_args++;
sp_variable_t *spvar= m_pcont->find_variable(i);
if (!arg_item) if (!arg_item)
break; break;
sp_variable_t *spvar= m_pcont->find_variable(i);
if (!spvar) if (!spvar)
continue; continue;
if (spvar->mode != sp_param_in) if (spvar->mode != sp_param_in)
{ {
if (!arg_item->is_splocal() && !item_is_user_var(arg_item)) Settable_routine_parameter *srp=
arg_item->get_settable_routine_parameter();
if (!srp)
{ {
my_error(ER_SP_NOT_VAR_ARG, MYF(0), i+1, m_qname.str); my_error(ER_SP_NOT_VAR_ARG, MYF(0), i+1, m_qname.str);
err_status= TRUE; err_status= TRUE;
break; break;
} }
srp->set_required_privilege(spvar->mode == sp_param_inout);
} }
if (spvar->mode == sp_param_out) if (spvar->mode == sp_param_out)
...@@ -1551,37 +1544,17 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) ...@@ -1551,37 +1544,17 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
if (spvar->mode == sp_param_in) if (spvar->mode == sp_param_in)
continue; continue;
if (arg_item->is_splocal()) Settable_routine_parameter *srp=
{ arg_item->get_settable_routine_parameter();
if (octx->set_variable(thd,
((Item_splocal*) arg_item)->get_var_idx(),
nctx->get_item(i)))
{
err_status= TRUE;
break;
}
}
else
{
Item_func_get_user_var *guv= item_is_user_var(arg_item);
if (guv) DBUG_ASSERT(srp);
{
Item *item= nctx->get_item(i);
Item_func_set_user_var *suv;
suv= new Item_func_set_user_var(guv->get_name(), item); if (srp->set_value(thd, octx, nctx->get_item(i)))
/* {
Item_func_set_user_var is not fixed after construction, err_status= TRUE;
call fix_fields().
*/
if ((err_status= test(!suv || suv->fix_fields(thd, &item) ||
suv->check() || suv->update())))
break; break;
} }
} }
}
} }
if (!save_spcont) if (!save_spcont)
...@@ -2414,12 +2387,7 @@ sp_instr_set_trigger_field::execute(THD *thd, uint *nextp) ...@@ -2414,12 +2387,7 @@ sp_instr_set_trigger_field::execute(THD *thd, uint *nextp)
int int
sp_instr_set_trigger_field::exec_core(THD *thd, uint *nextp) sp_instr_set_trigger_field::exec_core(THD *thd, uint *nextp)
{ {
int res= 0; const int res= (trigger_field->set_value(thd, value) ? -1 : 0);
Item *it= sp_prepare_func_item(thd, &value);
if (!it ||
!trigger_field->fixed && trigger_field->fix_fields(thd, 0) ||
(it->save_in_field(trigger_field->field, 0) < 0))
res= -1;
*nextp = m_ip+1; *nextp = m_ip+1;
return res; return res;
} }
......
...@@ -7216,12 +7216,18 @@ simple_ident_q: ...@@ -7216,12 +7216,18 @@ simple_ident_q:
YYABORT; YYABORT;
} }
DBUG_ASSERT(!new_row ||
(lex->trg_chistics.event == TRG_EVENT_INSERT ||
lex->trg_chistics.event == TRG_EVENT_UPDATE));
const bool read_only=
!(new_row && lex->trg_chistics.action_time == TRG_ACTION_BEFORE);
if (!(trg_fld= new Item_trigger_field(Lex->current_context(), if (!(trg_fld= new Item_trigger_field(Lex->current_context(),
new_row ? new_row ?
Item_trigger_field::NEW_ROW: Item_trigger_field::NEW_ROW:
Item_trigger_field::OLD_ROW, Item_trigger_field::OLD_ROW,
$3.str, $3.str,
Item_trigger_field::AT_READ))) SELECT_ACL,
read_only)))
YYABORT; YYABORT;
/* /*
...@@ -7857,11 +7863,13 @@ sys_option_value: ...@@ -7857,11 +7863,13 @@ sys_option_value:
it= new Item_null(); it= new Item_null();
} }
DBUG_ASSERT(lex->trg_chistics.action_time == TRG_ACTION_BEFORE &&
(lex->trg_chistics.event == TRG_EVENT_INSERT ||
lex->trg_chistics.event == TRG_EVENT_UPDATE));
if (!(trg_fld= new Item_trigger_field(Lex->current_context(), if (!(trg_fld= new Item_trigger_field(Lex->current_context(),
Item_trigger_field::NEW_ROW, Item_trigger_field::NEW_ROW,
$2.base_name.str, $2.base_name.str,
Item_trigger_field::AT_UPDATE) UPDATE_ACL, FALSE)) ||
) ||
!(sp_fld= new sp_instr_set_trigger_field(lex->sphead-> !(sp_fld= new sp_instr_set_trigger_field(lex->sphead->
instructions(), instructions(),
lex->spcont, lex->spcont,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment