Skip to content

Commit

Permalink
sql: VDBE tests for trigger existence
Browse files Browse the repository at this point in the history
Trigger presence in system should be tested on each VDBE
execution attempt, not on Parser iteration.

Part of #3435, #3273
  • Loading branch information
kshcherbatov committed Jun 18, 2018
1 parent 717e79c commit 0d37521
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 23 deletions.
46 changes: 46 additions & 0 deletions src/box/sql/build.c
Expand Up @@ -4145,4 +4145,50 @@ sqlite3WithDelete(sqlite3 * db, With * pWith)
sqlite3DbFree(db, pWith);
}
}

int
vdbe_emit_execution_halt_on_exists(struct Parse *parser, int space_id,
int index_id, const char *name_src,
int tarantool_error_code,
const char *error_src, bool no_error)
{
struct Vdbe *v = sqlite3GetVdbe(parser);
assert(v != NULL);

struct sqlite3 *db = parser->db;
char *name = NULL;
char *error = NULL;
name = sqlite3DbStrDup(db, name_src);
if (name == NULL) {
size_t size = strlen(name_src) + 1;
diag_set(OutOfMemory, size, "sqlite3DbStrDup", "name");
return -1;
}
error = sqlite3DbStrDup(db, error_src);
if (error == NULL) {
sqlite3DbFree(db, name);
size_t size = strlen(error_src) + 1;
diag_set(OutOfMemory, size, "sqlite3DbStrDup", "error");
return -1;
}

int cursor = parser->nTab++;
int entity_id =
SQLITE_PAGENO_FROM_SPACEID_AND_INDEXID(space_id, index_id);
emit_open_cursor(parser, cursor, entity_id);

int name_reg = parser->nMem++;
int label = sqlite3VdbeAddOp4(v, OP_String8, 0, name_reg, 0, name,
P4_DYNAMIC);
sqlite3VdbeAddOp4Int(v, OP_NoConflict, cursor, label + 3, name_reg, 1);
if (no_error) {
sqlite3VdbeAddOp0(v, OP_Halt);
} else {
sqlite3VdbeAddOp4(v, OP_Halt, SQL_TARANTOOL_ERROR,
ON_CONFLICT_ACTION_FAIL, 0, error, P4_DYNAMIC);
sqlite3VdbeChangeP5(v, tarantool_error_code);
}
sqlite3VdbeAddOp1(v, OP_Close, cursor);
return 0;
}
#endif /* !defined(SQLITE_OMIT_CTE) */
10 changes: 7 additions & 3 deletions src/box/sql/main.c
Expand Up @@ -1454,10 +1454,14 @@ sqlite3_errmsg(sqlite3 * db)
z = sqlite3ErrStr(SQLITE_NOMEM_BKPT);
} else {
testcase(db->pErr == 0);
z = (char *)sqlite3_value_text(db->pErr);
assert(!db->mallocFailed);
if (z == 0) {
z = sqlite3ErrStr(db->errCode);
if (db->errCode != SQL_TARANTOOL_ERROR) {
assert(!db->mallocFailed);
z = (char *)sqlite3_value_text(db->pErr);
if (z == NULL)
z = sqlite3ErrStr(db->errCode);
} else {
z = diag_last_error(diag_get())->errmsg;
}
}
return z;
Expand Down
24 changes: 24 additions & 0 deletions src/box/sql/sqliteInt.h
Expand Up @@ -4594,4 +4594,28 @@ extern int sqlite3InitDatabase(sqlite3 * db);
enum on_conflict_action
table_column_nullable_action(struct Table *tab, uint32_t column);

/**
* Generate VDBE code to halt execution with correct error if
* the object with specified name is already present in
* specified space.
* The function does not begin to hold the passed error pointer
* to memory.
*
* @param parser Parsing context.
* @param space_id Space to lookup identifier.
* @param index_id Index identifier containing string primary key.
* @param name Of object to test existence.
* @param tarantool_error_code to set on halt.
* @param error string to notify on halt.
* @param no_error Do not raise error flag.
*
* @retval -1 on memory allocation error.
* @retval 0 on success.
*/
int
vdbe_emit_execution_halt_on_exists(struct Parse *parser, int space_id,
int index_id, const char *name,
int tarantool_error_code,
const char *error_src, bool no_error);

#endif /* SQLITEINT_H */
25 changes: 13 additions & 12 deletions src/box/sql/trigger.c
Expand Up @@ -122,18 +122,6 @@ sqlite3BeginTrigger(Parse * pParse, /* The parse context of the CREATE TRIGGER s
if (sqlite3CheckIdentifierName(pParse, zName) != SQLITE_OK)
goto trigger_cleanup;

if (!pParse->parse_only &&
sqlite3HashFind(&db->pSchema->trigHash, zName) != NULL) {
if (!noErr) {
diag_set(ClientError, ER_TRIGGER_EXISTS, zName);
pParse->rc = SQL_TARANTOOL_ERROR;
pParse->nErr++;
} else {
assert(!db->init.busy);
}
goto trigger_cleanup;
}

const char *table_name = pTableName->a[0].zName;
uint32_t space_id;
if (schema_find_id(BOX_SPACE_ID, 2, table_name, strlen(table_name),
Expand Down Expand Up @@ -179,6 +167,19 @@ sqlite3BeginTrigger(Parse * pParse, /* The parse context of the CREATE TRIGGER s
pParse->nErr++;
goto trigger_cleanup;
}
if (!pParse->parse_only) {
const char *error_msg =
tt_sprintf(tnt_errcode_desc(ER_TRIGGER_EXISTS), zName);
if (vdbe_emit_execution_halt_on_exists(pParse, BOX_TRIGGER_ID,
0, zName,
ER_TRIGGER_EXISTS,
error_msg,
(noErr != 0)) != 0) {
pParse->rc = SQL_TARANTOOL_ERROR;
pParse->nErr++;
goto trigger_cleanup;
}
}

/*
* INSTEAD OF triggers can only appear on views and BEFORE triggers
Expand Down
19 changes: 13 additions & 6 deletions src/box/sql/vdbe.c
Expand Up @@ -960,16 +960,18 @@ case OP_HaltIfNull: { /* in3 */
*
* If P4 is not null then it is an error message string.
*
* P5 is a value between 0 and 4, inclusive, that modifies the P4 string.
* If P1 is SQL_TARANTOOL_ERROR then P5 is a ClientError code and
* P4 is error message to set. Else P5 is a value between 0 and 4,
* inclusive, that modifies the P4 string.
*
* 0: (no change)
* 1: NOT NULL contraint failed: P4
* 2: UNIQUE constraint failed: P4
* 3: CHECK constraint failed: P4
* 4: FOREIGN KEY constraint failed: P4
*
* If P5 is not zero and P4 is NULL, then everything after the ":" is
* omitted.
* If P5 is not zero and P4 is NULL, then everything after the
* ":" is omitted.
*
* There is an implied "Halt 0 0 0" instruction inserted at the very end of
* every program. So a jump past the last instruction of the program
Expand Down Expand Up @@ -1005,9 +1007,11 @@ case OP_Halt: {
p->rc = pOp->p1;
p->errorAction = (u8)pOp->p2;
p->pc = pcx;
assert(pOp->p5<=4);
if (p->rc) {
if (pOp->p5) {
if (p->rc == SQL_TARANTOOL_ERROR) {
assert(pOp->p4.z != NULL);
box_error_set(__FILE__, __LINE__, pOp->p5, pOp->p4.z);
} else if (pOp->p5 != 0) {
static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK",
"FOREIGN KEY" };
testcase( pOp->p5==1);
Expand All @@ -1029,7 +1033,10 @@ case OP_Halt: {
p->rc = SQLITE_BUSY;
} else {
assert(rc==SQLITE_OK || (p->rc&0xff)==SQLITE_CONSTRAINT);
rc = p->rc ? SQLITE_ERROR : SQLITE_DONE;
if (p->rc != SQL_TARANTOOL_ERROR)
rc = p->rc != SQLITE_OK ? SQLITE_ERROR : SQLITE_DONE;
else
rc = SQL_TARANTOOL_ERROR;
}
goto vdbe_return;
}
Expand Down
5 changes: 3 additions & 2 deletions src/box/sql/vdbeapi.c
Expand Up @@ -598,8 +598,9 @@ sqlite3Step(Vdbe * p)
* contains the value that would be returned if sqlite3_finalize()
* were called on statement p.
*/
assert(rc == SQLITE_ROW || rc == SQLITE_DONE || rc == SQLITE_ERROR
|| (rc & 0xff) == SQLITE_BUSY || rc == SQLITE_MISUSE);
assert(rc == SQLITE_ROW || rc == SQLITE_DONE || rc == SQLITE_ERROR ||
(rc & 0xff) == SQLITE_BUSY || rc == SQLITE_MISUSE ||
rc == SQL_TARANTOOL_ERROR);
if (p->isPrepareV2 && rc != SQLITE_ROW && rc != SQLITE_DONE) {
/* If this statement was prepared using sqlite3_prepare_v2(), and an
* error has occurred, then return the error code in p->rc to the
Expand Down

0 comments on commit 0d37521

Please sign in to comment.