Commit 9162fbe4 authored by Dmitry Shulga's avatar Dmitry Shulga

MDEV-34551: Column list in the trigger definition

Added support of the clause `UPDATE OF <columns>` for
BEFORE/AFTER UPDATE triggers. Triggers defined with this clause
are fired and run actions only in case an UPDATE statement affects
any of the listed columns. For columns not specified in the clause
`UPDATE OF <columns>`, an UPDATE statement with such columns as
targets don't result in running a trigger.

Output of SHOW TRIGGERS isn't affected by this task. Output of
the statement SHOW CREATE TRIGGER shows the clause `UPDATE OF <columns>`
if it was specified on trigger creation.

Tests accompany this task don't include tests that checking for cooperation of
the statement LOAD DATA and the clause `UPDATE OF <columns>` for
BEFORE/AFTER UPDATE triggers since the statement LOAD DATA is treated like
the statement INSERT INTO and therefore doesn't fire BEFORE/AFTER UPDATE
triggers.
parent 19cea738
......@@ -2488,3 +2488,157 @@ drop table t1;
#
# End of 10.6 tests
#
#
# MDEV-34551: Column list in the trigger definition
#
CREATE TABLE t1 (a INT, b INT, c INT);
INSERT INTO t1 VALUES (1, 2, 3);
# Test 1. The `OF` clause followed by <column name list> is allowed only
# in definition of FOR UPDATE trigger. Check it below.
CREATE TRIGGER t1_bi BEFORE INSERT OF a ON t1 FOR EACH ROW DO SET @a = 1;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'OF a ON t1 FOR EACH ROW DO SET @a = 1' at line 1
CREATE TRIGGER t1_bi AFTER INSERT OF a ON t1 FOR EACH ROW DO SET @a = 1;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'OF a ON t1 FOR EACH ROW DO SET @a = 1' at line 1
CREATE TRIGGER t1_bi BEFORE DELETE OF a ON t1 FOR EACH ROW DO SET @a = 1;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'OF a ON t1 FOR EACH ROW DO SET @a = 1' at line 1
CREATE TRIGGER t1_bi AFTER DELETE OF a ON t1 FOR EACH ROW DO SET @a = 1;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'OF a ON t1 FOR EACH ROW DO SET @a = 1' at line 1
# Test 2. Check correct handling of the `OF` clause
CREATE TABLE t2 (a_old INT, b_old INT, a_new INT, b_new INT);
CREATE TRIGGER t1_bu BEFORE UPDATE OF a, b ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a, OLD.b, NEW.a, NEW.b);
UPDATE t1 SET a = 10 WHERE a = 1;
# Expected output is the row (1, 2, 10, 2)
SELECT * FROM t2;
a_old b_old a_new b_new
1 2 10 2
TRUNCATE TABLE t2;
UPDATE t1 SET b = 20 WHERE b = 2;
# Expected output is the row (1, 2, 10, 20)
SELECT * FROM t2;
a_old b_old a_new b_new
10 2 10 20
TRUNCATE TABLE t2;
UPDATE t1 SET c = 30 WHERE c = 3;
# Expected empty rowset since the column name `c` is not listed
# in the `OF` clause of the CREATE TRIGGER statement
SELECT * FROM t2;
a_old b_old a_new b_new
TRUNCATE TABLE t2;
# Finally, check that SHOW CREATE TRIGGER displays right information
# about the `OF` clause.
SHOW CREATE TRIGGER t1_bu;
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation Created
t1_bu STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`root`@`localhost` TRIGGER t1_bu BEFORE UPDATE OF a, b ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a, OLD.b, NEW.a, NEW.b) latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci #
DROP TRIGGER t1_bu;
# Test 3. The same checks for AFTER UPDATE trigger.
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 2, 3);
CREATE TRIGGER t1_au AFTER UPDATE OF a, b ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a, OLD.b, NEW.a, NEW.b);
UPDATE t1 SET a = 10 WHERE a = 1;
# Expected output is the row (1, 2, 10, 2)
SELECT * FROM t2;
a_old b_old a_new b_new
1 2 10 2
TRUNCATE TABLE t2;
UPDATE t1 SET b = 20 WHERE b = 2;
# Expected output is the row (1, 2, 10, 20)
SELECT * FROM t2;
a_old b_old a_new b_new
10 2 10 20
TRUNCATE TABLE t2;
UPDATE t1 SET c = 30 WHERE c = 3;
# Expected empty rowset since the column name `c` is not listed
# in the OF clause of the CREATE TRIGGER statement
SELECT * FROM t2;
a_old b_old a_new b_new
# Finally, check that SHOW CREATE TRIGGER displays right information
# about the 'OF' clause.
SHOW CREATE TRIGGER t1_au;
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation Created
t1_au STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`root`@`localhost` TRIGGER t1_au AFTER UPDATE OF a, b ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a, OLD.b, NEW.a, NEW.b) latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci #
# Clean up
DROP TABLE t1, t2;
# Test 4. Multi-update and a FOR UPDATE trigger with the `OF` clause
# Test 4.1 Multi-update statement and BEFORE UPDATE trigger with the `OF` clause
CREATE TABLE t1 (a INT, b INT, c INT);
INSERT INTO t1 VALUES (1, 2, -1), (3, 4, -2);
CREATE TABLE t3 (a INT, b INT, c INT);
CREATE TRIGGER t1_bu BEFORE UPDATE OF b ON t1 FOR EACH ROW INSERT INTO t3 VALUES (NEW.a,NEW.b, NEW.c);
CREATE TABLE t2 (a INT, b INT);
INSERT INTO t2 VALUES (1, 1), (3, 3);
UPDATE t1, t2 SET t1.b = t1.b + 100 WHERE t1.a = t2.a;
SELECT * FROM t1;
a b c
1 102 -1
3 104 -2
# Show trigger's work results. Expected result set is (1, 102, -1), (3, 104, -2)
SELECT * FROM t3;
a b c
1 102 -1
3 104 -2
TRUNCATE TABLE t3;
UPDATE t1, t2 SET t1.c = t1.c + 200 WHERE t1.a = t2.a;
# Show trigger's work results. It's expected that no rows be output
# since the column `c` is not listed in the the `OF` clause.
SELECT * FROM t3;
a b c
# Clean up
DROP TABLE t1, t2, t3;
# Test 4.2 Multi-update statement and AFTER UPDATE trigger with the `OF` clause
CREATE TABLE t1 (a INT, b INT, c INT);
INSERT INTO t1 VALUES (1, 2, -1), (3, 4, -2);
CREATE TABLE t3 (a INT, b INT, c INT);
CREATE TRIGGER t1_au AFTER UPDATE OF b ON t1 FOR EACH ROW INSERT INTO t3 VALUES (NEW.a, NEW.b, NEW.c);
CREATE TABLE t2 (a INT, b INT);
INSERT INTO t2 VALUES (1, 1), (3, 3);
UPDATE t1, t2 SET t1.b = t1.b + 100 WHERE t1.a = t2.a;
SELECT * FROM t1;
a b c
1 102 -1
3 104 -2
# Show trigger's work results. Expected result set is (1, 102, -1), (3, 104, -2)
SELECT * FROM t3;
a b c
1 102 -1
3 104 -2
TRUNCATE TABLE t3;
UPDATE t1, t2 SET t1.c = t1.c + 200 WHERE t1.a = t2.a;
# Show trigger's work results. It's expected that no rows be output
# since the column `c` is not listed in the the `OF` clause.
SELECT * FROM t3;
a b c
# Clean up
DROP TABLE t1, t2, t3;
# Test 5. Check that a FOR UPDATE trigger with the `OF` clause cooperates correctly
# with other FOR UPDATE triggers that precedes or follows this one
CREATE TABLE t1 (a INT, b INT, c INT);
CREATE TABLE t2 (a_old INT, b_old INT, a_new INT, b_new INT);
INSERT INTO t1 VALUES (1, 2, 3);
CREATE TRIGGER t1_bu_0 BEFORE UPDATE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (-1, -1, -1, -1);
CREATE TRIGGER t1_bu BEFORE UPDATE OF a, b ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a, OLD.b, NEW.a, NEW.b);
CREATE TRIGGER t1_bu_1 BEFORE UPDATE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (-2, -2, -2, -2);
UPDATE t1 SET c = 30 WHERE c = 3;
#
# Expected rows in output are (-1, -1, -1, -1), (-2, -2, -2, -2) since
# the trigger t1_bu is not fired because the column `c` not listed at
# the clause `OF <columnd list>` in definition of the trigger t1_bu.
SELECT * FROM t2;
a_old b_old a_new b_new
-1 -1 -1 -1
-2 -2 -2 -2
TRUNCATE TABLE t2;
UPDATE t1 SET b = 200 WHERE b = 2;
#
# Expected rows in output are (-1, -1, -1, -1), (1, 2, 1, 200), (-2, -2, -2, -2)
# since the trigger t1_bu is fired because the column `b` listed at
# the clause `OF <columnd list>` in definition of the trigger t1_bu.
SELECT * FROM t2;
a_old b_old a_new b_new
-1 -1 -1 -1
1 2 1 200
-2 -2 -2 -2
# Clean up
DROP TABLE t1, t2;
#
# End of 11.7 tests
#
......@@ -2835,3 +2835,151 @@ drop table t1;
--echo #
--echo # End of 10.6 tests
--echo #
--echo #
--echo # MDEV-34551: Column list in the trigger definition
--echo #
CREATE TABLE t1 (a INT, b INT, c INT);
INSERT INTO t1 VALUES (1, 2, 3);
--echo # Test 1. The `OF` clause followed by <column name list> is allowed only
--echo # in definition of FOR UPDATE trigger. Check it below.
--error ER_PARSE_ERROR
CREATE TRIGGER t1_bi BEFORE INSERT OF a ON t1 FOR EACH ROW DO SET @a = 1;
--error ER_PARSE_ERROR
CREATE TRIGGER t1_bi AFTER INSERT OF a ON t1 FOR EACH ROW DO SET @a = 1;
--error ER_PARSE_ERROR
CREATE TRIGGER t1_bi BEFORE DELETE OF a ON t1 FOR EACH ROW DO SET @a = 1;
--error ER_PARSE_ERROR
CREATE TRIGGER t1_bi AFTER DELETE OF a ON t1 FOR EACH ROW DO SET @a = 1;
--echo # Test 2. Check correct handling of the `OF` clause
CREATE TABLE t2 (a_old INT, b_old INT, a_new INT, b_new INT);
CREATE TRIGGER t1_bu BEFORE UPDATE OF a, b ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a, OLD.b, NEW.a, NEW.b);
UPDATE t1 SET a = 10 WHERE a = 1;
--echo # Expected output is the row (1, 2, 10, 2)
SELECT * FROM t2;
TRUNCATE TABLE t2;
UPDATE t1 SET b = 20 WHERE b = 2;
--echo # Expected output is the row (1, 2, 10, 20)
SELECT * FROM t2;
TRUNCATE TABLE t2;
UPDATE t1 SET c = 30 WHERE c = 3;
--echo # Expected empty rowset since the column name `c` is not listed
--echo # in the `OF` clause of the CREATE TRIGGER statement
SELECT * FROM t2;
TRUNCATE TABLE t2;
--echo # Finally, check that SHOW CREATE TRIGGER displays right information
--echo # about the `OF` clause.
--replace_column 7 #
SHOW CREATE TRIGGER t1_bu;
DROP TRIGGER t1_bu;
--echo # Test 3. The same checks for AFTER UPDATE trigger.
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1, 2, 3);
CREATE TRIGGER t1_au AFTER UPDATE OF a, b ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a, OLD.b, NEW.a, NEW.b);
UPDATE t1 SET a = 10 WHERE a = 1;
--echo # Expected output is the row (1, 2, 10, 2)
SELECT * FROM t2;
TRUNCATE TABLE t2;
UPDATE t1 SET b = 20 WHERE b = 2;
--echo # Expected output is the row (1, 2, 10, 20)
SELECT * FROM t2;
TRUNCATE TABLE t2;
UPDATE t1 SET c = 30 WHERE c = 3;
--echo # Expected empty rowset since the column name `c` is not listed
--echo # in the OF clause of the CREATE TRIGGER statement
SELECT * FROM t2;
--echo # Finally, check that SHOW CREATE TRIGGER displays right information
--echo # about the 'OF' clause.
--replace_column 7 #
SHOW CREATE TRIGGER t1_au;
--echo # Clean up
DROP TABLE t1, t2;
--echo # Test 4. Multi-update and a FOR UPDATE trigger with the `OF` clause
--echo # Test 4.1 Multi-update statement and BEFORE UPDATE trigger with the `OF` clause
CREATE TABLE t1 (a INT, b INT, c INT);
INSERT INTO t1 VALUES (1, 2, -1), (3, 4, -2);
CREATE TABLE t3 (a INT, b INT, c INT);
CREATE TRIGGER t1_bu BEFORE UPDATE OF b ON t1 FOR EACH ROW INSERT INTO t3 VALUES (NEW.a,NEW.b, NEW.c);
CREATE TABLE t2 (a INT, b INT);
INSERT INTO t2 VALUES (1, 1), (3, 3);
UPDATE t1, t2 SET t1.b = t1.b + 100 WHERE t1.a = t2.a;
SELECT * FROM t1;
--echo # Show trigger's work results. Expected result set is (1, 102, -1), (3, 104, -2)
SELECT * FROM t3;
TRUNCATE TABLE t3;
UPDATE t1, t2 SET t1.c = t1.c + 200 WHERE t1.a = t2.a;
--echo # Show trigger's work results. It's expected that no rows be output
--echo # since the column `c` is not listed in the the `OF` clause.
SELECT * FROM t3;
--echo # Clean up
DROP TABLE t1, t2, t3;
--echo # Test 4.2 Multi-update statement and AFTER UPDATE trigger with the `OF` clause
CREATE TABLE t1 (a INT, b INT, c INT);
INSERT INTO t1 VALUES (1, 2, -1), (3, 4, -2);
CREATE TABLE t3 (a INT, b INT, c INT);
CREATE TRIGGER t1_au AFTER UPDATE OF b ON t1 FOR EACH ROW INSERT INTO t3 VALUES (NEW.a, NEW.b, NEW.c);
CREATE TABLE t2 (a INT, b INT);
INSERT INTO t2 VALUES (1, 1), (3, 3);
UPDATE t1, t2 SET t1.b = t1.b + 100 WHERE t1.a = t2.a;
SELECT * FROM t1;
--echo # Show trigger's work results. Expected result set is (1, 102, -1), (3, 104, -2)
SELECT * FROM t3;
TRUNCATE TABLE t3;
UPDATE t1, t2 SET t1.c = t1.c + 200 WHERE t1.a = t2.a;
--echo # Show trigger's work results. It's expected that no rows be output
--echo # since the column `c` is not listed in the the `OF` clause.
SELECT * FROM t3;
--echo # Clean up
DROP TABLE t1, t2, t3;
--echo # Test 5. Check that a FOR UPDATE trigger with the `OF` clause cooperates correctly
--echo # with other FOR UPDATE triggers that precedes or follows this one
CREATE TABLE t1 (a INT, b INT, c INT);
CREATE TABLE t2 (a_old INT, b_old INT, a_new INT, b_new INT);
INSERT INTO t1 VALUES (1, 2, 3);
CREATE TRIGGER t1_bu_0 BEFORE UPDATE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (-1, -1, -1, -1);
CREATE TRIGGER t1_bu BEFORE UPDATE OF a, b ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a, OLD.b, NEW.a, NEW.b);
CREATE TRIGGER t1_bu_1 BEFORE UPDATE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (-2, -2, -2, -2);
UPDATE t1 SET c = 30 WHERE c = 3;
--echo #
--echo # Expected rows in output are (-1, -1, -1, -1), (-2, -2, -2, -2) since
--echo # the trigger t1_bu is not fired because the column `c` not listed at
--echo # the clause `OF <columnd list>` in definition of the trigger t1_bu.
SELECT * FROM t2;
TRUNCATE TABLE t2;
UPDATE t1 SET b = 200 WHERE b = 2;
--echo #
--echo # Expected rows in output are (-1, -1, -1, -1), (1, 2, 1, 200), (-2, -2, -2, -2)
--echo # since the trigger t1_bu is fired because the column `b` listed at
--echo # the clause `OF <columnd list>` in definition of the trigger t1_bu.
SELECT * FROM t2;
--echo # Clean up
DROP TABLE t1, t2;
--echo #
--echo # End of 11.7 tests
--echo #
......@@ -570,3 +570,55 @@ disconnect con1;
connection default;
USE test;
End of 5.1 tests.
#
# MDEV-34551: Column list in the trigger definition
#
# Check that a FOR UPDATE trigger with the `OF` clause is dumped correctly.
CREATE TABLE t1 (a INT, b INT);
CREATE TRIGGER t1_bu BEFORE UPDATE OF a ON t1 FOR EACH ROW SET @a = NEW.a;
CREATE TRIGGER t1_au AFTER UPDATE OF a ON t1 FOR EACH ROW SET @a = NEW.a;
# dump the table t1 and its triggers. The `OF` clause should be dumped out
# for triggers t1_bu, t1_au.
/*M!999999\- enable the sandbox mode */
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `t1` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = latin1 */ ;
/*!50003 SET character_set_results = latin1 */ ;
/*!50003 SET collation_connection = latin1_swedish_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER t1_bu BEFORE UPDATE OF a ON t1 FOR EACH ROW SET @a = NEW.a */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = latin1 */ ;
/*!50003 SET character_set_results = latin1 */ ;
/*!50003 SET collation_connection = latin1_swedish_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER t1_au AFTER UPDATE OF a ON t1 FOR EACH ROW SET @a = NEW.a */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
# Clean up
DROP TABLE t1;
#
# End of 11.7 tests
#
......@@ -995,3 +995,23 @@ DROP USER mysqltest_u1@localhost;
USE test;
--echo End of 5.1 tests.
--echo #
--echo # MDEV-34551: Column list in the trigger definition
--echo #
--echo # Check that a FOR UPDATE trigger with the `OF` clause is dumped correctly.
CREATE TABLE t1 (a INT, b INT);
CREATE TRIGGER t1_bu BEFORE UPDATE OF a ON t1 FOR EACH ROW SET @a = NEW.a;
CREATE TRIGGER t1_au AFTER UPDATE OF a ON t1 FOR EACH ROW SET @a = NEW.a;
--echo # dump the table t1 and its triggers. The `OF` clause should be dumped out
--echo # for triggers t1_bu, t1_au.
--exec $MYSQL_DUMP --compact test t1
--echo # Clean up
DROP TABLE t1;
--echo #
--echo # End of 11.7 tests
--echo #
......@@ -9255,7 +9255,7 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
thd->bulk_param= nullptr;
if (triggers->process_triggers(thd, event, TRG_ACTION_BEFORE,
TRUE) ||
true, &fields) ||
not_null_fields_have_null_values(table))
{
thd->bulk_param= save_bulk_param;
......
......@@ -1590,6 +1590,11 @@ struct st_trg_chistics: public st_trg_execution_order
const char *ordering_clause_begin;
const char *ordering_clause_end;
/*
List of column names of a table on that the ON UPDATE trigger
must be fired.
*/
List<LEX_CSTRING> *on_update_col_names;
};
enum xa_option_words {XA_NONE, XA_JOIN, XA_RESUME, XA_ONE_PHASE,
......
......@@ -1220,6 +1220,52 @@ bool Trigger::add_to_file_list(void* param_arg)
}
/**
Check that there is a column in ON UPDATE trigger matching with some of
the table's column from UPDATE statement.
@param fields to be updated by the UPDATE statement
@return true in case there is a column in the target table that matches one
of columns specified by a trigger definition or no columns were
specified for the trigger at all, else return false.
*/
bool Trigger::match_updatable_columns(List<Item> &fields)
{
DBUG_ASSERT(event == TRG_EVENT_UPDATE);
/*
No table columns were specified in OF col1, col2 ... colN of
the statement CREATE TRIGGER BEFORE/AFTER UPDATE. It means that this
ON UPDATE trigger can't be fired on every UPDATE statement involving
the target table.
*/
if (!updatable_columns || updatable_columns->is_empty())
return true;
List_iterator_fast<Item> fields_it(fields);
List_iterator_fast<LEX_CSTRING> columns_it(*updatable_columns);
LEX_CSTRING *column_name;
Item_field *field;
/*
Stop search on the first matching of a column taken from UPDATE statement
with any column listed in trigger column list.
*/
while ((field= (Item_field*)fields_it++))
{
while ((column_name= columns_it++))
{
if (field->field_name.streq(*column_name))
return true;
}
}
return false;
}
/**
Deletes the .TRG file for a table.
......@@ -1538,6 +1584,64 @@ bool Table_triggers_list::prepare_record_accessors(TABLE *table)
}
/**
Deep copy of on update columns list created on parsing a trigger definition.
The destination list and its elements are allocated on table's memory root.
@param table_mem_root table mem_root from where a memory is allocated.
@param [out] dst_col_names destination list where to copy an original one
@param src_col_names source list that has to be copied
@return false on success, true on OOM error
*/
static bool
copy_on_update_columns_list(MEM_ROOT *table_mem_root,
List<LEX_CSTRING> **dst_col_names,
List<LEX_CSTRING> *src_col_names)
{
if (!src_col_names)
{
*dst_col_names= nullptr;
return false;
}
/*
In case the clause <OF column_list> is present in definition of a trigger,
the list lex.trg_chistics.on_update_col_names mustn't be empty.
For the case where this clause is missed, the pointer
lex.trg_chistics.on_update_col_names
itself has nullptr value. So, do assert check here that the list is not
empty.
*/
DBUG_ASSERT(!src_col_names->is_empty());
List<LEX_CSTRING> *result= new (table_mem_root) List<LEX_CSTRING>();
if (!result)
return true; // OOM
List_iterator_fast<LEX_CSTRING> columns_it(*src_col_names);
LEX_CSTRING *column_name;
while ((column_name= columns_it++))
{
LEX_CSTRING *cname= (LEX_CSTRING*)alloc_root(table_mem_root,
sizeof(LEX_CSTRING));
if (!cname)
return true; // OOM
*cname= safe_lexcstrdup_root(table_mem_root, *column_name);
if (!cname->str ||
result->push_back(cname, table_mem_root))
return true; // OOM
}
*dst_col_names= result;
return false;
}
/**
Check whenever .TRG file for table exist and load all triggers it contains.
......@@ -1733,6 +1837,11 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
trigger->connection_cl_name= creation_ctx->get_connection_cl()->coll_name;
trigger->db_cl_name= creation_ctx->get_db_cl()->coll_name;
if (copy_on_update_columns_list(&table->mem_root,
&trigger->updatable_columns,
lex.trg_chistics.on_update_col_names))
goto err_with_lex_cleanup;
/* event can only be TRG_EVENT_MAX in case of fatal parse errors */
if (lex.trg_chistics.event != TRG_EVENT_MAX)
trigger_list->add_trigger(lex.trg_chistics.event,
......@@ -2481,7 +2590,8 @@ bool Table_triggers_list::change_table_name(THD *thd,
bool Table_triggers_list::process_triggers(THD *thd,
trg_event_type event,
trg_action_time_type time_type,
bool old_row_is_record1)
bool old_row_is_record1,
List<Item> *fields_in_update_stmt)
{
bool err_status;
Sub_statement_state statement_state;
......@@ -2522,6 +2632,20 @@ bool Table_triggers_list::process_triggers(THD *thd,
do {
thd->lex->current_select= NULL;
/*
For BEFORE UPDATE trigger check that table fields specified by
the UPDATE statement matches with column names defined in FOR UPDATE
trigger definition, if any.
*/
if (event == TRG_EVENT_UPDATE &&
fields_in_update_stmt &&
!trigger->match_updatable_columns(*fields_in_update_stmt))
{
err_status= 0;
continue;
}
err_status=
trigger->body->execute_trigger(thd,
&trigger_table->s->db,
......
......@@ -30,6 +30,8 @@ struct TABLE_LIST;
class Query_tables_list;
typedef struct st_ddl_log_state DDL_LOG_STATE;
#include <sql_list.h>
/** Event on which trigger is invoked. */
enum trg_event_type
{
......@@ -115,7 +117,13 @@ class Trigger :public Sql_alloc
{
public:
Trigger(Table_triggers_list *base_arg, sp_head *code):
base(base_arg), body(code), next(0), action_order(0)
base(base_arg), body(code), next(0),
sql_mode{0},
hr_create_time{(unsigned long long)-1},
event{TRG_EVENT_MAX},
action_time{TRG_ACTION_MAX},
action_order{0},
updatable_columns{nullptr}
{
bzero((char *)&subject_table_grants, sizeof(subject_table_grants));
}
......@@ -141,6 +149,7 @@ class Trigger :public Sql_alloc
trg_event_type event;
trg_action_time_type action_time;
uint action_order;
List<LEX_CSTRING> *updatable_columns;
void get_trigger_info(LEX_CSTRING *stmt, LEX_CSTRING *body,
LEX_STRING *definer);
......@@ -148,6 +157,8 @@ class Trigger :public Sql_alloc
bool change_on_table_name(void* param_arg);
bool change_table_name(void* param_arg);
bool add_to_file_list(void* param_arg);
bool match_updatable_columns(List<Item> &fields);
};
typedef bool (Trigger::*Triggers_processor)(void *arg);
......@@ -248,7 +259,8 @@ class Table_triggers_list: public Sql_alloc
String *stmt_query, DDL_LOG_STATE *ddl_log_state);
bool process_triggers(THD *thd, trg_event_type event,
trg_action_time_type time_type,
bool old_row_is_record1);
bool old_row_is_record1,
List<Item> *fields_in_update_stmt= nullptr);
void empty_lists();
bool create_lists_needed_for_files(MEM_ROOT *root);
bool save_trigger_file(THD *thd, const LEX_CSTRING *db, const LEX_CSTRING *table_name);
......
......@@ -1068,7 +1068,8 @@ bool Sql_cmd_update::update_single_table(THD *thd)
if (table->triggers &&
unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, TRUE)))
TRG_ACTION_AFTER, true,
fields)))
{
error= 1;
thd->bulk_param= save_bulk_param;
......@@ -2392,7 +2393,8 @@ int multi_update::send_data(List<Item> &not_used_values)
}
if (table->triggers &&
unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, TRUE)))
TRG_ACTION_AFTER, true,
fields_for_table[offset])))
DBUG_RETURN(1);
}
else
......@@ -2652,7 +2654,8 @@ int multi_update::do_updates()
goto err2;
if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_BEFORE, TRUE))
TRG_ACTION_BEFORE, true,
fields_for_table[offset]))
goto err2;
if (!can_compare_record || compare_record(table))
......@@ -2712,7 +2715,8 @@ int multi_update::do_updates()
if (table->triggers &&
unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, TRUE)))
TRG_ACTION_AFTER, true,
fields_for_table[offset])))
goto err2;
}
......
......@@ -18189,6 +18189,52 @@ trigger_follows_precedes_clause:
}
;
opt_on_update_cols:
/* empty */
{
Lex->trg_chistics.on_update_col_names= NULL;
}
| OF_SYM on_update_cols
{
if (Lex->trg_chistics.event != TRG_EVENT_UPDATE)
{
thd->parse_error(ER_SYNTAX_ERROR, $1.pos());
MYSQL_YYABORT;
}
}
;
on_update_cols:
ident
{
List<LEX_CSTRING> *col_names_list;
LEX_CSTRING *col_name;
col_names_list=
new (thd->mem_root) List<LEX_CSTRING>;
col_name= (LEX_CSTRING *) thd->memdup(&$1, sizeof(LEX_CSTRING));
if (unlikely(col_names_list == NULL) ||
unlikely(col_name == NULL) ||
unlikely(col_names_list->push_back(col_name, thd->mem_root)))
MYSQL_YYABORT;
Lex->trg_chistics.on_update_col_names= col_names_list;
}
| on_update_cols ',' ident
{
LEX_CSTRING *col_name;
col_name= (LEX_CSTRING *) thd->memdup(&$3, sizeof(LEX_CSTRING));
if (unlikely(col_name == NULL) ||
unlikely(Lex->trg_chistics.on_update_col_names->push_back
(col_name, thd->mem_root)))
MYSQL_YYABORT;
}
;
trigger_tail:
remember_name
opt_if_not_exists
......@@ -18199,15 +18245,16 @@ trigger_tail:
sp_name
trg_action_time
trg_event
opt_on_update_cols
ON
remember_name /* $8 */
{ /* $9 */
remember_name /* $9 */
{ /* $10 */
Lex->raw_trg_on_table_name_begin= YYLIP->get_tok_start();
}
table_ident /* $10 */
table_ident /* $11 */
FOR_SYM
remember_name /* $12 */
{ /* $13 */
remember_name /* $13 */
{ /* $14 */
Lex->raw_trg_on_table_name_end= YYLIP->get_tok_start();
}
EACH_SYM
......@@ -18215,8 +18262,8 @@ trigger_tail:
{
Lex->trg_chistics.ordering_clause_begin= YYLIP->get_cpp_ptr();
}
trigger_follows_precedes_clause /* $17 */
{ /* $18 */
trigger_follows_precedes_clause /* $18 */
{ /* $19 */
LEX *lex= thd->lex;
Lex_input_stream *lip= YYLIP;
......@@ -18224,10 +18271,10 @@ trigger_tail:
my_yyabort_error((ER_SP_NO_RECURSIVE_CREATE, MYF(0), "TRIGGER"));
lex->stmt_definition_begin= $1;
lex->ident.str= $8;
lex->ident.length= $12 - $8;
lex->ident.str= $9;
lex->ident.length= $13 - $9;
lex->spname= $4;
(*static_cast<st_trg_execution_order*>(&lex->trg_chistics))= ($17);
(*static_cast<st_trg_execution_order*>(&lex->trg_chistics))= ($18);
lex->trg_chistics.ordering_clause_end= lip->get_cpp_ptr();
if (unlikely(!lex->make_sp_head(thd, $4, &sp_handler_trigger,
......@@ -18236,8 +18283,8 @@ trigger_tail:
lex->sphead->set_body_start(thd, lip->get_cpp_tok_start());
}
sp_proc_stmt /* $19 */ force_lookahead /* $20 */
{ /* $21 */
sp_proc_stmt /* $20 */ force_lookahead /* $21 */
{ /* $22 */
LEX *lex= Lex;
lex->sql_command= SQLCOM_CREATE_TRIGGER;
......@@ -18250,7 +18297,7 @@ trigger_tail:
lex->query_tables can be wiped out.
*/
if (!lex->first_select_lex()->
add_table_to_list(thd, $10, (LEX_CSTRING*) 0,
add_table_to_list(thd, $11, (LEX_CSTRING*) 0,
TL_OPTION_UPDATING, TL_READ_NO_INSERT,
MDL_SHARED_NO_WRITE))
MYSQL_YYABORT;
......
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