From 96a0292282313dbfdcc00258b757e6120a129c21 Mon Sep 17 00:00:00 2001 From: Kirill Shcherbatov <kshcherbatov@tarantool.org> Date: Thu, 12 Jul 2018 18:53:21 +0300 Subject: [PATCH] sql: restrict nullable action definitions This patch dissallows define multiple "NULL", "NOT NULL" options per column and fixes silent implicit behavior for invalid "NULL PRIMARY KEY" construction. Closes #3473. --- src/box/sql/build.c | 74 ++++++++++++++++++++++++++--------- src/box/sql/parse.y | 9 ++++- src/box/sql/sqliteInt.h | 17 +++++++- test/sql/on-conflict.result | 20 ++++++++++ test/sql/on-conflict.test.lua | 8 ++++ 5 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/box/sql/build.c b/src/box/sql/build.c index c39ac28111..8a42453c89 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -653,7 +653,12 @@ sqlite3AddColumn(Parse * pParse, Token * pName, Token * pType) struct field_def *column_def = &p->def->fields[p->def->field_count]; memcpy(column_def, &field_def_default, sizeof(field_def_default)); column_def->name = z; - column_def->nullable_action = ON_CONFLICT_ACTION_NONE; + /* + * Marker on_conflict_action_MAX is used to detect + * attempts to define NULL multiple time or to detect + * invalid primary key definition. + */ + column_def->nullable_action = on_conflict_action_MAX; column_def->is_nullable = true; if (pType->n == 0) { @@ -688,22 +693,30 @@ sqlite3AddColumn(Parse * pParse, Token * pName, Token * pType) pParse->constraintName.n = 0; } -/* - * This routine is called by the parser while in the middle of - * parsing a CREATE TABLE statement. A "NOT NULL" constraint has - * been seen on a column. This routine sets the notNull flag on - * the column currently under construction. - */ void -sqlite3AddNotNull(Parse * pParse, int onError) +sql_column_add_nullable_action(struct Parse *parser, + enum on_conflict_action nullable_action) { - Table *p; - p = pParse->pNewTable; - if (p == 0 || NEVER(p->def->field_count < 1)) + struct Table *p = parser->pNewTable; + if (p == NULL || NEVER(p->def->field_count < 1)) return; - p->def->fields[p->def->field_count - 1].nullable_action = (u8)onError; - p->def->fields[p->def->field_count - 1].is_nullable = - action_is_nullable(onError); + struct field_def *field = &p->def->fields[p->def->field_count - 1]; + if (field->nullable_action != on_conflict_action_MAX && + nullable_action != ON_CONFLICT_ACTION_DEFAULT) { + /* Prevent defining nullable_action many times. */ + const char *err_msg = + tt_sprintf("NULL declaration for column '%s' of table " + "'%s' has been already set to '%s'", + field->name, p->def->name, + on_conflict_action_strs[field-> + nullable_action]); + diag_set(ClientError, ER_SQL, err_msg); + parser->rc = SQL_TARANTOOL_ERROR; + parser->nErr++; + return; + } + field->nullable_action = nullable_action; + field->is_nullable = action_is_nullable(nullable_action); } /* @@ -849,6 +862,23 @@ sqlite3AddDefaultValue(Parse * pParse, ExprSpan * pSpan) sql_expr_delete(db, pSpan->pExpr, false); } +static int +field_def_create_for_pk(struct Parse *parser, struct field_def *field, + const char *space_name) +{ + if (field->nullable_action != ON_CONFLICT_ACTION_ABORT && + field->nullable_action != ON_CONFLICT_ACTION_DEFAULT && + field->nullable_action != on_conflict_action_MAX) { + diag_set(ClientError, ER_NULLABLE_PRIMARY, space_name); + parser->rc = SQL_TARANTOOL_ERROR; + parser->nErr++; + return -1; + } else if (field->nullable_action == on_conflict_action_MAX) { + field->nullable_action = ON_CONFLICT_ACTION_ABORT; + field->is_nullable = false; + } + return 0; +} /* * Designate the PRIMARY KEY for the table. pList is a list of names @@ -944,12 +974,11 @@ sqlite3AddPrimaryKey(Parse * pParse, /* Parsing context */ pList = 0; } - /* Mark every PRIMARY KEY column as NOT NULL. */ + /* Mark every PRIMARY KEY column as NOT NULL. */ for (uint32_t i = 0; i < pTab->def->field_count; i++) { if (pTab->aCol[i].is_primkey) { - pTab->def->fields[i].nullable_action - = ON_CONFLICT_ACTION_ABORT; - pTab->def->fields[i].is_nullable = false; + field_def_create_for_pk(pParse, &pTab->def->fields[i], + pTab->def->name); } } primary_key_exit: @@ -1611,6 +1640,15 @@ sqlite3EndTable(Parse * pParse, /* Parse context */ } } + /* Set default on_nullable action if required. */ + struct field_def *field = p->def->fields; + for (uint32_t i = 0; i < p->def->field_count; ++i, ++field) { + if (field->nullable_action == on_conflict_action_MAX) { + field->nullable_action = ON_CONFLICT_ACTION_NONE; + field->is_nullable = true; + } + } + if (check_on_conflict_replace_entries(p)) { sqlite3ErrorMsg(pParse, "only PRIMARY KEY constraint can " diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y index 0c510f565a..1e3f96ec6e 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -272,8 +272,13 @@ ccons ::= DEFAULT id(X). { // In addition to the type name, we also care about the primary key and // UNIQUE constraints. // -ccons ::= NULL onconf. -ccons ::= NOT NULL onconf(R). {sqlite3AddNotNull(pParse, R);} +ccons ::= NULL onconf(R). { + sql_column_add_nullable_action(pParse, ON_CONFLICT_ACTION_NONE); + /* Trigger nullability mismatch error if required. */ + if (R != ON_CONFLICT_ACTION_DEFAULT) + sql_column_add_nullable_action(pParse, R); +} +ccons ::= NOT NULL onconf(R). {sql_column_add_nullable_action(pParse, R);} ccons ::= PRIMARY KEY sortorder(Z) onconf(R) autoinc(I). {sqlite3AddPrimaryKey(pParse,0,R,I,Z);} ccons ::= UNIQUE onconf(R). {sql_create_index(pParse,0,0,0,R,0,0, diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index 07e7829e5e..62a86fc550 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -3516,7 +3516,22 @@ Table *sqlite3ResultSetOfSelect(Parse *, Select *); Index *sqlite3PrimaryKeyIndex(Table *); void sqlite3StartTable(Parse *, Token *, int); void sqlite3AddColumn(Parse *, Token *, Token *); -void sqlite3AddNotNull(Parse *, int); + +/** + * This routine is called by the parser while in the middle of + * parsing a CREATE TABLE statement. A "NOT NULL" constraint has + * been seen on a column. This routine sets the is_nullable flag + * on the column currently under construction. + * If nullable_action has been already set, this function raises + * an error. + * + * @param parser SQL Parser object. + * @param nullable_action on_conflict_action value. + */ +void +sql_column_add_nullable_action(struct Parse *parser, + enum on_conflict_action nullable_action); + void sqlite3AddPrimaryKey(Parse *, ExprList *, int, int, enum sort_order); /** diff --git a/test/sql/on-conflict.result b/test/sql/on-conflict.result index b4772d80e2..eed06d582b 100644 --- a/test/sql/on-conflict.result +++ b/test/sql/on-conflict.result @@ -99,3 +99,23 @@ box.sql.execute('DROP TABLE t1') box.sql.execute('DROP TABLE t2') --- ... +-- +-- gh-3473: Primary key can't be declared with NULL. +-- +box.sql.execute('CREATE TABLE te17 (s1 INT NULL PRIMARY KEY NOT NULL);') +--- +- error: Primary index of the space 'TE17' can not contain nullable parts +... +box.sql.execute('CREATE TABLE te17 (s1 INT NULL PRIMARY KEY);') +--- +- error: Primary index of the space 'TE17' can not contain nullable parts +... +box.sql.execute("CREATE TABLE test (a int PRIMARY KEY, b int NULL ON CONFLICT IGNORE);") +--- +- error: 'SQL error: NULL declaration for column ''B'' of table ''TEST'' has been + already set to ''none''' +... +box.sql.execute("CREATE TABLE test (a int, b int NULL, c int, PRIMARY KEY(a, b, c))") +--- +- error: Primary index of the space 'TEST' can not contain nullable parts +... diff --git a/test/sql/on-conflict.test.lua b/test/sql/on-conflict.test.lua index b6d92f7c35..347245a8c4 100644 --- a/test/sql/on-conflict.test.lua +++ b/test/sql/on-conflict.test.lua @@ -38,3 +38,11 @@ box.sql.execute('DROP TABLE p') box.sql.execute('DROP TABLE e') box.sql.execute('DROP TABLE t1') box.sql.execute('DROP TABLE t2') + +-- +-- gh-3473: Primary key can't be declared with NULL. +-- +box.sql.execute('CREATE TABLE te17 (s1 INT NULL PRIMARY KEY NOT NULL);') +box.sql.execute('CREATE TABLE te17 (s1 INT NULL PRIMARY KEY);') +box.sql.execute("CREATE TABLE test (a int PRIMARY KEY, b int NULL ON CONFLICT IGNORE);") +box.sql.execute("CREATE TABLE test (a int, b int NULL, c int, PRIMARY KEY(a, b, c))") -- GitLab