From 9c6c44835321a6331cd68080327899349083b25a Mon Sep 17 00:00:00 2001 From: AKhatskevich <avkhatskevich@tarantool.org> Date: Mon, 8 Oct 2018 19:26:59 +0300 Subject: [PATCH] sql: fix fk set null clause After changing behavior of the `IS` operator (#b3a3ddb571), `SET NULL` was rewritten to use `EQ` instead. Which doesn't respect NULLs. This commit fixes the null related behavior by emitting logical constructions equivalent for this case to old `IS`. The new expression works differently than old `IS` for nulls, however the difference doesn't change anything, because matched rows are then searched in a child table with `EQ` expression which do not match nulls. Before: `oldval` old_is `newval` Now: `oldval` is_null or (`newval` is_not_null and `oldval` eq `newval`) Closes #3645 --- src/box/sql/fkey.c | 43 +++++++++++++++++++++++++------------ test/sql-tap/fkey1.test.lua | 25 ++++++++++++++++++++- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c index 091778fc85..56885e448f 100644 --- a/src/box/sql/fkey.c +++ b/src/box/sql/fkey.c @@ -787,24 +787,39 @@ fkey_action_trigger(struct Parse *pParse, struct Table *pTab, struct fkey *fkey, /* * For ON UPDATE, construct the next term of the - * WHEN clause. The final WHEN clause will be like + * WHEN clause, which should return false in case + * there is a reason to for a broken constrant in + * a parent table: + * no_action_needed := `oldval` IS NULL OR + * (`newval` IS NOT NULL AND + * `newval` = `oldval`) + * + * The final WHEN clause will be like * this: * - * WHEN NOT(old.col1 = new.col1 AND ... AND - * old.colN = new.colN) + * WHEN NOT( no_action_needed(col1) AND ... + * no_action_needed(colN)) */ if (is_update) { - struct Expr *l, *r; - l = sqlite3PExpr(pParse, TK_DOT, - sqlite3ExprAlloc(db, TK_ID, &t_old, 0), - sqlite3ExprAlloc(db, TK_ID, &t_to_col, - 0)); - r = sqlite3PExpr(pParse, TK_DOT, - sqlite3ExprAlloc(db, TK_ID, &t_new, 0), - sqlite3ExprAlloc(db, TK_ID, &t_to_col, - 0)); - eq = sqlite3PExpr(pParse, TK_EQ, l, r); - when = sqlite3ExprAnd(db, when, eq); + struct Expr *old_val = sqlite3PExpr(pParse, TK_DOT, + sqlite3ExprAlloc(db, TK_ID, &t_old, 0), + sqlite3ExprAlloc(db, TK_ID, &t_to_col, 0)); + struct Expr *new_val = sqlite3PExpr(pParse, TK_DOT, + sqlite3ExprAlloc(db, TK_ID, &t_new, 0), + sqlite3ExprAlloc(db, TK_ID, &t_to_col, 0)); + struct Expr *old_is_null = sqlite3PExpr( + pParse, TK_ISNULL, + sqlite3ExprDup(db, old_val, 0), NULL); + eq = sqlite3PExpr(pParse, TK_EQ, old_val, + sqlite3ExprDup(db, new_val, 0)); + struct Expr *new_non_null = + sqlite3PExpr(pParse, TK_NOTNULL, new_val, NULL); + struct Expr *non_null_eq = + sqlite3PExpr(pParse, TK_AND, new_non_null, eq); + struct Expr *no_action_needed = + sqlite3PExpr(pParse, TK_OR, old_is_null, + non_null_eq); + when = sqlite3ExprAnd(db, when, no_action_needed); } if (action != FKEY_ACTION_RESTRICT && diff --git a/test/sql-tap/fkey1.test.lua b/test/sql-tap/fkey1.test.lua index 3c29b097df..b419e4d591 100755 --- a/test/sql-tap/fkey1.test.lua +++ b/test/sql-tap/fkey1.test.lua @@ -1,6 +1,6 @@ #!/usr/bin/env tarantool test = require("sqltester") -test:plan(18) +test:plan(25) -- This file implements regression tests for foreign keys. @@ -233,4 +233,27 @@ test:do_execsql_test( -- </fkey1-6.3> }) +-- gh-3645: update col=Null do not activates ON UPDATE trigger. + +test:do_select_tests( + "fkey1-7", + { + {"0", + [[ + CREATE TABLE T12 (A INTEGER PRIMARY KEY, + B CHAR(5) UNIQUE); + CREATE TABLE T13 (A INTEGER PRIMARY KEY, + B CHAR(5) UNIQUE, + FOREIGN KEY (B) REFERENCES T12 (B) ON UPDATE SET NULL); + INSERT INTO T12 VALUES (1,'a'); + INSERT INTO T13 VALUES (1,'a'); + ]], {}}, + {"1", "UPDATE T12 SET B = NULL", {}}, + {"2", "SELECT * FROM T12", {1, ""}}, + {"3", "SELECT * FROM T13", {1, ""}}, + {"4", "UPDATE T12 SET B = 'a'", {}}, + {"5", "SELECT * FROM T12", {1, "a"}}, + {"6", "SELECT * FROM T13", {1, ""}}, + }) + test:finish_test() -- GitLab