|
@@ -3,11 +3,19 @@
|
|
%%% See [https://www.postgresql.org/docs/current/sql-copy.html].
|
|
%%% See [https://www.postgresql.org/docs/current/sql-copy.html].
|
|
%%% See [https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-COPY].
|
|
%%% See [https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-COPY].
|
|
%%%
|
|
%%%
|
|
-%%% The copy data can then be delivered using Erlang
|
|
|
|
|
|
+%%% When `Format' is `text', copy data should then be delivered using Erlang
|
|
%%% <a href="https://erlang.org/doc/apps/stdlib/io_protocol.html">io protocol</a>.
|
|
%%% <a href="https://erlang.org/doc/apps/stdlib/io_protocol.html">io protocol</a>.
|
|
%%% See {@link file:write/2}, {@link io:put_chars/2}.
|
|
%%% See {@link file:write/2}, {@link io:put_chars/2}.
|
|
|
|
+%%% "End-of-data" marker `\.' at the end of TEXT or CSV data stream is not needed.
|
|
|
|
+%%%
|
|
|
|
+%%% When `Format' is `{binary, [epgsql_type()]}', recommended way to deliver data is
|
|
|
|
+%%% {@link epgsql:copy_send_rows/3}. IO-protocol can be used as well, as long as you can
|
|
|
|
+%%% do proper binary encoding of data tuples (header and trailer are sent automatically),
|
|
|
|
+%%% see [https://www.postgresql.org/docs/current/sql-copy.html#id-1.9.3.55.9.4.6].
|
|
|
|
+%%% When you don't know what are the correct type names for your columns, you could try to
|
|
|
|
+%%% construct equivalent `INSERT' or `SELECT' statement and call {@link epgsql:parse/2} command.
|
|
|
|
+%%% It will return `#statement{columns = [#column{type = TypeName}]}' with correct type names.
|
|
%%%
|
|
%%%
|
|
-%%% "End-of-data" marker `\.' at the end of TEXT or CSV data stream is not needed,
|
|
|
|
%%% {@link epgsql_cmd_copy_done} should be called in the end.
|
|
%%% {@link epgsql_cmd_copy_done} should be called in the end.
|
|
%%%
|
|
%%%
|
|
%%% This command should not be used with command pipelining!
|
|
%%% This command should not be used with command pipelining!
|
|
@@ -31,36 +39,35 @@
|
|
-include("../epgsql_copy.hrl").
|
|
-include("../epgsql_copy.hrl").
|
|
|
|
|
|
-record(copy_stdin,
|
|
-record(copy_stdin,
|
|
- {query :: iodata(), initiator :: pid()}).
|
|
|
|
|
|
+ {query :: iodata(),
|
|
|
|
+ initiator :: pid(),
|
|
|
|
+ format :: {binary, [epgsql:epgsql_type()]} | text}).
|
|
|
|
|
|
-init({SQL, Initiator}) ->
|
|
|
|
- #copy_stdin{query = SQL, initiator = Initiator}.
|
|
|
|
|
|
+init({SQL, Initiator, Format}) ->
|
|
|
|
+ #copy_stdin{query = SQL, initiator = Initiator, format = Format}.
|
|
|
|
|
|
-execute(Sock, #copy_stdin{query = SQL} = St) ->
|
|
|
|
|
|
+execute(Sock, #copy_stdin{query = SQL, format = Format} = St) ->
|
|
undefined = epgsql_sock:get_subproto_state(Sock), % assert we are not in copy-mode already
|
|
undefined = epgsql_sock:get_subproto_state(Sock), % assert we are not in copy-mode already
|
|
{PktType, PktData} = epgsql_wire:encode_query(SQL),
|
|
{PktType, PktData} = epgsql_wire:encode_query(SQL),
|
|
- {send, PktType, PktData, Sock, St}.
|
|
|
|
|
|
+ case Format of
|
|
|
|
+ text ->
|
|
|
|
+ {send, PktType, PktData, Sock, St};
|
|
|
|
+ {binary, _} ->
|
|
|
|
+ Header = epgsql_wire:encode_copy_header(),
|
|
|
|
+ {send_multi, [{PktType, PktData},
|
|
|
|
+ {?COPY_DATA, Header}], Sock, St}
|
|
|
|
+ end.
|
|
|
|
|
|
-%% CopyBothResponseщ
|
|
|
|
|
|
+%% CopyBothResponses
|
|
handle_message(?COPY_IN_RESPONSE, <<BinOrText, NumColumns:?int16, Formats/binary>>, Sock,
|
|
handle_message(?COPY_IN_RESPONSE, <<BinOrText, NumColumns:?int16, Formats/binary>>, Sock,
|
|
- #copy_stdin{initiator = Initiator}) ->
|
|
|
|
|
|
+ #copy_stdin{initiator = Initiator, format = RequestedFormat}) ->
|
|
ColumnFormats =
|
|
ColumnFormats =
|
|
[case Format of
|
|
[case Format of
|
|
0 -> text;
|
|
0 -> text;
|
|
1 -> binary
|
|
1 -> binary
|
|
end || <<Format:?int16>> <= Formats],
|
|
end || <<Format:?int16>> <= Formats],
|
|
length(ColumnFormats) =:= NumColumns orelse error(invalid_copy_in_response),
|
|
length(ColumnFormats) =:= NumColumns orelse error(invalid_copy_in_response),
|
|
- case BinOrText of
|
|
|
|
- 0 ->
|
|
|
|
- %% When BinOrText is 0, all "columns" should be 0 format as well.
|
|
|
|
- %% See https://www.postgresql.org/docs/current/protocol-message-formats.html
|
|
|
|
- %% CopyInResponse
|
|
|
|
- (lists:member(binary, ColumnFormats) == false)
|
|
|
|
- orelse error(invalid_copy_in_response);
|
|
|
|
- _ ->
|
|
|
|
- ok
|
|
|
|
- end,
|
|
|
|
- CopyState = #copy{initiator = Initiator},
|
|
|
|
|
|
+ CopyState = init_copy_state(BinOrText, RequestedFormat, ColumnFormats, Initiator),
|
|
Sock1 = epgsql_sock:set_attr(subproto_state, CopyState, Sock),
|
|
Sock1 = epgsql_sock:set_attr(subproto_state, CopyState, Sock),
|
|
Res = {ok, ColumnFormats},
|
|
Res = {ok, ColumnFormats},
|
|
{finish, Res, Res, epgsql_sock:set_packet_handler(on_copy_from_stdin, Sock1)};
|
|
{finish, Res, Res, epgsql_sock:set_packet_handler(on_copy_from_stdin, Sock1)};
|
|
@@ -69,3 +76,28 @@ handle_message(?ERROR, Error, _Sock, _State) ->
|
|
{sync_required, Result};
|
|
{sync_required, Result};
|
|
handle_message(_, _, _, _) ->
|
|
handle_message(_, _, _, _) ->
|
|
unknown.
|
|
unknown.
|
|
|
|
+
|
|
|
|
+init_copy_state(0, text, ColumnFormats, Initiator) ->
|
|
|
|
+ %% When BinOrText is 0, all "columns" should be 0 format as well.
|
|
|
|
+ %% See https://www.postgresql.org/docs/current/protocol-message-formats.html
|
|
|
|
+ %% CopyInResponse
|
|
|
|
+ (lists:member(binary, ColumnFormats) == false)
|
|
|
|
+ orelse error(invalid_copy_in_response),
|
|
|
|
+ #copy{initiator = Initiator, format = text};
|
|
|
|
+init_copy_state(1, {binary, ColumnTypes}, ColumnFormats, Initiator) ->
|
|
|
|
+ %% https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-COPY
|
|
|
|
+ %% "As of the present implementation, all columns in a given COPY operation will use the same
|
|
|
|
+ %% format, but the message design does not assume this."
|
|
|
|
+ (lists:member(text, ColumnFormats) == false)
|
|
|
|
+ orelse error(invalid_copy_in_response),
|
|
|
|
+ NumColumns = length(ColumnFormats),
|
|
|
|
+ %% Eg, `epgsql:copy_from_stdin(C, "COPY tab (a, b, c) WITH (FORMAT binary)", {binary, [int2, int4]})'
|
|
|
|
+ %% so number of columns in SQL is not same as number of types in `binary'
|
|
|
|
+ (NumColumns == length(ColumnTypes))
|
|
|
|
+ orelse error({column_count_mismatch, ColumnTypes, NumColumns}),
|
|
|
|
+ #copy{initiator = Initiator, format = binary, binary_types = ColumnTypes};
|
|
|
|
+init_copy_state(ServerExpectedFormat, RequestedFormat, _, _Initiator) ->
|
|
|
|
+ %% Eg, `epgsql:copy_from_stdin(C, "COPY ... WITH (FORMAT text)", {binary, ...})' or
|
|
|
|
+ %% `epgsql:copy_from_stdin(C, "COPY ... WITH (FORMAT binary)", text)' or maybe PostgreSQL
|
|
|
|
+ %% got some new format epgsql is not aware of
|
|
|
|
+ error({format_mismatch, RequestedFormat, ServerExpectedFormat}).
|