From 717df5ae926bbe46f7529f8dc6cc62512affd35d Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi <16166434+thalesmg@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:42:56 -0300 Subject: [PATCH 1/2] fix: handle bad datetime parameters more gracefully Fixes https://emqx.atlassian.net/browse/EMQX-13306 --- src/commands/epgsql_cmd_prepared_query2.erl | 37 +++++++++++++-------- src/epgsql_idatetime.erl | 4 ++- src/epgsql_wire.erl | 17 +++++++--- test/epgsql_SUITE.erl | 19 +++++++++++ 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/commands/epgsql_cmd_prepared_query2.erl b/src/commands/epgsql_cmd_prepared_query2.erl index 74f27a1a..6996b91e 100644 --- a/src/commands/epgsql_cmd_prepared_query2.erl +++ b/src/commands/epgsql_cmd_prepared_query2.erl @@ -32,7 +32,7 @@ init({Name, Parameters}) -> #pquery2{name = Name, params = Parameters}. -execute(Sock, #pquery2{name = Name, params = Params} = State) -> +execute(Sock, #pquery2{name = Name} = State) -> case maps:get(Name, epgsql_sock:get_stmts(Sock), undefined) of undefined -> Error = #error{ @@ -43,19 +43,28 @@ execute(Sock, #pquery2{name = Name, params = Params} = State) -> extra = [] }, {finish, {error, Error}, Sock}; - #statement{types = Types} = Stmt -> - TypedParams = zip(Name, Types, Params), - #statement{name = StatementName, columns = Columns} = Stmt, - Codec = epgsql_sock:get_codec(Sock), - Bin1 = epgsql_wire:encode_parameters(TypedParams, Codec), - Bin2 = epgsql_wire:encode_formats(Columns), - Commands = - [ - epgsql_wire:encode_bind("", StatementName, Bin1, Bin2), - epgsql_wire:encode_execute("", 0), - epgsql_wire:encode_sync() - ], - {send_multi, Commands, Sock, State#pquery2{stmt = Stmt}} + #statement{} = Stmt -> + do_execute(Sock, State, Stmt) + end. + +do_execute(Sock, State, Statement) -> + #pquery2{name = Name, params = Params} = State, + #statement{types = Types, name = StatementName, columns = Columns} = Statement, + TypedParams = zip(Name, Types, Params), + Codec = epgsql_sock:get_codec(Sock), + try + Bin1 = epgsql_wire:encode_parameters(TypedParams, Codec), + Bin2 = epgsql_wire:encode_formats(Columns), + Commands = + [ + epgsql_wire:encode_bind("", StatementName, Bin1, Bin2), + epgsql_wire:encode_execute("", 0), + epgsql_wire:encode_sync() + ], + {send_multi, Commands, Sock, State#pquery2{stmt = Statement}} + catch + throw:#{} = Context -> + {finish, {error, Context}, Sock} end. zip(Name, Types, Params) -> diff --git a/src/epgsql_idatetime.erl b/src/epgsql_idatetime.erl index 13d6f03c..c6fceecb 100644 --- a/src/epgsql_idatetime.erl +++ b/src/epgsql_idatetime.erl @@ -96,7 +96,9 @@ i2timestamp2(D, T) -> timestamp2i({Date, Time}) -> D = date2j(Date) - ?POSTGRES_EPOC_JDATE, - D * ?USECS_PER_DAY + time2i(Time). + D * ?USECS_PER_DAY + time2i(Time); +timestamp2i(X) -> + throw(bad_param). now2i({MegaSecs, Secs, MicroSecs}) -> (MegaSecs * 1000000 + Secs) * 1000000 + MicroSecs - ?POSTGRES_EPOC_USECS. diff --git a/src/epgsql_wire.erl b/src/epgsql_wire.erl index 8e7cb5cc..d9c81c31 100644 --- a/src/epgsql_wire.erl +++ b/src/epgsql_wire.erl @@ -265,10 +265,19 @@ encode_parameters([], Count, Formats, Values, _Codec) -> [<>, Formats, <> | lists:reverse(Values)]; encode_parameters([P | T], Count, Formats, Values, Codec) -> - {Format, Value} = encode_parameter(P, Codec), - Formats2 = <>, - Values2 = [Value | Values], - encode_parameters(T, Count + 1, Formats2, Values2, Codec). + try + {Format, Value} = encode_parameter(P, Codec), + Formats2 = <>, + Values2 = [Value | Values], + encode_parameters(T, Count + 1, Formats2, Values2, Codec) + catch + throw:bad_param -> + {Type, Value0} = P, + throw(#{reason => bad_param, + index => Count, + type => Type, + value => Value0}) + end. %% @doc encode single 'typed' parameter -spec encode_parameter({Type, Val :: any()}, diff --git a/test/epgsql_SUITE.erl b/test/epgsql_SUITE.erl index cbaecfe7..a11d4da2 100644 --- a/test/epgsql_SUITE.erl +++ b/test/epgsql_SUITE.erl @@ -97,6 +97,7 @@ groups() -> prepared_query, prepared_query2, + prepared_query2_bad_params, select, insert, update, @@ -488,6 +489,24 @@ prepared_query2(Config) -> Module:prepared_query2(C, "non_existent_query", [4]) end). +%% Checks that we don't crash ungracefully if user provides parameters that cannot be +%% encoded by the intended codec. +prepared_query2_bad_params(Config) -> + Module = ?config(module, Config), + epgsql_ct:with_connection(Config, fun(C) -> + Name = "bad_params", + Column = get_type_col(timestamp), + SQL = io_lib:format("insert into test_table2(~s) values ($1)", [Column]), + {ok, _} = Module:parse2(C, Name, SQL, []), + {error, #{ reason := bad_param + , index := 0 + , type := timestamp + , value := <<"2024-06-30 00:10:00">> + }} = + Module:prepared_query2(C, Name, [<<"2024-06-30 00:10:00">>]), + ok + end). + select(Config) -> Module = ?config(module, Config), epgsql_ct:with_connection(Config, fun(C) -> From 852c89bdae22f1f4476bc493cd9aa14ae0c9bda6 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi <16166434+thalesmg@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:53:18 -0300 Subject: [PATCH 2/2] chore: bump app vsn --- src/epgsql.app.src | 2 +- src/epgsql.appup.src | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/epgsql.app.src b/src/epgsql.app.src index 4c2979cb..1f3fa2f0 100644 --- a/src/epgsql.app.src +++ b/src/epgsql.app.src @@ -1,6 +1,6 @@ {application, epgsql, [{description, "PostgreSQL Client"}, - {vsn, "4.7.1.3"}, + {vsn, "4.7.1.4"}, {modules, []}, {registered, []}, {applications, [kernel, diff --git a/src/epgsql.appup.src b/src/epgsql.appup.src index ba358a67..3713a2be 100644 --- a/src/epgsql.appup.src +++ b/src/epgsql.appup.src @@ -1,5 +1,5 @@ %% -*- mode: erlang -*- -{"4.7.1.3", +{"4.7.1.4", [ {<<"4\\.6\\.[0-9]">>, [ {load_module, epgsql, brutal_purge, soft_purge, []},