Giter Site home page Giter Site logo

pgo's Introduction

PGO

Tests codecov Hex.pm

PG...Oh god not nother Postgres client in Erlang...

Why

  • No message passing. Clients checkout the socket and use it directly.
  • Binary protocol with input oids cached.
  • Simple and direct. Tries to limit runtime options as much as possible.
  • Instrumented with Telemetry and OpenCensus
  • Mix apps currently too hard to use in a Rebar3 project.

Requirements

Erlang/OTP 21.3 and above.

Use

Pools defined in the pgo application's environment will be started on boot. You can also add pools dynamically with pgo:start_pool/3.

To try pgo simply modify config/example.config by replacing the host, database, user and password values for the database you wish to connect to:

[
  {pgo, [{pools, [{default, #{pool_size => 10,
                              host => "127.0.0.1",
                              database => "test",
                              user => "test"}}]}]}
].

default is the name of the pool, size is the number of connections to create for the pool. Or you can start the pool through pgo:start_pool/2 which creates it as a child of pgo's simple one for one:

> pgo:start_pool(default, #{pool_size => 5, host => "127.0.0.1", database => "test", user => "test"}). 

Or start a pool as a child of your application's supervisor:

ChildSpec = #{id => pgo_pool,
              start => {pgo_pool, start_link, [Name, PoolConfig]},
              shutdown => 1000},

Then start a shell with rebar3 shell, it will boot the applications which will start the pool automatically if it is configured through sys.config.

> pgo:query("select 1").
#{command => select, num_rows => 1, rows => [{1}]}
> pgo:transaction(fun() ->
>     pgo:query("INSERT INTO my_table(name) VALUES('Name 1')"),
>     pgo:query("INSERT INTO my_table(name) VALUES('Name 2')")
> end).
#{command => insert,num_rows => 1,rows => []}

Options

Pool configuration includes the Postgres connection information, pool configuration like size and defaults for options used at query time.

#{host => string(),
  port => integer(),
  user => string(),
  password => string(),
  database => string(),

  %% pool specific settings
  pool_size => integer(),
  queue_target => integer(),
  queue_interval => integer(),
  idle_interval => integer(),

  %% gen_tcp socket options
  socket_options => [gen_tcp:socket_option()],

  %% defaults for options used at query time
  queue => boolean(),
  trace => boolean(),
  decode_opts => [decode_option()]}

The query time options can also be set through options passed to pgo:query/3:

decode_fun() :: fun((row(), fields()) -> row()) | undefined.

decode_option() :: return_rows_as_maps | {return_rows_as_maps, boolean()} |
                   column_name_as_atom | {column_name_as_atom, boolean()} |
                   {decode_fun, decode_fun()}.
                         
#{pool => atom(),
  trace => boolean(),
  queue => boolean(),
  decode_opts => [decode_option()]}

Query Options

  • pool (default: default): Name of the pool to use for checking out a connection to the database.
  • return_rows_as_maps (default: false): When true each row is returned as a map of column name to value instead of a list of values.
  • column_name_as_atom (default: false): If true converts each column name in the result to an atom.
  • decode_fun (default: undefined): Optional function for performing transformations on each row in a result. It must be a 2-arity function returning a list or map for the row and takes the row (as a list or map) and a list of #row_description_field{} records.
  • queue (default: true): Whether to wait for a connection from the pool if none are available.
  • trace (default: false): pgo is instrumented with OpenCensus and when this option is true a span will be created (if sampled).

Database Settings

  • host (default: 127.0.0.1): Database server hostname.
  • port (default: 5432): Port the server is listening on.
  • user: Username to connect to database as.
  • password: Password for the user.
  • database: Name of database to use.
  • ssl (default: false): Whether to use SSL or not.
  • ssl_options: List of SSL options to use if ssl is true. See the Erlang SSL connect options.
  • connection_parameters (default: []): List of 2-tuples, where key and value must be binary strings. You can include any Postgres connection parameter here, such as {<<"application_name">>, <<"myappname">>} and {<<"timezone">>, <<"GMT">>}.

Pool Settings

  • pool_size (default: 1): Number of connections to keep open with the database
  • queue_target (default: 50) and queue_interval (default: 1000): Checking out connections is handled through a queue. If it takes longer than queue_target to get out of the queue for longer than queue_interval then the queue_target will be doubled and checkouts will start to be dropped if that target is surpassed.
  • idle_interval (default: 1000): The database is pinged every idle_interval when the connection is idle.

Erlang TCP Socket Settings

  • socket_options (default []): Addition options to pass to gen_tcp:connect such as inet6 for IPv6 support.

Telemetry and Tracing

A Telemetry event [pgo, query] can be attached to for receiving the time a query takes as well as other metadata for each query.

OpenCensus spans can be enabled for queries and transactions by either setting the trace_default to true for the pool:

> pgo:start_pool(default, #{host => "127.0.0.1", 
                            database => "test", 
                            user => "test",
                            pool_size => 5,
                            trace_default => true}]). 

