-module(epgsql_copy_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("stdlib/include/assert.hrl").
-include("epgsql.hrl").

-export([
    init_per_suite/1,
    all/0,
    end_per_suite/1,

    from_stdin_text/1,
    from_stdin_csv/1,
    from_stdin_binary/1,
    from_stdin_io_apis/1,
    from_stdin_with_terminator/1,
    from_stdin_corrupt_data/1
]).

init_per_suite(Config) ->
    [{module, epgsql}|Config].

end_per_suite(_Config) ->
    ok.

all() ->
    [
     from_stdin_text,
     from_stdin_csv,
     from_stdin_binary,
     from_stdin_io_apis,
     from_stdin_with_terminator,
     from_stdin_corrupt_data
    ].

  @doc Test that COPY in text format works
from_stdin_text(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                ?assertEqual(
                   ok,
                   io:put_chars(C,
                                "10\thello world\n"
                                "11\t\\N\n"
                                "12\tline 12\n")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "13\tline 13\n")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "14\tli")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "ne 14\n")),
                ?assertEqual(
                   {ok, 5},
                   Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{10, <<"hello world">>},
                            {11, null},
                            {12, <<"line 12">>},
                            {13, <<"line 13">>},
                            {14, <<"line 14">>}]},
                   Module:equery(C,
                                 "SELECT id, value FROM test_table1"
                                 " WHERE id IN (10, 11, 12, 13, 14) ORDER BY id"))
        end).

  @doc Test that COPY in CSV format works
