diff --git a/sysbench/db_driver.h b/sysbench/db_driver.h index f969b93..d46b510 100644 --- a/sysbench/db_driver.h +++ b/sysbench/db_driver.h @@ -226,6 +226,7 @@ typedef struct db_conn db_error_t error; /* Database-independent error code */ int sql_errno; /* Database-specific error code */ const char *sql_state; /* Database-specific SQL state */ + const char *sql_errmsg; /* Database-specific error message */ db_driver_t *driver; /* DB driver for this connection */ void *ptr; /* Driver-specific data */ db_result_t rs; /* Result set */ @@ -245,6 +246,7 @@ typedef struct db_conn sizeof(void *) + sizeof(void *) + sizeof(void *) + + sizeof(void *) + sizeof(db_result_t) + sizeof(db_conn_state_t) + sizeof(int) + diff --git a/sysbench/drivers/mysql/drv_mysql.c b/sysbench/drivers/mysql/drv_mysql.c index bd789ba..e73af37 100644 --- a/sysbench/drivers/mysql/drv_mysql.c +++ b/sysbench/drivers/mysql/drv_mysql.c @@ -709,9 +709,13 @@ static db_error_t check_error(db_conn_t *sb_con, const char *func, DEBUG("mysql_errno(%p) = %u", con, sb_con->sql_errno); sb_con->sql_errno = (int) error; + sb_con->sql_state = mysql_sqlstate(con); DEBUG("mysql_state(%p) = %s", con, sb_con->sql_state); + sb_con->sql_errmsg = mysql_error(con); + DEBUG("mysql_error(%p) = %s", con, sb_con->sql_errmsg); + /* Check if the error code is specified in --mysql-ignore-errors, and return DB_ERROR_IGNORABLE if so, or DB_ERROR_FATAL otherwise @@ -724,7 +728,7 @@ static db_error_t check_error(db_conn_t *sb_con, const char *func, if (error == tmp || !strcmp(val, "all")) { - log_text(LOG_DEBUG, "Ignoring error %u %s, ", error, mysql_error(con)); + log_text(LOG_DEBUG, "Ignoring error %u %s, ", error, sb_con->sql_errmsg); /* Check if we should reconnect */ switch (error) @@ -751,10 +755,10 @@ static db_error_t check_error(db_conn_t *sb_con, const char *func, if (query) log_text(LOG_FATAL, "%s returned error %u (%s) for query '%s'", - func, error, mysql_error(con), query); + func, error, sb_con->sql_errmsg, query); else log_text(LOG_FATAL, "%s returned error %u (%s)", - func, error, mysql_error(con)); + func, error, sb_con->sql_errmsg); *type = DB_STAT_ERROR; diff --git a/sysbench/drivers/pgsql/drv_pgsql.c b/sysbench/drivers/pgsql/drv_pgsql.c index 8dc18d9..5b055f6 100644 --- a/sysbench/drivers/pgsql/drv_pgsql.c +++ b/sysbench/drivers/pgsql/drv_pgsql.c @@ -497,8 +497,7 @@ static db_error_t pgsql_check_status(db_conn_t *con, PGresult *pgres, rs->stat_type = DB_STAT_ERROR; con->sql_state = PQresultErrorField(pgres, PG_DIAG_SQLSTATE); - - const char * const errmsg = PQerrorMessage(pgcon); + con->sql_errmsg = PQerrorMessage(pgcon); if (!strcmp(con->sql_state, "40P01") /* deadlock_detected */ || !strcmp(con->sql_state, "23505") /* unique violation */) @@ -508,7 +507,8 @@ static db_error_t pgsql_check_status(db_conn_t *con, PGresult *pgres, } else { - log_text(LOG_FATAL, "%s() failed: %d %s", funcname, status, errmsg); + log_text(LOG_FATAL, "%s() failed: %d %s", funcname, status, + con->sql_errmsg); if (query != NULL) log_text(LOG_FATAL, "failed query was: %s", query); diff --git a/sysbench/lua/internal/sysbench.lua b/sysbench/lua/internal/sysbench.lua index 6fcd6aa..05e0ab5 100644 --- a/sysbench/lua/internal/sysbench.lua +++ b/sysbench/lua/internal/sysbench.lua @@ -46,6 +46,14 @@ function thread_run(thread_id) end end +-- ---------------------------------------------------------------------- +-- Hooks +-- ---------------------------------------------------------------------- + +sysbench.hooks = { + -- sql_error = , +} + -- ---------------------------------------------------------------------- -- Compatibility wrappers/aliases. These may be removed in later versions -- ---------------------------------------------------------------------- diff --git a/sysbench/lua/internal/sysbench.sql.lua b/sysbench/lua/internal/sysbench.sql.lua index fd86f2c..c37d6a3 100644 --- a/sysbench/lua/internal/sysbench.sql.lua +++ b/sysbench/lua/internal/sysbench.sql.lua @@ -74,11 +74,18 @@ typedef struct sql_error_t error; /* Driver-independent error code */ int sql_errno; /* Driver-specific error code */ const char *sql_state; /* Database-specific SQL state */ + const char *sql_errmsg; /* Database-specific error message */ + sql_driver *driver; /* DB driver for this connection */ const char opaque[?]; } sql_connection; -typedef struct db_stmt sql_statement; +typedef struct +{ + sql_connection *connection; + + const char opaque[?]; +} sql_statement; /* Result set definition */ @@ -208,33 +215,54 @@ function connection_methods.disconnect(self) return assert(ffi.C.db_connection_close(self) == 0) end -function connection_methods.check_error(self, rs) +function connection_methods.check_error(self, rs, query) if rs ~= nil or self.error == sysbench.sql.error.NONE then return rs end - local sql_state = self.sql_state ~= nil and - ffi.string(self.sql_state) or 'unknown' - - if self.error == sysbench.sql.error.IGNORABLE then - -- Throw an error containing the SQL error number provided by the SQL - -- driver. It can be caught by the user script to do some extra steps to - -- restart a transaction (e.g. reprepare statements after a - -- reconnect). Otherwise it will be caught by thread_run() in - -- sysbench.lua, in which case the event will be restarted. - error({ errcode = sysbench.error.RESTART_EVENT, - error = self.sql_errno, - sql_errno = self.sql_errno, - sql_state = sql_state}) + if self.sql_state == nil or self.sql_errmsg == nil then + -- It must be an API error, don't bother trying to downgrade it an + -- ignorable error + error("SQL API error", 3) end - error(string.format("SQL error, sql_errno = %d, sql_state = '%s'", - self.sql_errno, sql_state)) + local sql_state = ffi.string(self.sql_state) + local sql_errmsg = ffi.string(self.sql_errmsg) + + -- Create an error descriptor containing connection, failed query, SQL error + -- number, state and error message provided by the SQL driver + errdesc = { + connection = self, + query = query, + sql_errno = self.sql_errno, + sql_state = sql_state, + sql_errmsg = sql_errmsg + } + + -- Check if the error has already been marked as ignorable by the driver, or + -- there is an error hook that allows downgrading it to IGNORABLE + if (self.error == sysbench.sql.error.FATAL and + type(sysbench.hooks.sql_error_ignorable) == "function" and + sysbench.hooks.sql_error_ignorable(errdesc)) or + self.error == sysbench.sql.error.IGNORABLE + then + -- Throw a 'restart event' exception that can be caught by the user script + -- to do some extra steps to restart a transaction (e.g. reprepare + -- statements after a reconnect). Otherwise it will be caught by + -- thread_run() in sysbench.lua, in which case the entire current event + -- will be restarted without extra processing. + errdesc.errcode = sysbench.error.RESTART_EVENT + error(errdesc, 3) + end + + -- Just throw a regular error message on a fatal error + error(string.format("SQL error, errno = %d, state = '%s': %s", + self.sql_errno, sql_state, sql_errmsg), 2) end function connection_methods.query(self, query) local rs = ffi.C.db_query(self, query, #query) - return self:check_error(rs) + return self:check_error(rs, query) end function connection_methods.bulk_insert_init(self, query) @@ -259,6 +287,7 @@ end -- row from the result set, if available, or nil otherwise function connection_methods.query_row(self, query) local rs = self:query(query) + if rs == nil then return nil end @@ -321,7 +350,8 @@ function statement_methods.bind_param(self, ...) end function statement_methods.execute(self) - return ffi.C.db_execute(self) + local rs = ffi.C.db_execute(self) + return self.connection:check_error(rs, '') end function statement_methods.close(self) diff --git a/sysbench/sb_lua.c b/sysbench/sb_lua.c index 8fbf35b..f63bee1 100644 --- a/sysbench/sb_lua.c +++ b/sysbench/sb_lua.c @@ -182,7 +182,7 @@ static void check_connection(lua_State *L, sb_lua_ctxt_t *ctxt) static bool func_available(lua_State *L, const char *func) { lua_getglobal(L, func); - bool rc = !lua_isnil(L, -1) && lua_type(L, -1) == LUA_TFUNCTION; + bool rc = lua_isfunction(L, -1); lua_pop(L, 1); return rc; @@ -1110,3 +1110,18 @@ int sb_lua_event_stop(lua_State *L) return 0; } + +/* Check if a specified hook exists */ + +bool sb_lua_hook_defined(lua_State *L, const char *name) +{ + lua_getglobal(L, "sysbench"); + lua_getfield(L, -1, "hooks"); + lua_getfield(L, -1, name); + + bool rc = lua_isfunction(L, -1); + + lua_pop(L, 3); + + return rc; +} diff --git a/sysbench/sb_lua.h b/sysbench/sb_lua.h index cffcf2b..412dc76 100644 --- a/sysbench/sb_lua.h +++ b/sysbench/sb_lua.h @@ -1,5 +1,5 @@ /* Copyright (C) 2006 MySQL AB - Copyright (C) 2006-2016 Alexey Kopytov + Copyright (C) 2006-2017 Alexey Kopytov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,12 @@ #endif #include "sysbench.h" +#include "lua.h" /* Load a specified Lua script */ sb_test_t *sb_load_lua(const char *testname); + +bool sb_lua_hook_defined(lua_State *L, const char *name); + +int sb_lua_hook_call(const char *name); diff --git a/tests/include/api_sql_common.sh b/tests/include/api_sql_common.sh index 1f774cb..eb6650d 100644 --- a/tests/include/api_sql_common.sh +++ b/tests/include/api_sql_common.sh @@ -109,10 +109,9 @@ function event() print('--') end EOF - +sysbench $SB_ARGS run # Failed connection handling -sysbench $SB_ARGS run SB_ARGS="--verbosity=1 --test=$CRAMTMP/api_sql.lua --max-requests=1 --num-threads=1 $DB_DRIVER_ARGS" cat >$CRAMTMP/api_sql.lua <$CRAMTMP/api_sql.lua < (esc) + query = \tINSERT INTO t VALUES (NULL) (esc) + sql_state = \t23000 (esc) + sql_errmsg = \tColumn 'a' cannot be null (esc) + */api_sql.lua:*: SQL error, errno = 1048, state = '23000': Column 'a' cannot be null (glob) + FATAL: mysql_drv_query() returned error 1406 (Data too long for column 'a' at row 1) for query 'INSERT INTO t VALUES ("test")' + Got an error descriptor: + sql_errno = \t1406 (esc) + connection = \t (esc) + query = \tINSERT INTO t VALUES ("test") (esc) + sql_state = \t22001 (esc) + sql_errmsg = \tData too long for column 'a' at row 1 (esc) + */api_sql.lua:*: SQL error, errno = 1406, state = '22001': Data too long for column 'a' at row 1 (glob) + FATAL: mysql_drv_query() returned error 1051 (Unknown table 'sbtest.t') for query 'DROP TABLE t' + Got an error descriptor: + sql_errno = \t1051 (esc) + connection = \t (esc) + query = \tDROP TABLE t (esc) + sql_state = \t42S02 (esc) + sql_errmsg = \tUnknown table 'sbtest.t' (esc) + */api_sql.lua:*: SQL error, errno = 1051, state = '42S02': Unknown table 'sbtest.t' (glob) + -- 1 2 3