Or by passing #{trace => true} in the options for a query or transaction:

> pgo:query("select 1", [], #{trace => true}).
#{command => select, num_rows => 1, rows => [{1}]}
> pgo:transaction(fun() ->
>     pgo:query("INSERT INTO my_table(name) VALUES('Name 1')"),
>     pgo:query("INSERT INTO my_table(name) VALUES('Name 2')")
> end, #{trace => true}).
#{command => insert,num_rows => 1,rows => []}

Note that since this is optional the opencensus application is not included as a dependency of pgo. So it must be included as a rebar3 dependency and runtime dependency (listed in your application's .app.src applications or the list of applications for relx to include in a release).

Running Tests

Pool functionality is tested with common test suites:

$ rebar3 ct

Postgres query functionality is tested with eunit, create user test and database test:

$ rebar3 eunit

Acknowledgements

Much is owed to https://github.com/semiocast/pgsql (especially for protocol step logic) and https://github.com/epgsql/epgsql/ (especially for some decoding logic).

The pool implementation is owed to James Fish's found in db_connection PR 108. While db_connection and postgrex as a whole were both used as inspiration as well.

pgo's People

Contributors

asabil avatar benbro avatar crowdhailer avatar getong avatar lpil avatar starbelly avatar tsloughter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pgo's Issues

Error encoding infinity

pgo:query(<<"SELECT tstzrange($1, $2, '[)');">>, [{{2020,1,1},{0,0,0}}, infinity]).
{error,function_clause}

Set pgo:query/3 timeout

Is there a way to set a timeout for pgo:query/3? I couldn't find a way to increase it.
I have SQL query that deleted many records and takes a long time.

Execute multiple statements in one string

Hello!

Is it possible to execute multiple statements in one string?

My use case is for schema migrations. I wish to have multiple DDL statements in one file, read the file, and then execute the statements from that file.

Currently I seem to have to split the statements into one per string or per file, which is rather awkward UX.

Thanks!
Louis

Getting field information in decode_fun/2 when no rows are returned

Is it possible to get the field information when no matching rows are returned. For example, selecting an empty table and providing the decode fun? decode_fun/2 provides a nice opportunity to describe a query but unfortunately it does not get called because of the receive_loop0/6 that matches on #no_data{} . When using psql (as well as other drivers like Go and Java) it returns the column names at minimum.

I am poking around the pgo_protocol.erl code, but am having a hard time tracking the DecodeOpts.

Transaction break on unique constraint error

If you have a users table with unique email column and you try to insert the same email twice in a transaction pgo:query/1 will give you an error:

{error, {pgsql_error, #{code := <<"23505">>}}}

But a transaction throw an exception instead of returning this error tuple:

pgo:transaction(fun() ->
    pgo:query(<<"INSERT INTO users (email) VALUES(<<\"[email protected]\">>);">>)
end).
** exception error: no match of right hand side value #{command => rollback,num_rows => nil,rows => []}
     in function  pgo:transaction/3 (pgo/src/pgo.erl, line 152)

This line should probably allow rollback:

#{command := commit} = pgo_handler:extended_query(Conn, "COMMIT", [],
     #{queue_time => undefined}),

Error decoding Interval

pgo:query(<<"SELECT '7 days'::interval;">>).
** exception error: no function clause matching pg_time:decode_time(<<0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0>>) 

default pool name

The pool name is used to register the pid and find it:

gen_server:start_link({local, Pool}, ?MODULE, {Pool, PoolConfig1}, []).
erlang:whereis(Pool)

Having a default pool name is useful but isn't using 'default' as the default pool name dangerous?
That's a generic word and if any other app in the release will register a process with the same name it will break.
Will it be a good idea to change the default pool name to 'pgo_default_pool' or similar?

Support for SCRAM Authentication

Support for SCRAM password authentication was introduced in PostgreSQL 10; PostgreSQL 14 defaults to using scram-sha-256 for password hashing by default:

Change the default of the password_encryption server parameter to scram-sha-256

While the md5 is still supported, it is gradually being phased out in favor of SCRAM.

If you need some reference implementations beyond the PostgreSQL source, here is a list of known PostgreSQL drivers + SCRAM status ( I also have a presentation on the topic that shows the workflow).

badmatch error in pg_pool

I got this error in the log:

** Generic server default terminating
** Last message in was {'ETS-TRANSFER',#Ref<0.825018040.2050031153.49530>,
                           <0.18195.0>,#Ref<0.825018040.2049031153.102544>}
** When Server state == {ready,#Ref<0.825018040.2049031153.102544>,
                               #{delay => 0,
                                 idle => #Ref<0.825018040.2049966081.69934>,
                                 idle_interval => 1000,interval => 1000,
                                 next => -566460740002,
                                 poll => #Ref<0.825018040.2049966081.69933>,
                                 slow => false,target => 50}}
** Reason for termination ==
** {{badmatch,
        [{'__info__',<0.913.0>,#Ref<0.825018040.2049966081.69942>,
             {conn,<0.913.0>,
                 {sslsocket,
                     {gen_tcp,#Port<0.11>,tls_connection,undefined},
                     [<0.1350.0>,<0.1129.0>]},
                 ssl,default,
                 #{<<"DateStyle">> => <<"ISO, MDY">>,
                   <<"IntervalStyle">> => <<"postgres">>,
                   <<"TimeZone">> => <<"Etc/UTC">>,
                   <<"application_name">> => <<"[email protected]">>,
                   <<"client_encoding">> => <<"UTF8">>,
                   <<"integer_datetimes">> => <<"on">>,
                   <<"is_superuser">> => <<"off">>,
                   <<"server_encoding">> => <<"UTF8">>,
                   <<"server_version">> => <<"12.2 (Ubuntu 12.2-4)">>,
                   <<"session_authorization">> => <<"app">>,
                   <<"standard_conforming_strings">> => <<"on">>},
                 true,false,[]},
             {conn,<0.913.0>,
                 {sslsocket,
                     {gen_tcp,#Port<0.11>,tls_connection,undefined},
                     [<0.1350.0>,<0.1129.0>]},
                 ssl,default,
                 #{<<"DateStyle">> => <<"ISO, MDY">>,
                   <<"IntervalStyle">> => <<"postgres">>,
                   <<"TimeZone">> => <<"Etc/UTC">>,
                   <<"application_name">> => <<"[email protected]">>,
                   <<"client_encoding">> => <<"UTF8">>,
                   <<"integer_datetimes">> => <<"on">>,
                   <<"is_superuser">> => <<"off">>,
                   <<"server_encoding">> => <<"UTF8">>,
                   <<"server_version">> => <<"12.2 (Ubuntu 12.2-4)">>,
                   <<"session_authorization">> => <<"app">>,
                   <<"standard_conforming_strings">> => <<"on">>},
                 true,false,[]}}]},
    [{pgo_pool,delete_holder,2,
         [{file,
              "pgo/src/pgo_pool.erl"},
          {line,385}]},
     {pgo_pool,handle_info,2,
         [{file,
              "pgo/src/pgo_pool.erl"},
          {line,134}]},
     {gen_server,try_dispatch,4,[{file,"gen_server.erl"},{line,680}]},
     {gen_server,handle_msg,6,[{file,"gen_server.erl"},{line,756}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,226}]}]}

{badmatch,{error,closed}} in pgo_connection

To reproduce the error, restart DB server on a different machine.
pgo_handler:ping(Conn) in line 123 should handle {error, closed} response?

handle_event(cast, {ping, Holder}, Data=#data{pool=Pool,
                                              holder=Holder,
                                              queue=QueueTid,
                                              conn=Conn}) ->
    ok = pgo_handler:ping(Conn),
    NewHolder = pgo_pool:update(Pool, QueueTid, ?MODULE, Conn),
    {keep_state, Data#data{holder=NewHolder}};

Log:

supervisor: {<0.724.0>,pgo_connection_sup}
errorContext: child_terminated
reason: {{badmatch,{error,closed}},
         [{pgo_connection,handle_event,3,
                          [{file,"pgo/src/pgo_connection.erl"},
                           {line,123}]},
          {gen_statem,loop_state_callback,11,
                      [{file,"gen_statem.erl"},{line,1166}]},
          {proc_lib,init_p_do_apply,3,
                    [{file,"proc_lib.erl"},{line,226}]}]}
offender: [{pid,<0.838.0>},
           {id,pgo_connection},
           {mfargs,
               {pgo_connection,start_link,
                   [#Ref<0.1330457315.1750590656.121213>,<0.233.0>,
                    default,<0.824.0>,
                    #{database => "app",
                      host => "localhost",
                      password =>
                          "*******",
                      pool_size => 5,ssl => true,
                      ssl_options => [{verify,verify_none}],
                      user => "app"}]}},
           {restart_type,permanent},
           {shutdown,100},
           {child_type,worker}]

implement ping

Currently the ping to check on connections is not being done and simply acts as a success. Implement a ping to the postgres server to verify the connection is working and reconnect if it is not.

Cannot connect to Postgres on AWS

I have a Postgres instance on AWS (from Heroku) which I can connect to by other means (psql, Ruby, etc.). However, I cannot connect using pgo.

The sanitized url example:

postgres://user:[email protected]:5432/databasename

I tried testing this in the rebar3 shell:

$ rebar3 shell
1> application:ensure_all_started(pgo).
{ok,[backoff,opentelemetry_api,pg_types,pgo]}
2> application:ensure_all_started(ssl).
{ok,[]}
3> pgo:start_pool(default, #{pool_size => 5, host => "ec2-nn-nnn-nnn-nn.compute-1.amazonaws.com", database => "databasename", user => "user", password => "password"}).
{ok,<0.254.0>}
4> pgo:query("SELECT id, author_id, slug, title FROM posts WHERE id = 5").
{error,none_available}

Notes

  • I also tried ssl => true in pgo:start_pool; same {error,none_available} result though.

Reference: lpil/pgo#21

Decoding error when calling a function that returns void

I made a function to do a complex update so I had no need for a return value, hence the void return type.

My use of pgo looked like this:

pgo:query(<<"SELECT foo($1)">>, [Id])

This resulted in pgo erroring out not knowing how to decode the value which is understandable.

The easy workaround was to set the return type to integer, and return 0 in the function.

I'm not sure if this really needs to be fixed, but I thought it was worth reporting the error since it failed on a valid postgres query.

Client side HA with target_session_attrs

With libpq it is possible to specify multiple hosts and require a read-write connection. The client will connect all hosts until it finds the primary. This is a nice way to support HA on the client without a need to put a load balancer in front of the postgres cluster.
pg_auto_failover uses this feature for HA.

We can use SELECT pg_is_in_recovery(); to check if the node is primary or not and periodically check it to see if a failover happened and we need to find a new primary. default_transaction_read_only might also be needed.

Is there a way to achieve this with pgo?

Crash when executing `do` statement

Hello!

If I execute this statement:

do $$
begin

insert into hex_user
  (username, email, hex_url)
values
  ('one', '[email protected]', null);

insert into hex_user
  (username, email, hex_url)
values
  ('two', '[email protected]', null);

end
$$

pgo crashes after successfully running the statement.

exception error: no match of right hand side value do
  in function  pgo_handler:receive_loop0/6 (/Users/louis/src/gleam/packages/build/dev/erlang/pgo/src/pgo_handler.erl, line 462)
  in call from pgo_handler:extended_query/6 (/Users/louis/src/gleam/packages/build/dev/erlang/pgo/src/pgo_handler.erl, line 363)
  in call from otel_tracer_noop:with_span/5 (/Users/louis/src/gleam/packages/build/dev/erlang/opentelemetry_api/src/otel_tracer_noop.erl, line 59)
  in call from pgo:query/3 (/Users/louis/src/gleam/packages/build/dev/erlang/pgo/src/pgo.erl, line 103)
  in call from gleam_pgo_ffi:query/3 (/Users/louis/src/gleam/packages/build/dev/erlang/gleam_pgo/_gleam_artefacts/gleam_pgo_ffi.erl, line 63)
  in call from gleam@pgo:execute/4 (/Users/louis/src/gleam/packages/build/dev/erlang/gleam_pgo/_gleam_artefacts/[email protected], line 946)
  in call from packages:main/0 (/Users/louis/src/gleam/packages/build/dev/erlang/packages/_gleam_artefacts/packages.erl, line 19)โŽ 

The line in question:

{Command, NumRows} = decode_tag(Tag),

The crash happens after the statement runs, so the rows are successfully inserted.

# select * from hex_user;
 id | username |    email    | hex_url 
----+----------+-------------+---------
  1 | one      | [email protected] | 
  2 | two      | [email protected] | 
(2 rows)

Related to #68

Thanks,
Louis

Query works with pgapp but not pgo - return value issue udt_name::regtype

The following query returns the correct udt_names when using pgapp such as integer[]:

-define(SELECT_COLUMNS, "SELECT table_name, column_name, ordinal_position, data_type, udt_name::regtype
                         FROM information_schema.columns
                         WHERE table_schema = $1 AND table_name = $2").

pgo:query(?SELECT_COLUMNS, [Schema, TableName], #{decode_opts => [{return_rows_as_maps, true}, {column_name_as_atom, true}]})

With the pgo client, it returns values with the format:

{regtype,<<0,0,0,23>>}

IPv6 support

Hello!

Can this library be used with IPv6? Looking at Elixir's Postgrex I believe I need to add inet6 as a socket option but I could not spot a way to do this.

Thanks,
Louis

** exception error: undefined function persistent_term:get/2

Using the latest pull of pgo and pg_types, there seems to be an error related to the pg_types dependency. Running something simple like pgo:query("select 1"). throws an odd error -

2> pgo:query("select 1").
** exception error: undefined function persistent_term:get/2
     in function  pg_types:lookup_type_info/2 (src/pg_types.erl, line 85)
     in call from pgo_protocol:decode_row_description_message0/5 (src/pgo_protocol.erl, line 435)
     in call from pgo_protocol:decode_row_description_message/3 (src/pgo_protocol.erl, line 418)
     in call from pgo_handler:receive_message/4 (src/pgo_handler.erl, line 462)
     in call from pgo_handler:flush_until_ready_for_query/2 (src/pgo_handler.erl, line 437)
     in call from pgo_handler:extended_query/6 (src/pgo_handler.erl, line 297)
     in call from pgo_handler:extended_query/5 (src/pgo_handler.erl, line 73)

Admittedly, this should probably be reported at pg_types, but I'm facing it wrt pgo...

Query works with pgapp but not pgo - binding issue with $1::regclass

The following query:

-define(SELECT_PRIMARY_KEYS, "SELECT a.attname "
                             "FROM   pg_index i "
                             "JOIN   pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) "
                             "WHERE  i.indrelid = $1::regclass "
                             "AND    i.indisprimary").

works and returns the correct results when using pgapp.

pgapp:equery(reader_pool, ?SELECT_PRIMARY_KEYS, [Schema ++ "." ++ TableName])

When executed using pgo, I get the following error:

pgo:query(Q, ["public.example_a"]).
{error,{pgsql_error,#{code => <<"22P03">>,file => <<"postgres.c">>,
                      line => <<"1743">>,
                      message =>
                          <<"incorrect binary data format in bind parameter 1">>,
                      routine => <<"exec_bind_message">>,severity => <<"ERROR">>,
                      {unknown,86} => <<"ERROR">>}}}

If I use an explicit string instead of parameters it works:

6> Q1 = "SELECT a.attname FROM   pg_index i JOIN   pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE  i.indrelid = 'public.example_a'::regclass AND    i.indisprimary".
"SELECT a.attname FROM   pg_index i JOIN   pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE  i.indrelid = 'public.example_a'::regclass AND    i.indisprimary"
7> pgo:query(Q1, []).                                                                                                                                                                          #{command => select,num_rows => 2,
  rows => [{<<"column_a">>},{<<"column_b">>}]}
8>

Passing value of datatype interval in parametrized query

I am trying to pass an interval as a query parameter. In the example query below, the table structure is simple - name is of type text, and creation_date is of type timestamp. So when I do something like

>pgo:query("select name from new_table where 
current_timestamp - creation_date < '6 days'"). 

it works nicely. But what I really want to do is get that value of 6 days from the server. So I try something like

>pgo:query("select name from new_table where
current_timestamp - timestamp < $1", ["6 days"]).

it throws an error. I tried '6 days', "'6 days'" and a few other concoctions, all throw errors.

The error message I get (when I pass "2 days" or "'2 days'" as the query parameter) looks like:

{error,{pgsql_error,#{code => <<"08P01">>,file => <<"pqformat.c">>,
                          line => <<"575">>,
                          message => <<"insufficient data left in message">>,
                          routine => <<"pq_copymsgbytes">>,severity => <<"ERROR">>,
                          {unknown,86} => <<"ERROR">>}}}

And when I pass '2 days' as the parameter, it throws a badarg error:
{error,{badarg,'2 days'}}

Most likely, passing '2 days' is confusing to the parser since an Erlang atom is enclosed in single quotes, and a string gets passed as is but doesn't get typecast into type interval by postgres automatically. I am not sure if/how this should be tackled...

SSL bugs

pgo:open/2 is always using the tcp socket even when pgo_handler:setup_ssl/2 is creating ssl socket.

Insert json string into JSONB column

Hello!

I have a JSON string that I wish to insert into a JSONB column directly rather than parse into a map to then be serialized again by pgo. Is this possible?

Thanks,
Louis

tstzrange parentheses

tstzrange in PostgreSQL:

tstzrange('2019-12-01 00:00:00+00','2019-12-02 00:00:00+00','[)')

In pgo:

{{{{2019,12,1},{0,0,0}},{{2019,12,2},{0,0,0}}},{true,false}}

Is it possible to remove one pair of parentheses to make the representation closer to PostgreSQL?

{DateTime1, DateTime2, {LowerBound, UpperBound}}

instead of

{{DateTime1, DateTime2}, {LowerBound, UpperBound}}

Lock transactions

Add support for checking the reference that comes with the checked out connection to reject any requests with a connection after a deadline or after it was already checked back in.

Cryptic {error, badarg} return

Accidentally set a map with a field value of {ok, 12345} instead of 12345. Calling pgo:query/2 with the param list returns only {error, badarg}, which is correct but challenging to debug.

Start unnamed pools

Hello!

Would it be possible to remove the requirement for pools to be named?

Currently if we want to dynamically connect to a database (for example, using different databases for different users in a multitenant system) we need to dynamically generate an atom and have some mechanism for recycling these atoms to avoid hitting the atom limit. This can be challenging to implement.

If we could reference the pool using a pid or a reference rather than a name we wouldn't have this issue.

Thanks,
Louis

date param error

This query gives badarg error in v0.10.0:

pgo:query(<<"SELECT $1::date;">>, [{2020,1,1}]).

It works in v0.8.0:

#{command => select,num_rows => 1,rows => [{{2020,1,1}}]}

Warning message from telemetry

Accessing the database for just about anything using pgo gives a warning -
Using execute/3 with a single event value is deprecated. Use a measurement map instead.

On checking around a bit, this error message seems to be from telemetry. Some other apps seem to have had the same issue, e.g. nats-io/nats.ex#85
But I am uncertain if their solution strategy of simply hiding telemetry warning is the right one..

Can't connect to aiven db

Hello! After setting up an aiven db and testing the connection with rebar3 shell, it seems that it can't connect:

Pool creation:

pgo:start_pool(test, #{pool_size => 5, host => "[REDACTED].a.aivencloud.com", database => "defaultdb", port => 22381, user => "avnadmin", password => "[REDACTED]", ssl => true, ssl_options => [{{cacertfile, "./ca.pem"}, {verify, verify_peer}]}).

Test command:

pgo:query("select id from post", #{pool => test}).

and (because I don't know if it needs to be a string)

pgo:query("select id from post", #{pool => "test"}).

Return from both queries

{error,none_available}

Can someone help me?

unhandled message in pgo_pool

warning: ** Undefined handle_info in pgo_pool, ** Unhandled message: {timeout,#Ref<0.4064511622.2734686209.231877>,{-576460631271,-576460633271}}

parse json_agg as json

Is it possible to parse the json and jsob types?
Currenty pgo returns the column as binary.
OTP 27 will have a json module so it might make sense.

CREATE TEMPORARY TABLE j (
    id SERIAL PRIMARY KEY,
    name varchar(10)
);

INSERT INTO j (name) VALUES ('n1');
INSERT INTO j (name) VALUES ('n2');

SELECT json_agg(json_build_array(id, name)) FROM j;
pgo:query(<<"SELECT json_agg(json_build_array(id, name)) FROM j;">>).
#{command => select,
  rows => [{<<"[[1, \"n1\"], [2, \"n2\"]]">>}],
  num_rows => 1}

I can't use array_agg instead of json_agg because arrays support only single data type.

SELECT array_agg(ARRAY[id, name]) FROM j;
ERROR:  ARRAY types integer and character varying cannot be matched

Can't connect to PostgreSQL 14 with SSL

I'm using Erlang OTP-24.3.3 and PostgreSQL 12 with SSL authentication successfully.
I can't connect to a new database server running PostgreSQL 14.
I don't see any error in the database log file. On the client I only see the error {error,none_available} when trying to use pgo:query/2.

I'm using self signed ssl cert:

[
 {pgo, [{pools, [{default, #{pool_size => 5,
                          host => "<db-ip>",
                          database => "mydb",
                          user => "myuser",
                          password => "mypassword",
                          ssl => true,
                          ssl_options => [{cacertfile, "/path/to/ca.crt"},
                                                   {certfile,   "/path/to/db.crt"},
                                                   {keyfile,    "/path/to/db.key"},
                                                   {verify,     verify_peer},
                                                   {server_name_indication, "db.myserver.com"}]
                            }
  }]}]}
].
postgresql.conf
ssl = on
ssl_ca_file = '/path/to/ca.crt'
ssl_cert_file = '/path/to/db.crt'
ssl_key_file = '/path/to/db.key'

and:

pg_hba.conf
hostssl all all 0.0.0.0/0 md5 clientcert=verify-ca

I've also tried to change password_encryption to md5 in postgresql.conf because of the missing scram support in pgo:

password_encryption = md5

Any idea what's missing?

Function as password

Would you accept a PR that allows the password to be a zero-arity function? There are a few use cases for it. One example is pulling the password from a secrets store. Another is using token that is regenerated after a period of time, like AWS IAM authentication in Postgres RDS

function_clause error when querying if tstzrange contains timestamptz

This gives an error:

pgo:query(<<"SELECT tstzrange('2020-01-01 00:00:00+00', '2021-01-01 00:00:00+00', '[)') @> $1;">>, [{{2020, 2, 1}, {0, 0, 0}}]).
{error,function_clause}

Casting to timestamptz works:

pgo:query(<<"SELECT tstzrange('2020-01-01 00:00:00+00', '2021-01-01 00:00:00+00', '[)') @> $1::timestamptz;">>, [{{2020, 2, 1}, {0, 0, 0}}]).
#{command => select,num_rows => 1,rows => [{true}]}

Can not boot to shell (dev)

Trying to boot to shell from within pgo repo produces the following on mac os x high sierra.

Erlang/OTP 20 [erts-9.2.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [kernel-poll:false]

Eshell V9.2.1  (abort with ^G)
1> ===> The rebar3 shell is a development tool; to deploy applications in production, consider using releases (http://www.rebar3.org/docs/releases)
===> Booted backoff
===> Booted rfc3339
===> Booted wts
===> Booted ctx
===> Booted counters
===> Booted opencensus
===> Booted pgo

=ERROR REPORT==== 24-Jul-2018::19:07:06 ===
** Generic server default terminating
** Last message in was {timeout,#Ref<0.3893124177.3797942274.14932>,
                                {-576460745633,-576460747616}}
** When Server state == {ready,#Ref<0.3893124177.3798073345.21252>,
                               #{delay => 0,
                                 idle => #Ref<0.3893124177.3797942274.14932>,
                                 idle_interval => 1000,interval => 1000,
                                 next => -576460746616,
                                 poll => #Ref<0.3893124177.3797942274.14933>,
                                 slow => false,target => 50}}
** Reason for termination ==
** {{badmatch,[{'__info__',<0.769.0>,undefined,
                           {conn,<0.769.0>,#Port<0.12296>,default},
                           {conn,<0.769.0>,#Port<0.12296>,default}}]},
    [{pgo_pool,ping,3,
               [{file,"/Users/starbelly/devel/erlang/pgo/_build/default/lib/pgo/src/pgo_pool.erl"},
                {line,199}]},
     {gen_server,try_dispatch,4,[{file,"gen_server.erl"},{line,616}]},
     {gen_server,handle_msg,6,[{file,"gen_server.erl"},{line,686}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}

=ERROR REPORT==== 24-Jul-2018::19:07:06 ===
** Generic server <0.762.0> terminating
** Last message in was {'EXIT',<0.761.0>,
                        {{badmatch,
                          [{'__info__',<0.769.0>,undefined,
                            {conn,<0.769.0>,#Port<0.12296>,default},
                            {conn,<0.769.0>,#Port<0.12296>,default}}]},
                         [{pgo_pool,ping,3,
                           [{file,
                             "/Users/starbelly/devel/erlang/pgo/_build/default/lib/pgo/src/pgo_pool.erl"},
                            {line,199}]},
                          {gen_server,try_dispatch,4,
                           [{file,"gen_server.erl"},{line,616}]},
                          {gen_server,handle_msg,6,
                           [{file,"gen_server.erl"},{line,686}]},
                          {proc_lib,init_p_do_apply,3,
                           [{file,"proc_lib.erl"},{line,247}]}]}}
** When Server state == {state,
                            {<0.762.0>,pgo_pool_sup},
                            rest_for_one,
                            [{child,<0.765.0>,type_server,
                                 {pgo_type_server,start_link,
                                     [default,
                                      [{size,5},
                                       {host,"127.0.0.1"},
                                       {database,"test"},
                                       {user,"test"}]]},
                                 permanent,5000,worker,
                                 [pgo_type_server]},
                             {child,<0.764.0>,connection_starter,
                                 {pgo_connection_starter,start_link,
                                     [<0.761.0>,5]},
                                 permanent,5000,worker,
                                 [pgo_connection_starter]},
                             {child,<0.763.0>,connection_sup,
                                 {pgo_connection_sup,start_link,
                                     [#Ref<0.3893124177.3798073345.21252>,
                                      <0.761.0>,default,
                                      [{size,5},
                                       {host,"127.0.0.1"},
                                       {database,"test"},
                                       {user,"test"}]]},
                                 permanent,5000,supervisor,
                                 [pgo_connection_sup]}],
                            undefined,5,10,[],0,pgo_pool_sup,
                            [#Ref<0.3893124177.3798073345.21252>,<0.761.0>,
                             default,
                             [{size,5},
                              {host,"127.0.0.1"},
                              {database,"test"},
                              {user,"test"}]]}
** Reason for termination ==
** {{badmatch,[{'__info__',<0.769.0>,undefined,
                           {conn,<0.769.0>,#Port<0.12296>,default},
                           {conn,<0.769.0>,#Port<0.12296>,default}}]},
    [{pgo_pool,ping,3,
               [{file,"/Users/starbelly/devel/erlang/pgo/_build/default/lib/pgo/src/pgo_pool.erl"},
                {line,199}]},
     {gen_server,try_dispatch,4,[{file,"gen_server.erl"},{line,616}]},
     {gen_server,handle_msg,6,[{file,"gen_server.erl"},{line,686}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}

Timestamp with time zone

Hello!

How can one insert and fetch timestamps with time zones? I can only work out how to use Erlang timestamp tuples, which does not contain time zone information.

Cheers,
Louis

Take into account local Unix domain sockets for host

Great job with this lib, and thank you all for your work, found an issue today after updating to 0.13:

{<<"net.peer.name">>, iolist_to_binary(Host)},

The above line is crashing my app as I work with local Unix domain sockets for DB connection, the code should check for a tuple before applying the above conversion.

Add metrics about how the pool is operating

Need to expose metrics at least about how many connections are checked out, how long checkouts are taking, maybe how long before checkins are taking too? and number of connections that get dropped.

Is it possible to get more information when unable to connect to a database

Hello!

Thanks for this library, it rocks

I was wondering if there's a way to get more information out of PGO when it fails to connect due to incorrect credentials. Currently it seems to boot OK but once you attempt to make a query it returns none_available.

Postgrex will repeatly log that it has been unable to connect, which can be useful for telling there's an immediate problem when starting the application.

Thanks,
Louis

FORMAT ERROR

https://github.com/erleans/pgo/blob/master/src/pgo_protocol.erl#L117

FORMAT ERROR: "error encoding bind message class=~s exception~s stacktrace=~p" - [error,
                                                                                  {pgo_protocol,
                                                                                   {parameters,
                                                                                    1,
                                                                                    6}},
                                                                                  [{pgo_protocol,
                                                                                    encode_bind_message,
                                                                                    5,
                                                                                    [{file,
                                                                                      "/home/bryan/git/gruvy/_build/default/lib/pgo/src/pgo_protocol.erl"},
                                                                                     {line,
                                                                                      117}]},
                                                                                   {pgo_handler,
                                                                                    encode_bind_describe_execute,
                                                                                    3,
                                                                                    [{file,
                                                                                      "/home/bryan/git/gruvy/_build/default/lib/pgo/src/pgo_handler.erl"},
                                                                                     {line,
                                                                                      313}]},
                                                                                   {pgo_handler,
                                                                                    receive_loop0,
                                                                                    6,
                                                                                    [{file,
                                                                                      "/home/bryan/git/gruvy/_build/default/lib/pgo/src/pgo_handler.erl"},
                                                                                     {line,
                                                                                      354}]},
                                                                                   {pgo_handler,
                                                                                    extended_query,
                                                                                    6,
                                                                                    [{file,
                                                                                      "/home/bryan/git/gruvy/_build/default/lib/pgo/src/pgo_handler.erl"},
                                                                                     {line,
                                                                                      289}]},
                                                                                   {pgo_handler,
                                                                                    extended_query,
                                                                                    5,
                                                                                    [{file,
                                                                                      "/home/bryan/git/gruvy/_build/default/lib/pgo/src/pgo_handler.erl"},
                                                                                     {line,
                                                                                      73}]},
                                                                                   {pgo,
                                                                                    query,
                                                                                    3,
                                                                                    [{file,
                                                                                      "/home/bryan/git/gruvy/_build/default/lib/pgo/src/pgo.erl"},
                                                                                     {line,
                                                                                      102}]},
                                                                                   {address_db,
                                                                                    lookup,
                                                                                    1,
                                                                                    [{file,
                                                                                      "/home/bryan/git/gruvy/apps/gruvy_model/src/public/address_db.erl"},
                                                                                     {line,
                                                                                      102}]},
                                                                                   {service_accounts,
                                                                                    ensure_address,
                                                                                    1,
                                                                                    [{file,
                                                                                      "/home/bryan/git/gruvy/apps/gruvy_services/src/service_accounts.erl"},
                                                                                     {line,
                                                                                      97}]}]]

Doesn't compile using erlang.mk

The app seems to need an endless list of dependencies. I've had to download a very large number of dependencies manually, well over 200 MB so far, and it is still complaining about missing more. Did this app really need to be so heavy, especially with Prometheus - maybe a bit ironic that one cannot use a Erlang package with getting Golang and over a dozen of Go packages.

It seems I'm missing something painfully obvious and that there is a more straightforward way of getting it to compile using erlang.mk?

function_clause error in pgo_connection

pgo/src/pgo_connection.erl, line 119
https://github.com/erleans/pgo/blob/master/src/pgo_connection.erl#L119

** State machine <0.803.0> terminating
** Last event = {info,
                    {ssl_closed,
                        {sslsocket,
                            {gen_tcp,#Port<0.6257>,tls_connection,undefined},
                            [<0.12752.0>,<0.12751.0>]}}}
** When server state  = {enqueued,
                         {data,undefined,undefined,
                          #Ref<0.2546141266.2774349602.182528>,
                          {conn,<0.803.0>,
                           {sslsocket,
                            {gen_tcp,#Port<0.272832>,tls_connection,undefined},
                            [<0.24162.16>,<0.24160.26>]},
                           ssl,default,
                           #{<<"DateStyle">> => <<"ISO, MDY">>,
                             <<"IntervalStyle">> => <<"postgres">>,
                             <<"TimeZone">> => <<"Etc/UTC">>,
                             <<"application_name">> =>
                              <<"[email protected]">>,
                             <<"client_encoding">> => <<"UTF8">>,
                             <<"integer_datetimes">> => <<"on">>,
                             <<"is_superuser">> => <<"off">>,
                             <<"server_encoding">> => <<"UTF8">>,
                             <<"server_version">> =>
                              <<"12.2 (Ubuntu 12.2-4)">>,
                             <<"session_authorization">> => <<"app">>,
                             <<"standard_conforming_strings">> => <<"on">>},
                           true,false,[]},
                          #{database => "app",
                            host => "127.0.0.1",
                            password =>
                             "**************",
                            pool_size => 5,ssl => true,
                            ssl_options => [{verify,verify_none}],
                            user => "app"},
                          #Ref<0.3336231166.2770582461.101011>,default,
                          <0.823.0>,<0.824.0>,
                          {backoff,1000,10000,1000,normal,undefined,
                           undefined}}}

Accessing query errors in a structured format.

A sample of the possible reasons a query with PGO might fail.

"null value in column \"thread_id\" violates not-null constraint"
"column \"zz\" does not exist"
"relation \"moo\" does not exist"
** (exit) exited in: :pgo_pool.checkout(:defaultzz, [])
 ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its :"application isn't started"
{wrong_number_of_arguments,1,0}
{other,none_available} -> When the DB is down

Is there any level at which these errors are more structured. so that I can act on each type individually.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.