from_stdin_csv(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT csv, QUOTE '''')")),
                ?assertEqual(
                   ok,
                   io:put_chars(C,
                                "20,'hello world'\n"
                                "21,\n"
                                "22,line 22\n")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "23,'line 23'\n")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "24,'li")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "ne 24'\n")),
                ?assertEqual(
                   {ok, 5},
                   Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{20, <<"hello world">>},
                            {21, null},
                            {22, <<"line 22">>},
                            {23, <<"line 23">>},
                            {24, <<"line 24">>}]},
                   Module:equery(C,
                                 "SELECT id, value FROM test_table1"
                                 " WHERE id IN (20, 21, 22, 23, 24) ORDER BY id"))
        end).

  @doc Test that COPY in binary format works
from_stdin_binary(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [binary, binary]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT binary)",
                     {binary, [int4, text]})),
                %% Batch of rows
                ?assertEqual(
                   ok,
                   Module:copy_send_rows(
                     C,
                     [{60, <<"hello world">>},
                      {61, null},
                      {62, "line 62"}],
                     5000)),
                %% Single row
                ?assertEqual(
                   ok,
                   Module:copy_send_rows(
                     C,
                     [{63, <<"line 63">>}],
                     1000)),
                %% Rows as lists
                ?assertEqual(
                   ok,
                   Module:copy_send_rows(
                     C,
                     [
                      [64, <<"line 64">>],
                      [65, <<"line 65">>]
                     ],
                     infinity)),
                ?assertEqual({ok, 6}, Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{60, <<"hello world">>},
                            {61, null},
                            {62, <<"line 62">>},
                            {63, <<"line 63">>},
                            {64, <<"line 64">>},
                            {65, <<"line 65">>}]},
                   Module:equery(C,
                                 "SELECT id, value FROM test_table1"
                                 " WHERE id IN (60, 61, 62, 63, 64, 65) ORDER BY id"))
        end).

%% @doc Tests that different IO-protocol APIs work
from_stdin_io_apis(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                ?assertEqual(ok, io:format(C, "30\thello world\n", [])),
                ?assertEqual(ok, io:format(C, "~b\t~s\n", [31, "line 31"])),
                %% Output "32\thello\n" in multiple calls
                ?assertEqual(ok, io:write(C, 32)),
                ?assertEqual(ok, io:put_chars(C, "\t")),
                ?assertEqual(ok, io:write(C, hello)),
                ?assertEqual(ok, io:nl(C)),
                %% Using `file` API
                ?assertEqual(ok, file:write(C, "33\tline 33\n34\tline 34\n")),
                %% Binary
                ?assertEqual(ok, io:put_chars(C, <<"35\tline 35\n">>)),
                ?assertEqual(ok, file:write(C, <<"36\tline 36\n">>)),
  IoData
                ?assertEqual(ok, io:put_chars(C, [<<"37">>, $\t, <<"line 37">>, <<$\n>>])),
                ?assertEqual(ok, file:write(C, [["38", <<$\t>>], [<<"line 38">>, $\n]])),
                %% Raw IO-protocol message-passing
                Ref = erlang:make_ref(),
                C ! {io_request, self(), Ref, {put_chars, unicode, "39\tline 39\n"}},
                ?assertEqual(ok, receive {io_reply, Ref, Resp} -> Resp
                                 after 5000 ->
                                         timeout
                                 end),
                %% Not documented!
                ?assertEqual(ok, io:requests(
                                   C,
                                   [{put_chars, unicode, "40\tline 40\n"},
                                    {put_chars, latin1, "41\tline 41\n"},
                                    {format, "~w\t~s", [42, "line 42"]},
                                    nl])),
                ?assertEqual(
                   {ok, 13},
                   Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{30, <<"hello world">>},
                            {31, <<"line 31">>},
                            {32, <<"hello">>},
                            {33, <<"line 33">>},
                            {34, <<"line 34">>},
                            {35, <<"line 35">>},
                            {36, <<"line 36">>},
                            {37, <<"line 37">>},
                            {38, <<"line 38">>},
                            {39, <<"line 39">>},
                            {40, <<"line 40">>},
                            {41, <<"line 41">>},
                            {42, <<"line 42">>}
                            ]},
                   Module:equery(
                     C,
                     "SELECT id, value FROM test_table1"
                     " WHERE id IN (30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42)"
                     " ORDER BY id"))
        end).

%% @doc Tests that "end-of-data" terminator is successfully ignored
from_stdin_with_terminator(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                %% TEXT
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                ?assertEqual(ok, io:put_chars(
                                   C,
                                   "50\tline 50\n"
                                   "51\tline 51\n"
                                   "\\.\n")),
                ?assertEqual({ok, 2}, Module:copy_done(C)),
                %% CSV
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT csv)")),
                ?assertEqual(ok, io:put_chars(
                                   C,
                                   "52,line 52\n"
                                   "53,line 53\n"
                                   "\\.\n")),
                ?assertEqual({ok, 2}, Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{50, <<"line 50">>},
                            {51, <<"line 51">>},
                            {52, <<"line 52">>},
                            {53, <<"line 53">>}
                            ]},
                   Module:equery(C,
                                 "SELECT id, value FROM test_table1"
                                 " WHERE id IN (50, 51, 52, 53) ORDER BY id"))
        end).

from_stdin_corrupt_data(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                %% Wrong number of arguments to io:format
                Fmt = "~w\t~s\n",
                ?assertMatch({error, {fun_exception, {error, badarg, _Stack}}},
                             io:request(C, {format, Fmt, []})),
                ?assertError(badarg, io:format(C, Fmt, [])),
  Wrong return value from IO function
                ?assertEqual({error, {fun_return_not_characters, node()}},
                             io:request(C, {put_chars, unicode, erlang, node, []})),
                ?assertEqual({ok, 0}, Module:copy_done(C)),
                %%
                %% Corrupt text format
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                ?assertEqual(ok, io:put_chars(
                                   C,
                                   "42\n43\nwasd\n")),
                ?assertMatch(
                   #error{codename = bad_copy_file_format,
                          severity = error},
                   receive
                       {epgsql, C, {error, Err}} ->
                           Err
                   after 5000 ->
                           timeout
                   end),
                ?assertEqual({error, not_in_copy_mode},
                             io:request(C, {put_chars, unicode, "queque\n"})),
                ?assertError(badarg, io:format(C, "~w\n~s\n", [60, "wasd"])),
                %%
                %% Corrupt CSV format
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT csv)")),
                ?assertEqual(ok, io:put_chars(
                                   C,
                                   "42\n43\nwasd\n")),
                ?assertMatch(
                   #error{codename = bad_copy_file_format,
                          severity = error},
                   receive
                       {epgsql, C, {error, Err}} ->
                           Err
                   after 5000 ->
                           timeout
                   end),
                %%
                %% Corrupt binary format
                ?assertEqual(
                   {ok, [binary, binary]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT binary)",
                     {binary, [int4, text]})),
                ?assertEqual(
                   ok,
                   Module:copy_send_rows(C, [{44, <<"line 44">>}], 1000)),
                ?assertEqual(ok, io:put_chars(C, "45\tThis is not ok!\n")),
                ?assertMatch(
                   #error{codename = bad_copy_file_format,
                          severity = error},
                   receive
                       {epgsql, C, {error, Err}} ->
                           Err
                   after 5000 ->
                           timeout
                   end),
                %% Connection is still usable
                ?assertMatch(
                   {ok, _, [{1}]},
                   Module:equery(C, "SELECT 1", []))
        end).

 Batch of rows
 Single row
 Rows as lists
 @doc Tests that different IO-protocol APIs work
 Output "32\thello\n" in multiple calls
 Using `file` API
 Binary
 Raw IO-protocol message-passing
 Not documented!
 @doc Tests that "end-of-data" terminator is successfully ignored
 TEXT
 CSV
 Wrong number of arguments to io:format

 Corrupt text format

 Corrupt CSV format

 Corrupt binary format
 Connection is still usable
-module(epgsql_copy_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("stdlib/include/assert.hrl").
-include("epgsql.hrl").

-export([
    init_per_suite/1,
    all/0,
    end_per_suite/1,

    from_stdin_text/1,
    from_stdin_csv/1,
    from_stdin_binary/1,
    from_stdin_io_apis/1,
    from_stdin_with_terminator/1,
    from_stdin_corrupt_data/1
]).

init_per_suite(Config) ->
    [{module, epgsql}|Config].

end_per_suite(_Config) ->
    ok.

all() ->
    [
     from_stdin_text,
     from_stdin_csv,
     from_stdin_binary,
     from_stdin_io_apis,
     from_stdin_with_terminator,
     from_stdin_corrupt_data
    ].

  @doc Test that COPY in text format works
from_stdin_text(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                ?assertEqual(
                   ok,
                   io:put_chars(C,
                                "10\thello world\n"
                                "11\t\\N\n"
                                "12\tline 12\n")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "13\tline 13\n")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "14\tli")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "ne 14\n")),
                ?assertEqual(
                   {ok, 5},
                   Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{10, <<"hello world">>},
                            {11, null},
                            {12, <<"line 12">>},
                            {13, <<"line 13">>},
                            {14, <<"line 14">>}]},
                   Module:equery(C,
                                 "SELECT id, value FROM test_table1"
                                 " WHERE id IN (10, 11, 12, 13, 14) ORDER BY id"))
        end).

  @doc Test that COPY in CSV format works
from_stdin_csv(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT csv, QUOTE '''')")),
                ?assertEqual(
                   ok,
                   io:put_chars(C,
                                "20,'hello world'\n"
                                "21,\n"
                                "22,line 22\n")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "23,'line 23'\n")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "24,'li")),
                ?assertEqual(
                   ok,
                   io:put_chars(C, "ne 24'\n")),
                ?assertEqual(
                   {ok, 5},
                   Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{20, <<"hello world">>},
                            {21, null},
                            {22, <<"line 22">>},
                            {23, <<"line 23">>},
                            {24, <<"line 24">>}]},
                   Module:equery(C,
                                 "SELECT id, value FROM test_table1"
                                 " WHERE id IN (20, 21, 22, 23, 24) ORDER BY id"))
        end).

  @doc Test that COPY in binary format works
