1%%% Behaviour module for epgsql_sock commands.
2%%%
3%%% Copyright (C) 2017 - Sergey Prokhorov.  All rights reserved.
4
5-module(epgsql_command).
6-export([init/2, execute/3, handle_message/5]).
7
8-export_type([command/0, state/0]).
9
10-type command() :: module().
11-type state() :: any().
12
13%% Initialize command's state. Called when command is received by epgsql_sock process.
14-callback init(any()) -> state().
15
16-type execute_return() ::
17        {ok, epgsql_sock:pg_sock(), state()}
18      | {stop, Reason :: any(), Response :: any(), epgsql_sock:pg_sock()}.
19%% Execute command. It should send commands to socket.
20%% May be called many times if 'handle_message' will return 'requeue'.
21-callback execute(epgsql_sock:pg_sock(), state()) -> execute_return().
22
23-type handle_message_return() ::
24        {noaction, epgsql_sock:pg_sock()}
25        %% Do nothing; remember changed state
26      | {noaction, epgsql_sock:pg_sock(), state()}
27        %% Add result to resultset (eg, `{ok, Count}' `{ok, Cols, Rows}', `{error, #error{}}'
28        %% It may be returned many times for eg, `squery' with multiple
29        %% queries separated by ';'
30        %% See epgsql_sock:get_results/1
31      | {add_result, Data :: any(), Notification :: any(), epgsql_sock:pg_sock(), state()}
32        %% Add new row to current resultset;
33        %% See epgsql_sock:get_rows/1
34      | {add_row, tuple(), epgsql_sock:pg_sock(), state()}
35        %% Finish command execution, reply to the client and go to next command
36      | {finish, Result :: any(), Notification :: any(), epgsql_sock:pg_sock()}
37        %% Stop `epgsql_sock' process
38      | {stop, Reason :: any(), Response :: any(), epgsql_sock:pg_sock()}
39        %% Call 'execute' and reschedule command.
40        %% It's forbidden to call epgsql_sock:send from `handle_message'.
41        %% If you need to do so, you should set some flag in state and
42        %% reschedule command.
43        %% See `epgsql_cmd_connect' for reference.
44      | {requeue, epgsql_sock:pg_sock(), state()}
45        %% Protocol synchronization error (eg, unexpected packet)
46        %% Drop command queue and don't accept any command except 'sync'
47      | {sync_required, Why :: any()}
48        %% Unknown packet. Terminate `epgsql_sock' process
49      | unknown.
50%% Handle incoming packet
51-callback handle_message(Type :: byte(), Payload :: binary() | epgsql:query_error(),
52                         epgsql_sock:pg_sock(), state()) -> handle_message_return().
53
54-spec init(command(), any()) -> state().
55init(Command, Args) ->
56    Command:init(Args).
57
58-spec execute(command(), epgsql_sock:pg_sock(), state()) -> execute_return().
59execute(Command, PgSock, CmdState) ->
60    Command:execute(PgSock, CmdState).
61
62-spec handle_message(command(), Type :: byte(), Payload :: binary() | epgsql:query_error(),
63                     epgsql_sock:pg_sock(), state()) -> handle_message_return().
64handle_message(Command, Type, Payload, PgSock, State) ->
65    Command:handle_message(Type, Payload, PgSock, State).
66