Current section
Files
Jump to
Current section
Files
lib/posterize.ex
defmodule :posterize do
@moduledoc """
an erlang API for postgrex, a postgres client
"""
@typedoc """
a connection process name, pid or reference
a connection reference is used when making multiple requests to the same
connection, see `transaction/3`
"""
@type conn :: DBConnection.conn | {:sbroker, DBConnection.conn} | {:poolboy, DBConnection.conn}
@pool_timeout 5000
@timeout 15000
@max_rows 500
### PUBLIC API ###
@doc """
start the connection process and connect to postgres
## options
* `hostname` - server hostname (default: PGHOST env variable, then localhost);
* `port` - server port (default: PGPORT env variable, then 5432);
* `database` - database (required);
* `username` - username (default: PGUSER env variable, then USER env var);
* `password` - user password (default PGPASSWORD env variable);
* `parameters` - proplist of connection parameters;
* `timeout` - connect timeout in milliseconds (default: `#{@timeout}`);
* `ssl` - set to `true` if ssl should be used (default: `false`);
* `ssl_opts` - a list of ssl options, see ssl docs;
* `socket_options` - options to be given to the underlying socket;
* `sync_connect` - block in `start_link/1` until connection is set up (default: `false`)
* `extensions` - a list of `{Module, Opts}` pairs where `Module` is
an implemention of the `Postgrex.Extension` behaviour and `Opts` are the
extension options;
* `decode_binary` - either `copy` to copy binary values when decoding with
default extensions that return binaries or `reference` to use a reference
counted binary of the binary received from the socket. referencing a
potentially larger binary can be more efficient if the binary value is going
to be garbage collected soon because a copy is avoided. however the larger
binary can not be garbage collected until all the refences are garbage
collected (defaults to `copy`);
* `prepare` - how to prepare queries, either `named` to use named queries
or unnamed to force unnamed queries (default: `named`);
* `transactions` - set to `strict` to error on unexpected transaction state,
otherwise set to `naive` (default: `naive`);
* `pool` - the pool module to use, see `DBConnection` for pool dependent
options, this option must be included with all requests contacting the pool
if not `DBConnection.Connection` (default: `'Elixir.DBConnection.Sojourn'`);
* `null` - the atom to use as a stand in for postgres' `NULL` in encoding
and decoding (default: `null`);
`Postgrex` (and posterize) use the `DBConnection` framework and support all
`DBConnection` options. see `DBConnection` for more information
"""
@spec start_link(Keyword.t) :: {:ok, pid} | {:error, Postgrex.Error.t | term}
def start_link(opts) do
# use sbroker as the default pool
opts = Keyword.put_new(opts, :pool, DBConnection.Sojourn)
# use :null as the default null value, instead of nil
opts = Keyword.put_new(opts, :null, :null)
# append default extensions after user extensions
opts = Keyword.update(opts, :extensions, default_xts, &(&1 ++ default_xts))
Postgrex.start_link(opts)
end
defp default_xts, do: :posterize_xt_datetime_utils.stack
@doc """
runs an (extended) query and returns the result as `{ok, Result}`
or `{error, Error}` if there was a database error. parameters can be
set in the query as `$1` embedded in the query string. parameters are given as
a list of erlang values. see the README for information on how posterize
encodes and decodes erlang values by default. see `Postgrex.Result` for the
result data
## options
* `pool_timeout` - time to wait in the queue for the connection
(default: `#{@pool_timeout}`)
* `queue` - whether to wait for connection in a queue (default: `true`);
* `timeout` - query request timeout (default: `#{@timeout}`);
* `decode_mapper` - fun to map each row in the result to a term after
decoding, (default: `fun(X) -> X end`);
* `pool` - the pool module to use, must match that set on `start_link/1`
(default: `'Elixir.DBConnection.Sojourn'`);
* `null` - the atom to use as a stand in for postgres' `NULL` in encoding
and decoding (default: `null`);
* `mode` - set to `savepoint` to use a savepoint to rollback to before the
query on error, otherwise set to `transaction` (default: `transaction`);
* `copy_data` - whether to add copy data as a final parameter for use with
`COPY .. FROM STDIN` queries, if the query is not copying to the database
the data is sent but silently discarded (default: `false`);
## examples
posterize:query(Conn, "CREATE TABLE posts (id serial, title text)", []).
posterize:query(Conn, "INSERT INTO posts (title) VALUES ('my title')", []).
posterize:query(Conn, "SELECT title FROM posts", []).
posterize:query(Conn, "SELECT id FROM posts WHERE title like $1", [<<"%my%">>]).
posterize:query(Conn, "COPY posts TO STDOUT", []).
posterize:query(Conn, "COPY ints FROM STDIN", ["1\\n2\\n3\\n"], [copy_data: true])
"""
@spec query(conn, iodata, list, Keyword.t) :: {:ok, Postgrex.Result.t} | {:error, String.t}
def query(conn, statement, params, opts) do
# use sbroker as the default pool
opts = Keyword.put_new(opts, :pool, DBConnection.Sojourn)
# use :null as the default null value, instead of nil
opts = Keyword.put_new(opts, :null, :null)
Postgrex.query(conn, statement, params, opts)
end
def query(conn, statement, params), do: query(conn, statement, params, [])
@doc """
prepares an (extended) query and returns a prepared query as `{ok, Query}`
or `{error, Error}` if there was an error. execute the returned query with
`execute/3,4`
## options
see `query/3,4`
## examples
{ok, Query} = posterize:prepare(Conn, "name", "CREATE TABLE posts (id serial, title text)").
"""
@spec prepare(conn, iodata, iodata, Keyword.t) :: {:ok, Postgrex.Query.t} | {:error, Postgrex.Error.t}
def prepare(conn, name, statement, opts) do
# use sbroker as the default pool
opts = Keyword.put_new(opts, :pool, DBConnection.Sojourn)
# use :null as the default null value, instead of nil
opts = Keyword.put_new(opts, :null, :null)
Postgrex.prepare(conn, name, statement, opts)
end
def prepare(conn, name, statement), do: prepare(conn, name, statement, [])
@doc """
runs an (extended) prepared query and returns the result as `{ok, Result}`
or `{error, Error}` if there was an error
## options
see `query/3,4`
## examples
{ok, Query} = posterize:prepare(Conn, "", "CREATE TABLE posts (id serial, title text)"),
{ok, Result} = posterize:execute(Conn, Query, []).
{ok, Query} = posterize:prepare(Conn, "", "SELECT id FROM posts WHERE title like $1"),
{ok, Result} = posterize:execute(Conn, Query, [<<"%my%">>]).
"""
@spec execute(conn, Postgrex.Query.t, list, Keyword.t) ::
{:ok, Postgrex.Result.t} | {:error, Postgrex.Error.t}
def execute(conn, query, params, opts) do
# use sbroker as the default pool
opts = Keyword.put_new(opts, :pool, DBConnection.Sojourn)
# use :null as the default null value, instead of nil
opts = Keyword.put_new(opts, :null, :null)
Postgrex.execute(conn, query, params, opts)
end
def execute(conn, query, params), do: execute(conn, query, params, [])
@doc """
closes an (extended) prepared query and returns `ok` or `{error, Error`}
if there was an error. closing a query releases any resources held by
postgresql for a prepared query with that name
## options
* `pool_timeout` - time to wait in the queue for the connection
(default: `#{@pool_timeout}`)
* `queue` - whether to wait for connection in a queue (default: `true`);
* `timeout` - query request timeout (default: `#{@timeout}`);
* `pool` - the pool module to use, must match that set on `start_link/1`
(default: `'Elixir.DBConnection.Sojourn'`);
* `mode` - set to `savepoint` to use a savepoint to rollback to before the
query on error, otherwise set to `transaction` (default: `transaction`);
## examples
{ok, Query} = posterize:prepare(Conn, "", "CREATE TABLE posts (id serial, title text)"),
ok = posterize:close(Conn, Query).
"""
@spec close(conn, Postgrex.Query.t, Keyword.t) :: :ok | {:error, Postgrex.Error.t}
def close(conn, query, opts) do
# use sbroker as the default pool
opts = Keyword.put_new(opts, :pool, DBConnection.Sojourn)
Postgrex.close(conn, query, opts)
end
def close(conn, query), do: close(conn, query, [])
@doc """
acquire a lock on a connection and run a series of requests inside a
transaction. the result of the transaction fun is return inside an `ok`
tuple: `{ok, Result}`
to use the locked connection call the request with the connection
reference passed as the single argument to the `Fun`. if the
connection disconnects all future calls using that connection
reference will fail
`rollback/2` rolls back the transaction and causes the function to
return `{error, Error}`
`transaction/3` can be nested multiple times if the connection
reference is used to start a nested transaction. the top level
transaction function is the actual transaction
## options
* `pool_timeout` - time to wait in the queue for the connection
(default: `#{@pool_timeout}`)
* `queue` - whether to wait for connection in a queue (default: `true`);
* `timeout` - transaction timeout (default: `#{@timeout}`);
* `pool` - the pool module to use, must match that set on `start_link/1`
(default: `'Elixir.DBConnection.Sojourn'`);
* `mode` - set to `savepoint` to use a savepoint to rollback to before the
query on error, otherwise set to `transaction` (default: `transaction`);
the `timeout` is for the duration of the transaction and all nested
transactions and requests. this timeout overrides timeouts set by internal
transactions and requests. the `pool` and `mode` will be used for all
requests inside the transaction function
## example
Fun = fun(Conn) -> posterize:query(Conn, "", "SELECT title FROM posts", []) end,
{ok, Result} = posterize:transaction(Conn, Fun).
"""
@spec transaction(conn, ((DBConnection.t) -> result), Keyword.t) ::
{:ok, result} | {:error, any} when result: var
def transaction(conn, fun, opts) do
# use sbroker as the default pool
opts = Keyword.put_new(opts, :pool, DBConnection.Sojourn)
Postgrex.transaction(conn, fun, opts)
end
def transaction(conn, fun), do: transaction(conn, fun, [])
@doc """
rollback a transaction, does not return
aborts the current transaction fun. if inside multiple `transaction/3`
functions, bubbles up to the top level
## example
{error, oops} = posterize:transaction(Conn, fun(Conn) ->
posterize:rollback(Conn, bar),
io:format("never reaches here!~n", [])
end).
"""
@spec rollback(DBConnection.t, any) :: no_return()
defdelegate rollback(conn, any), to: DBConnection
@doc """
returns a cached map of connection parameters.
## options
* `timeout` - Call timeout (default: `#{@timeout}`)
* `pool` - the pool module to use, must match that set on `start_link/1`
(default: `'Elixir.DBConnection.Sojourn'`);
"""
@spec parameters(pid, Keyword.t) :: map
def parameters(pid, opts \\ []) do
# use sbroker as the default pool
opts = Keyword.put_new(opts, :pool, DBConnection.Sojourn)
Postgrex.parameters(pid, opts)
end
end