Current section

Files

Jump to
erlcass src erlcass.erl
Raw

src/erlcass.erl

-module(erlcass).
-include("erlcass.hrl").
-include("erlcass_internals.hrl").
-behaviour(gen_server).
-export([
start_link/0,
% metrics
get_metrics/0,
% queries
query/1,
query_async/1,
query_async/3,
query_new_statement/1,
% prepared statements
add_prepare_statement/2,
async_execute/1,
async_execute/2,
async_execute/3,
async_execute/4,
async_execute/5,
execute/1,
execute/2,
execute/3,
% batch
batch_execute/3,
batch_async_execute/3,
% schema metadata
get_schema_metadata/0,
get_schema_metadata/1,
get_schema_metadata/2,
% low level methods to deal with statements
bind_prepared_statement/1,
bind_prepared_params_by_name/2,
bind_prepared_params_by_index/2,
% gen_server callbacks
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-record(erlcass_stm, {session, stm}).
-record(state, {session}).
-type query() :: binary() | {binary(), integer()} | {binary(), list()}.
-type statement_ref() :: #erlcass_stm{}.
-type bind_type() :: ?BIND_BY_INDEX | ?BIND_BY_NAME.
-type batch_type() :: ?CASS_BATCH_TYPE_LOGGED | ?CASS_BATCH_TYPE_UNLOGGED | ?CASS_BATCH_TYPE_COUNTER.
-type tag() :: reference().
-type recv_pid() :: pid() | null.
-type query_result() :: ok | {ok, list(), list()} | {error, reason()}.
-spec get_metrics() ->
{ok, list()} | {error, reason()}.
get_metrics() ->
call(get_metrics).
-spec get_schema_metadata() ->
{ok, list()} | {error, reason()}.
get_schema_metadata() ->
call(get_schema_metadata).
-spec get_schema_metadata(binary()) ->
{ok, list()} | {error, reason()}.
get_schema_metadata(Keyspace) ->
call({get_schema_metadata, Keyspace}).
-spec get_schema_metadata(binary(), binary()) ->
{ok, list()} | {error, reason()}.
get_schema_metadata(Keyspace, Table) ->
call({get_schema_metadata, Keyspace, Table}).
%non prepared query statements
-spec query_new_statement(query()) ->
{ok, tag()} | {error, reason()}.
query_new_statement(Query) ->
erlcass_nif:cass_statement_new(Query).
-spec query_async(query()) ->
{ok, tag()} | {error, reason()}.
query_async(Q) ->
query_async(Q, self(), make_ref()).
-spec query_async(query(), recv_pid(), any()) ->
{ok, tag()} | {error, reason()}.
query_async(Q, ReceiverPid, Tag) ->
case query_new_statement(Q) of
{ok, Stm} ->
case call({execute_normal_statements, get_identifier(ReceiverPid, Q), Stm, ReceiverPid, Tag}) of
ok ->
{ok, Tag};
Error ->
Error
end;
Error ->
Error
end.
-spec query(query()) ->
query_result().
query(Q) ->
case query_async(Q) of
{ok, Tag} ->
receive_response(Tag);
Error ->
Error
end.
%prepared statements
-spec add_prepare_statement(atom(), query()) ->
ok | {error, reason()}.
add_prepare_statement(Identifier, Query) ->
call({add_prepare_statement, Identifier, Query}, ?RESPONSE_TIMEOUT).
-spec bind_prepared_statement(atom()) ->
{ok, statement_ref()} | {error, reason()}.
bind_prepared_statement(Identifier) ->
case erlcass_stm_sessions:get(Identifier) of
{Session, PrepStatement} ->
case erlcass_nif:cass_prepared_bind(PrepStatement) of
{ok, StmRef} ->
{ok, #erlcass_stm{session = Session, stm = StmRef}};
Error ->
Error
end;
undefined ->
{error, undefined};
Error ->
Error
end.
-spec bind_prepared_params_by_name(statement_ref(), list()) ->
ok | {error, reason()}.
bind_prepared_params_by_name(Stm, Params) ->
erlcass_nif:cass_statement_bind_parameters(Stm#erlcass_stm.stm, ?BIND_BY_NAME, Params).
-spec bind_prepared_params_by_index(statement_ref(), list()) ->
ok | {error, reason()}.
bind_prepared_params_by_index(Stm, Params) ->
erlcass_nif:cass_statement_bind_parameters(Stm#erlcass_stm.stm, ?BIND_BY_INDEX, Params).
-spec async_execute(atom()) ->
{ok, tag()} | {error, reason()}.
async_execute(Identifier) ->
case bind_prepared_statement(Identifier) of
{ok, Stm} ->
Tag = make_ref(),
ReceiverPid = self(),
case erlcass_nif:cass_session_execute(get_identifier(ReceiverPid, Identifier), Stm#erlcass_stm.session, Stm#erlcass_stm.stm, ReceiverPid, Tag) of
ok ->
{ok, Tag};
Error ->
Error
end;
Error ->
Error
end.
-spec async_execute(atom() | query(), list()) ->
{ok, tag()} | {error, reason()}.
async_execute(Identifier, Params) ->
async_execute(Identifier, ?BIND_BY_INDEX, Params).
-spec async_execute(atom() | binary(), bind_type(), list()) ->
{ok, tag()} | {error, reason()}.
async_execute(Identifier, BindType, Params) ->
Tag = make_ref(),
case async_execute(Identifier, BindType, Params, self(), Tag) of
ok ->
{ok, Tag};
Error ->
Error
end.
-spec async_execute(atom() | binary(), bind_type(), list(), any()) ->
ok | {error, reason()}.
async_execute(Identifier, BindType, Params, Tag) ->
async_execute(Identifier, BindType, Params, self(), Tag).
-spec async_execute(atom(), bind_type(), list(), recv_pid(), any()) ->
ok | {error, reason()}.
async_execute(Identifier, BindType, Params, ReceiverPid, Tag) ->
case bind_prepared_statement(Identifier) of
{ok, Stm} ->
case erlcass_nif:cass_statement_bind_parameters(Stm#erlcass_stm.stm, BindType, Params) of
ok ->
erlcass_nif:cass_session_execute(get_identifier(ReceiverPid, Identifier), Stm#erlcass_stm.session, Stm#erlcass_stm.stm, ReceiverPid, Tag);
Error ->
Error
end;
Error ->
Error
end.
-spec execute(atom()) ->
query_result().
execute(Identifier) ->
case async_execute(Identifier) of
{ok, Tag} ->
receive_response(Tag);
Error ->
Error
end.
-spec execute(atom(), list()) ->
query_result().
execute(Identifier, Params) ->
execute(Identifier, ?BIND_BY_INDEX, Params).
-spec execute(atom(), bind_type(), list()) ->
query_result().
execute(Identifier, BindType, Params) ->
case async_execute(Identifier, BindType, Params) of
{ok, Tag} ->
receive_response(Tag);
Error ->
Error
end.
-spec batch_async_execute(batch_type(), list(), list()) ->
{ok, tag()} | {error, reason()}.
batch_async_execute(BatchType, StmList, Options) ->
call({batch_execute, BatchType, StmList, Options}).
-spec batch_execute(batch_type(), list(), list()) ->
query_result().
batch_execute(BatchType, StmList, Options) ->
case batch_async_execute(BatchType, StmList, Options) of
{ok, Tag} ->
receive_response(Tag);
Error ->
Error
end.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%internal functions
init([]) ->
process_flag(trap_exit, true),
ok = erlcass_stm_sessions:create(),
case session_create() of
{ok, SessionRef} ->
session_prepare_cached_statements(SessionRef),
{ok, #state{session = SessionRef}};
Error ->
{stop, Error, shutdown, #state{}}
end.
handle_call({execute_normal_statements, Identifier, StmRef, ReceiverPid, Tag}, _From, State) ->
{reply, erlcass_nif:cass_session_execute(Identifier, State#state.session, StmRef, ReceiverPid, Tag), State};
handle_call({batch_execute, BatchType, StmList, Options}, From, State) ->
{FromPid, _} = From,
{reply, erlcass_nif:cass_session_execute_batch(State#state.session, BatchType, filter_stm_list(StmList), Options, FromPid), State};
handle_call({add_prepare_statement, Identifier, Query}, From, State) ->
case erlcass_stm_cache:find(Identifier) of
false ->
ok = erlcass_nif:cass_session_prepare(State#state.session, self(), Query, {From, Identifier, Query}),
{noreply, State};
_ ->
{reply, {error, already_exist}, State}
end;
handle_call(get_schema_metadata, _From, State) ->
{reply, erlcass_nif:cass_session_get_schema_metadata(State#state.session), State};
handle_call({get_schema_metadata, Keyspace}, _From, State) ->
{reply, erlcass_nif:cass_session_get_schema_metadata(State#state.session, Keyspace), State};
handle_call({get_schema_metadata, Keyspace, Table}, _From, State) ->
{reply, erlcass_nif:cass_session_get_schema_metadata(State#state.session, Keyspace, Table), State};
handle_call(get_metrics, _From, #state{session = Session} = State) ->
{reply, erlcass_nif:cass_session_get_metrics(Session), State}.
handle_cast(Request, State) ->
?ERROR_MSG("session ~p received unexpected cast: ~p", [self(), Request]),
{noreply, State}.
handle_info({prepared_statement_result, Result, {From, Identifier, Query}}, #state{session = Session} = State) ->
?INFO_MSG("session: ~p prepared statement id: ~p result: ~p", [self(), Identifier, Result]),
case Result of
{ok, StmRef} ->
erlcass_stm_cache:set(Identifier, Query),
erlcass_stm_sessions:set(Identifier, Session, StmRef),
gen_server:reply(From, ok);
_ ->
gen_server:reply(From, Result)
end,
{noreply, State};
handle_info(Info, State) ->
?ERROR_MSG("session ~p received unexpected message: ~p", [self(), Info]),
{noreply, State}.
terminate(Reason, #state {session = Session}) ->
Self = self(),
?INFO_MSG("closing session ~p with reason: ~p", [Self, Reason]),
case do_close(Session, Self, ?CONNECT_TIMEOUT) of
ok ->
?INFO_MSG("session ~p closed completed", [Self]);
Error ->
?ERROR_MSG("session ~p closed with error: ~p", [Self, Error])
end.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
% internals
receive_response(Tag) ->
receive
{execute_statement_result, Tag, Result} ->
Result
after ?RESPONSE_TIMEOUT ->
{error, timeout}
end.
do_connect(Session, Pid) ->
case erlcass_utils:get_env(keyspace) of
{ok, Keyspace} ->
erlcass_nif:cass_session_connect(Session, Pid, Keyspace);
_ ->
erlcass_nif:cass_session_connect(Session, Pid)
end.
do_close(undefined, _Pid, _Timeout) ->
ok;
do_close(Session, Pid, Timeout) ->
case erlcass_nif:cass_session_close(Session, Pid) of
ok ->
receive
{session_closed, Pid, Result} ->
Result
after Timeout ->
{error, timeout}
end
end.
session_create() ->
case erlcass_nif:cass_session_new() of
{ok, Session} ->
Self = self(),
case do_connect(Session, Self) of
ok ->
receive
{session_connected, Self, Result} ->
?INFO_MSG("session ~p connection complete result: ~p", [Self, Result]),
{ok, Session}
after ?CONNECT_TIMEOUT ->
?ERROR_MSG("session ~p connection timeout", [Self]),
{error, connect_session_timeout}
end;
Error ->
Error
end;
Error ->
Error
end.
session_prepare_cached_statements(SessionRef) ->
FunPrepareStm = fun({Identifier, Query}) ->
Tag = make_ref(),
Self = self(),
case erlcass_nif:cass_session_prepare(SessionRef, Self, Query, Tag) of
ok ->
receive
{prepared_statement_result, Result, Tag} ->
?INFO_MSG("session ~p prepared cached statement id: ~p result: ~p", [Self, Identifier, Result]),
case Result of
{ok, StmRef} ->
erlcass_stm_sessions:set(Identifier, SessionRef, StmRef);
_ ->
throw({error, {failed_to_prepare_cached_stm, Identifier, Result}})
end
after ?RESPONSE_TIMEOUT ->
throw({error, {failed_to_prepare_cached_stm, Identifier, timeout}})
end;
Error ->
throw(Error)
end
end,
ok = lists:foreach(FunPrepareStm, erlcass_stm_cache:to_list()).
filter_stm_list(StmList) ->
filter_stm_list(StmList, []).
filter_stm_list([H|T], Acc) ->
case is_record(H, erlcass_stm) of
true ->
filter_stm_list(T, [H#erlcass_stm.stm | Acc]);
_ ->
filter_stm_list(T, [H | Acc])
end;
filter_stm_list([], Acc) ->
Acc.
call(Message) ->
call(Message, 5000).
call(Message, Timeout) ->
try
gen_server:call(?MODULE, Message, Timeout)
catch
exit:{noproc, _} ->
{error, erlcass_not_started};
_: Exception ->
{error, Exception}
end.
get_identifier(null, Identifier) ->
id2bin(Identifier);
get_identifier(_, _Identifier) ->
null.
-spec id2bin(Id) -> Bin when
Id :: atom() | binary() | {atom() | binary(), term()},
Bin :: binary().
id2bin(Id) when is_atom(Id) ->
atom_to_binary(Id, utf8);
id2bin(Id) when is_binary(Id) ->
Id;
id2bin({Id, _Opts}) ->
id2bin(Id).