diff options
Diffstat (limited to 'src/ejabberd_sql.erl')
-rw-r--r-- | src/ejabberd_sql.erl | 160 |
1 files changed, 100 insertions, 60 deletions
diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index e6c5c0212..5e35e344c 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -37,12 +37,12 @@ sql_query_t/1, sql_transaction/2, sql_bloc/2, - abort/1, - restart/1, - use_new_schema/0, - sql_query_to_iolist/1, + abort/1, + restart/1, + use_new_schema/0, + sql_query_to_iolist/1, escape/1, - standard_escape/1, + standard_escape/1, escape_like/1, escape_like_arg/1, escape_like_arg_circumflex/1, @@ -55,7 +55,8 @@ freetds_config/0, odbcinst_config/0, init_mssql/1, - keep_alive/2]). + keep_alive/2, + to_list/2]). %% gen_fsm callbacks -export([init/1, handle_event/3, handle_sync_event/4, @@ -68,6 +69,7 @@ -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). +-include("ejabberd_stacktrace.hrl"). -record(state, {db_ref = self() :: pid(), @@ -136,7 +138,7 @@ start_link(Host, StartInterval) -> -spec sql_query(binary(), sql_query()) -> sql_query_result(). sql_query(Host, Query) -> - check_error(sql_call(Host, {sql_query, Query}), Query). + sql_call(Host, {sql_query, Query}). %% SQL transaction based on a list of queries %% This function automatically @@ -172,10 +174,16 @@ sql_call(Host, Msg) -> end. keep_alive(Host, PID) -> - sync_send_event(PID, + case sync_send_event(PID, {sql_cmd, {sql_query, ?KEEPALIVE_QUERY}, p1_time_compat:monotonic_time(milli_seconds)}, - query_timeout(Host)). + query_timeout(Host)) of + {selected,_,[[<<"1">>]]} -> + ok; + _Err -> + ?ERROR_MSG("keep alive query failed, closing connection: ~p", [_Err]), + sync_send_event(PID, force_timeout, query_timeout(Host)) + end. sync_send_event(Pid, Msg, Timeout) -> try p1_fsm:sync_send_event(Pid, Msg, Timeout) @@ -252,6 +260,10 @@ to_bool(true) -> true; to_bool(1) -> true; to_bool(_) -> false. +to_list(EscapeFun, Val) -> + Escaped = lists:join(<<",">>, lists:map(EscapeFun, Val)), + [<<"(">>, Escaped, <<")">>]. + encode_term(Term) -> escape(list_to_binary( erl_prettypr:format(erl_syntax:abstract(Term), @@ -335,10 +347,10 @@ connecting(connect, #state{host = Host} = State) -> State2 = get_db_version(State1), {next_state, session_established, State2}; {error, Reason} -> - ?INFO_MSG("~p connection failed:~n** Reason: ~p~n** " - "Retry after: ~p seconds", - [State#state.db_type, Reason, - State#state.start_interval div 1000]), + ?WARNING_MSG("~p connection failed:~n** Reason: ~p~n** " + "Retry after: ~p seconds", + [State#state.db_type, Reason, + State#state.start_interval div 1000]), p1_fsm:send_event_after(State#state.start_interval, connect), {next_state, connecting, State} @@ -389,6 +401,8 @@ session_established(Request, {Who, _Ref}, State) -> session_established({sql_cmd, Command, From, Timestamp}, State) -> run_sql_cmd(Command, From, State, Timestamp); +session_established(force_timeout, State) -> + {stop, timeout, State}; session_established(Event, State) -> ?WARNING_MSG("unexpected event in 'session_established': ~p", [Event]), @@ -503,24 +517,26 @@ outer_transaction(F, NRestarts, _Reason) -> end, sql_query_internal([<<"begin;">>]), put(?NESTING_KEY, PreviousNestingLevel + 1), - Result = (catch F()), - put(?NESTING_KEY, PreviousNestingLevel), - case Result of - {aborted, Reason} when NRestarts > 0 -> - sql_query_internal([<<"rollback;">>]), - outer_transaction(F, NRestarts - 1, Reason); - {aborted, Reason} when NRestarts =:= 0 -> - ?ERROR_MSG("SQL transaction restarts exceeded~n** " - "Restarts: ~p~n** Last abort reason: " - "~p~n** Stacktrace: ~p~n** When State " - "== ~p", - [?MAX_TRANSACTION_RESTARTS, Reason, - erlang:get_stacktrace(), get(?STATE_KEY)]), - sql_query_internal([<<"rollback;">>]), - {aborted, Reason}; - {'EXIT', Reason} -> - sql_query_internal([<<"rollback;">>]), {aborted, Reason}; - Res -> sql_query_internal([<<"commit;">>]), {atomic, Res} + try F() of + Res -> + sql_query_internal([<<"commit;">>]), + {atomic, Res} + catch + ?EX_RULE(throw, {aborted, Reason}, _) when NRestarts > 0 -> + sql_query_internal([<<"rollback;">>]), + outer_transaction(F, NRestarts - 1, Reason); + ?EX_RULE(throw, {aborted, Reason}, Stack) when NRestarts =:= 0 -> + ?ERROR_MSG("SQL transaction restarts exceeded~n** " + "Restarts: ~p~n** Last abort reason: " + "~p~n** Stacktrace: ~p~n** When State " + "== ~p", + [?MAX_TRANSACTION_RESTARTS, Reason, + ?EX_STACK(Stack), get(?STATE_KEY)]), + sql_query_internal([<<"rollback;">>]), + {aborted, Reason}; + ?EX_RULE(exit, Reason, _) -> + sql_query_internal([<<"rollback;">>]), + {aborted, Reason} end. execute_bloc(F) -> @@ -586,17 +602,12 @@ sql_query_internal(#sql_query{} = Query) -> {error, <<"killed">>}; exit:{normal, _} -> {error, <<"terminated unexpectedly">>}; - Class:Reason -> - ST = erlang:get_stacktrace(), + ?EX_RULE(Class, Reason, Stack) -> ?ERROR_MSG("Internal error while processing SQL query: ~p", - [{Class, Reason, ST}]), + [{Class, Reason, ?EX_STACK(Stack)}]), {error, <<"internal error">>} end, - case Res of - {error, <<"No SQL-driver information available.">>} -> - {updated, 0}; - _Else -> Res - end; + check_error(Res, Query); sql_query_internal(F) when is_function(F) -> case catch execute_fun(F) of {'EXIT', Reason} -> {error, Reason}; @@ -621,17 +632,12 @@ sql_query_internal(Query) -> [Query], self(), [{timeout, QueryTimeout - 1000}, {result_type, binary}])), - %% ?INFO_MSG("MySQL, Received result~n~p~n", [R]), R; sqlite -> Host = State#state.host, sqlite_to_odbc(Host, sqlite3:sql_exec(sqlite_db(Host), Query)) end, - case Res of - {error, <<"No SQL-driver information available.">>} -> - {updated, 0}; - _Else -> Res - end. + check_error(Res, Query). select_sql_query(Queries, State) -> select_sql_query( @@ -733,12 +739,11 @@ sql_query_format_res({selected, _, Rows}, SQLQuery) -> try [(SQLQuery#sql_query.format_res)(Row)] catch - Class:Reason -> - ST = erlang:get_stacktrace(), + ?EX_RULE(Class, Reason, Stack) -> ?ERROR_MSG("Error while processing " "SQL query result: ~p~n" "row: ~p", - [{Class, Reason, ST}, Row]), + [{Class, Reason, ?EX_STACK(Stack)}, Row]), [] end end, Rows), @@ -750,14 +755,23 @@ sql_query_to_iolist(SQLQuery) -> generic_sql_query_format(SQLQuery). %% Generate the OTP callback return tuple depending on the driver result. -abort_on_driver_error({error, <<"query timed out">>} = - Reply, +abort_on_driver_error({error, + <<"query timed out">>} = Reply, From) -> p1_fsm:reply(From, Reply), {stop, timeout, get(?STATE_KEY)}; abort_on_driver_error({error, - <<"Failed sending data on socket", _/binary>>} = - Reply, + <<"Failed sending data on socket", _/binary>>} = Reply, + From) -> + p1_fsm:reply(From, Reply), + {stop, closed, get(?STATE_KEY)}; +abort_on_driver_error({error, + <<"SQL connection failed">>} = Reply, + From) -> + p1_fsm:reply(From, Reply), + {stop, timeout, get(?STATE_KEY)}; +abort_on_driver_error({error, + <<"Communication link failure">>} = Reply, From) -> p1_fsm:reply(From, Reply), {stop, closed, get(?STATE_KEY)}; @@ -773,6 +787,7 @@ odbc_connect(SQLServer, Timeout) -> ejabberd:start_app(odbc), odbc:connect(binary_to_list(SQLServer), [{scrollable_cursors, off}, + {extended_errors, on}, {tuple_row, off}, {timeout, Timeout}, {binary_strings, on}]). @@ -1099,20 +1114,45 @@ query_timeout(LServer) -> timer:seconds( ejabberd_config:get_option({sql_query_timeout, LServer}, 60)). +%% ***IMPORTANT*** This error format requires extended_errors turned on. +extended_error({"08S01", _, Reason}) -> + % TCP Provider: The specified network name is no longer available + ?DEBUG("ODBC Link Failure: ~s", [Reason]), + <<"Communication link failure">>; +extended_error({"08001", _, Reason}) -> + % Login timeout expired + ?DEBUG("ODBC Connect Timeout: ~s", [Reason]), + <<"SQL connection failed">>; +extended_error({"IMC01", _, Reason}) -> + % The connection is broken and recovery is not possible + ?DEBUG("ODBC Link Failure: ~s", [Reason]), + <<"Communication link failure">>; +extended_error({"IMC06", _, Reason}) -> + % The connection is broken and recovery is not possible + ?DEBUG("ODBC Link Failure: ~s", [Reason]), + <<"Communication link failure">>; +extended_error({Code, _, Reason}) -> + ?DEBUG("ODBC Error ~s: ~s", [Code, Reason]), + iolist_to_binary(Reason); +extended_error(Error) -> + Error. + check_error({error, Why} = Err, _Query) when Why == killed -> Err; -check_error({error, Why} = Err, #sql_query{} = Query) -> +check_error({error, Why}, #sql_query{} = Query) -> + Err = extended_error(Why), ?ERROR_MSG("SQL query '~s' at ~p failed: ~p", - [Query#sql_query.hash, Query#sql_query.loc, Why]), - Err; -check_error({error, Why} = Err, Query) -> + [Query#sql_query.hash, Query#sql_query.loc, Err]), + {error, Err}; +check_error({error, Why}, Query) -> + Err = extended_error(Why), case catch iolist_to_binary(Query) of SQuery when is_binary(SQuery) -> - ?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Why]); + ?ERROR_MSG("SQL query '~s' failed: ~p", [SQuery, Err]); _ -> - ?ERROR_MSG("SQL query ~p failed: ~p", [Query, Why]) + ?ERROR_MSG("SQL query ~p failed: ~p", [Query, Err]) end, - Err; + {error, Err}; check_error(Result, _Query) -> Result. @@ -1128,7 +1168,7 @@ opt_type(sql_username) -> fun iolist_to_binary/1; opt_type(sql_ssl) -> fun(B) when is_boolean(B) -> B end; opt_type(sql_ssl_verify) -> fun(B) when is_boolean(B) -> B end; opt_type(sql_ssl_certfile) -> fun ejabberd_pkix:try_certfile/1; -opt_type(sql_ssl_cafile) -> fun misc:try_read_file/1; +opt_type(sql_ssl_cafile) -> fun ejabberd_pkix:try_certfile/1; opt_type(sql_query_timeout) -> fun (I) when is_integer(I), I > 0 -> I end; opt_type(sql_connect_timeout) -> |