from_stdin_binary(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [binary, binary]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT binary)",
                     {binary, [int4, text]})),
                ?assertEqual(
                   ok,
                   Module:copy_send_rows(
                     C,
                     [{60, <<"hello world">>},
                      {61, null},
                      {62, "line 62"}],
                     5000)),
                ?assertEqual(
                   ok,
                   Module:copy_send_rows(
                     C,
                     [{63, <<"line 63">>}],
                     1000)),
                ?assertEqual(
                   ok,
                   Module:copy_send_rows(
                     C,
                     [
                      [64, <<"line 64">>],
                      [65, <<"line 65">>]
                     ],
                     infinity)),
                ?assertEqual({ok, 6}, Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{60, <<"hello world">>},
                            {61, null},
                            {62, <<"line 62">>},
                            {63, <<"line 63">>},
                            {64, <<"line 64">>},
                            {65, <<"line 65">>}]},
                   Module:equery(C,
                                 "SELECT id, value FROM test_table1"
                                 " WHERE id IN (60, 61, 62, 63, 64, 65) ORDER BY id"))
        end).

from_stdin_io_apis(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                ?assertEqual(ok, io:format(C, "30\thello world\n", [])),
                ?assertEqual(ok, io:format(C, "~b\t~s\n", [31, "line 31"])),
                ?assertEqual(ok, io:write(C, 32)),
                ?assertEqual(ok, io:put_chars(C, "\t")),
                ?assertEqual(ok, io:write(C, hello)),
                ?assertEqual(ok, io:nl(C)),
                ?assertEqual(ok, file:write(C, "33\tline 33\n34\tline 34\n")),
                ?assertEqual(ok, io:put_chars(C, <<"35\tline 35\n">>)),
                ?assertEqual(ok, file:write(C, <<"36\tline 36\n">>)),
  IoData
                ?assertEqual(ok, io:put_chars(C, [<<"37">>, $\t, <<"line 37">>, <<$\n>>])),
                ?assertEqual(ok, file:write(C, [["38", <<$\t>>], [<<"line 38">>, $\n]])),
                Ref = erlang:make_ref(),
                C ! {io_request, self(), Ref, {put_chars, unicode, "39\tline 39\n"}},
                ?assertEqual(ok, receive {io_reply, Ref, Resp} -> Resp
                                 after 5000 ->
                                         timeout
                                 end),
                ?assertEqual(ok, io:requests(
                                   C,
                                   [{put_chars, unicode, "40\tline 40\n"},
                                    {put_chars, latin1, "41\tline 41\n"},
                                    {format, "~w\t~s", [42, "line 42"]},
                                    nl])),
                ?assertEqual(
                   {ok, 13},
                   Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{30, <<"hello world">>},
                            {31, <<"line 31">>},
                            {32, <<"hello">>},
                            {33, <<"line 33">>},
                            {34, <<"line 34">>},
                            {35, <<"line 35">>},
                            {36, <<"line 36">>},
                            {37, <<"line 37">>},
                            {38, <<"line 38">>},
                            {39, <<"line 39">>},
                            {40, <<"line 40">>},
                            {41, <<"line 41">>},
                            {42, <<"line 42">>}
                            ]},
                   Module:equery(
                     C,
                     "SELECT id, value FROM test_table1"
                     " WHERE id IN (30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42)"
                     " ORDER BY id"))
        end).

