diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c index 1ec153815bd2f6d22b2897950d5ab20924406273..705caf1ca7c9d8d11a6ab1ca4056119353315353 100644 --- a/extra/mkkeywordhash.c +++ b/extra/mkkeywordhash.c @@ -281,6 +281,7 @@ static Keyword aKeywordTable[] = { { "VARCHAR", "TK_ID", RESERVED, true }, { "WHENEVER", "TK_STANDARD", RESERVED, true }, { "WHILE", "TK_STANDARD", RESERVED, true }, + { "TRUNCATE", "TK_TRUNCATE", ALWAYS, true }, }; /* Number of keywords */ diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c index 06811778f26979e1514137588d9cb99fb8432cb8..c9d237fdf6d78ef3c5d7380715990ab52643ba68 100644 --- a/src/box/sql/delete.c +++ b/src/box/sql/delete.c @@ -71,6 +71,50 @@ sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where, sql_select_delete(db, select); } +void +sql_table_truncate(struct Parse *parse, struct SrcList *tab_list) +{ + assert(tab_list->nSrc == 1); + + struct Vdbe *v = sqlite3GetVdbe(parse); + if (v == NULL) + goto cleanup; + + const char *tab_name = tab_list->a->zName; + uint32_t space_id = box_space_id_by_name(tab_name, strlen(tab_name)); + if (space_id == BOX_ID_NIL) { + diag_set(ClientError, ER_NO_SUCH_SPACE, tab_name); + goto tarantool_error; + } + struct space *space = space_cache_find(space_id); + assert(space != NULL); + + struct Table *table = sqlite3LocateTable(parse, LOCATE_NOERR, tab_name); + if (table != NULL && sqlite3FkReferences(table) != NULL) { + const char *err_msg = + tt_sprintf("can not truncate space '%s' because other " + "objects depend on it", space->def->name); + diag_set(ClientError, ER_SQL, err_msg); + goto tarantool_error; + } + if (space->def->opts.is_view) { + const char *err_msg = + tt_sprintf("can not truncate space '%s' because it is " + "a view", space->def->name); + diag_set(ClientError, ER_SQL, err_msg); + goto tarantool_error; + } + sqlite3VdbeAddOp2(v, OP_Clear, space->def->id, true); +cleanup: + sqlite3SrcListDelete(parse->db, tab_list); + return; + +tarantool_error: + parse->rc = SQL_TARANTOOL_ERROR; + parse->nErr++; + goto cleanup; +} + void sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, struct Expr *where) diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y index 71518e0a0ec1f72ce0d53e08f06066cbf3e74e30..0e50d21fb21efd28bc319e421b7554f48c768483 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -733,6 +733,12 @@ cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). { sql_table_delete_from(pParse,X,W); } +/////////////////////////// The TRUNCATE statement ///////////////////////////// +// +cmd ::= TRUNCATE TABLE fullname(X). { + sql_table_truncate(pParse, X); +} + %type where_opt {Expr*} %destructor where_opt {sql_expr_delete(pParse->db, $$, false);} diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index 8cc2288a391eda1dcedf98ad31338918a050010a..5ca91b2e0c2517e7b03ff46c1822dee447d0b77c 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.h @@ -3719,6 +3719,15 @@ void sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list, struct Expr *where); +/** + * Generate a code for TRUNCATE TABLE statement. + * + * @param parse Parsing context. + * @param tab_list List of single table to truncate. + */ +void +sql_table_truncate(struct Parse *parse, struct SrcList *tab_list); + void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *, enum on_conflict_action); WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *, diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 1a75f77ebc2894075c3d3f978c5eefff745e3f3f..dc22c26933860a8599da24870ea67f47e71e3998 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -39,6 +39,7 @@ * in this file for details. If in doubt, do not deviate from existing * commenting and indentation practices when changing or adding code. */ +#include "box/box.h" #include "box/txn.h" #include "box/session.h" #include "sqliteInt.h" @@ -4562,8 +4563,9 @@ case OP_IdxGE: { /* jump */ break; } -/* Opcode: Clear P1 * * * * +/* Opcode: Clear P1 P2 * * * * Synopsis: space id = P1 + * If P2 is not 0, use Truncate semantics. * * Delete all contents of the space, which space id is given * in P1 argument. It is worth mentioning, that clearing routine @@ -4575,7 +4577,13 @@ case OP_Clear: { uint32_t space_id = pOp->p1; struct space *space = space_by_id(space_id); assert(space != NULL); - rc = tarantoolSqlite3ClearTable(space); + rc = 0; + if (pOp->p2 > 0) { + if (box_truncate(space_id) != 0) + rc = SQL_TARANTOOL_ERROR; + } else { + rc = tarantoolSqlite3ClearTable(space); + } if (rc) goto abort_due_to_error; break; } diff --git a/test/sql/delete.result b/test/sql/delete.result index c33079cd7187dd33a213b08452af202901c3c607..52f9969c1c34510a321b4cd324ac11051e4a6d24 100644 --- a/test/sql/delete.result +++ b/test/sql/delete.result @@ -59,3 +59,79 @@ box.sql.execute("INSERT INTO t2 VALUES (0);") box.sql.execute("DROP TABLE t2;") --- ... +-- +-- gh-2201: TRUNCATE TABLE operation. +-- +-- can't truncate system table. +box.sql.execute("TRUNCATE TABLE \"_sql_stat1\";") +--- +- error: Can't truncate a system space, space '_sql_stat1' +... +box.sql.execute("CREATE TABLE t1(id INT PRIMARY KEY, a INT, b STR);") +--- +... +box.sql.execute("INSERT INTO t1 VALUES(1, 1, 'one');") +--- +... +box.sql.execute("INSERT INTO t1 VALUES(2, 2, 'two');") +--- +... +-- Can't truncate in transaction. +box.sql.execute("START TRANSACTION") +--- +... +box.sql.execute("TRUNCATE TABLE t1;") +--- +- error: DDL does not support multi-statement transactions +... +box.sql.execute("ROLLBACK") +--- +... +-- Can't truncate view. +box.sql.execute("CREATE VIEW v1 AS SELECT * FROM t1;") +--- +... +box.sql.execute("TRUNCATE TABLE v1;") +--- +- error: 'SQL error: can not truncate space ''V1'' because it is a view' +... +-- Can't truncate table with FK. +box.sql.execute("CREATE TABLE t2(x INT PRIMARY KEY REFERENCES t1(id));") +--- +... +box.sql.execute("TRUNCATE TABLE t1;") +--- +- error: 'SQL error: can not truncate space ''T1'' because other objects depend on + it' +... +-- Table triggers should be ignored. +box.sql.execute("DROP TABLE t2;") +--- +... +box.sql.execute("CREATE TABLE t2(x INT PRIMARY KEY);") +--- +... +box.sql.execute("CREATE TRIGGER trig2 BEFORE DELETE ON t1 BEGIN INSERT INTO t2 VALUES(old.x); END;") +--- +... +box.sql.execute("TRUNCATE TABLE t1;") +--- +... +box.sql.execute("SELECT * FROM t1;") +--- +- [] +... +box.sql.execute("SELECT * FROM t2;") +--- +- [] +... +-- Cleanup. +box.sql.execute("DROP VIEW v1"); +--- +... +box.sql.execute("DROP TABLE t1;") +--- +... +box.sql.execute("DROP TABLE t2;") +--- +... diff --git a/test/sql/delete.test.lua b/test/sql/delete.test.lua index 1721989604523b55e45b7c58b0d99bd987147dbf..0477d227c84efa6ce4d6b477e97c7c0a68ccf63a 100644 --- a/test/sql/delete.test.lua +++ b/test/sql/delete.test.lua @@ -37,3 +37,41 @@ box.sql.execute("CREATE TRIGGER t2 BEFORE INSERT ON t2 BEGIN DELETE FROM t1; END box.sql.execute("INSERT INTO t2 VALUES (0);") box.sql.execute("DROP TABLE t2;") + + +-- +-- gh-2201: TRUNCATE TABLE operation. +-- + +-- can't truncate system table. +box.sql.execute("TRUNCATE TABLE \"_sql_stat1\";") + +box.sql.execute("CREATE TABLE t1(id INT PRIMARY KEY, a INT, b STR);") +box.sql.execute("INSERT INTO t1 VALUES(1, 1, 'one');") +box.sql.execute("INSERT INTO t1 VALUES(2, 2, 'two');") + +-- Can't truncate in transaction. +box.sql.execute("START TRANSACTION") +box.sql.execute("TRUNCATE TABLE t1;") +box.sql.execute("ROLLBACK") + +-- Can't truncate view. +box.sql.execute("CREATE VIEW v1 AS SELECT * FROM t1;") +box.sql.execute("TRUNCATE TABLE v1;") + +-- Can't truncate table with FK. +box.sql.execute("CREATE TABLE t2(x INT PRIMARY KEY REFERENCES t1(id));") +box.sql.execute("TRUNCATE TABLE t1;") + +-- Table triggers should be ignored. +box.sql.execute("DROP TABLE t2;") +box.sql.execute("CREATE TABLE t2(x INT PRIMARY KEY);") +box.sql.execute("CREATE TRIGGER trig2 BEFORE DELETE ON t1 BEGIN INSERT INTO t2 VALUES(old.x); END;") +box.sql.execute("TRUNCATE TABLE t1;") +box.sql.execute("SELECT * FROM t1;") +box.sql.execute("SELECT * FROM t2;") + +-- Cleanup. +box.sql.execute("DROP VIEW v1"); +box.sql.execute("DROP TABLE t1;") +box.sql.execute("DROP TABLE t2;")