|
@@ -52,19 +52,20 @@
|
|
-export([init/1, code_change/3, terminate/2]).
|
|
-export([init/1, code_change/3, terminate/2]).
|
|
|
|
|
|
%% loop callback
|
|
%% loop callback
|
|
--export([on_message/3, on_replication/3]).
|
|
|
|
|
|
+-export([on_message/3, on_replication/3, on_copy_from_stdin/3]).
|
|
|
|
|
|
%% Comand's APIs
|
|
%% Comand's APIs
|
|
-export([set_net_socket/3, init_replication_state/1, set_attr/3, get_codec/1,
|
|
-export([set_net_socket/3, init_replication_state/1, set_attr/3, get_codec/1,
|
|
get_rows/1, get_results/1, notify/2, send/2, send/3, send_multi/2,
|
|
get_rows/1, get_results/1, notify/2, send/2, send/3, send_multi/2,
|
|
get_parameter_internal/2,
|
|
get_parameter_internal/2,
|
|
- get_replication_state/1, set_packet_handler/2]).
|
|
|
|
|
|
+ get_subproto_state/1, set_packet_handler/2]).
|
|
|
|
|
|
-export_type([transport/0, pg_sock/0, error/0]).
|
|
-export_type([transport/0, pg_sock/0, error/0]).
|
|
|
|
|
|
-include("epgsql.hrl").
|
|
-include("epgsql.hrl").
|
|
-include("protocol.hrl").
|
|
-include("protocol.hrl").
|
|
-include("epgsql_replication.hrl").
|
|
-include("epgsql_replication.hrl").
|
|
|
|
+-include("epgsql_copy.hrl").
|
|
|
|
|
|
-type transport() :: {call, any()}
|
|
-type transport() :: {call, any()}
|
|
| {cast, pid(), reference()}
|
|
| {cast, pid(), reference()}
|
|
@@ -72,6 +73,7 @@
|
|
|
|
|
|
-type tcp_socket() :: port(). %gen_tcp:socket() isn't exported prior to erl 18
|
|
-type tcp_socket() :: port(). %gen_tcp:socket() isn't exported prior to erl 18
|
|
-type repl_state() :: #repl{}.
|
|
-type repl_state() :: #repl{}.
|
|
|
|
+-type copy_state() :: #copy{}.
|
|
|
|
|
|
-type error() :: {error, sync_required | closed | sock_closed | sock_error}.
|
|
-type error() :: {error, sync_required | closed | sock_closed | sock_error}.
|
|
|
|
|
|
@@ -79,7 +81,7 @@
|
|
sock :: tcp_socket() | ssl:sslsocket() | undefined,
|
|
sock :: tcp_socket() | ssl:sslsocket() | undefined,
|
|
data = <<>>,
|
|
data = <<>>,
|
|
backend :: {Pid :: integer(), Key :: integer()} | undefined,
|
|
backend :: {Pid :: integer(), Key :: integer()} | undefined,
|
|
- handler = on_message :: on_message | on_replication | undefined,
|
|
|
|
|
|
+ handler = on_message :: on_message | on_replication | on_copy_from_stdin | undefined,
|
|
codec :: epgsql_binary:codec() | undefined,
|
|
codec :: epgsql_binary:codec() | undefined,
|
|
queue = queue:new() :: queue:queue({epgsql_command:command(), any(), transport()}),
|
|
queue = queue:new() :: queue:queue({epgsql_command:command(), any(), transport()}),
|
|
current_cmd :: epgsql_command:command() | undefined,
|
|
current_cmd :: epgsql_command:command() | undefined,
|
|
@@ -92,7 +94,7 @@
|
|
sync_required :: boolean() | undefined,
|
|
sync_required :: boolean() | undefined,
|
|
txstatus :: byte() | undefined, % $I | $T | $E,
|
|
txstatus :: byte() | undefined, % $I | $T | $E,
|
|
complete_status :: atom() | {atom(), integer()} | undefined,
|
|
complete_status :: atom() | {atom(), integer()} | undefined,
|
|
- repl :: repl_state() | undefined,
|
|
|
|
|
|
+ subproto_state :: repl_state() | copy_state() | undefined,
|
|
connect_opts :: epgsql:connect_opts() | undefined}).
|
|
connect_opts :: epgsql:connect_opts() | undefined}).
|
|
|
|
|
|
-opaque pg_sock() :: #state{}.
|
|
-opaque pg_sock() :: #state{}.
|
|
@@ -145,7 +147,7 @@ set_net_socket(Mod, Socket, State) ->
|
|
|
|
|
|
-spec init_replication_state(pg_sock()) -> pg_sock().
|
|
-spec init_replication_state(pg_sock()) -> pg_sock().
|
|
init_replication_state(State) ->
|
|
init_replication_state(State) ->
|
|
- State#state{repl = #repl{}}.
|
|
|
|
|
|
+ State#state{subproto_state = #repl{}}.
|
|
|
|
|
|
-spec set_attr(atom(), any(), pg_sock()) -> pg_sock().
|
|
-spec set_attr(atom(), any(), pg_sock()) -> pg_sock().
|
|
set_attr(backend, {_Pid, _Key} = Backend, State) ->
|
|
set_attr(backend, {_Pid, _Key} = Backend, State) ->
|
|
@@ -158,8 +160,8 @@ set_attr(codec, Codec, State) ->
|
|
State#state{codec = Codec};
|
|
State#state{codec = Codec};
|
|
set_attr(sync_required, Value, State) ->
|
|
set_attr(sync_required, Value, State) ->
|
|
State#state{sync_required = Value};
|
|
State#state{sync_required = Value};
|
|
-set_attr(replication_state, Value, State) ->
|
|
|
|
- State#state{repl = Value};
|
|
|
|
|
|
+set_attr(subproto_state, Value, State) ->
|
|
|
|
+ State#state{subproto_state = Value};
|
|
set_attr(connect_opts, ConnectOpts, State) ->
|
|
set_attr(connect_opts, ConnectOpts, State) ->
|
|
State#state{connect_opts = ConnectOpts}.
|
|
State#state{connect_opts = ConnectOpts}.
|
|
|
|
|
|
@@ -172,9 +174,9 @@ set_packet_handler(Handler, State) ->
|
|
get_codec(#state{codec = Codec}) ->
|
|
get_codec(#state{codec = Codec}) ->
|
|
Codec.
|
|
Codec.
|
|
|
|
|
|
--spec get_replication_state(pg_sock()) -> repl_state().
|
|
|
|
-get_replication_state(#state{repl = Repl}) ->
|
|
|
|
- Repl.
|
|
|
|
|
|
+-spec get_subproto_state(pg_sock()) -> repl_state() | copy_state() | undefined.
|
|
|
|
+get_subproto_state(#state{subproto_state = SubState}) ->
|
|
|
|
+ SubState.
|
|
|
|
|
|
-spec get_rows(pg_sock()) -> [tuple()].
|
|
-spec get_rows(pg_sock()) -> [tuple()].
|
|
get_rows(#state{rows = Rows}) ->
|
|
get_rows(#state{rows = Rows}) ->
|
|
@@ -212,11 +214,11 @@ handle_call(get_cmd_status, _From, #state{complete_status = Status} = State) ->
|
|
|
|
|
|
handle_call({standby_status_update, FlushedLSN, AppliedLSN}, _From,
|
|
handle_call({standby_status_update, FlushedLSN, AppliedLSN}, _From,
|
|
#state{handler = on_replication,
|
|
#state{handler = on_replication,
|
|
- repl = #repl{last_received_lsn = ReceivedLSN} = Repl} = State) ->
|
|
|
|
|
|
+ subproto_state = #repl{last_received_lsn = ReceivedLSN} = Repl} = State) ->
|
|
send(State, ?COPY_DATA, epgsql_wire:encode_standby_status_update(ReceivedLSN, FlushedLSN, AppliedLSN)),
|
|
send(State, ?COPY_DATA, epgsql_wire:encode_standby_status_update(ReceivedLSN, FlushedLSN, AppliedLSN)),
|
|
Repl1 = Repl#repl{last_flushed_lsn = FlushedLSN,
|
|
Repl1 = Repl#repl{last_flushed_lsn = FlushedLSN,
|
|
last_applied_lsn = AppliedLSN},
|
|
last_applied_lsn = AppliedLSN},
|
|
- {reply, ok, State#state{repl = Repl1}}.
|
|
|
|
|
|
+ {reply, ok, State#state{subproto_state = Repl1}}.
|
|
|
|
|
|
handle_cast({{Method, From, Ref} = Transport, Command, Args}, State)
|
|
handle_cast({{Method, From, Ref} = Transport, Command, Args}, State)
|
|
when ((Method == cast) or (Method == incremental)),
|
|
when ((Method == cast) or (Method == incremental)),
|
|
@@ -259,7 +261,12 @@ handle_info({inet_reply, _, ok}, State) ->
|
|
{noreply, State};
|
|
{noreply, State};
|
|
|
|
|
|
handle_info({inet_reply, _, Status}, State) ->
|
|
handle_info({inet_reply, _, Status}, State) ->
|
|
- {stop, Status, flush_queue(State, {error, Status})}.
|
|
|
|
|
|
+ {stop, Status, flush_queue(State, {error, Status})};
|
|
|
|
+
|
|
|
|
+handle_info({io_request, From, ReplyAs, Request}, #state{handler = on_copy_from_stdin} = State) ->
|
|
|
|
+ Response = handle_io_request(Request, State),
|
|
|
|
+ io_reply(Response, From, ReplyAs),
|
|
|
|
+ {noreply, State}.
|
|
|
|
|
|
terminate(_Reason, #state{sock = undefined}) -> ok;
|
|
terminate(_Reason, #state{sock = undefined}) -> ok;
|
|
terminate(_Reason, #state{mod = gen_tcp, sock = Sock}) -> gen_tcp:close(Sock);
|
|
terminate(_Reason, #state{mod = gen_tcp, sock = Sock}) -> gen_tcp:close(Sock);
|
|
@@ -400,7 +407,7 @@ do_send(gen_tcp, Sock, Bin) ->
|
|
do_send(ssl, Sock, Bin) ->
|
|
do_send(ssl, Sock, Bin) ->
|
|
ssl:send(Sock, Bin).
|
|
ssl:send(Sock, Bin).
|
|
|
|
|
|
-loop(#state{data = Data, handler = Handler, repl = Repl} = State) ->
|
|
|
|
|
|
+loop(#state{data = Data, handler = Handler, subproto_state = Repl} = State) ->
|
|
case epgsql_wire:decode_message(Data) of
|
|
case epgsql_wire:decode_message(Data) of
|
|
{Type, Payload, Tail} ->
|
|
{Type, Payload, Tail} ->
|
|
case ?MODULE:Handler(Type, Payload, State#state{data = Tail}) of
|
|
case ?MODULE:Handler(Type, Payload, State#state{data = Tail}) of
|
|
@@ -411,14 +418,16 @@ loop(#state{data = Data, handler = Handler, repl = Repl} = State) ->
|
|
end;
|
|
end;
|
|
_ ->
|
|
_ ->
|
|
%% in replication mode send feedback after each batch of messages
|
|
%% in replication mode send feedback after each batch of messages
|
|
- case (Repl =/= undefined) andalso (Repl#repl.feedback_required) of
|
|
|
|
|
|
+ case Handler == on_replication
|
|
|
|
+ andalso (Repl =/= undefined)
|
|
|
|
+ andalso (Repl#repl.feedback_required) of
|
|
true ->
|
|
true ->
|
|
#repl{last_received_lsn = LastReceivedLSN,
|
|
#repl{last_received_lsn = LastReceivedLSN,
|
|
last_flushed_lsn = LastFlushedLSN,
|
|
last_flushed_lsn = LastFlushedLSN,
|
|
last_applied_lsn = LastAppliedLSN} = Repl,
|
|
last_applied_lsn = LastAppliedLSN} = Repl,
|
|
send(State, ?COPY_DATA, epgsql_wire:encode_standby_status_update(
|
|
send(State, ?COPY_DATA, epgsql_wire:encode_standby_status_update(
|
|
LastReceivedLSN, LastFlushedLSN, LastAppliedLSN)),
|
|
LastReceivedLSN, LastFlushedLSN, LastAppliedLSN)),
|
|
- {noreply, State#state{repl = Repl#repl{feedback_required = false}}};
|
|
|
|
|
|
+ {noreply, State#state{subproto_state = Repl#repl{feedback_required = false}}};
|
|
_ ->
|
|
_ ->
|
|
{noreply, State}
|
|
{noreply, State}
|
|
end
|
|
end
|
|
@@ -488,6 +497,50 @@ flush_queue(#state{current_cmd = undefined} = State, _) ->
|
|
flush_queue(State, Error) ->
|
|
flush_queue(State, Error) ->
|
|
flush_queue(finish(State, Error), Error).
|
|
flush_queue(finish(State, Error), Error).
|
|
|
|
|
|
|
|
+%% @doc Handler for IO protocol version of COPY FROM STDIN
|
|
|
|
+%%
|
|
|
|
+%% COPY FROM STDIN is implemented as Erlang
|
|
|
|
+%% <a href="https://erlang.org/doc/apps/stdlib/io_protocol.html">io protocol</a>.
|
|
|
|
+handle_io_request({put_chars, Encoding, Chars}, State) ->
|
|
|
|
+ send(State, ?COPY_DATA, encode_chars(Encoding, Chars));
|
|
|
|
+handle_io_request({put_chars, Encoding, Mod, Fun, Args}, State) ->
|
|
|
|
+ try apply(Mod, Fun, Args) of
|
|
|
|
+ Chars when is_binary(Chars);
|
|
|
|
+ is_list(Chars) ->
|
|
|
|
+ handle_io_request({put_chars, Encoding, Chars}, State);
|
|
|
|
+ Other ->
|
|
|
|
+ {error, {fun_return_not_characters, Other}}
|
|
|
|
+ catch T:R:S ->
|
|
|
|
+ {error, {fun_exception, {T, R, S}}}
|
|
|
|
+ end;
|
|
|
|
+handle_io_request({setopts, _}, _State) ->
|
|
|
|
+ {error, request};
|
|
|
|
+handle_io_request(getopts, _State) ->
|
|
|
|
+ {error, request};
|
|
|
|
+handle_io_request({requests, Requests}, State) ->
|
|
|
|
+ try_requests(Requests, State, ok).
|
|
|
|
+
|
|
|
|
+try_requests([Req | Requests], State, _) ->
|
|
|
|
+ case handle_io_request(Req, State) of
|
|
|
|
+ {error, _} = Err ->
|
|
|
|
+ Err;
|
|
|
|
+ Other ->
|
|
|
|
+ try_requests(Requests, State, Other)
|
|
|
|
+ end;
|
|
|
|
+try_requests([], _, LastRes) ->
|
|
|
|
+ LastRes.
|
|
|
|
+
|
|
|
|
+io_reply(Result, From, ReplyAs) ->
|
|
|
|
+ From ! {io_reply, ReplyAs, Result}.
|
|
|
|
+
|
|
|
|
+encode_chars(_, Bin) when is_binary(Bin) ->
|
|
|
|
+ Bin;
|
|
|
|
+encode_chars(unicode, Chars) when is_list(Chars) ->
|
|
|
|
+ unicode:characters_to_binary(Chars);
|
|
|
|
+encode_chars(latin1, Chars) when is_list(Chars) ->
|
|
|
|
+ unicode:characters_to_binary(Chars, latin1).
|
|
|
|
+
|
|
|
|
+
|
|
to_binary(B) when is_binary(B) -> B;
|
|
to_binary(B) when is_binary(B) -> B;
|
|
to_binary(L) when is_list(L) -> list_to_binary(L).
|
|
to_binary(L) when is_list(L) -> list_to_binary(L).
|
|
|
|
|
|
@@ -549,12 +602,26 @@ on_message(?NOTIFICATION, <<Pid:?int32, Strings/binary>>, State) ->
|
|
on_message(Msg, Payload, State) ->
|
|
on_message(Msg, Payload, State) ->
|
|
command_handle_message(Msg, Payload, State).
|
|
command_handle_message(Msg, Payload, State).
|
|
|
|
|
|
|
|
+%% @doc Handle "copy subprotocol" for COPY .. FROM STDIN
|
|
|
|
+%%
|
|
|
|
+%% Activated by `epgsql_cmd_copy_from_stdin' and deactivated by `epgsql_cmd_copy_done'
|
|
|
|
+on_copy_from_stdin(?COMMAND_COMPLETE, Bin, State) ->
|
|
|
|
+ _Complete = epgsql_wire:decode_complete(Bin),
|
|
|
|
+ {noreply, State#state{subproto_state = undefined, handler = on_message}};
|
|
|
|
+on_copy_from_stdin(?ERROR, Err, State) ->
|
|
|
|
+ Reason = epgsql_wire:decode_error(Err),
|
|
|
|
+ {stop, {error, Reason}, State};
|
|
|
|
+on_copy_from_stdin(M, Data, Sock) when M == ?NOTICE;
|
|
|
|
+ M == ?NOTIFICATION;
|
|
|
|
+ M == ?PARAMETER_STATUS ->
|
|
|
|
+ on_message(M, Data, Sock).
|
|
|
|
+
|
|
|
|
|
|
%% CopyData for Replication mode
|
|
%% CopyData for Replication mode
|
|
on_replication(?COPY_DATA, <<?PRIMARY_KEEPALIVE_MESSAGE:8, LSN:?int64, _Timestamp:?int64, ReplyRequired:8>>,
|
|
on_replication(?COPY_DATA, <<?PRIMARY_KEEPALIVE_MESSAGE:8, LSN:?int64, _Timestamp:?int64, ReplyRequired:8>>,
|
|
- #state{repl = #repl{last_flushed_lsn = LastFlushedLSN,
|
|
|
|
- last_applied_lsn = LastAppliedLSN,
|
|
|
|
- align_lsn = AlignLsn} = Repl} = State) ->
|
|
|
|
|
|
+ #state{subproto_state = #repl{last_flushed_lsn = LastFlushedLSN,
|
|
|
|
+ last_applied_lsn = LastAppliedLSN,
|
|
|
|
+ align_lsn = AlignLsn} = Repl} = State) ->
|
|
Repl1 =
|
|
Repl1 =
|
|
case ReplyRequired of
|
|
case ReplyRequired of
|
|
1 when AlignLsn ->
|
|
1 when AlignLsn ->
|
|
@@ -571,14 +638,14 @@ on_replication(?COPY_DATA, <<?PRIMARY_KEEPALIVE_MESSAGE:8, LSN:?int64, _Timestam
|
|
Repl#repl{feedback_required = true,
|
|
Repl#repl{feedback_required = true,
|
|
last_received_lsn = LSN}
|
|
last_received_lsn = LSN}
|
|
end,
|
|
end,
|
|
- {noreply, State#state{repl = Repl1}};
|
|
|
|
|
|
+ {noreply, State#state{subproto_state = Repl1}};
|
|
|
|
|
|
%% CopyData for Replication mode
|
|
%% CopyData for Replication mode
|
|
on_replication(?COPY_DATA, <<?X_LOG_DATA, StartLSN:?int64, EndLSN:?int64,
|
|
on_replication(?COPY_DATA, <<?X_LOG_DATA, StartLSN:?int64, EndLSN:?int64,
|
|
_Timestamp:?int64, WALRecord/binary>>,
|
|
_Timestamp:?int64, WALRecord/binary>>,
|
|
- #state{repl = Repl} = State) ->
|
|
|
|
|
|
+ #state{subproto_state = Repl} = State) ->
|
|
Repl1 = handle_xlog_data(StartLSN, EndLSN, WALRecord, Repl),
|
|
Repl1 = handle_xlog_data(StartLSN, EndLSN, WALRecord, Repl),
|
|
- {noreply, State#state{repl = Repl1}};
|
|
|
|
|
|
+ {noreply, State#state{subproto_state = Repl1}};
|
|
on_replication(?ERROR, Err, State) ->
|
|
on_replication(?ERROR, Err, State) ->
|
|
Reason = epgsql_wire:decode_error(Err),
|
|
Reason = epgsql_wire:decode_error(Err),
|
|
{stop, {error, Reason}, State};
|
|
{stop, {error, Reason}, State};
|