from_stdin_with_terminator(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                ?assertEqual(ok, io:put_chars(
                                   C,
                                   "50\tline 50\n"
                                   "51\tline 51\n"
                                   "\\.\n")),
                ?assertEqual({ok, 2}, Module:copy_done(C)),
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT csv)")),
                ?assertEqual(ok, io:put_chars(
                                   C,
                                   "52,line 52\n"
                                   "53,line 53\n"
                                   "\\.\n")),
                ?assertEqual({ok, 2}, Module:copy_done(C)),
                ?assertMatch(
                   {ok, _, [{50, <<"line 50">>},
                            {51, <<"line 51">>},
                            {52, <<"line 52">>},
                            {53, <<"line 53">>}
                            ]},
                   Module:equery(C,
                                 "SELECT id, value FROM test_table1"
                                 " WHERE id IN (50, 51, 52, 53) ORDER BY id"))
        end).

from_stdin_corrupt_data(Config) ->
    Module = ?config(module, Config),
    epgsql_ct:with_connection(
        Config,
        fun(C) ->
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                Fmt = "~w\t~s\n",
                ?assertMatch({error, {fun_exception, {error, badarg, _Stack}}},
                             io:request(C, {format, Fmt, []})),
                ?assertError(badarg, io:format(C, Fmt, [])),
  Wrong return value from IO function
                ?assertEqual({error, {fun_return_not_characters, node()}},
                             io:request(C, {put_chars, unicode, erlang, node, []})),
                ?assertEqual({ok, 0}, Module:copy_done(C)),
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT text)")),
                ?assertEqual(ok, io:put_chars(
                                   C,
                                   "42\n43\nwasd\n")),
                ?assertMatch(
                   #error{codename = bad_copy_file_format,
                          severity = error},
                   receive
                       {epgsql, C, {error, Err}} ->
                           Err
                   after 5000 ->
                           timeout
                   end),
                ?assertEqual({error, not_in_copy_mode},
                             io:request(C, {put_chars, unicode, "queque\n"})),
                ?assertError(badarg, io:format(C, "~w\n~s\n", [60, "wasd"])),
                ?assertEqual(
                   {ok, [text, text]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT csv)")),
                ?assertEqual(ok, io:put_chars(
                                   C,
                                   "42\n43\nwasd\n")),
                ?assertMatch(
                   #error{codename = bad_copy_file_format,
                          severity = error},
                   receive
                       {epgsql, C, {error, Err}} ->
                           Err
                   after 5000 ->
                           timeout
                   end),
                ?assertEqual(
                   {ok, [binary, binary]},
                   Module:copy_from_stdin(
                     C, "COPY test_table1 (id, value) FROM STDIN WITH (FORMAT binary)",
                     {binary, [int4, text]})),
                ?assertEqual(
                   ok,
                   Module:copy_send_rows(C, [{44, <<"line 44">>}], 1000)),
                ?assertEqual(ok, io:put_chars(C, "45\tThis is not ok!\n")),
                ?assertMatch(
                   #error{codename = bad_copy_file_format,
                          severity = error},
                   receive
                       {epgsql, C, {error, Err}} ->
                           Err
                   after 5000 ->
                           timeout
                   end),
                ?assertMatch(
                   {ok, _, [{1}]},
                   Module:equery(C, "SELECT 1", []))
        end).
