Implement customer error hooks.
If a Lua script defines the sysbench.hooks.sql_error_ignorable function, sysbench calls it whenever an SQL error occurs and passes information about connection, failed query, SQL error number, state and message as arguments. If the function returns true, the error is ignored and the entire event is restarted. Otherwise sysbench aborts scripts execution.
This commit is contained in:
@ -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) +
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -46,6 +46,14 @@ function thread_run(thread_id)
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- Hooks
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
sysbench.hooks = {
|
||||
-- sql_error = <func>,
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- Compatibility wrappers/aliases. These may be removed in later versions
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
@ -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, '<prepared statement>')
|
||||
end
|
||||
|
||||
function statement_methods.close(self)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2006 MySQL AB
|
||||
Copyright (C) 2006-2016 Alexey Kopytov <akopytov@gmail.com>
|
||||
Copyright (C) 2006-2017 Alexey Kopytov <akopytov@gmail.com>
|
||||
|
||||
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);
|
||||
|
||||
@ -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 <<EOF
|
||||
@ -122,9 +121,39 @@ function event()
|
||||
print(m)
|
||||
end
|
||||
EOF
|
||||
|
||||
sysbench $SB_ARGS --mysql-host="non-existing" --pgsql-host="non-existing" run
|
||||
|
||||
# Error hooks
|
||||
SB_ARGS="--verbosity=1 --test=$CRAMTMP/api_sql.lua --max-requests=1 --num-threads=1 $DB_DRIVER_ARGS"
|
||||
cat >$CRAMTMP/api_sql.lua <<EOF
|
||||
function sysbench.hooks.sql_error_ignorable(e)
|
||||
print("Got an error descriptor:")
|
||||
for k,v in pairs(e) do print(" " .. k .. " = ", v) end
|
||||
end
|
||||
|
||||
function event()
|
||||
local drv = sysbench.sql.driver()
|
||||
local con = drv:connect()
|
||||
local queries = {
|
||||
"CREATE TABLE t(a CHAR(1) NOT NULL)",
|
||||
"INSERT INTO t VALUES (1)",
|
||||
"INSERT INTO t VALUES (NULL)",
|
||||
[[INSERT INTO t VALUES ("test")]],
|
||||
"DROP TABLE t",
|
||||
"DROP TABLE t"
|
||||
}
|
||||
print('--')
|
||||
con:query("DROP TABLE IF EXISTS t")
|
||||
for i = 1, #queries do
|
||||
local e, m = pcall(function () con:query(queries[i]) end)
|
||||
if not e then print(m) end
|
||||
end
|
||||
print('--')
|
||||
end
|
||||
EOF
|
||||
sysbench $SB_ARGS run
|
||||
|
||||
|
||||
########################################################################
|
||||
# Multiple connections test
|
||||
########################################################################
|
||||
|
||||
@ -50,7 +50,7 @@ SQL Lua API + MySQL tests
|
||||
--
|
||||
(last message repeated 1 times)
|
||||
ALERT: attempt to use an already closed connection
|
||||
[string "sysbench.sql.lua"]:*: SQL error, sql_errno = 0, sql_state = 'unknown' (glob)
|
||||
*/api_sql.lua:*: SQL API error (glob)
|
||||
ALERT: attempt to close an already closed connection
|
||||
--
|
||||
4
|
||||
@ -59,6 +59,32 @@ SQL Lua API + MySQL tests
|
||||
FATAL: unable to connect to MySQL server on host 'non-existing', port 3306, aborting...
|
||||
FATAL: error 2005: Unknown MySQL server host 'non-existing' (0)
|
||||
connection creation failed
|
||||
--
|
||||
FATAL: mysql_drv_query() returned error 1048 (Column 'a' cannot be null) for query 'INSERT INTO t VALUES (NULL)'
|
||||
Got an error descriptor:
|
||||
sql_errno = \t1048 (esc)
|
||||
connection = \t<sql_connection> (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<sql_connection> (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<sql_connection> (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
|
||||
|
||||
Reference in New Issue
Block a user