diff --git a/src/box/sql/build.c b/src/box/sql/build.c index c39ac281113a279b858776513da285778a1422a0..8a42453c89c1ed05675934417121b77881b04980 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 0c510f565a7a6c318d1b2bae139624043d0cb833..1e3f96ec6e851e0a4457bbc8e47e0d19337713ae 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 07e7829e5e94bacd3bf75bce5dafccca9ce19ab8..62a86fc550bd7df507f005534b5ff100f1ac660c 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 b4772d80e2e96cc276adc2ad0e4db18087abc5e4..eed06d582ba6d0c057f9aeb1832e9116aea2ad63 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 b6d92f7c352bb780b7a3cda4e99f93e9cd9e3516..347245a8c46c07c8b2dd9e1ed3f7e292e4146d14 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))")