[tarantool-patches] Re: [PATCH] sql: fix fk set null clause

n.pettik korablev at tarantool.org
Tue Oct 9 22:14:37 MSK 2018


> After changing behavior of the `IS` operator (#b3a3ddb571),
> `SET NULL` was rewritten to use `EQ` instead. Which do not respect

Nit: doesn’t.

> NULLs.
> 
> This commit fixes the null related behavior by emitting logical
> constructions equivalent for this case to old `IS`.
> Those constructions are not equal to the old `EQ` in common case.
> Before:
> `oldval` old_is `newval`
> Now:
> `oldval` is_null or (`newval` is_not_null and `oldval` eq `newval`)

I guess it is not completely correct: if oldval == NULL and newval != NULL,
than ‘before’ condition will be false, but ’now’ will be true. Did I miss smth?

> 
> Closes #3645
> ---
> Issue: https://github.com/tarantool/tarantool/issues/3645
> Branch: https://github.com/tarantool/tarantool/tree/kh/gh-3642-set-null
> 
> src/box/sql/fkey.c                    | 49 ++++++++++++++------
> test/sql-tap/gh3645-set-null.test.lua | 84 +++++++++++++++++++++++++++++++++++
> 2 files changed, 119 insertions(+), 14 deletions(-)
> create mode 100755 test/sql-tap/gh3645-set-null.test.lua
> 
> diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
> index 091778fc8..0abf42c84 100644
> --- a/src/box/sql/fkey.c
> +++ b/src/box/sql/fkey.c
> @@ -787,24 +787,45 @@ 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);
> +			Expr *old_val = sqlite3PExpr(

Nit: struct Expr *...

> +				pParse, TK_DOT,
> +				sqlite3ExprAlloc(db, TK_ID, &t_old, 0),
> +				sqlite3ExprAlloc(db, TK_ID, &t_to_col,
> +						 0));
> +			Expr *newVal = sqlite3PExpr(

Nit: struct Expr new_val = …
And use everywhere else ‘struct' prefix.

> +				pParse, TK_DOT,
> +				sqlite3ExprAlloc(db, TK_ID, &t_new, 0),
> +				sqlite3ExprAlloc(db, TK_ID, &t_to_col,
> +						 0));
> +			Expr *old_is_null = sqlite3PExpr(
> +				pParse, TK_ISNULL,
> +				sqlite3ExprDup(db, old_val, 0), NULL);
> +			eq = sqlite3PExpr(
> +				pParse, TK_EQ,
> +				sqlite3ExprDup(db, old_val, 0),
> +				sqlite3ExprDup(db, newVal, 0));
> +			Expr *new_non_null = sqlite3PExpr(
> +				pParse, TK_NOTNULL, newVal, NULL);
> +			Expr *non_null_eq = sqlite3PExpr(
> +				pParse, TK_AND, new_non_null, eq);
> +			Expr *no_action_needed = sqlite3PExpr(
> +				pParse, TK_OR, old_is_null,
> +				non_null_eq);
> +			when = sqlite3ExprAnd(db, when, no_action_needed);

How memory for old_val will be released? It’s twice dupped, but original
value will be not freed. new_val is once dupped and once used, for example.
I guess it looks like leak.

Overall, I suggest cosmetic diff (indentation + struct prefixes):

+++ b/src/box/sql/fkey.c
@@ -801,30 +801,24 @@ fkey_action_trigger(struct Parse *pParse, struct Table *pTab, struct fkey *fkey,
                 *        no_action_needed(colN))
                 */
                if (is_update) {
-                       Expr *old_val = sqlite3PExpr(
-                               pParse, TK_DOT,
+                       struct Expr *old_val = sqlite3PExpr(pParse, TK_DOT,
                                sqlite3ExprAlloc(db, TK_ID, &t_old, 0),
-                               sqlite3ExprAlloc(db, TK_ID, &t_to_col,
-                                                0));
-                       Expr *newVal = sqlite3PExpr(
-                               pParse, TK_DOT,
+                               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));
-                       Expr *old_is_null = sqlite3PExpr(
-                               pParse, TK_ISNULL,
+                               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,
-                               sqlite3ExprDup(db, old_val, 0),
-                               sqlite3ExprDup(db, newVal, 0));
-                       Expr *new_non_null = sqlite3PExpr(
-                               pParse, TK_NOTNULL, newVal, NULL);
-                       Expr *non_null_eq = sqlite3PExpr(
-                               pParse, TK_AND, new_non_null, eq);
-                       Expr *no_action_needed = sqlite3PExpr(
-                               pParse, TK_OR, old_is_null,
-                               non_null_eq);
+                       eq = sqlite3PExpr(pParse, TK_EQ,
+                                         sqlite3ExprDup(db, old_val, 0),
+                                         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);

> 		}
> 
> 		if (action != FKEY_ACTION_RESTRICT &&
> diff --git a/test/sql-tap/gh3645-set-null.test.lua b/test/sql-tap/gh3645-set-null.test.lua
> new file mode 100755
> index 000000000..9067c52f1
> --- /dev/null
> +++ b/test/sql-tap/gh3645-set-null.test.lua

I wouldn’t create separate test-file for this ticket: you could
use one of fkey1-4 test suites.

> @@ -0,0 +1,84 @@
> +#!/usr/bin/env tarantool
> +test = require("sqltester")
> +test:plan(11)
> +
> +test:do_catchsql_test(
> +	"set-null-1.0",
> +	[[
> +		CREATE TABLE T1 (a INTEGER PRIMARY KEY,
> +            b char(5) unique);
> +		CREATE TABLE T2 (a INTEGER PRIMARY KEY,
> +            b char(5) unique,
> +            foreign key (b) references t1 (b) on update set null);

Nit: use uppercase for SQL keywords.

> +        INSERT INTO T1 VALUES (1,'a');
> +        INSERT INTO T2 VALUES (1,'a’);

Nit: indentation is broken.

> +	]], {0});
> +
> +test:do_catchsql_test(
> +	"set-null-1.1",
> +	[[
> +        UPDATE T1 SET B = NULL;
> +	]], {0});
> +
> +test:do_catchsql_test(

Why do you use catchsql? I guess execsql would be more suitable here
and for the rest tests as well (except for UPDATE statements).

> +test:do_catchsql_test(
> +	"set-null-2.0",
> +	[[
> +		CREATE TABLE T3 (a INTEGER PRIMARY KEY,
> +            b char(5) unique);
> +		CREATE TABLE T4 (a INTEGER PRIMARY KEY,
> +            b char(5) unique,
> +            foreign key (b) references t3 (b) on update cascade);
> +        INSERT INTO T3 VALUES (1,'a');
> +        INSERT INTO T4 VALUES (1,'a');
> +	]], {0});

Indentation is broken.
How ‘ON UPDATE CASCADE’ clause is related to ’SET NULL’?






More information about the Tarantool-patches mailing list