aboutsummaryrefslogtreecommitdiff
path: root/src/ejabberd_sql.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/ejabberd_sql.erl')
-rw-r--r--src/ejabberd_sql.erl160
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) ->