From 1cfa939b7f066dbdc0295fc3f4c1e120a1a9ff6a Mon Sep 17 00:00:00 2001 From: Chulki Lee Date: Wed, 17 Jul 2019 15:03:59 -0700 Subject: [PATCH] mix format (#472) --- .formatter.exs | 3 + lib/postgrex.ex | 71 ++- lib/postgrex/binary_extension.ex | 3 +- lib/postgrex/binary_utils.ex | 20 +- lib/postgrex/error.ex | 4 +- lib/postgrex/error_code.ex | 36 +- lib/postgrex/extension.ex | 18 +- lib/postgrex/extensions/array.ex | 48 +- lib/postgrex/extensions/bit_string.ex | 21 +- lib/postgrex/extensions/bool.ex | 10 +- lib/postgrex/extensions/box.ex | 1 + lib/postgrex/extensions/circle.ex | 1 + lib/postgrex/extensions/date.ex | 15 +- lib/postgrex/extensions/float4.ex | 6 +- lib/postgrex/extensions/float8.ex | 14 +- lib/postgrex/extensions/hstore.ex | 40 +- lib/postgrex/extensions/int2.ex | 6 +- lib/postgrex/extensions/int4.ex | 8 +- lib/postgrex/extensions/int8.ex | 8 +- lib/postgrex/extensions/interval.ex | 5 +- lib/postgrex/extensions/json.ex | 9 +- lib/postgrex/extensions/jsonb.ex | 14 +- lib/postgrex/extensions/line.ex | 1 + lib/postgrex/extensions/line_segment.ex | 1 + lib/postgrex/extensions/macaddr.ex | 3 +- lib/postgrex/extensions/name.ex | 8 +- lib/postgrex/extensions/numeric.ex | 28 +- lib/postgrex/extensions/oid.ex | 15 +- lib/postgrex/extensions/path.ex | 11 +- lib/postgrex/extensions/point.ex | 2 + lib/postgrex/extensions/polygon.ex | 9 +- lib/postgrex/extensions/raw.ex | 20 +- lib/postgrex/extensions/record.ex | 3 + lib/postgrex/extensions/tid.ex | 5 +- lib/postgrex/extensions/time.ex | 7 +- lib/postgrex/extensions/timestamptz.ex | 15 +- lib/postgrex/extensions/timetz.ex | 10 +- lib/postgrex/extensions/tsvector.ex | 35 +- lib/postgrex/extensions/uuid.ex | 8 +- lib/postgrex/extensions/void_binary.ex | 5 +- lib/postgrex/extensions/void_text.ex | 5 +- lib/postgrex/messages.ex | 189 ++++-- lib/postgrex/notifications.ex | 2 +- lib/postgrex/parameters.ex | 3 +- lib/postgrex/protocol.ex | 76 ++- lib/postgrex/query.ex | 55 +- lib/postgrex/scram.ex | 2 +- lib/postgrex/stream.ex | 15 +- lib/postgrex/super_extension.ex | 20 +- lib/postgrex/type_info.ex | 22 +- lib/postgrex/type_module.ex | 435 +++++++++---- lib/postgrex/type_server.ex | 33 +- lib/postgrex/types.ex | 76 ++- lib/postgrex/utils.ex | 53 +- test/alter_test.exs | 338 +++++----- test/calendar_test.exs | 388 +++++++++--- test/client_test.exs | 26 +- test/custom_extensions_test.exs | 36 +- test/error_test.exs | 46 +- test/login_test.exs | 63 +- test/notification_test.exs | 8 +- test/postgrex_test.exs | 1 + test/query_test.exs | 792 ++++++++++++++++++------ test/schema_test.exs | 52 +- test/stream_test.exs | 491 ++++++++------- test/test_helper.exs | 45 +- test/transaction_test.exs | 572 +++++++++-------- test/tsvector_test.exs | 68 +- test/type_module_test.exs | 14 +- test/type_server_test.exs | 72 ++- 70 files changed, 2947 insertions(+), 1598 deletions(-) create mode 100644 .formatter.exs diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 000000000..682fc6c6b --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,3 @@ +[ + inputs: ["{mix,.formatter}.exs", "{lib,test}/**/*.{ex,exs}"] +] diff --git a/lib/postgrex.ex b/lib/postgrex.ex index 67f24e73d..9b4ae32d5 100644 --- a/lib/postgrex.ex +++ b/lib/postgrex.ex @@ -19,32 +19,32 @@ defmodule Postgrex do A connection reference is used when making multiple requests to the same connection, see `transaction/3`. """ - @type conn :: DBConnection.conn + @type conn :: DBConnection.conn() @type start_option :: - {:hostname, String.t} - | {:socket_dir, Path.t} - | {:socket, Path.t} - | {:port, :inet.port_number} - | {:database, String.t} - | {:username, String.t} - | {:password, String.t} + {:hostname, String.t()} + | {:socket_dir, Path.t()} + | {:socket, Path.t()} + | {:port, :inet.port_number()} + | {:database, String.t()} + | {:username, String.t()} + | {:password, String.t()} | {:parameters, keyword} | {:timeout, timeout} | {:connect_timeout, timeout} | {:handshake_timeout, timeout} | {:ssl, boolean} - | {:ssl_opts, [:ssl.ssl_option]} - | {:socket_options, [:gen_tcp.connect_option]} + | {:ssl_opts, [:ssl.ssl_option()]} + | {:socket_options, [:gen_tcp.connect_option()]} | {:prepare, :named | :unnamed} | {:transactions, :strict | :naive} | {:types, module} | {:disconnect_on_error_codes, [atom]} - | DBConnection.start_option + | DBConnection.start_option() @type option :: {:mode, :transaction | :savepoint} - | DBConnection.option + | DBConnection.option() @type execute_option :: {:decode_mapper, (list -> term)} @@ -148,7 +148,7 @@ defmodule Postgrex do This cause the connection process to attempt to reconnect according to the backoff configuration. """ - @spec start_link([start_option]) :: {:ok, pid} | {:error, Postgrex.Error.t | term} + @spec start_link([start_option]) :: {:ok, pid} | {:error, Postgrex.Error.t() | term} def start_link(opts) do ensure_deps_started!(opts) opts = Postgrex.Utils.default_opts(opts) @@ -189,7 +189,8 @@ defmodule Postgrex do Postgrex.query(conn, "COPY posts TO STDOUT", []) """ - @spec query(conn, iodata, list, [execute_option]) :: {:ok, Postgrex.Result.t} | {:error, Exception.t} + @spec query(conn, iodata, list, [execute_option]) :: + {:ok, Postgrex.Result.t()} | {:error, Exception.t()} def query(conn, statement, params, opts \\ []) do if name = Keyword.get(opts, :cache_statement) do query = %Query{name: name, cache: :statement, statement: IO.iodata_to_binary(statement)} @@ -198,7 +199,7 @@ defmodule Postgrex do {:ok, _, result} -> {:ok, result} - {:error, %Postgrex.Error{postgres: %{code: :feature_not_supported}}} = error-> + {:error, %Postgrex.Error{postgres: %{code: :feature_not_supported}}} = error -> with %DBConnection{} <- conn, :error <- DBConnection.status(conn) do error @@ -225,7 +226,7 @@ defmodule Postgrex do Runs an (extended) query and returns the result or raises `Postgrex.Error` if there was an error. See `query/3`. """ - @spec query!(conn, iodata, list, [execute_option]) :: Postgrex.Result.t + @spec query!(conn, iodata, list, [execute_option]) :: Postgrex.Result.t() def query!(conn, statement, params, opts \\ []) do case query(conn, statement, params, opts) do {:ok, result} -> result @@ -255,7 +256,8 @@ defmodule Postgrex do Postgrex.prepare(conn, "", "CREATE TABLE posts (id serial, title text)") """ - @spec prepare(conn, iodata, iodata, [option]) :: {:ok, Postgrex.Query.t} | {:error, Exception.t} + @spec prepare(conn, iodata, iodata, [option]) :: + {:ok, Postgrex.Query.t()} | {:error, Exception.t()} def prepare(conn, name, statement, opts \\ []) do query = %Query{name: name, statement: statement} opts = Keyword.put(opts, :postgrex_prepare, true) @@ -266,7 +268,7 @@ defmodule Postgrex do Prepares an (extended) query and returns the prepared query or raises `Postgrex.Error` if there was an error. See `prepare/4`. """ - @spec prepare!(conn, iodata, iodata, [option]) :: Postgrex.Query.t + @spec prepare!(conn, iodata, iodata, [option]) :: Postgrex.Query.t() def prepare!(conn, name, statement, opts \\ []) do opts = Keyword.put(opts, :postgrex_prepare, true) DBConnection.prepare!(conn, %Query{name: name, statement: statement}, opts) @@ -298,7 +300,7 @@ defmodule Postgrex do """ @spec prepare_execute(conn, iodata, iodata, list, [execute_option]) :: - {:ok, Postgrex.Query.t, Postgrex.Result.t} | {:error, Postgrex.Error.t} + {:ok, Postgrex.Query.t(), Postgrex.Result.t()} | {:error, Postgrex.Error.t()} def prepare_execute(conn, name, statement, params, opts \\ []) do query = %Query{name: name, statement: statement} DBConnection.prepare_execute(conn, query, params, opts) @@ -309,7 +311,7 @@ defmodule Postgrex do `Postgrex.Error` if there was an error. See `prepare_execute/5`. """ @spec prepare_execute!(conn, iodata, iodata, list, [execute_option]) :: - {Postgrex.Query.t, Postgrex.Result.t} + {Postgrex.Query.t(), Postgrex.Result.t()} def prepare_execute!(conn, name, statement, params, opts \\ []) do query = %Query{name: name, statement: statement} DBConnection.prepare_execute!(conn, query, params, opts) @@ -343,8 +345,8 @@ defmodule Postgrex do query = Postgrex.prepare!(conn, "", "SELECT id FROM posts WHERE title like $1") Postgrex.execute(conn, query, ["%my%"]) """ - @spec execute(conn, Postgrex.Query.t, list, [execute_option]) :: - {:ok, Postgrex.Query.t, Postgrex.Result.t} | {:error, Postgrex.Error.t} + @spec execute(conn, Postgrex.Query.t(), list, [execute_option]) :: + {:ok, Postgrex.Query.t(), Postgrex.Result.t()} | {:error, Postgrex.Error.t()} def execute(conn, query, params, opts \\ []) do DBConnection.execute(conn, query, params, opts) end @@ -353,8 +355,8 @@ defmodule Postgrex do Runs an (extended) prepared query and returns the result or raises `Postgrex.Error` if there was an error. See `execute/4`. """ - @spec execute!(conn, Postgrex.Query.t, list, [execute_option]) :: - Postgrex.Result.t + @spec execute!(conn, Postgrex.Query.t(), list, [execute_option]) :: + Postgrex.Result.t() def execute!(conn, query, params, opts \\ []) do DBConnection.execute!(conn, query, params, opts) end @@ -381,7 +383,7 @@ defmodule Postgrex do query = Postgrex.prepare!(conn, "", "CREATE TABLE posts (id serial, title text)") Postgrex.close(conn, query) """ - @spec close(conn, Postgrex.Query.t, [option]) :: :ok | {:error, Exception.t} + @spec close(conn, Postgrex.Query.t(), [option]) :: :ok | {:error, Exception.t()} def close(conn, query, opts \\ []) do with {:ok, _} <- DBConnection.close(conn, query, opts) do :ok @@ -392,7 +394,7 @@ defmodule Postgrex do Closes an (extended) prepared query and returns `:ok` or raises `Postgrex.Error` if there was an error. See `close/3`. """ - @spec close!(conn, Postgrex.Query.t, [option]) :: :ok + @spec close!(conn, Postgrex.Query.t(), [option]) :: :ok def close!(conn, query, opts \\ []) do DBConnection.close!(conn, query, opts) :ok @@ -433,8 +435,9 @@ defmodule Postgrex do Postgrex.query!(conn, "SELECT title FROM posts", []) end) """ - @spec transaction(conn, ((DBConnection.t) -> result), [option]) :: - {:ok, result} | {:error, any} when result: var + @spec transaction(conn, (DBConnection.t() -> result), [option]) :: + {:ok, result} | {:error, any} + when result: var def transaction(conn, fun, opts \\ []) do DBConnection.transaction(conn, fun, opts) end @@ -452,7 +455,7 @@ defmodule Postgrex do IO.puts "never reaches here!" end) """ - @spec rollback(DBConnection.t, reason :: any) :: no_return() + @spec rollback(DBConnection.t(), reason :: any) :: no_return() defdelegate rollback(conn, reason), to: DBConnection @doc """ @@ -472,7 +475,7 @@ defmodule Postgrex do @doc """ Returns a supervisor child specification for a DBConnection pool. """ - @spec child_spec([start_option]) :: Supervisor.Spec.spec + @spec child_spec([start_option]) :: Supervisor.Spec.spec() def child_spec(opts) do ensure_deps_started!(opts) opts = Postgrex.Utils.default_opts(opts) @@ -519,9 +522,10 @@ defmodule Postgrex do Enum.into(File.stream!("posts"), stream) end) """ - @spec stream(DBConnection.t, iodata | Postgrex.Query.t, list, [option]) :: Postgrex.Stream.t + @spec stream(DBConnection.t(), iodata | Postgrex.Query.t(), list, [option]) :: + Postgrex.Stream.t() when option: execute_option | {:max_rows, pos_integer} - def stream(%DBConnection{} = conn, query, params, options \\ []) do + def stream(%DBConnection{} = conn, query, params, options \\ []) do options = Keyword.put_new(options, :max_rows, @max_rows) %Postgrex.Stream{conn: conn, query: query, params: params, options: options} end @@ -529,7 +533,8 @@ defmodule Postgrex do ## Helpers defp ensure_deps_started!(opts) do - if Keyword.get(opts, :ssl, false) and not List.keymember?(:application.which_applications(), :ssl, 0) do + if Keyword.get(opts, :ssl, false) and + not List.keymember?(:application.which_applications(), :ssl, 0) do raise """ SSL connection can not be established because `:ssl` application is not started, you can add it to `extra_application` in your `mix.exs`: diff --git a/lib/postgrex/binary_extension.ex b/lib/postgrex/binary_extension.ex index 786c16dd4..3e7b9c245 100644 --- a/lib/postgrex/binary_extension.ex +++ b/lib/postgrex/binary_extension.ex @@ -3,7 +3,6 @@ defmodule Postgrex.BinaryExtension do defmacro __using__(matching) do quote location: :keep do - @behaviour Postgrex.Extension def init(_), do: nil @@ -12,7 +11,7 @@ defmodule Postgrex.BinaryExtension do def format(_), do: :binary - defoverridable [init: 1] + defoverridable init: 1 end end end diff --git a/lib/postgrex/binary_utils.ex b/lib/postgrex/binary_utils.ex index d81e618ff..6ad9417c3 100644 --- a/lib/postgrex/binary_utils.ex +++ b/lib/postgrex/binary_utils.ex @@ -2,42 +2,42 @@ defmodule Postgrex.BinaryUtils do @moduledoc false defmacro int64 do - quote do: signed-64 + quote do: signed - 64 end defmacro int32 do - quote do: signed-32 + quote do: signed - 32 end defmacro int16 do - quote do: signed-16 + quote do: signed - 16 end defmacro uint16 do - quote do: unsigned-16 + quote do: unsigned - 16 end defmacro uint32 do - quote do: unsigned-32 + quote do: unsigned - 32 end defmacro int8 do - quote do: signed-8 + quote do: signed - 8 end defmacro float64 do - quote do: float-64 + quote do: float - 64 end defmacro float32 do - quote do: float-32 + quote do: float - 32 end defmacro binary(size) do - quote do: binary-size(unquote(size)) + quote do: binary - size(unquote(size)) end defmacro binary(size, unit) do - quote do: binary-size(unquote(size))-unit(unquote(unit)) + quote do: binary - size(unquote(size)) - unit(unquote(unit)) end end diff --git a/lib/postgrex/error.ex b/lib/postgrex/error.ex index 6d1ef8f59..7b5d3000f 100644 --- a/lib/postgrex/error.ex +++ b/lib/postgrex/error.ex @@ -47,7 +47,7 @@ defmodule Postgrex.Error do case metadata do [] -> [] - _ -> ["\n" | metadata] + _ -> ["\n" | metadata] end end @@ -57,4 +57,4 @@ end defmodule Postgrex.QueryError do defexception [:message] -end \ No newline at end of file +end diff --git a/lib/postgrex/error_code.ex b/lib/postgrex/error_code.ex index dc8240d76..398d9b96a 100644 --- a/lib/postgrex/error_code.ex +++ b/lib/postgrex/error_code.ex @@ -4,13 +4,15 @@ defmodule Postgrex.ErrorCode do # https://github.com/postgres/postgres/blob/master/src/backend/utils/errcodes.txt @external_resource errcodes_path = Path.join(__DIR__, "errcodes.txt") - errcodes = for line <- File.stream!(errcodes_path), - match?(<<_code::(5 * 8), " ", _::binary>>, line) do - case String.split(line, " ", trim: true) do - [code, _, _, name] -> {code, name |> String.trim |> String.to_atom} - [code, _, _] -> {code} # duplicated code without name + errcodes = + for line <- File.stream!(errcodes_path), + match?(<<_code::5*8, " ", _::binary>>, line) do + case String.split(line, " ", trim: true) do + [code, _, _, name] -> {code, name |> String.trim() |> String.to_atom()} + # duplicated code without name + [code, _, _] -> {code} + end end - end {errcodes, duplicates} = Enum.split_with(errcodes, &match?({_, _}, &1)) @@ -30,13 +32,14 @@ defmodule Postgrex.ErrorCode do iex> code_to_name("23505") :unique_violation """ - @spec code_to_name(String.t) :: atom | no_return + @spec code_to_name(String.t()) :: atom | no_return def code_to_name(code) for {code, errcodes} <- Enum.group_by(errcodes, &elem(&1, 0)) do [{^code, name}] = errcodes def code_to_name(unquote(code)), do: unquote(name) end + def code_to_name(_), do: nil @doc ~S""" @@ -47,23 +50,30 @@ defmodule Postgrex.ErrorCode do iex> name_to_code(:prohibited_sql_statement_attempted) "2F003" """ - @spec name_to_code(atom) :: String.t + @spec name_to_code(atom) :: String.t() def name_to_code(name) @code_decision_table [ - string_data_right_truncation: "22001", # 01004 not used - modifying_sql_data_not_permitted: nil, # 38002 or 2F002 not used - prohibited_sql_statement_attempted: "2F003", # 38003 not used - reading_sql_data_not_permitted: nil, # 38004 or 2F004 not used - null_value_not_allowed: "22004" # 39004 not used + # 01004 not used + string_data_right_truncation: "22001", + # 38002 or 2F002 not used + modifying_sql_data_not_permitted: nil, + # 38003 not used + prohibited_sql_statement_attempted: "2F003", + # 38004 or 2F004 not used + reading_sql_data_not_permitted: nil, + # 39004 not used + null_value_not_allowed: "22004" ] for {name, errcodes} <- Enum.group_by(errcodes, &elem(&1, 1)) do case Keyword.fetch(@code_decision_table, name) do {:ok, nil} -> :ok + {:ok, code} -> def name_to_code(unquote(name)), do: unquote(code) + :error -> [{code, ^name}] = errcodes def name_to_code(unquote(name)), do: unquote(code) diff --git a/lib/postgrex/extension.ex b/lib/postgrex/extension.ex index a7cf3b7b3..e56324112 100644 --- a/lib/postgrex/extension.ex +++ b/lib/postgrex/extension.ex @@ -73,17 +73,19 @@ defmodule Postgrex.Extension do user options. The state returned from this function will be passed to other callbacks. """ - @callback init(Keyword.t) :: state + @callback init(Keyword.t()) :: state @doc """ Specifies the types the extension matches, see `Postgrex.TypeInfo` for specification of the fields. """ - @callback matching(state) :: [type: String.t, - send: String.t, - receive: String.t, - input: String.t, - output: String.t] + @callback matching(state) :: [ + type: String.t(), + send: String.t(), + receive: String.t(), + input: String.t(), + output: String.t() + ] @doc """ Returns the format the type should be encoded as. See @@ -104,7 +106,7 @@ defmodule Postgrex.Extension do end """ - @callback encode(state) :: Macro.t + @callback encode(state) :: Macro.t() @doc """ Returns a quoted list of clauses that decode a binary to an Elixir value. @@ -120,5 +122,5 @@ defmodule Postgrex.Extension do end end """ - @callback decode(state) :: Macro.t + @callback decode(state) :: Macro.t() end diff --git a/lib/postgrex/extensions/array.ex b/lib/postgrex/extensions/array.ex index e50b6cced..19406f866 100644 --- a/lib/postgrex/extensions/array.ex +++ b/lib/postgrex/extensions/array.ex @@ -20,6 +20,7 @@ defmodule Postgrex.Extensions.Array do # encode_list/2 defined by TypeModule encoder = &encode_list(&1, type) unquote(__MODULE__).encode(list, oid, encoder) + other, _, _ -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a list") end @@ -27,9 +28,9 @@ defmodule Postgrex.Extensions.Array do def decode(_) do quote location: :keep do - <>, [oid], [type] -> - <> = binary + <>, [oid], [type] -> + <> = binary # decode_list/2 defined by TypeModule flat = decode_list(data, type) @@ -42,8 +43,8 @@ defmodule Postgrex.Extensions.Array do def encode(list, elem_oid, encoder) do {data, ndims, lengths} = encode(list, 0, [], encoder) - lengths = for len <- Enum.reverse(lengths), do: <> - iodata = [<>, lengths, data] + lengths = for len <- Enum.reverse(lengths), do: <> + iodata = [<>, lengths, data] [<> | iodata] end @@ -51,49 +52,56 @@ defmodule Postgrex.Extensions.Array do {"", ndims, lengths} end - defp encode([head|tail] = list, ndims, lengths, encoder) when is_list(head) do - lengths = [length(list)|lengths] + defp encode([head | tail] = list, ndims, lengths, encoder) when is_list(head) do + lengths = [length(list) | lengths] {data, ndims, lengths} = encode(head, ndims, lengths, encoder) - [dimlength|_] = lengths - - rest = Enum.reduce(tail, [], fn sublist, acc -> - {data, _, [len|_]} = encode(sublist, ndims, lengths, encoder) - if len != dimlength do - raise ArgumentError, "nested lists must have lists with matching lengths" - end - [acc|data] + [dimlength | _] = lengths + + rest = + Enum.reduce(tail, [], fn sublist, acc -> + {data, _, [len | _]} = encode(sublist, ndims, lengths, encoder) + + if len != dimlength do + raise ArgumentError, "nested lists must have lists with matching lengths" + end + + [acc | data] end) - {[data|rest], ndims + 1, lengths} + {[data | rest], ndims + 1, lengths} end defp encode(list, ndims, lengths, encoder) do - {encoder.(list), ndims+1, [length(list) | lengths]} + {encoder.(list), ndims + 1, [length(list) | lengths]} end def decode(dims, elems) do case decode_dims(dims, []) do [] when elems == [] -> [] + [length] when length(elems) == length -> Enum.reverse(elems) + lengths -> {array, []} = nest(elems, lengths) array end end - defp decode_dims(<>, acc) do + defp decode_dims(<>, acc) do decode_dims(rest, [len | acc]) end + defp decode_dims(<<>>, acc) do Enum.reverse(acc) end # elems and lengths in reverse order defp nest(elems, [len]) do - nest_inner(elems, len, []) + nest_inner(elems, len, []) end + defp nest(elems, [len | lengths]) do nest(elems, len, lengths, []) end @@ -101,6 +109,7 @@ defmodule Postgrex.Extensions.Array do defp nest(elems, 0, _, acc) do {acc, elems} end + defp nest(elems, n, lengths, acc) do {row, elems} = nest(elems, lengths) nest(elems, n - 1, lengths, [row | acc]) @@ -109,6 +118,7 @@ defmodule Postgrex.Extensions.Array do defp nest_inner(elems, 0, acc) do {acc, elems} end + defp nest_inner([elem | elems], n, acc) do nest_inner(elems, n - 1, [elem | acc]) end diff --git a/lib/postgrex/extensions/bit_string.ex b/lib/postgrex/extensions/bit_string.ex index 22498c4de..bf06629a1 100644 --- a/lib/postgrex/extensions/bit_string.ex +++ b/lib/postgrex/extensions/bit_string.ex @@ -1,22 +1,28 @@ defmodule Postgrex.Extensions.BitString do @moduledoc false import Postgrex.BinaryUtils, warn: false - use Postgrex.BinaryExtension, [send: "bit_send", send: "varbit_send"] + use Postgrex.BinaryExtension, send: "bit_send", send: "varbit_send" def init(opts), do: Keyword.fetch!(opts, :decode_binary) def encode(_) do quote location: :keep do val when is_binary(val) -> - [<<(byte_size(val) + 4)::int32, bit_size(val)::uint32>> | val] + [<> | val] + val when is_bitstring(val) -> bin_size = byte_size(val) last_pos = bin_size - 1 <> = val pad = 8 - bit_size(last) bit_count = bit_size(val) - [<<(bin_size + 4)::int32, bit_count::uint32>>, binary | - <>] + + [ + <>, + binary + | <> + ] + val -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(val, "a bitstring") end @@ -24,16 +30,17 @@ defmodule Postgrex.Extensions.BitString do def decode(:copy) do quote location: :keep do - <> -> + <> -> copy = :binary.copy(value) <> = copy bits end end + def decode(:reference) do quote location: :keep do - <> -> - <> = value + <> -> + <> = value bits end end diff --git a/lib/postgrex/extensions/bool.ex b/lib/postgrex/extensions/bool.ex index 153670b89..41689c2c0 100644 --- a/lib/postgrex/extensions/bool.ex +++ b/lib/postgrex/extensions/bool.ex @@ -6,9 +6,11 @@ defmodule Postgrex.Extensions.Bool do def encode(_) do quote location: :keep do true -> - <<1 :: int32, 1>> + <<1::int32, 1>> + false -> - <<1 :: int32, 0>> + <<1::int32, 0>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a boolean") end @@ -16,8 +18,8 @@ defmodule Postgrex.Extensions.Bool do def decode(_) do quote location: :keep do - <<1 :: int32, 1>> -> true - <<1 :: int32, 0>> -> false + <<1::int32, 1>> -> true + <<1::int32, 0>> -> false end end end diff --git a/lib/postgrex/extensions/box.ex b/lib/postgrex/extensions/box.ex index c11658bf8..64e436223 100644 --- a/lib/postgrex/extensions/box.ex +++ b/lib/postgrex/extensions/box.ex @@ -11,6 +11,7 @@ defmodule Postgrex.Extensions.Box do encoded_p2 = Point.encode_point(p2, Postgrex.Box) # 2 points -> 16 bytes each [<<32::int32>>, encoded_p1 | encoded_p2] + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Line) end diff --git a/lib/postgrex/extensions/circle.ex b/lib/postgrex/extensions/circle.ex index 0fbdb0645..e8e250fb3 100644 --- a/lib/postgrex/extensions/circle.ex +++ b/lib/postgrex/extensions/circle.ex @@ -8,6 +8,7 @@ defmodule Postgrex.Extensions.Circle do %Postgrex.Circle{center: %Postgrex.Point{x: x, y: y}, radius: r} when is_number(x) and is_number(y) and is_number(r) and r >= 0 -> <<24::int32, x::float64, y::float64, r::float64>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Path) end diff --git a/lib/postgrex/extensions/date.ex b/lib/postgrex/extensions/date.ex index 3911c5327..250600070 100644 --- a/lib/postgrex/extensions/date.ex +++ b/lib/postgrex/extensions/date.ex @@ -11,13 +11,15 @@ defmodule Postgrex.Extensions.Date do quote location: :keep do %Date{calendar: Calendar.ISO} = date -> unquote(__MODULE__).encode_elixir(date) + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Date) end end + def decode(_) do quote location: :keep do - <<4 :: int32, days :: int32>> -> + <<4::int32, days::int32>> -> unquote(__MODULE__).day_to_elixir(days) end end @@ -26,20 +28,25 @@ defmodule Postgrex.Extensions.Date do def encode_elixir(%Date{year: year, month: month, day: day}) when year <= @max_year do date = {year, month, day} - <<4 :: int32, :calendar.date_to_gregorian_days(date) - @gd_epoch :: int32>> + <<4::int32, :calendar.date_to_gregorian_days(date) - @gd_epoch::int32>> end + def encode_elixir(%Date{} = date) do - raise ArgumentError, "#{inspect date} is beyond the maximum year #{@max_year}" + raise ArgumentError, "#{inspect(date)} is beyond the maximum year #{@max_year}" end def day_to_elixir(days) do days = days + @gd_epoch + if days in 0..@max_days do days |> :calendar.gregorian_days_to_date() |> Date.from_erl!() else - raise ArgumentError, "Postgrex can only decode dates with days between 0 and #{@max_days}, got: #{inspect days}" + raise ArgumentError, + "Postgrex can only decode dates with days between 0 and #{@max_days}, got: #{ + inspect(days) + }" end end end diff --git a/lib/postgrex/extensions/float4.ex b/lib/postgrex/extensions/float4.ex index 19840ada6..edc4a3bb6 100644 --- a/lib/postgrex/extensions/float4.ex +++ b/lib/postgrex/extensions/float4.ex @@ -7,12 +7,16 @@ defmodule Postgrex.Extensions.Float4 do quote location: :keep do n when is_number(n) -> <<4::int32, n::float32>> + :NaN -> <<4::int32, 0::1, 255, 1::1, 0::22>> + :inf -> <<4::int32, 0::1, 255, 0::23>> + :"-inf" -> <<4::int32, 1::1, 255, 0::23>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a float") end @@ -23,7 +27,7 @@ defmodule Postgrex.Extensions.Float4 do <<4::int32, 0::1, 255, 0::23>> -> :inf <<4::int32, 1::1, 255, 0::23>> -> :"-inf" <<4::int32, _::1, 255, _::23>> -> :NaN - <<4::int32, float::float32>> -> float + <<4::int32, float::float32>> -> float end end end diff --git a/lib/postgrex/extensions/float8.ex b/lib/postgrex/extensions/float8.ex index 84ccf471c..b6e8f7ec3 100644 --- a/lib/postgrex/extensions/float8.ex +++ b/lib/postgrex/extensions/float8.ex @@ -6,13 +6,17 @@ defmodule Postgrex.Extensions.Float8 do def encode(_) do quote location: :keep do n when is_number(n) -> - <<8 :: int32, n :: float64>> + <<8::int32, n::float64>> + :NaN -> - <<8 :: int32, 0::1, 2047::11, 1::1, 0::51>> + <<8::int32, 0::1, 2047::11, 1::1, 0::51>> + :inf -> - <<8 :: int32, 0::1, 2047::11, 0::52>> + <<8::int32, 0::1, 2047::11, 0::52>> + :"-inf" -> - <<8 :: int32, 1::1, 2047::11, 0::52>> + <<8::int32, 1::1, 2047::11, 0::52>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a float") end @@ -23,7 +27,7 @@ defmodule Postgrex.Extensions.Float8 do <<8::int32, 0::1, 2047::11, 0::52>> -> :inf <<8::int32, 1::1, 2047::11, 0::52>> -> :"-inf" <<8::int32, _::1, 2047::11, _::52>> -> :NaN - <<8::int32, float::float64>> -> float + <<8::int32, float::float64>> -> float end end end diff --git a/lib/postgrex/extensions/hstore.ex b/lib/postgrex/extensions/hstore.ex index 804b1adeb..0cd52d31a 100644 --- a/lib/postgrex/extensions/hstore.ex +++ b/lib/postgrex/extensions/hstore.ex @@ -9,7 +9,8 @@ defmodule Postgrex.Extensions.HStore do quote location: :keep do %{} = map -> data = unquote(__MODULE__).encode_hstore(map) - [<> | data] + [<> | data] + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a map") end @@ -17,7 +18,7 @@ defmodule Postgrex.Extensions.HStore do def decode(mode) do quote do - <> -> + <> -> unquote(__MODULE__).decode_hstore(data, unquote(mode)) end end @@ -25,14 +26,16 @@ defmodule Postgrex.Extensions.HStore do ## Helpers def encode_hstore(hstore_map) do - keys_and_values = Enum.reduce hstore_map, "", fn ({key, value}, acc) -> + keys_and_values = + Enum.reduce(hstore_map, "", fn {key, value}, acc -> [acc, encode_hstore_key(key), encode_hstore_value(value)] - end + end) + [<> | keys_and_values] end defp encode_hstore_key(key) when is_binary(key) do - encode_hstore_value key + encode_hstore_value(key) end defp encode_hstore_key(key) when is_nil(key) do @@ -51,6 +54,7 @@ defmodule Postgrex.Extensions.HStore do def decode_hstore(<<_length::int32, pairs::binary>>, :reference) do decode_hstore_ref(pairs, %{}) end + def decode_hstore(<<_length::int32, pairs::binary>>, :copy) do decode_hstore_copy(pairs, %{}) end @@ -60,13 +64,18 @@ defmodule Postgrex.Extensions.HStore do end # in the case of a NULL value, there won't be a length - defp decode_hstore_ref(<>, acc) do + defp decode_hstore_ref( + <>, + acc + ) do decode_hstore_ref(rest, Map.put(acc, key, nil)) end - defp decode_hstore_ref(<>, acc) do + defp decode_hstore_ref( + <>, + acc + ) do decode_hstore_ref(rest, Map.put(acc, key, value)) end @@ -75,13 +84,18 @@ defmodule Postgrex.Extensions.HStore do end # in the case of a NULL value, there won't be a length - defp decode_hstore_copy(<>, acc) do + defp decode_hstore_copy( + <>, + acc + ) do decode_hstore_copy(rest, Map.put(acc, :binary.copy(key), nil)) end - defp decode_hstore_copy(<>, acc) do + defp decode_hstore_copy( + <>, + acc + ) do decode_hstore_copy(rest, Map.put(acc, :binary.copy(key), :binary.copy(value))) end end diff --git a/lib/postgrex/extensions/int2.ex b/lib/postgrex/extensions/int2.ex index 9d16367fe..07f2fe891 100644 --- a/lib/postgrex/extensions/int2.ex +++ b/lib/postgrex/extensions/int2.ex @@ -7,9 +7,11 @@ defmodule Postgrex.Extensions.Int2 do def encode(_) do range = Macro.escape(@int2_range) + quote location: :keep do int when is_integer(int) and int in unquote(range) -> - <<2 :: int32, int :: int16>> + <<2::int32, int::int16>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, unquote(range)) end @@ -17,7 +19,7 @@ defmodule Postgrex.Extensions.Int2 do def decode(_) do quote location: :keep do - <<2 :: int32, int :: int16>> -> int + <<2::int32, int::int16>> -> int end end end diff --git a/lib/postgrex/extensions/int4.ex b/lib/postgrex/extensions/int4.ex index 01fb67434..19667337e 100644 --- a/lib/postgrex/extensions/int4.ex +++ b/lib/postgrex/extensions/int4.ex @@ -3,13 +3,15 @@ defmodule Postgrex.Extensions.Int4 do import Postgrex.BinaryUtils, warn: false use Postgrex.BinaryExtension, send: "int4send" - @int4_range -2147483648..2147483647 + @int4_range -2_147_483_648..2_147_483_647 def encode(_) do range = Macro.escape(@int4_range) + quote location: :keep do int when is_integer(int) and int in unquote(range) -> - <<4 :: int32, int :: int32>> + <<4::int32, int::int32>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, unquote(range)) end @@ -17,7 +19,7 @@ defmodule Postgrex.Extensions.Int4 do def decode(_) do quote location: :keep do - <<4 :: int32, int :: int32>> -> int + <<4::int32, int::int32>> -> int end end end diff --git a/lib/postgrex/extensions/int8.ex b/lib/postgrex/extensions/int8.ex index 020cb6f6c..4af0f3a9e 100644 --- a/lib/postgrex/extensions/int8.ex +++ b/lib/postgrex/extensions/int8.ex @@ -3,13 +3,15 @@ defmodule Postgrex.Extensions.Int8 do import Postgrex.BinaryUtils, warn: false use Postgrex.BinaryExtension, send: "int8send" - @int8_range -9223372036854775808..9223372036854775807 + @int8_range -9_223_372_036_854_775_808..9_223_372_036_854_775_807 def encode(_) do range = Macro.escape(@int8_range) + quote location: :keep do int when is_integer(int) and int in unquote(range) -> - <<8 :: int32, int :: int64>> + <<8::int32, int::int64>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, unquote(range)) end @@ -17,7 +19,7 @@ defmodule Postgrex.Extensions.Int8 do def decode(_) do quote location: :keep do - <<8 :: int32, int :: int64>> -> int + <<8::int32, int::int64>> -> int end end end diff --git a/lib/postgrex/extensions/interval.ex b/lib/postgrex/extensions/interval.ex index 5a178637a..2dc501557 100644 --- a/lib/postgrex/extensions/interval.ex +++ b/lib/postgrex/extensions/interval.ex @@ -7,7 +7,8 @@ defmodule Postgrex.Extensions.Interval do quote location: :keep do %Postgrex.Interval{months: months, days: days, secs: secs} -> microsecs = secs * 1_000_000 - <<16 :: int32, microsecs :: int64, days :: int32, months :: int32>> + <<16::int32, microsecs::int64, days::int32, months::int32>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Interval) end @@ -15,7 +16,7 @@ defmodule Postgrex.Extensions.Interval do def decode(_) do quote location: :keep do - <<16 :: int32, microsecs :: int64, days :: int32, months :: int32>> -> + <<16::int32, microsecs::int64, days::int32, months::int32>> -> secs = div(microsecs, 1_000_000) %Postgrex.Interval{months: months, days: days, secs: secs} end diff --git a/lib/postgrex/extensions/json.ex b/lib/postgrex/extensions/json.ex index 1560e2d1f..31e07ba62 100644 --- a/lib/postgrex/extensions/json.ex +++ b/lib/postgrex/extensions/json.ex @@ -8,11 +8,13 @@ defmodule Postgrex.Extensions.JSON do Keyword.get_lazy(opts, :json, fn -> Application.get_env(:postgrex, :json_library, Jason) end) + {json, Keyword.get(opts, :decode_binary, :copy)} end def matching({nil, _}), do: [] + def matching(_), do: [type: "json"] @@ -23,21 +25,22 @@ defmodule Postgrex.Extensions.JSON do quote location: :keep do map -> data = unquote(library).encode_to_iodata!(map) - [<> | data] + [<> | data] end end def decode({library, :copy}) do quote location: :keep do - <> -> + <> -> json |> :binary.copy() |> unquote(library).decode!() end end + def decode({library, :reference}) do quote location: :keep do - <> -> + <> -> unquote(library).decode!(json) end end diff --git a/lib/postgrex/extensions/jsonb.ex b/lib/postgrex/extensions/jsonb.ex index 1aef5c0c2..03b55c9aa 100644 --- a/lib/postgrex/extensions/jsonb.ex +++ b/lib/postgrex/extensions/jsonb.ex @@ -7,11 +7,13 @@ defmodule Postgrex.Extensions.JSONB do Keyword.get_lazy(opts, :json, fn -> Application.get_env(:postgrex, :json_library, Jason) end) + {json, Keyword.get(opts, :decode_binary, :copy)} end def matching({nil, _}), do: [] + def matching(_), do: [type: "jsonb"] @@ -22,23 +24,25 @@ defmodule Postgrex.Extensions.JSONB do quote location: :keep do map -> data = unquote(library).encode_to_iodata!(map) - [<<(IO.iodata_length(data) + 1) :: int32, 1>> | data] + [<> | data] end end def decode({library, :copy}) do quote location: :keep do - <> -> - <<1, json :: binary>> = data + <> -> + <<1, json::binary>> = data + json |> :binary.copy() |> unquote(library).decode!() end end + def decode({library, :reference}) do quote location: :keep do - <> -> - <<1, json :: binary>> = data + <> -> + <<1, json::binary>> = data unquote(library).decode!(json) end end diff --git a/lib/postgrex/extensions/line.ex b/lib/postgrex/extensions/line.ex index 460867557..3196d5b69 100644 --- a/lib/postgrex/extensions/line.ex +++ b/lib/postgrex/extensions/line.ex @@ -8,6 +8,7 @@ defmodule Postgrex.Extensions.Line do %Postgrex.Line{a: a, b: b, c: c} when is_number(a) and is_number(b) and is_number(c) -> # a, b, c are 8 bytes each <<24::int32, a::float64, b::float64, c::float64>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Line) end diff --git a/lib/postgrex/extensions/line_segment.ex b/lib/postgrex/extensions/line_segment.ex index f71a3cf0e..9b057705b 100644 --- a/lib/postgrex/extensions/line_segment.ex +++ b/lib/postgrex/extensions/line_segment.ex @@ -11,6 +11,7 @@ defmodule Postgrex.Extensions.LineSegment do encoded_p2 = Point.encode_point(p2, Postgrex.LineSegment) # 2 points -> 16 bytes each [<<32::int32>>, encoded_p1 | encoded_p2] + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Line) end diff --git a/lib/postgrex/extensions/macaddr.ex b/lib/postgrex/extensions/macaddr.ex index 251afa62a..b22436016 100644 --- a/lib/postgrex/extensions/macaddr.ex +++ b/lib/postgrex/extensions/macaddr.ex @@ -1,12 +1,13 @@ defmodule Postgrex.Extensions.MACADDR do @moduledoc false import Postgrex.BinaryUtils, warn: false - use Postgrex.BinaryExtension, [send: "macaddr_send"] + use Postgrex.BinaryExtension, send: "macaddr_send" def encode(_) do quote location: :keep do %Postgrex.MACADDR{address: {a, b, c, d, e, f}} -> <<6::int32, a, b, c, d, e, f>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.MACADDR) end diff --git a/lib/postgrex/extensions/name.ex b/lib/postgrex/extensions/name.ex index 53e9fe309..5b73c6b3e 100644 --- a/lib/postgrex/extensions/name.ex +++ b/lib/postgrex/extensions/name.ex @@ -8,7 +8,8 @@ defmodule Postgrex.Extensions.Name do def encode(_) do quote location: :keep do name when is_binary(name) and byte_size(name) < 64 -> - [<> | name] + [<> | name] + other -> msg = "a binary string of less than 64 bytes" raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, msg) @@ -17,12 +18,13 @@ defmodule Postgrex.Extensions.Name do def decode(:reference) do quote location: :keep do - <> -> name + <> -> name end end + def decode(:copy) do quote location: :keep do - <> -> :binary.copy(name) + <> -> :binary.copy(name) end end end diff --git a/lib/postgrex/extensions/numeric.ex b/lib/postgrex/extensions/numeric.ex index 667098534..3e9213b71 100644 --- a/lib/postgrex/extensions/numeric.ex +++ b/lib/postgrex/extensions/numeric.ex @@ -8,9 +8,11 @@ defmodule Postgrex.Extensions.Numeric do %Decimal{} = decimal -> data = unquote(__MODULE__).encode_numeric(decimal) [<> | data] + n when is_float(n) -> data = unquote(__MODULE__).encode_numeric(Decimal.from_float(n)) [<> | data] + n when is_integer(n) -> data = unquote(__MODULE__).encode_numeric(Decimal.new(n)) [<> | data] @@ -19,7 +21,7 @@ defmodule Postgrex.Extensions.Numeric do def decode(_) do quote location: :keep do - <> -> + <> -> unquote(__MODULE__).decode_numeric(data) end end @@ -27,11 +29,11 @@ defmodule Postgrex.Extensions.Numeric do ## Helpers def encode_numeric(%Decimal{coef: coef}) when coef in [:qNaN, :sNaN] do - <<0 :: int16, 0 :: int16, 0xC000 :: uint16, 0 :: int16>> + <<0::int16, 0::int16, 0xC000::uint16, 0::int16>> end def encode_numeric(%Decimal{coef: :inf} = decimal) do - raise ArgumentError, "cannot represent #{inspect decimal} as numeric type" + raise ArgumentError, "cannot represent #{inspect(decimal)} as numeric type" end def encode_numeric(%Decimal{sign: sign, coef: coef, exp: exp}) do @@ -46,8 +48,8 @@ defmodule Postgrex.Extensions.Numeric do num_digits = length(digits) weight = max(length(integer_digits) - 1, 0) - bin = for digit <- digits, into: "", do: <> - [<> | bin] + bin = for digit <- digits, into: "", do: <> + [<> | bin] end defp encode_sign(1), do: 0x0000 @@ -57,6 +59,7 @@ defmodule Postgrex.Extensions.Numeric do integer_base = pow10(scale) {div(coef, integer_base), rem(coef, integer_base), scale} end + defp split_parts(coef, scale) when scale < 0 do integer_base = pow10(-scale) {coef * integer_base, 0, 0} @@ -74,15 +77,16 @@ defmodule Postgrex.Extensions.Numeric do defp pending_scale(num, scale), do: pending_scale(div(num, 10), scale - 1) defp encode_digits(coef, digits) when coef < 10_000 do - [coef|digits] + [coef | digits] end + defp encode_digits(coef, digits) do digit = rem(coef, 10_000) coef = div(coef, 10_000) - encode_digits(coef, [digit|digits]) + encode_digits(coef, [digit | digits]) end - def decode_numeric(<>) do + def decode_numeric(<>) do decode_numeric(ndigits, weight, sign, scale, tail) end @@ -104,17 +108,17 @@ defmodule Postgrex.Extensions.Numeric do defp scale(coef, diff) when diff < 0, do: div(coef, pow10(-diff)) defp scale(coef, diff) when diff > 0, do: coef * pow10(diff) - Enum.reduce 0..100, 1, fn x, acc -> + Enum.reduce(0..100, 1, fn x, acc -> defp pow10(unquote(x)), do: unquote(acc) acc * 10 - end + end) defp pow10(num) when num > 100, do: pow10(100) * pow10(num - 100) defp decode_numeric_int("", weight, acc), do: {acc, weight} - defp decode_numeric_int(<>, weight, acc) do - acc = (acc * 10_000) + digit + defp decode_numeric_int(<>, weight, acc) do + acc = acc * 10_000 + digit decode_numeric_int(tail, weight - 1, acc) end end diff --git a/lib/postgrex/extensions/oid.ex b/lib/postgrex/extensions/oid.ex index 7eb5366e5..1880ddb1b 100644 --- a/lib/postgrex/extensions/oid.ex +++ b/lib/postgrex/extensions/oid.ex @@ -10,14 +10,19 @@ defmodule Postgrex.Extensions.OID do def encode(_) do range = Macro.escape(@oid_range) + quote location: :keep do oid when is_integer(oid) and oid in unquote(range) -> - <<4 :: int32, oid :: uint32>> + <<4::int32, oid::uint32>> + binary when is_binary(binary) -> - msg = "you tried to use a binary for an oid type " <> - "(#{binary}) when an integer was expected. See " <> - "https://github.com/elixir-ecto/postgrex#oid-type-encoding" + msg = + "you tried to use a binary for an oid type " <> + "(#{binary}) when an integer was expected. See " <> + "https://github.com/elixir-ecto/postgrex#oid-type-encoding" + raise ArgumentError, msg + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, unquote(range)) end @@ -25,7 +30,7 @@ defmodule Postgrex.Extensions.OID do def decode(_) do quote location: :keep do - <<4 :: int32, oid :: uint32>> -> oid + <<4::int32, oid::uint32>> -> oid end end end diff --git a/lib/postgrex/extensions/path.ex b/lib/postgrex/extensions/path.ex index ebbc12ce2..81e715581 100644 --- a/lib/postgrex/extensions/path.ex +++ b/lib/postgrex/extensions/path.ex @@ -10,17 +10,19 @@ defmodule Postgrex.Extensions.Path do %Postgrex.Path{open: o, points: ps} when is_list(ps) and is_boolean(o) -> open_byte = Path.open_to_byte(o) len = length(ps) - encoded_points = Enum.reduce(ps, [], - fn(p, acc) -> [acc | Point.encode_point(p, Postgrex.Path)] end) + + encoded_points = + Enum.reduce(ps, [], fn p, acc -> [acc | Point.encode_point(p, Postgrex.Path)] end) # 1 byte for open/closed flag, 4 for length, 16 for each point nbytes = 5 + 16 * len [<>, open_byte, <> | encoded_points] + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Path) end end - + def decode(_) do quote location: :keep do <> -> @@ -29,7 +31,7 @@ defmodule Postgrex.Extensions.Path do end def decode_path(<>) do - open = (o == 0) + open = o == 0 points = decode_points(point_data, []) %Postgrex.Path{open: open, points: points} end @@ -38,6 +40,7 @@ defmodule Postgrex.Extensions.Path do def open_to_byte(false), do: 1 defp decode_points(<<>>, points), do: Enum.reverse(points) + defp decode_points(<>, points) do decode_points(rest, [%Postgrex.Point{x: x, y: y} | points]) end diff --git a/lib/postgrex/extensions/point.ex b/lib/postgrex/extensions/point.ex index 686d5eeac..035a2bfce 100644 --- a/lib/postgrex/extensions/point.ex +++ b/lib/postgrex/extensions/point.ex @@ -7,6 +7,7 @@ defmodule Postgrex.Extensions.Point do quote location: :keep do %Postgrex.Point{x: x, y: y} -> <<16::int32, x::float64, y::float64>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Point) end @@ -22,6 +23,7 @@ defmodule Postgrex.Extensions.Point do def encode_point(%Postgrex.Point{x: x, y: y}, _) do <> end + def encode_point(other, wanted) do raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, wanted) end diff --git a/lib/postgrex/extensions/polygon.ex b/lib/postgrex/extensions/polygon.ex index ea027dfba..1a2403576 100644 --- a/lib/postgrex/extensions/polygon.ex +++ b/lib/postgrex/extensions/polygon.ex @@ -9,13 +9,17 @@ defmodule Postgrex.Extensions.Polygon do quote location: :keep do %Postgrex.Polygon{vertices: vertices} when is_list(vertices) -> len = length(vertices) - vert = Enum.reduce(vertices, [], - fn(v, acc) -> [acc | Point.encode_point(v, Postgrex.Polygon)] end) + + vert = + Enum.reduce(vertices, [], fn v, acc -> + [acc | Point.encode_point(v, Postgrex.Polygon)] + end) # 32 bits for len, 64 for each x and each y nbytes = 4 + 16 * len [<>, <> | vert] + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Postgrex.Polygon) end @@ -35,6 +39,7 @@ defmodule Postgrex.Extensions.Polygon do end defp decode_vertices(<<>>, v), do: Enum.reverse(v) + defp decode_vertices(<>, v) do decode_vertices(rest, [%Postgrex.Point{x: x, y: y} | v]) end diff --git a/lib/postgrex/extensions/raw.ex b/lib/postgrex/extensions/raw.ex index 5b4b89142..a0c30a299 100644 --- a/lib/postgrex/extensions/raw.ex +++ b/lib/postgrex/extensions/raw.ex @@ -1,17 +1,24 @@ defmodule Postgrex.Extensions.Raw do @moduledoc false import Postgrex.BinaryUtils, warn: false + use Postgrex.BinaryExtension, - [send: "bpcharsend", send: "textsend", send: "varcharsend", - send: "byteasend", send: "enum_send", send: "unknownsend", - send: "citextsend", send: "charsend"] + send: "bpcharsend", + send: "textsend", + send: "varcharsend", + send: "byteasend", + send: "enum_send", + send: "unknownsend", + send: "citextsend", + send: "charsend" def init(opts), do: Keyword.fetch!(opts, :decode_binary) def encode(_) do quote location: :keep do bin when is_binary(bin) -> - [<> | bin] + [<> | bin] + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a binary") end @@ -19,12 +26,13 @@ defmodule Postgrex.Extensions.Raw do def decode(:copy) do quote location: :keep do - <> -> :binary.copy(value) + <> -> :binary.copy(value) end end + def decode(:reference) do quote location: :keep do - <> -> value + <> -> value end end end diff --git a/lib/postgrex/extensions/record.ex b/lib/postgrex/extensions/record.ex index d33982d53..3fe51ce8a 100644 --- a/lib/postgrex/extensions/record.ex +++ b/lib/postgrex/extensions/record.ex @@ -13,6 +13,7 @@ defmodule Postgrex.Extensions.Record do def oids(%Postgrex.TypeInfo{comp_elems: []}, _), do: nil + def oids(%Postgrex.TypeInfo{comp_elems: comp_oids}, _), do: comp_oids @@ -22,6 +23,7 @@ defmodule Postgrex.Extensions.Record do # encode_tuple/3 defined by TypeModule data = encode_tuple(tuple, oids, types) [<> | data] + other, _, _ -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a tuple") end @@ -33,6 +35,7 @@ defmodule Postgrex.Extensions.Record do <> = binary # decode_tuple/3 defined by TypeModule decode_tuple(data, count, types) + <>, oids, types -> <<_::int32, data::binary>> = binary # decode_tuple/3 defined by TypeModule diff --git a/lib/postgrex/extensions/tid.ex b/lib/postgrex/extensions/tid.ex index 3c1f9aa13..150857993 100644 --- a/lib/postgrex/extensions/tid.ex +++ b/lib/postgrex/extensions/tid.ex @@ -6,7 +6,8 @@ defmodule Postgrex.Extensions.TID do def encode(_) do quote location: :keep do {block, tuple} -> - <<6 :: int32, block :: uint32, tuple :: uint16>> + <<6::int32, block::uint32, tuple::uint16>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a tuple of 2 integers") end @@ -14,7 +15,7 @@ defmodule Postgrex.Extensions.TID do def decode(_) do quote location: :keep do - <<6 :: int32, block :: uint32, tuple :: uint16>> -> + <<6::int32, block::uint32, tuple::uint16>> -> {block, tuple} end end diff --git a/lib/postgrex/extensions/time.ex b/lib/postgrex/extensions/time.ex index 589e15958..c8c98bd11 100644 --- a/lib/postgrex/extensions/time.ex +++ b/lib/postgrex/extensions/time.ex @@ -1,7 +1,7 @@ defmodule Postgrex.Extensions.Time do @moduledoc false import Postgrex.BinaryUtils, warn: false - use Postgrex.BinaryExtension, [send: "time_send"] + use Postgrex.BinaryExtension, send: "time_send" def encode(_) do quote location: :keep do @@ -15,7 +15,7 @@ defmodule Postgrex.Extensions.Time do def decode(_) do quote location: :keep do - <<8 :: int32, microsecs :: int64>> -> + <<8::int32, microsecs::int64>> -> unquote(__MODULE__).microsecond_to_elixir(microsecs) end end @@ -25,12 +25,13 @@ defmodule Postgrex.Extensions.Time do def encode_elixir(%Time{hour: hour, minute: min, second: sec, microsecond: {usec, _}}) when hour in 0..23 and min in 0..59 and sec in 0..59 and usec in 0..999_999 do time = {hour, min, sec} - <<8 :: int32, :calendar.time_to_seconds(time) * 1_000_000 + usec :: int64>> + <<8::int32, :calendar.time_to_seconds(time) * 1_000_000 + usec::int64>> end def microsecond_to_elixir(microsec) do sec = div(microsec, 1_000_000) microsec = rem(microsec, 1_000_000) + sec |> :calendar.seconds_to_time() |> Time.from_erl!({microsec, 6}) diff --git a/lib/postgrex/extensions/timestamptz.ex b/lib/postgrex/extensions/timestamptz.ex index cc7c78f1e..e69465808 100644 --- a/lib/postgrex/extensions/timestamptz.ex +++ b/lib/postgrex/extensions/timestamptz.ex @@ -1,14 +1,15 @@ defmodule Postgrex.Extensions.TimestampTZ do @moduledoc false import Postgrex.BinaryUtils, warn: false - use Postgrex.BinaryExtension, [send: "timestamptz_send"] + use Postgrex.BinaryExtension, send: "timestamptz_send" @gs_epoch :calendar.datetime_to_gregorian_seconds({{2000, 1, 1}, {0, 0, 0}}) @max_year 294_276 @gs_unix_epoch :calendar.datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}) @us_epoch (@gs_epoch - @gs_unix_epoch) * 1_000_000 - @gs_max :calendar.datetime_to_gregorian_seconds({{@max_year+1, 1, 1}, {0, 0, 0}}) - @gs_unix_epoch + @gs_max :calendar.datetime_to_gregorian_seconds({{@max_year + 1, 1, 1}, {0, 0, 0}}) - + @gs_unix_epoch @us_max @gs_max * 1_000_000 def encode(_) do @@ -24,7 +25,7 @@ defmodule Postgrex.Extensions.TimestampTZ do def decode(_) do quote location: :keep do - <<8 :: int32, microsecs :: int64>> -> + <<8::int32, microsecs::int64>> -> unquote(__MODULE__).microsecond_to_elixir(microsecs) end end @@ -34,13 +35,15 @@ defmodule Postgrex.Extensions.TimestampTZ do def encode_elixir(%DateTime{utc_offset: 0, std_offset: 0} = datetime) do case DateTime.to_unix(datetime, :microsecond) do microsecs when microsecs < @us_max -> - <<8 :: int32, microsecs - @us_epoch :: int64>> + <<8::int32, microsecs - @us_epoch::int64>> + _ -> - raise ArgumentError, "#{inspect datetime} is beyond the maximum year 294276" + raise ArgumentError, "#{inspect(datetime)} is beyond the maximum year 294276" end end + def encode_elixir(%DateTime{} = datetime) do - raise ArgumentError, "#{inspect datetime} is not in UTC" + raise ArgumentError, "#{inspect(datetime)} is not in UTC" end def microsecond_to_elixir(microsecs) do diff --git a/lib/postgrex/extensions/timetz.ex b/lib/postgrex/extensions/timetz.ex index 14f10dd12..1a488629b 100644 --- a/lib/postgrex/extensions/timetz.ex +++ b/lib/postgrex/extensions/timetz.ex @@ -1,7 +1,7 @@ defmodule Postgrex.Extensions.TimeTZ do @moduledoc false import Postgrex.BinaryUtils, warn: false - use Postgrex.BinaryExtension, [send: "timetz_send"] + use Postgrex.BinaryExtension, send: "timetz_send" @day (:calendar.time_to_seconds({23, 59, 59}) + 1) * 1_000_000 @@ -9,6 +9,7 @@ defmodule Postgrex.Extensions.TimeTZ do quote location: :keep do %Time{calendar: Calendar.ISO} = time -> unquote(__MODULE__).encode_elixir(time) + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, Time) end @@ -16,7 +17,7 @@ defmodule Postgrex.Extensions.TimeTZ do def decode(_) do quote location: :keep do - <<12 :: int32, microsecs :: int64, tz :: int32>> -> + <<12::int32, microsecs::int64, tz::int32>> -> unquote(__MODULE__).microsecond_to_elixir(microsecs, tz) end end @@ -27,8 +28,10 @@ defmodule Postgrex.Extensions.TimeTZ do case microsec + tz * 1_000_000 do adjusted_microsec when adjusted_microsec < 0 -> @day + adjusted_microsec + adjusted_microsec when adjusted_microsec < @day -> adjusted_microsec + adjusted_microsec -> adjusted_microsec - @day end @@ -37,7 +40,7 @@ defmodule Postgrex.Extensions.TimeTZ do def encode_elixir(%Time{hour: hour, minute: min, second: sec, microsecond: {usec, _}}) when hour in 0..23 and min in 0..59 and sec in 0..59 and usec in 0..999_999 do time = {hour, min, sec} - <<12 :: int32, :calendar.time_to_seconds(time) * 1_000_000 + usec :: int64, 0 :: int32>> + <<12::int32, :calendar.time_to_seconds(time) * 1_000_000 + usec::int64, 0::int32>> end def microsecond_to_elixir(microsec, tz) do @@ -49,6 +52,7 @@ defmodule Postgrex.Extensions.TimeTZ do defp microsecond_to_elixir(microsec) do sec = div(microsec, 1_000_000) microsec = rem(microsec, 1_000_000) + sec |> :calendar.seconds_to_time() |> Time.from_erl!({microsec, 6}) diff --git a/lib/postgrex/extensions/tsvector.ex b/lib/postgrex/extensions/tsvector.ex index 27af644e2..9b819181f 100644 --- a/lib/postgrex/extensions/tsvector.ex +++ b/lib/postgrex/extensions/tsvector.ex @@ -3,13 +3,14 @@ defmodule Postgrex.Extensions.TSVector do import Postgrex.BinaryUtils, warn: false use Bitwise - use Postgrex.BinaryExtension, [send: "tsvectorsend"] + use Postgrex.BinaryExtension, send: "tsvectorsend" def encode(_) do quote location: :keep do values when is_list(values) -> encoded_tsvectors = unquote(__MODULE__).encode_tsvector(values) <> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a list of tsvectors") end @@ -30,11 +31,15 @@ defmodule Postgrex.Extensions.TSVector do end defp encode_lexemes(values) do - values |> Enum.map(fn(x) -> encode_positions(x) end) |> IO.iodata_to_binary + values |> Enum.map(fn x -> encode_positions(x) end) |> IO.iodata_to_binary() end defp encode_positions(%Postgrex.Lexeme{word: word, positions: positions}) do - positions = Enum.map(positions, fn({position, weight}) -> <> end) + positions = + Enum.map(positions, fn {position, weight} -> + <> + end) + [word, 0, <> | positions] end @@ -46,14 +51,28 @@ defmodule Postgrex.Extensions.TSVector do [word, <>] = :binary.split(words, <<0>>) positions_bytes = positions_count * 2 <> = rest - positions = for <>, do: {position, decode_weight(weight)} + + positions = + for <>, do: {position, decode_weight(weight)} + [%Postgrex.Lexeme{word: word, positions: positions} | decode_tsvector_values(remaining_data)] end - defp encode_weight_binary(:A) do 3 end - defp encode_weight_binary(:B) do 2 end - defp encode_weight_binary(:C) do 1 end - defp encode_weight_binary(nil) do 0 end + defp encode_weight_binary(:A) do + 3 + end + + defp encode_weight_binary(:B) do + 2 + end + + defp encode_weight_binary(:C) do + 1 + end + + defp encode_weight_binary(nil) do + 0 + end defp decode_weight(0), do: nil defp decode_weight(1), do: :C diff --git a/lib/postgrex/extensions/uuid.ex b/lib/postgrex/extensions/uuid.ex index 4f07b1084..59fef036c 100644 --- a/lib/postgrex/extensions/uuid.ex +++ b/lib/postgrex/extensions/uuid.ex @@ -8,7 +8,8 @@ defmodule Postgrex.Extensions.UUID do def encode(_) do quote location: :keep do uuid when is_binary(uuid) and byte_size(uuid) == 16 -> - [<<16 :: int32>> | uuid] + [<<16::int32>> | uuid] + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "a binary of 16 bytes") end @@ -16,12 +17,13 @@ defmodule Postgrex.Extensions.UUID do def decode(:copy) do quote location: :keep do - <<16 :: int32, uuid :: binary-16>> -> :binary.copy(uuid) + <<16::int32, uuid::binary-16>> -> :binary.copy(uuid) end end + def decode(:reference) do quote location: :keep do - <<16 :: int32, uuid :: binary-16>> -> uuid + <<16::int32, uuid::binary-16>> -> uuid end end end diff --git a/lib/postgrex/extensions/void_binary.ex b/lib/postgrex/extensions/void_binary.ex index 9fbc5e968..3829bf94c 100644 --- a/lib/postgrex/extensions/void_binary.ex +++ b/lib/postgrex/extensions/void_binary.ex @@ -6,7 +6,8 @@ defmodule Postgrex.Extensions.VoidBinary do def encode(_) do quote location: :keep do :void -> - <<0 :: int32>> + <<0::int32>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "the atom :void") end @@ -14,7 +15,7 @@ defmodule Postgrex.Extensions.VoidBinary do def decode(_) do quote location: :keep do - <<0 :: int32>> -> :void + <<0::int32>> -> :void end end end diff --git a/lib/postgrex/extensions/void_text.ex b/lib/postgrex/extensions/void_text.ex index 886ec5d25..767e6b511 100644 --- a/lib/postgrex/extensions/void_text.ex +++ b/lib/postgrex/extensions/void_text.ex @@ -12,7 +12,8 @@ defmodule Postgrex.Extensions.VoidText do def encode(_) do quote location: :keep do :void -> - <<0 :: int32>> + <<0::int32>> + other -> raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, "the atom :void") end @@ -20,7 +21,7 @@ defmodule Postgrex.Extensions.VoidText do def decode(_) do quote location: :keep do - <<0 :: int32>> -> :void + <<0::int32>> -> :void end end end diff --git a/lib/postgrex/messages.ex b/lib/postgrex/messages.ex index 08fd17948..730862650 100644 --- a/lib/postgrex/messages.ex +++ b/lib/postgrex/messages.ex @@ -7,13 +7,39 @@ defmodule Postgrex.Messages do @protocol_vsn_major 3 @protocol_vsn_minor 0 - @auth_types [ ok: 0, kerberos: 2, cleartext: 3, md5: 5, scm: 6, gss: 7, - gss_cont: 8, sspi: 9, sasl: 10, sasl_cont: 11, sasl_fin: 12 ] - - @error_fields [ severity: ?S, code: ?C, message: ?M, detail: ?D, hint: ?H, - position: ?P, internal_position: ?p, internal_query: ?q, - where: ?W, schema: ?s, table: ?t, column: ?c, data_type: ?d, - constraint: ?n, file: ?F, line: ?L, routine: ?R ] + @auth_types [ + ok: 0, + kerberos: 2, + cleartext: 3, + md5: 5, + scm: 6, + gss: 7, + gss_cont: 8, + sspi: 9, + sasl: 10, + sasl_cont: 11, + sasl_fin: 12 + ] + + @error_fields [ + severity: ?S, + code: ?C, + message: ?M, + detail: ?D, + hint: ?H, + position: ?P, + internal_position: ?p, + internal_query: ?q, + where: ?W, + schema: ?s, + table: ?t, + column: ?c, + data_type: ?d, + constraint: ?n, + file: ?F, + line: ?L, + routine: ?R + ] defrecord :msg_auth, [:type, :data] defrecord :msg_startup, [:params] @@ -34,8 +60,7 @@ defmodule Postgrex.Messages do defrecord :msg_row_desc, [:fields] defrecord :msg_no_data, [] defrecord :msg_notify, [:pg_pid, :channel, :payload] - defrecord :msg_bind, [:name_port, :name_stat, :param_formats, :params, - :result_formats] + defrecord :msg_bind, [:name_port, :name_stat, :param_formats, :params, :result_formats] defrecord :msg_execute, [:name_port, :max_rows] defrecord :msg_sync, [] defrecord :msg_bind_complete, [] @@ -54,79 +79,89 @@ defmodule Postgrex.Messages do defrecord :msg_ssl_request, [] defrecord :msg_cancel_request, [:pid, :key] - defrecord :row_field, [:name, :table_oid, :column, :type_oid, :type_size, - :type_mod, :format] + defrecord :row_field, [:name, :table_oid, :column, :type_oid, :type_size, :type_mod, :format] ### decoders ### # auth - def parse(<>, ?R, size) do + def parse(<>, ?R, size) do type = decode_auth_type(type) + data = case type do :md5 -> - <> = rest + <> = rest data + :gss_cont -> rest_size = size - 2 - <> = rest + <> = rest data + :sasl -> rest + :sasl_cont -> rest + :sasl_fin -> rest + _ -> nil end + msg_auth(type: type, data: data) end # backend_key - def parse(<>, ?K, _size) do + def parse(<>, ?K, _size) do msg_backend_key(pid: pid, key: key) end # ready - def parse(<>, ?Z, _size) do - status = case status do - ?I -> :idle - ?T -> :transaction - ?E -> :error - end + def parse(<>, ?Z, _size) do + status = + case status do + ?I -> :idle + ?T -> :transaction + ?E -> :error + end + msg_ready(status: status) end # parameter_desc - def parse(<>, ?t, _size) do - oids = for <>, do: oid + def parse(<>, ?t, _size) do + oids = for <>, do: oid msg_parameter_desc(type_oids: oids) end - def parse(<>, ?t, size) do + def parse(<>, ?t, size) do len = div(size - 2, 4) - case <> do - <<^overflow_len :: uint16>> -> + + case <> do + <<^overflow_len::uint16>> -> msg_too_many_parameters(len: len, max_len: 0xFFFF) + _ -> raise "invalid parameter description" end end # row_desc - def parse(<>, ?T, _size) do + def parse(<>, ?T, _size) do fields = decode_row_fields(rest, len) msg_row_desc(fields: fields) end # data_row - def parse(<<_ :: uint16, rest :: binary>>, ?D, _size) do + def parse(<<_::uint16, rest::binary>>, ?D, _size) do msg_data_row(values: rest) end # notify - def parse(<>, ?A, _size) do + def parse(<>, ?A, _size) do {channel, rest} = decode_string(rest) {payload, ""} = decode_string(rest) msg_notify(pg_pid: pg_pid, channel: channel, payload: payload) @@ -227,18 +262,20 @@ defmodule Postgrex.Messages do size = IO.iodata_length(data) + 4 if first do - [first, <>, data] + [first, <>, data] else - [<>, data] - end + [<>, data] + end end # startup defp encode(msg_startup(params: params)) do - params = Enum.reduce(params, [], fn {key, value}, acc -> - [acc, to_string(key), 0, value, 0] - end) - vsn = <<@protocol_vsn_major :: int16, @protocol_vsn_minor :: int16>> + params = + Enum.reduce(params, [], fn {key, value}, acc -> + [acc, to_string(key), 0, value, 0] + end) + + vsn = <<@protocol_vsn_major::int16, @protocol_vsn_minor::int16>> {nil, [vsn, params, 0]} end @@ -254,17 +291,19 @@ defmodule Postgrex.Messages do # parse defp encode(msg_parse(name: name, statement: statement, type_oids: oids)) do - oids = for oid <- oids, into: "", do: <> - len = <> + oids = for oid <- oids, into: "", do: <> + len = <> {?P, [name, 0, statement, 0, len, oids]} end # describe defp encode(msg_describe(type: type, name: name)) do - byte = case type do - :statement -> ?S - :portal -> ?P - end + byte = + case type do + :statement -> ?S + :portal -> ?P + end + {?D, [byte, name, 0]} end @@ -275,29 +314,38 @@ defmodule Postgrex.Messages do # close defp encode(msg_close(type: type, name: name)) do - byte = case type do - :statement -> ?S - :portal -> ?P - end + byte = + case type do + :statement -> ?S + :portal -> ?P + end + {?C, [byte, name, 0]} end # bind - defp encode(msg_bind(name_port: port, name_stat: stat, param_formats: param_formats, - params: params, result_formats: result_formats)) do - pfs = for format <- param_formats, into: "", do: <> - rfs = for format <- result_formats, into: "", do: <> - - len_pfs = <> - len_rfs = <> - len_params = <> + defp encode( + msg_bind( + name_port: port, + name_stat: stat, + param_formats: param_formats, + params: params, + result_formats: result_formats + ) + ) do + pfs = for format <- param_formats, into: "", do: <> + rfs = for format <- result_formats, into: "", do: <> + + len_pfs = <> + len_rfs = <> + len_params = <> {?B, [port, 0, stat, 0, len_pfs, pfs, len_params, params, len_rfs, rfs]} end # execute defp encode(msg_execute(name_port: port, max_rows: rows)) do - {?E, [port, 0, <>]} + {?E, [port, 0, <>]} end # sync @@ -312,12 +360,12 @@ defmodule Postgrex.Messages do # ssl_request defp encode(msg_ssl_request()) do - {nil, <<1234 :: int16, 5679 :: int16>>} + {nil, <<1234::int16, 5679::int16>>} end # cancel_request defp encode(msg_cancel_request(pid: pid, key: key)) do - {nil, <<1234 :: int16, 5678 :: int16, pid :: int32, key :: int32>>} + {nil, <<1234::int16, 5678::int16, pid::int32, key::int32>>} end # copy_data @@ -337,14 +385,14 @@ defmodule Postgrex.Messages do ### encode helpers ### - defp format(:text), do: 0 + defp format(:text), do: 0 defp format(:binary), do: 1 ### decode helpers ### defp decode_fields(<<0>>), do: [] - defp decode_fields(<>) do + defp decode_fields(<>) do type = decode_field_type(field) {string, rest} = decode_string(rest) [{type, string} | decode_fields(rest)] @@ -352,7 +400,7 @@ defmodule Postgrex.Messages do defp decode_string(bin) do {pos, 1} = :binary.match(bin, <<0>>) - {string, <<0, rest :: binary>>} = :erlang.split_binary(bin, pos) + {string, <<0, rest::binary>>} = :erlang.split_binary(bin, pos) {string, rest} end @@ -365,11 +413,21 @@ defmodule Postgrex.Messages do defp decode_row_field(rest) do {name, rest} = decode_string(rest) - <> = rest - field = row_field(name: name, table_oid: table_oid, column: column, type_oid: type_oid, - type_size: type_size, type_mod: type_mod, format: format) + + <> = rest + + field = + row_field( + name: name, + table_oid: table_oid, + column: column, + type_oid: type_oid, + type_size: type_size, + type_mod: type_mod, + format: format + ) + {field, rest} end @@ -380,6 +438,7 @@ defmodule Postgrex.Messages do Enum.each(@error_fields, fn {field, char} -> def decode_field_type(unquote(char)), do: unquote(field) end) + def decode_field_type(_), do: :unknown defp decode_format(0), do: :text diff --git a/lib/postgrex/notifications.ex b/lib/postgrex/notifications.ex index 2114ce8e8..fe58265c1 100644 --- a/lib/postgrex/notifications.ex +++ b/lib/postgrex/notifications.ex @@ -215,7 +215,7 @@ defmodule Postgrex.Notifications do {:ok, protocol} -> {:noreply, %{state | protocol: protocol}, state.idle_timeout} - {error, reason, protocol} when error in[:error, :disconnect] -> + {error, reason, protocol} when error in [:error, :disconnect] -> {:stop, reason, %{state | protocol: protocol}} end end diff --git a/lib/postgrex/parameters.ex b/lib/postgrex/parameters.ex index 89e6a3793..210452e2f 100644 --- a/lib/postgrex/parameters.ex +++ b/lib/postgrex/parameters.ex @@ -48,8 +48,7 @@ defmodule Postgrex.Parameters do end def init(nil) do - opts = [:public, :named_table, {:read_concurrency, true}, - {:write_concurrency, true}] + opts = [:public, :named_table, {:read_concurrency, true}, {:write_concurrency, true}] state = :ets.new(__MODULE__, opts) {:ok, state} end diff --git a/lib/postgrex/protocol.ex b/lib/postgrex/protocol.ex index 276fd05f1..bd2da5a4c 100644 --- a/lib/postgrex/protocol.ex +++ b/lib/postgrex/protocol.ex @@ -46,12 +46,14 @@ defmodule Postgrex.Protocol do defmacrop new_status(opts, fields \\ []) do defaults = - quote(do: [ - notify: notify(unquote(opts)), - mode: mode(unquote(opts)), - messages: [], - prepare: false - ]) + quote( + do: [ + notify: notify(unquote(opts)), + mode: mode(unquote(opts)), + messages: [], + prepare: false + ] + ) {:%{}, [], Keyword.merge(defaults, fields)} end @@ -788,6 +790,7 @@ defmodule Postgrex.Protocol do %{parameters: parameters} = s version = Postgrex.Utils.parse_version(parameters["server_version"]) statement = Types.bootstrap_query(version, types) + if statement do bootstrap_send(s, status, statement, buffer) else @@ -1132,8 +1135,7 @@ defmodule Postgrex.Protocol do %Query{name: name} = query %{buffer: buffer} = s - msgs = - [msg_close(type: :statement, name: name)] ++ parse_describe_msgs(query, [msg_flush()]) + msgs = [msg_close(type: :statement, name: name)] ++ parse_describe_msgs(query, [msg_flush()]) with :ok <- msg_send(%{s | buffer: nil}, msgs, buffer), {:ok, s, buffer} <- recv_close(s, status, buffer), @@ -1323,12 +1325,15 @@ defmodule Postgrex.Protocol do defp reload_describe_result(s, param_oids, nil, buffer) do {:reload, param_oids, s, buffer} end + defp reload_describe_result(%{types: types} = s, param_oids, result_oids, buffer) do case fetch_type_info(result_oids, types) do {:ok, _} -> {:reload, param_oids, s, buffer} + {:reload, reload_oids} -> {:reload, MapSet.union(param_oids, reload_oids), s, buffer} + {:error, err} -> {:disconnect, err, %{s | buffer: buffer}} end @@ -1420,8 +1425,8 @@ defmodule Postgrex.Protocol do with :ok <- msg_send(s, msgs, buffer), {:ok, s, buffer} <- recv_close(s, status, buffer), - {:ok, _, %{buffer: buffer} = s} - <- recv_transaction(s, status, buffer) do + {:ok, _, %{buffer: buffer} = s} <- + recv_transaction(s, status, buffer) do reload_spawn(%{s | buffer: nil}, status, query, oids, buffer) else {:disconnect, _err, _s} = disconnect -> @@ -1453,14 +1458,17 @@ defmodule Postgrex.Protocol do end defp fetch_type_info(oids, types, infos \\ [], reloads \\ MapSet.new()) + defp fetch_type_info([], _, infos, reloads) do case MapSet.size(reloads) do 0 -> {:ok, Enum.reverse(infos)} + _ -> {:reload, reloads} end end + defp fetch_type_info([oid | oids], types, infos, reloads) do case Postgrex.Types.fetch(oid, types) do {:ok, info} -> @@ -1476,13 +1484,15 @@ defmodule Postgrex.Protocol do end defp reload_spawn(s, status, query, oids, buffer) do - Logger.warn(fn() -> - [inspect(query) | - " uses unknown oid(s) #{Enum.join(oids, ", ")} causing bootstrap"] + Logger.warn(fn -> + [ + inspect(query) + | " uses unknown oid(s) #{Enum.join(oids, ", ")} causing bootstrap" + ] end) ref = make_ref() - {_, mon} = spawn_monitor(fn() -> reload_init(s, status, oids, ref, buffer) end) + {_, mon} = spawn_monitor(fn -> reload_init(s, status, oids, ref, buffer) end) receive do {:DOWN, ^mon, _, _, {^ref, s, buffer}} -> @@ -1525,7 +1535,7 @@ defmodule Postgrex.Protocol do else %{types_lock: {server, ref}} = status {type_infos, _, _, _} = acc - sorted_infos = Enum.sort_by(type_infos, &(&1.oid)) + sorted_infos = Enum.sort_by(type_infos, & &1.oid) TypeServer.update(server, ref, sorted_infos) {:ok, %{s | buffer: buffer}} end @@ -1538,9 +1548,11 @@ defmodule Postgrex.Protocol do defp reload_send(s, status, statement, acc, buffer) do msg = msg_query(statement: statement) + case msg_send(s, msg, buffer) do :ok -> reload_recv(s, status, acc, buffer) + {:disconnect, err, s} -> bootstrap_fail(s, err, status) end @@ -1550,16 +1562,21 @@ defmodule Postgrex.Protocol do case msg_recv(s, :infinity, buffer) do {:ok, msg_row_desc(), buffer} -> reload_recv(s, status, acc, buffer) + {:ok, msg_data_row(values: values), buffer} -> reload_recv(s, status, reload_row(acc, values, types), buffer) + {:ok, msg_command_complete(), buffer} -> reload_complete(s, status, acc, buffer) + {:ok, msg_error(fields: fields), buffer} -> err = Postgrex.Error.exception(postgres: fields) bootstrap_fail(s, err, status, buffer) + {:ok, msg, buffer} -> s = handle_msg(s, status, msg) reload_recv(s, status, acc, buffer) + {:disconnect, err, s} -> bootstrap_fail(s, err, status) end @@ -1567,22 +1584,25 @@ defmodule Postgrex.Protocol do defp reload_row({type_infos, oids, missing, current}, values, types) do %Postgrex.TypeInfo{oid: oid} = type_info = Types.build_type_info(values) + missing = missing |> put_missing_oids(type_info, oids, types) |> MapSet.delete(oid) + {[type_info | type_infos], MapSet.put(oids, oid), missing, current} end defp put_missing_oids(missing, type_info, new, types) do - %Postgrex.TypeInfo{array_elem: array_elem, base_type: base_type, - comp_elems: comp_elems} = type_info + %Postgrex.TypeInfo{array_elem: array_elem, base_type: base_type, comp_elems: comp_elems} = + type_info + for oid <- [array_elem, base_type | comp_elems], oid !== 0, not MapSet.member?(new, oid), not bootstrapped?(types, oid), - do: oid, - into: missing + do: oid, + into: missing end defp bootstrapped?(types, oid) do @@ -1605,6 +1625,7 @@ defmodule Postgrex.Protocol do next = MapSet.delete(missing, prev) current = MapSet.union(next, prev) reload(s, status, Enum.to_list(next), {type_infos, new, MapSet.new(), current}, buffer) + {:disconnect, _, _} = error -> error end @@ -1614,8 +1635,10 @@ defmodule Postgrex.Protocol do case oids |> Enum.to_list() |> fetch_type_info(types) do {:ok, _} -> reload_prepare(%{s | buffer: buffer}, status, query) + {:error, err} -> disconnect(s, err, buffer) + {:reload, oids} -> msg = "oid(s) #{Enum.join(oids, ", ")} lack type information after bootstrap" disconnect(s, RuntimeError.exception(message: msg), buffer) @@ -1647,8 +1670,7 @@ defmodule Postgrex.Protocol do end defp lock_error(s, fun) do - msg = - "connection is locked copying to or from the database and can not #{fun} transaction" + msg = "connection is locked copying to or from the database and can not #{fun} transaction" {:disconnect, RuntimeError.exception(msg), s} end @@ -1750,6 +1772,7 @@ defmodule Postgrex.Protocol do else {:error, %Postgrex.Error{} = err, s, buffer} -> query_delete(s, err, query) + error_ready(s, status, err, buffer) |> maybe_disconnect() @@ -1791,10 +1814,10 @@ defmodule Postgrex.Protocol do defp maybe_disconnect({:error, _, %{disconnect_on_error_codes: []}} = result), do: result - defp maybe_disconnect({:error, - %Postgrex.Error{postgres: %{code: code}} = error, - %{disconnect_on_error_codes: codes} = state - } = result) do + defp maybe_disconnect( + {:error, %Postgrex.Error{postgres: %{code: code}} = error, + %{disconnect_on_error_codes: codes} = state} = result + ) do if code in codes do {:disconnect, error, state} else @@ -2761,7 +2784,7 @@ defmodule Postgrex.Protocol do tag = acc |> String.trim_trailing("_") - |> String.to_atom + |> String.to_atom() {tag, nil} end @@ -2954,6 +2977,7 @@ defmodule Postgrex.Protocol do defp disconnect(%{connection_id: connection_id} = s, %Postgrex.Error{} = err, buffer) do {:disconnect, %{err | connection_id: connection_id}, %{s | buffer: buffer}} end + defp disconnect(s, %RuntimeError{} = err, buffer) do {:disconnect, err, %{s | buffer: buffer}} end diff --git a/lib/postgrex/query.ex b/lib/postgrex/query.ex index 79613fc9c..9f311db46 100644 --- a/lib/postgrex/query.ex +++ b/lib/postgrex/query.ex @@ -21,21 +21,34 @@ defmodule Postgrex.Query do """ @type t :: %__MODULE__{ - cache: :reference | :statement, - ref: reference | nil, - name: iodata, - statement: iodata, - param_oids: [Postgrex.Types.oid] | nil, - param_formats: [:binary | :text] | nil, - param_types: [Postgrex.Types.type] | nil, - columns: [String.t] | nil, - result_oids: [Postgrex.Types.oid] | nil, - result_formats: [:binary | :text] | nil, - result_types: [Postgrex.Types.type] | nil, - types: Postgrex.Types.state | nil} - - defstruct [:ref, :name, :statement, :param_oids, :param_formats, :param_types, - :columns, :result_oids, :result_formats, :result_types, :types, cache: :reference] + cache: :reference | :statement, + ref: reference | nil, + name: iodata, + statement: iodata, + param_oids: [Postgrex.Types.oid()] | nil, + param_formats: [:binary | :text] | nil, + param_types: [Postgrex.Types.type()] | nil, + columns: [String.t()] | nil, + result_oids: [Postgrex.Types.oid()] | nil, + result_formats: [:binary | :text] | nil, + result_types: [Postgrex.Types.type()] | nil, + types: Postgrex.Types.state() | nil + } + + defstruct [ + :ref, + :name, + :statement, + :param_oids, + :param_formats, + :param_types, + :columns, + :result_oids, + :result_formats, + :result_types, + :types, + cache: :reference + ] end defimpl DBConnection.Query, for: Postgrex.Query do @@ -47,13 +60,13 @@ defimpl DBConnection.Query, for: Postgrex.Query do end def parse(query, _) do - raise ArgumentError, "query #{inspect query} has already been prepared" + raise ArgumentError, "query #{inspect(query)} has already been prepared" end def describe(query, _), do: query def encode(%{types: nil} = query, _params, _) do - raise ArgumentError, "query #{inspect query} has not been prepared" + raise ArgumentError, "query #{inspect(query)} has not been prepared" end def encode(query, params, _) do @@ -62,18 +75,21 @@ defimpl DBConnection.Query, for: Postgrex.Query do case Postgrex.Types.encode_params(params, param_types, types) do encoded when is_list(encoded) -> encoded + :error -> raise ArgumentError, - "parameters must be of length #{length param_types} for query #{inspect query}" + "parameters must be of length #{length(param_types)} for query #{inspect(query)}" end end def decode(_, %Postgrex.Result{rows: nil} = res, _opts) do res end + def decode(_, %Postgrex.Result{rows: rows} = res, opts) do %Postgrex.Result{res | rows: decode_map(rows, opts)} end + def decode(_, %Postgrex.Copy{} = copy, _opts) do copy end @@ -82,7 +98,7 @@ defimpl DBConnection.Query, for: Postgrex.Query do defp decode_map(data, opts) do case opts[:decode_mapper] do - nil -> Enum.reverse(data) + nil -> Enum.reverse(data) mapper -> decode_map(data, mapper, []) end end @@ -90,6 +106,7 @@ defimpl DBConnection.Query, for: Postgrex.Query do defp decode_map([row | data], mapper, decoded) do decode_map(data, mapper, [mapper.(row) | decoded]) end + defp decode_map([], _, decoded) do decoded end diff --git a/lib/postgrex/scram.ex b/lib/postgrex/scram.ex index 00805b19f..ef624f621 100644 --- a/lib/postgrex/scram.ex +++ b/lib/postgrex/scram.ex @@ -5,7 +5,7 @@ defmodule Postgrex.SCRAM do @nonce_length 24 @nonce_rand_bytes div(@nonce_length * 6, 8) @nonce_prefix "n,,n=,r=" - @nonce_encoded_size <> + @nonce_encoded_size <> def challenge do nonce = @nonce_rand_bytes |> :crypto.strong_rand_bytes() |> Base.encode64() diff --git a/lib/postgrex/stream.ex b/lib/postgrex/stream.ex index 6f272dbed..a0b7a4f70 100644 --- a/lib/postgrex/stream.ex +++ b/lib/postgrex/stream.ex @@ -3,11 +3,13 @@ defmodule Postgrex.Stream do defstruct [:conn, :query, :params, :options] @type t :: %Postgrex.Stream{} end + defmodule Postgrex.Cursor do @moduledoc false defstruct [:portal, :ref, :connection_id, :mode] @type t :: %Postgrex.Cursor{} end + defmodule Postgrex.Copy do @moduledoc false defstruct [:portal, :ref, :connection_id, :query] @@ -16,6 +18,7 @@ end defimpl Enumerable, for: Postgrex.Stream do alias Postgrex.Query + def reduce(%Postgrex.Stream{query: %Query{} = query} = stream, acc, fun) do %Postgrex.Stream{conn: conn, params: params, options: opts} = stream stream = %DBConnection.Stream{conn: conn, query: query, params: params, opts: opts} @@ -24,7 +27,7 @@ defimpl Enumerable, for: Postgrex.Stream do def reduce(%Postgrex.Stream{query: statement} = stream, acc, fun) do %Postgrex.Stream{conn: conn, params: params, options: opts} = stream - query = %Query{name: "" , statement: statement} + query = %Query{name: "", statement: statement} opts = Keyword.put(opts, :function, :prepare_open) stream = %DBConnection.PrepareStream{conn: conn, query: query, params: params, opts: opts} DBConnection.reduce(stream, acc, fun) @@ -55,6 +58,7 @@ defimpl Collectable, for: Postgrex.Stream do %Query{} -> copy = DBConnection.execute!(conn, query, params, opts) {:ok, make_into(conn, stream, copy, opts)} + query -> query = %Query{name: "", statement: query} {_, copy} = DBConnection.prepare_execute!(conn, query, params, opts) @@ -71,6 +75,7 @@ defimpl Collectable, for: Postgrex.Stream do :ok, {:cont, data} -> _ = DBConnection.execute!(conn, copy, {:copy_data, ref, data}, opts) :ok + :ok, close when close in [:done, :halt] -> _ = DBConnection.execute!(conn, copy, {:copy_done, ref}, opts) stream @@ -83,11 +88,11 @@ defimpl DBConnection.Query, for: Postgrex.Copy do import Postgrex.Messages def parse(copy, _) do - raise "can not prepare #{inspect copy}" + raise "can not prepare #{inspect(copy)}" end def describe(copy, _) do - raise "can not describe #{inspect copy}" + raise "can not describe #{inspect(copy)}" end def encode(%Copy{ref: ref}, {:copy_data, ref, data}, _) do @@ -96,7 +101,7 @@ defimpl DBConnection.Query, for: Postgrex.Copy do rescue ArgumentError -> reraise ArgumentError, - "expected iodata to copy to database, got: " <> inspect(data) + "expected iodata to copy to database, got: " <> inspect(data) else iodata -> {:copy_data, iodata} @@ -108,7 +113,7 @@ defimpl DBConnection.Query, for: Postgrex.Copy do end def decode(copy, _result, _opts) do - raise "can not describe #{inspect copy}" + raise "can not describe #{inspect(copy)}" end end diff --git a/lib/postgrex/super_extension.ex b/lib/postgrex/super_extension.ex index 65233d00f..11e20e29d 100644 --- a/lib/postgrex/super_extension.ex +++ b/lib/postgrex/super_extension.ex @@ -3,19 +3,21 @@ defmodule Postgrex.SuperExtension do @type state :: term - @callback init(Keyword.t) :: state + @callback init(Keyword.t()) :: state - @callback matching(state) :: [type: String.t, - send: String.t, - receive: String.t, - input: String.t, - output: String.t] + @callback matching(state) :: [ + type: String.t(), + send: String.t(), + receive: String.t(), + input: String.t(), + output: String.t() + ] @callback format(state) :: :super_binary - @callback oids(Postgrex.TypeInfo.t, state) :: nil | [Postgrex.Types.oid] + @callback oids(Postgrex.TypeInfo.t(), state) :: nil | [Postgrex.Types.oid()] - @callback encode(state) :: Macro.t + @callback encode(state) :: Macro.t() - @callback decode(state) :: Macro.t + @callback decode(state) :: Macro.t() end diff --git a/lib/postgrex/type_info.ex b/lib/postgrex/type_info.ex index e8e58b455..7d3603e3c 100644 --- a/lib/postgrex/type_info.ex +++ b/lib/postgrex/type_info.ex @@ -23,16 +23,16 @@ defmodule Postgrex.TypeInfo do alias Postgrex.Types @type t :: %__MODULE__{ - oid: Types.oid, - type: String.t, - send: String.t, - receive: String.t, - output: String.t, - input: String.t, - array_elem: Types.oid, - base_type: Types.oid, - comp_elems: [Types.oid]} + oid: Types.oid(), + type: String.t(), + send: String.t(), + receive: String.t(), + output: String.t(), + input: String.t(), + array_elem: Types.oid(), + base_type: Types.oid(), + comp_elems: [Types.oid()] + } - defstruct [:oid, :type, :send, :receive, :output, :input, :array_elem, - :base_type, :comp_elems] + defstruct [:oid, :type, :send, :receive, :output, :input, :array_elem, :base_type, :comp_elems] end diff --git a/lib/postgrex/type_module.ex b/lib/postgrex/type_module.ex index 93ef8a641..fa2a6d7d5 100644 --- a/lib/postgrex/type_module.ex +++ b/lib/postgrex/type_module.ex @@ -7,6 +7,7 @@ defmodule Postgrex.TypeModule do opts = opts |> Keyword.put_new(:decode_binary, :copy) + config = configure(extensions, opts) define_inline(module, config, opts) end @@ -16,7 +17,7 @@ defmodule Postgrex.TypeModule do defp directives(config) do requires = for {extension, _} <- config do - quote do: require unquote(extension) + quote do: require(unquote(extension)) end quote do @@ -28,6 +29,7 @@ defmodule Postgrex.TypeModule do defp attributes(opts) do null = Keyword.get(opts, :null) + quote do @moduledoc false unquote(bin_opt_info(opts)) @@ -39,21 +41,22 @@ defmodule Postgrex.TypeModule do defp bin_opt_info(opts) do if Keyword.get(opts, :bin_opt_info) do - quote do: @compile :bin_opt_info + quote do: @compile(:bin_opt_info) else [] end end @anno (if :erlang.system_info(:otp_release) >= '19' do - [generated: true] - else - [line: -1] - end) + [generated: true] + else + [line: -1] + end) defp find(config) do clauses = Enum.flat_map(config, &find_clauses/1) clauses = clauses ++ quote do: (_ -> nil) + quote @anno do def find(type_info, formats) do case {type_info, formats} do @@ -73,22 +76,23 @@ defmodule Postgrex.TypeModule do defp find_clause(extension, opts, key, value, :super_binary) do quote do {%{unquote(key) => unquote(value)} = type_info, formats} - when formats in [:any, :binary] -> + when formats in [:any, :binary] -> oids = unquote(extension).oids(type_info, unquote(opts)) {:super_binary, unquote(extension), oids} end end + defp find_clause(extension, _opts, key, value, format) do quote do {%{unquote(key) => unquote(value)}, formats} - when formats in [:any, unquote(format)] -> + when formats in [:any, unquote(format)] -> {unquote(format), unquote(extension)} end end defp maybe_rewrite(ast, extension, cases, opts) do if Postgrex.Utils.default_extension?(extension) and - not Keyword.get(opts, :debug_defaults, false) do + not Keyword.get(opts, :debug_defaults, false) do ast else rewrite(ast, cases) @@ -99,6 +103,7 @@ defmodule Postgrex.TypeModule do Macro.prewalk(ast, fn {kind, meta, [{fun, _, args}, block]} when kind in [:def, :defp] and is_list(args) -> {kind, meta, [{fun, clause_meta, args}, block]} + other -> other end) @@ -106,7 +111,7 @@ defmodule Postgrex.TypeModule do defp encode(config, define_opts) do encodes = - for {extension, {opts, [_|_], format}} <- config do + for {extension, {opts, [_ | _], format}} <- config do encode = extension.encode(opts) clauses = @@ -135,6 +140,7 @@ defmodule Postgrex.TypeModule do defp encode_params([param | params], [type | types], encoded) do encode_params(params, types, [encode_value(param, type) | encoded]) end + defp encode_params([], [], encoded), do: Enum.reverse(encoded) defp encode_params(params, _, _) when is_list(params), do: :error @@ -146,20 +152,24 @@ defmodule Postgrex.TypeModule do use Postgrex.BinaryExtension, type: "typeinthedb" """ end + def encode_tuple(tuple, oids, types) do encode_tuple(tuple, 1, oids, types, []) end + defp encode_tuple(tuple, n, [oid | oids], [type | types], acc) do param = :erlang.element(n, tuple) acc = [acc, <> | encode_value(param, type)] encode_tuple(tuple, n + 1, oids, types, acc) end + defp encode_tuple(tuple, n, [], [], acc) when tuple_size(tuple) < n do acc end + defp encode_tuple(tuple, n, [], [], _) when is_tuple(tuple) do raise DBConnection.EncodeError, - "expected a tuple of size #{n - 1}, got: #{inspect tuple}" + "expected a tuple of size #{n - 1}, got: #{inspect(tuple)}" end def encode_list(list, type) do @@ -169,6 +179,7 @@ defmodule Postgrex.TypeModule do defp encode_list([value | rest], type, acc) do encode_list(rest, type, [acc | encode_value(value, type)]) end + defp encode_list([], _, acc) do acc end @@ -178,6 +189,7 @@ defmodule Postgrex.TypeModule do defp encode_type(extension, :super_binary, clause) do encode_super(extension, clause) end + defp encode_type(extension, _, clause) do encode_extension(extension, clause) end @@ -186,6 +198,7 @@ defmodule Postgrex.TypeModule do case split_extension(clause) do {pattern, guard, body} -> encode_extension(extension, pattern, guard, body) + {pattern, body} -> encode_extension(extension, pattern, body) end @@ -211,6 +224,7 @@ defmodule Postgrex.TypeModule do case split_super(clause) do {pattern, sub_oids, sub_types, guard, body} -> encode_super(extension, pattern, sub_oids, sub_types, guard, body) + {pattern, sub_oids, sub_types, body} -> encode_super(extension, pattern, sub_oids, sub_types, body) end @@ -218,8 +232,8 @@ defmodule Postgrex.TypeModule do defp encode_super(extension, pattern, sub_oids, sub_types, guard, body) do quote do - defp unquote(extension)(unquote(pattern), unquote(sub_oids), - unquote(sub_types)) when unquote(guard) do + defp unquote(extension)(unquote(pattern), unquote(sub_oids), unquote(sub_types)) + when unquote(guard) do unquote(body) end end @@ -227,8 +241,7 @@ defmodule Postgrex.TypeModule do defp encode_super(extension, pattern, sub_oids, sub_types, body) do quote do - defp unquote(extension)(unquote(pattern), - unquote(sub_oids), unquote(sub_types)) do + defp unquote(extension)(unquote(pattern), unquote(sub_oids), unquote(sub_types)) do unquote(body) end end @@ -239,6 +252,7 @@ defmodule Postgrex.TypeModule do @compile {:inline, [{unquote(extension), 3}]} end end + defp encode_inline(extension, _) do quote do @compile {:inline, [{unquote(extension), 1}]} @@ -250,6 +264,7 @@ defmodule Postgrex.TypeModule do defp unquote(extension)(@null, _sub_oids, _sub_types), do: <<-1::int32>> end end + defp encode_null(extension, _) do quote do defp unquote(extension)(@null), do: <<-1::int32>> @@ -263,6 +278,7 @@ defmodule Postgrex.TypeModule do end end end + defp encode_value(extension, _) do quote do def encode_value(value, unquote(extension)) do @@ -272,32 +288,31 @@ defmodule Postgrex.TypeModule do end defp decode(config, define_opts) do - rest = quote do: rest - acc = quote do: acc - rem = quote do: rem - full = quote do: full - rows = quote do: rows + rest = quote do: rest + acc = quote do: acc + rem = quote do: rem + full = quote do: full + rows = quote do: rows row_dispatch = - for {extension, {_, [_|_], format}} <- config do + for {extension, {_, [_ | _], format}} <- config do decode_row_dispatch(extension, format, rest, acc, rem, full, rows) end next_dispatch = decode_rows_dispatch(rest, acc, rem, full, rows) - row_dispatch = row_dispatch ++ next_dispatch + row_dispatch = row_dispatch ++ next_dispatch decodes = - for {extension, {opts, [_|_], format}} <- config do + for {extension, {opts, [_ | _], format}} <- config do decode = extension.decode(opts) clauses = for clause <- decode do - decode_type(extension, format, clause, - row_dispatch, rest, acc, rem, full, rows) + decode_type(extension, format, clause, row_dispatch, rest, acc, rem, full, rows) end - null_clauses = decode_null(extension, format, - row_dispatch, rest, acc, rem, full, rows) + null_clauses = decode_null(extension, format, row_dispatch, rest, acc, rem, full, rows) + quote location: :keep do unquote(clauses |> maybe_rewrite(extension, decode, define_opts)) @@ -322,48 +337,73 @@ defmodule Postgrex.TypeModule do decode_rows(binary, byte_size(binary), types, rows) end - defp decode_rows(<>, - rem, unquote(full), unquote(rows)) when rem > size do + defp decode_rows( + <>, + rem, + unquote(full), + unquote(rows) + ) + when rem > size do unquote(rem) = rem - (1 + size) unquote(acc) = [] + case unquote(full) do unquote(dispatch) end end + defp decode_rows(<>, rem, _, rows) do - more = (size + 1) - rem + more = size + 1 - rem {:more, [?D, <> | rest], rows, more} end + defp decode_rows(<>, _, _, rows) do {:more, [?D | rest], rows, 0} end + defp decode_rows(<>, _, _, rows) do {:more, [], rows, 0} end + defp decode_rows(<>, _, _, rows) do {:ok, rows, rest} end end end - defp decode_row_dispatch(extension, :super_binary, rest, acc, - rem, full, rows) do + defp decode_row_dispatch(extension, :super_binary, rest, acc, rem, full, rows) do [clause] = quote do [{unquote(extension), sub_oids, sub_types} | types] -> - unquote(extension)(unquote(rest), sub_oids, sub_types, - types, unquote(acc), - unquote(rem), unquote(full), unquote(rows)) + unquote(extension)( + unquote(rest), + sub_oids, + sub_types, + types, + unquote(acc), + unquote(rem), + unquote(full), + unquote(rows) + ) end + clause end + defp decode_row_dispatch(extension, _, rest, acc, rem, full, rows) do [clause] = quote do [unquote(extension) | types2] -> - unquote(extension)(unquote(rest), types2, unquote(acc), - unquote(rem), unquote(full), unquote(rows)) + unquote(extension)( + unquote(rest), + types2, + unquote(acc), + unquote(rem), + unquote(full), + unquote(rows) + ) end + clause end @@ -379,9 +419,9 @@ defmodule Postgrex.TypeModule do rest = quote do: rest dispatch = - for {extension, {_, [_|_], format}} <- config do - decode_list_dispatch(extension, format, rest) - end + for {extension, {_, [_ | _], format}} <- config do + decode_list_dispatch(extension, format, rest) + end quote do def decode_list(<>, type) do @@ -398,67 +438,89 @@ defmodule Postgrex.TypeModule do {unquote(extension), sub_oids, sub_types} -> unquote(extension)(unquote(rest), sub_oids, sub_types, []) end + clause end + defp decode_list_dispatch(extension, _, rest) do [clause] = quote do unquote(extension) -> unquote(extension)(unquote(rest), []) end + clause end defp decode_tuple(config) do rest = quote do: rest oids = quote do: oids - n = quote do: n - acc = quote do: acc + n = quote do: n + acc = quote do: acc dispatch = - for {extension, {_, [_|_], format}} <- config do - decode_tuple_dispatch(extension, format, rest, oids, n, acc) - end + for {extension, {_, [_ | _], format}} <- config do + decode_tuple_dispatch(extension, format, rest, oids, n, acc) + end quote do def decode_tuple(<>, count, types) when is_integer(count) do decode_tuple(rest, count, types, 0, []) end + def decode_tuple(<>, oids, types) do decode_tuple(rest, oids, types, 0, []) end - defp decode_tuple(<>, - [oid | unquote(oids)], types, - unquote(n), unquote(acc)) do + defp decode_tuple( + <>, + [oid | unquote(oids)], + types, + unquote(n), + unquote(acc) + ) do case types do unquote(dispatch) end end + defp decode_tuple(<<>>, [], [], n, acc) do :erlang.make_tuple(n, @null, acc) end - defp decode_tuple(<>, - rem, types, - unquote(n), unquote(acc)) when rem > 0 do + + defp decode_tuple( + <>, + rem, + types, + unquote(n), + unquote(acc) + ) + when rem > 0 do case Postgrex.Types.fetch(oid, types) do {:ok, {:binary, type}} -> unquote(oids) = rem - 1 + case [type | types] do unquote(dispatch) end + {:ok, {:text, _}} -> - msg = "oid `#{oid}` was bootstrapped in text format and can not " <> - "be decoded inside an anonymous record" + msg = + "oid `#{oid}` was bootstrapped in text format and can not " <> + "be decoded inside an anonymous record" + raise RuntimeError, msg + {:error, %TypeInfo{type: pg_type}, _mod} -> msg = "type `#{pg_type}` can not be handled by the configured extensions" raise RuntimeError, msg + {:error, nil, _mod} -> msg = "oid `#{oid}` was not bootstrapped and lacks type information" raise RuntimeError, msg end end + defp decode_tuple(<<>>, 0, _types, n, acc) do :erlang.make_tuple(n, @null, acc) end @@ -469,59 +531,69 @@ defmodule Postgrex.TypeModule do [clause] = quote do [{unquote(extension), sub_oids, sub_types} | types] -> - unquote(extension)(unquote(rest), sub_oids, sub_types, - unquote(oids), types, unquote(n) + 1, unquote(acc)) + unquote(extension)( + unquote(rest), + sub_oids, + sub_types, + unquote(oids), + types, + unquote(n) + 1, + unquote(acc) + ) end + clause end + defp decode_tuple_dispatch(extension, _, rest, oids, n, acc) do [clause] = quote do [unquote(extension) | types] -> - unquote(extension)(unquote(rest), unquote(oids), types, - unquote(n) + 1, unquote(acc)) + unquote(extension)(unquote(rest), unquote(oids), types, unquote(n) + 1, unquote(acc)) end + clause end - defp decode_type(extension, :super_binary, clause, - dispatch, rest, acc, rem, full, rows) do + defp decode_type(extension, :super_binary, clause, dispatch, rest, acc, rem, full, rows) do decode_super(extension, clause, dispatch, rest, acc, rem, full, rows) end - defp decode_type(extension, _, clause, - dispatch, rest, acc, rem, full, rows) do + + defp decode_type(extension, _, clause, dispatch, rest, acc, rem, full, rows) do decode_extension(extension, clause, dispatch, rest, acc, rem, full, rows) end - defp decode_null(extension, :super_binary, - dispatch, rest, acc, rem, full, rows) do + defp decode_null(extension, :super_binary, dispatch, rest, acc, rem, full, rows) do decode_super_null(extension, dispatch, rest, acc, rem, full, rows) end - defp decode_null(extension, _, - dispatch, rest, acc, rem, full, rows) do + + defp decode_null(extension, _, dispatch, rest, acc, rem, full, rows) do decode_extension_null(extension, dispatch, rest, acc, rem, full, rows) end - defp decode_extension(extension, clause, - dispatch, rest, acc, rem, full, rows) do + defp decode_extension(extension, clause, dispatch, rest, acc, rem, full, rows) do case split_extension(clause) do {pattern, guard, body} -> - decode_extension(extension, pattern, guard, body, - dispatch, rest, acc, rem, full, rows) + decode_extension(extension, pattern, guard, body, dispatch, rest, acc, rem, full, rows) + {pattern, body} -> - decode_extension(extension, pattern, body, - dispatch, rest, acc, rem, full, rows) + decode_extension(extension, pattern, body, dispatch, rest, acc, rem, full, rows) end end - defp decode_extension(extension, pattern, guard, body, - dispatch, rest, acc, rem, full, rows) do + defp decode_extension(extension, pattern, guard, body, dispatch, rest, acc, rem, full, rows) do quote do - defp unquote(extension)(<>, - types, acc, - unquote(rem), unquote(full), unquote(rows)) + defp unquote(extension)( + <>, + types, + acc, + unquote(rem), + unquote(full), + unquote(rows) + ) when unquote(guard) do unquote(acc) = [unquote(body) | acc] + case types do unquote(dispatch) end @@ -532,20 +604,25 @@ defmodule Postgrex.TypeModule do unquote(extension)(rest, [unquote(body) | acc]) end - defp unquote(extension)(<>, - oids, types, n, acc) when unquote(guard) do + defp unquote(extension)(<>, oids, types, n, acc) + when unquote(guard) do decode_tuple(rest, oids, types, n, [{n, unquote(body)} | acc]) end end end - defp decode_extension(extension, pattern, body, - dispatch, rest, acc, rem, full, rows) do + defp decode_extension(extension, pattern, body, dispatch, rest, acc, rem, full, rows) do quote do - defp unquote(extension)(<>, - types, acc, - unquote(rem), unquote(full), unquote(rows)) do + defp unquote(extension)( + <>, + types, + acc, + unquote(rem), + unquote(full), + unquote(rows) + ) do unquote(acc) = [unquote(body) | acc] + case types do unquote(dispatch) end @@ -556,8 +633,7 @@ defmodule Postgrex.TypeModule do unquote(extension)(rest, [decoded | acc]) end - defp unquote(extension)(<>, - oids, types, n, acc) do + defp unquote(extension)(<>, oids, types, n, acc) do decode_tuple(rest, oids, types, n, [{n, unquote(body)} | acc]) end end @@ -565,10 +641,16 @@ defmodule Postgrex.TypeModule do defp decode_extension_null(extension, dispatch, rest, acc, rem, full, rows) do quote do - defp unquote(extension)(<<-1::int32, unquote(rest)::binary>>, - types, acc, - unquote(rem), unquote(full), unquote(rows)) do + defp unquote(extension)( + <<-1::int32, unquote(rest)::binary>>, + types, + acc, + unquote(rem), + unquote(full), + unquote(rows) + ) do unquote(acc) = [@null | acc] + case types do unquote(dispatch) end @@ -582,8 +664,7 @@ defmodule Postgrex.TypeModule do acc end - defp unquote(extension)(<<-1::int32, rest::binary>>, - oids, types, n, acc) do + defp unquote(extension)(<<-1::int32, rest::binary>>, oids, types, n, acc) do decode_tuple(rest, oids, types, n, acc) end end @@ -593,6 +674,7 @@ defmodule Postgrex.TypeModule do case head do [{:when, _, [pattern, guard]}] -> {pattern, guard, body} + [pattern] -> {pattern, body} end @@ -601,65 +683,135 @@ defmodule Postgrex.TypeModule do defp decode_super(extension, clause, dispatch, rest, acc, rem, full, rows) do case split_super(clause) do {pattern, oids, types, guard, body} -> - decode_super(extension, pattern, oids, types, guard, body, - dispatch, rest, acc, rem, full, rows) - {pattern, oids, types, body} -> - decode_super(extension, pattern, oids, types, body, - dispatch, rest, acc, rem, full, rows) - end - end + decode_super( + extension, + pattern, + oids, + types, + guard, + body, + dispatch, + rest, + acc, + rem, + full, + rows + ) - defp decode_super(extension, pattern, sub_oids, sub_types, guard, body, - dispatch, rest, acc, rem, full, rows) do + {pattern, oids, types, body} -> + decode_super(extension, pattern, oids, types, body, dispatch, rest, acc, rem, full, rows) + end + end + + defp decode_super( + extension, + pattern, + sub_oids, + sub_types, + guard, + body, + dispatch, + rest, + acc, + rem, + full, + rows + ) do quote do - defp unquote(extension)(<>, - unquote(sub_oids), unquote(sub_types), - types, acc, - unquote(rem), unquote(full), unquote(rows)) + defp unquote(extension)( + <>, + unquote(sub_oids), + unquote(sub_types), + types, + acc, + unquote(rem), + unquote(full), + unquote(rows) + ) when unquote(guard) do unquote(acc) = [unquote(body) | acc] + case types do unquote(dispatch) end end - defp unquote(extension)(<>, - unquote(sub_oids), unquote(sub_types), acc) + defp unquote(extension)( + <>, + unquote(sub_oids), + unquote(sub_types), + acc + ) when unquote(guard) do acc = [unquote(body) | acc] unquote(extension)(rest, unquote(sub_oids), unquote(sub_types), acc) end - defp unquote(extension)(<>, - unquote(sub_oids), unquote(sub_types), - oids, types, n, acc) when unquote(guard) do + defp unquote(extension)( + <>, + unquote(sub_oids), + unquote(sub_types), + oids, + types, + n, + acc + ) + when unquote(guard) do decode_tuple(rest, oids, types, n, [{n, unquote(body)} | acc]) end end end - defp decode_super(extension, pattern, sub_oids, sub_types, body, - dispatch, rest, acc, rem, full, rows) do + defp decode_super( + extension, + pattern, + sub_oids, + sub_types, + body, + dispatch, + rest, + acc, + rem, + full, + rows + ) do quote do - defp unquote(extension)(<>, - unquote(sub_oids), unquote(sub_types), - types, acc, - unquote(rem), unquote(full), unquote(rows)) do + defp unquote(extension)( + <>, + unquote(sub_oids), + unquote(sub_types), + types, + acc, + unquote(rem), + unquote(full), + unquote(rows) + ) do unquote(acc) = [unquote(body) | acc] + case types do unquote(dispatch) end end - defp unquote(extension)(<>, - unquote(sub_oids), unquote(sub_types), acc) do + defp unquote(extension)( + <>, + unquote(sub_oids), + unquote(sub_types), + acc + ) do acc = [unquote(body) | acc] unquote(extension)(rest, unquote(sub_oids), unquote(sub_types), acc) end - defp unquote(extension)(<>, - unquote(sub_oids), unquote(sub_types), - oids, types, n, acc) do + defp unquote(extension)( + <>, + unquote(sub_oids), + unquote(sub_types), + oids, + types, + n, + acc + ) do acc = [{n, unquote(body)} | acc] decode_tuple(rest, oids, types, n, acc) end @@ -668,17 +820,24 @@ defmodule Postgrex.TypeModule do defp decode_super_null(extension, dispatch, rest, acc, rem, full, rows) do quote do - defp unquote(extension)(<<-1::int32, unquote(rest)::binary>>, - _sub_oids, _sub_types, types, acc, - unquote(rem), unquote(full), unquote(rows)) do + defp unquote(extension)( + <<-1::int32, unquote(rest)::binary>>, + _sub_oids, + _sub_types, + types, + acc, + unquote(rem), + unquote(full), + unquote(rows) + ) do unquote(acc) = [@null | acc] + case types do unquote(dispatch) end end - defp unquote(extension)(<<-1::int32, rest::binary>>, - sub_oids, sub_types, acc) do + defp unquote(extension)(<<-1::int32, rest::binary>>, sub_oids, sub_types, acc) do unquote(extension)(rest, sub_oids, sub_types, [@null | acc]) end @@ -686,9 +845,15 @@ defmodule Postgrex.TypeModule do acc end - defp unquote(extension)(<<-1::int32, rest::binary>>, - _sub_oids, _sub_types, - oids, types, n, acc) do + defp unquote(extension)( + <<-1::int32, rest::binary>>, + _sub_oids, + _sub_types, + oids, + types, + n, + acc + ) do decode_tuple(rest, oids, types, n, acc) end end @@ -698,6 +863,7 @@ defmodule Postgrex.TypeModule do case head do [{:when, _, [pattern, sub_oids, sub_types, guard]}] -> {pattern, sub_oids, sub_types, guard, body} + [pattern, sub_oids, sub_types] -> {pattern, sub_oids, sub_types, body} end @@ -709,18 +875,25 @@ defmodule Postgrex.TypeModule do end defp configure({extension, opts}) do - state = extension.init(opts) + state = extension.init(opts) matching = extension.matching(state) - format = extension.format(state) + format = extension.format(state) {extension, {state, matching, format}} end + defp configure(extension) do configure({extension, []}) end defp define_inline(module, config, opts) do - quoted = [directives(config), attributes(opts), find(config), - encode(config, opts), decode(config, opts)] + quoted = [ + directives(config), + attributes(opts), + find(config), + encode(config, opts), + decode(config, opts) + ] + Module.create(module, quoted, Macro.Env.location(__ENV__)) end end diff --git a/lib/postgrex/type_server.ex b/lib/postgrex/type_server.ex index 6c3ffb404..ac4b7d081 100644 --- a/lib/postgrex/type_server.ex +++ b/lib/postgrex/type_server.ex @@ -10,7 +10,7 @@ defmodule Postgrex.TypeServer do @doc """ Starts a type server. """ - @spec start_link({module, pid, keyword}) :: GenServer.on_start + @spec start_link({module, pid, keyword}) :: GenServer.on_start() def start_link({module, starter, opts}) do GenServer.start_link(__MODULE__, {module, starter}, opts) end @@ -22,7 +22,7 @@ defmodule Postgrex.TypeServer do If another process got the lock we wait for it to finish. """ @spec fetch(pid) :: - {:lock, reference, Postgrex.Types.state} | :noproc | :error + {:lock, reference, Postgrex.Types.state()} | :noproc | :error def fetch(server) do try do GenServer.call(server, :fetch, @timeout) @@ -36,10 +36,11 @@ defmodule Postgrex.TypeServer do @doc """ Update the type server using the given reference and configuration. """ - @spec update(pid, reference, [Postgrex.TypeInfo.t]) :: :ok - def update(server, ref, [_|_] = type_infos) do + @spec update(pid, reference, [Postgrex.TypeInfo.t()]) :: :ok + def update(server, ref, [_ | _] = type_infos) do GenServer.call(server, {:update, ref, type_infos}, @timeout) end + def update(server, ref, []) do done(server, ref) end @@ -57,15 +58,20 @@ defmodule Postgrex.TypeServer do def init({module, starter}) do _ = Process.flag(:trap_exit, true) Process.link(starter) - state = %__MODULE__{types: Postgrex.Types.new(module), - connections: MapSet.new([starter]), - waiting: :queue.new()} + + state = %__MODULE__{ + types: Postgrex.Types.new(module), + connections: MapSet.new([starter]), + waiting: :queue.new() + } + {:ok, state} end def handle_call(:fetch, from, %{lock: nil} = state) do lock(state, from) end + def handle_call(:fetch, from, %{lock: ref} = state) when is_reference(ref) do wait(state, from) end @@ -84,6 +90,7 @@ defmodule Postgrex.TypeServer do when is_reference(ref) do next(state) end + def handle_info({:DOWN, ref, _, _, _}, state) do down(state, ref) end @@ -109,8 +116,13 @@ defmodule Postgrex.TypeServer do %{connections: connections, waiting: waiting} = state Process.link(pid) mref = Process.monitor(pid) - state = %{state | connections: MapSet.put(connections, pid), - waiting: :queue.in({mref, from}, waiting)} + + state = %{ + state + | connections: MapSet.put(connections, pid), + waiting: :queue.in({mref, from}, waiting) + } + {:noreply, state} end @@ -126,6 +138,7 @@ defmodule Postgrex.TypeServer do {{:value, {mref, from}}, waiting} -> GenServer.reply(from, {:lock, mref, types}) {:noreply, %{state | lock: mref, waiting: waiting}} + {:empty, waiting} -> check_processes(%{state | lock: nil, waiting: waiting}) end @@ -142,11 +155,13 @@ defmodule Postgrex.TypeServer do defp check_processes(%{lock: ref} = state) when is_reference(ref) do {:noreply, state} end + defp check_processes(%{connections: connections} = state) do case MapSet.size(connections) do 0 -> timeout = Application.fetch_env!(:postgrex, :type_server_reap_after) {:noreply, state, timeout} + _ -> {:noreply, state} end diff --git a/lib/postgrex/types.ex b/lib/postgrex/types.ex index bdef41425..8126aa472 100644 --- a/lib/postgrex/types.ex +++ b/lib/postgrex/types.ex @@ -15,7 +15,7 @@ defmodule Postgrex.Types do @typedoc """ State used by the encoder/decoder functions """ - @opaque state :: {module, :ets.tid} + @opaque state :: {module, :ets.tid()} @typedoc """ Term used to describe type information @@ -36,6 +36,7 @@ defmodule Postgrex.Types do case :ets.info(table, :owner) do owner when is_pid(owner) -> {:ok, owner} + :undefined -> :error end @@ -50,11 +51,11 @@ defmodule Postgrex.Types do # since there might be a lot them and most likely # they won't be used; subsequent bootstrap will # fetch them along with any other "new" types - filter_oids = - """ - WHERE (t.typrelid = 0) - AND (t.typelem = 0 OR t.typelem NOT IN (SELECT oid FROM pg_catalog.pg_type WHERE typrelid!=0)) - """ + filter_oids = """ + WHERE (t.typrelid = 0) + AND (t.typelem = 0 OR t.typelem NOT IN (SELECT oid FROM pg_catalog.pg_type WHERE typrelid!=0)) + """ + build_bootstrap_query(version, filter_oids) _ -> @@ -65,8 +66,7 @@ defmodule Postgrex.Types do defp build_bootstrap_query(version, filter_oids) do {typelem, join_domain} = if version >= {9, 0, 0} do - {"coalesce(d.typelem, t.typelem)", - "LEFT JOIN pg_type AS d ON t.typbasetype = d.oid"} + {"coalesce(d.typelem, t.typelem)", "LEFT JOIN pg_type AS d ON t.typbasetype = d.oid"} else {"t.typelem", ""} end @@ -95,28 +95,22 @@ defmodule Postgrex.Types do end @doc false - @spec reload_query({pos_integer, non_neg_integer, non_neg_integer}, [oid, ...], state) :: binary | nil + @spec reload_query({pos_integer, non_neg_integer, non_neg_integer}, [oid, ...], state) :: + binary | nil def reload_query(version, oids, {_, table}) do case Enum.reject(oids, &:ets.member(table, &1)) do [] -> nil + oids -> build_bootstrap_query(version, "WHERE t.oid IN (#{Enum.join(oids, ", ")})") end end @doc false - @spec build_type_info(binary) :: TypeInfo.t + @spec build_type_info(binary) :: TypeInfo.t() def build_type_info(row) do - [oid, - type, - send, - receive, - output, - input, - array_oid, - base_oid, - comp_oids] = row_decode(row) + [oid, type, send, receive, output, input, array_oid, base_oid, comp_oids] = row_decode(row) oid = String.to_integer(oid) array_oid = String.to_integer(array_oid) base_oid = String.to_integer(base_oid) @@ -131,19 +125,24 @@ defmodule Postgrex.Types do input: :binary.copy(input), array_elem: array_oid, base_type: base_oid, - comp_elems: comp_oids} + comp_elems: comp_oids + } end @doc false - @spec associate_type_infos([TypeInfo.t], state) :: :ok + @spec associate_type_infos([TypeInfo.t()], state) :: :ok def associate_type_infos(type_infos, {module, table}) do - _ = for %TypeInfo{oid: oid} = type_info <- type_infos do - true = :ets.insert_new(table, {oid, type_info, nil}) - end - _ = for %TypeInfo{oid: oid} = type_info <- type_infos do - info = find(type_info, :any, module, table) - true = :ets.update_element(table, oid, {3, info}) - end + _ = + for %TypeInfo{oid: oid} = type_info <- type_infos do + true = :ets.insert_new(table, {oid, type_info, nil}) + end + + _ = + for %TypeInfo{oid: oid} = type_info <- type_infos do + info = find(type_info, :any, module, table) + true = :ets.update_element(table, oid, {3, info}) + end + :ok end @@ -151,13 +150,17 @@ defmodule Postgrex.Types do case apply(module, :find, [type_info, formats]) do {:super_binary, extension, nil} -> {:binary, {extension, nil, {module, table}}} + {:super_binary, extension, sub_oids} when formats == :any -> super_find(sub_oids, extension, module, table) || find(type_info, :text, module, table) + {:super_binary, extension, sub_oids} -> super_find(sub_oids, extension, module, table) + nil -> nil + info -> info end @@ -167,6 +170,7 @@ defmodule Postgrex.Types do case sub_find(sub_oids, module, table, []) do {:ok, sub_types} -> {:binary, {extension, sub_oids, sub_types}} + :error -> nil end @@ -176,25 +180,31 @@ defmodule Postgrex.Types do case :ets.lookup(table, oid) do [{_, _, {:binary, types}}] -> sub_find(oids, module, table, [types | acc]) + [{_, type_info, _}] -> case find(type_info, :binary, module, table) do {:binary, types} -> sub_find(oids, module, table, [types | acc]) + nil -> :error end + [] -> :error end end + defp sub_find([], _, _, acc) do {:ok, Enum.reverse(acc)} end defp row_decode(<<>>), do: [] + defp row_decode(<<-1::int32, rest::binary>>) do [nil | row_decode(rest)] end + defp row_decode(<>) do [value | row_decode(rest)] end @@ -213,8 +223,8 @@ defmodule Postgrex.Types do defp parse_oids(bin, acc) do case Integer.parse(bin) do - {int, "," <> rest} -> parse_oids(rest, [int|acc]) - {int, "}"} -> Enum.reverse([int|acc]) + {int, "," <> rest} -> parse_oids(rest, [int | acc]) + {int, "}"} -> Enum.reverse([int | acc]) end end @@ -300,14 +310,15 @@ defmodule Postgrex.Types do @doc false @spec decode_rows(binary, [type], [row], state) :: - {:more, iodata, [row], non_neg_integer} | {:ok, [row], binary} when row: var + {:more, iodata, [row], non_neg_integer} | {:ok, [row], binary} + when row: var def decode_rows(binary, types, rows, {mod, _}) do apply(mod, :decode_rows, [binary, types, rows]) end @doc false @spec fetch(oid, state) :: - {:ok, {:binary | :text, type}} | {:error, TypeInfo.t | nil, module} + {:ok, {:binary | :text, type}} | {:error, TypeInfo.t() | nil, module} def fetch(oid, {mod, table}) do try do :ets.lookup_element(table, oid, 3) @@ -317,6 +328,7 @@ defmodule Postgrex.Types do else {_, _} = info -> {:ok, info} + nil -> fetch_type_info(oid, mod, table) end diff --git a/lib/postgrex/utils.ex b/lib/postgrex/utils.ex index 021953aaa..75cba8d7e 100644 --- a/lib/postgrex/utils.ex +++ b/lib/postgrex/utils.ex @@ -38,7 +38,8 @@ defmodule Postgrex.Utils do Postgrex.Extensions.TSVector, Postgrex.Extensions.UUID, Postgrex.Extensions.VoidBinary, - Postgrex.Extensions.VoidText] + Postgrex.Extensions.VoidText + ] @doc """ Checks if a given extension is a default extension. @@ -46,12 +47,13 @@ defmodule Postgrex.Utils do for ext <- @extensions do def default_extension?(unquote(ext)), do: true end + def default_extension?(_), do: false @doc """ List all default extensions. """ - @spec default_extensions(Keyword.t) :: [{module(), Keyword.t}] + @spec default_extensions(Keyword.t()) :: [{module(), Keyword.t()}] def default_extensions(opts \\ []) do Enum.map(@extensions, &{&1, opts}) end @@ -78,7 +80,7 @@ defmodule Postgrex.Utils do @doc """ Fills in the given `opts` with default options. """ - @spec default_opts(Keyword.t) :: Keyword.t + @spec default_opts(Keyword.t()) :: Keyword.t() def default_opts(opts) do opts |> Keyword.put_new(:username, System.get_env("PGUSER") || System.get_env("USER")) @@ -98,38 +100,40 @@ defmodule Postgrex.Utils do """ def encode_msg(%Postgrex.TypeInfo{type: type}, observed, expected) do "Postgrex expected #{to_desc(expected)} that can be encoded/cast to " <> - "type #{inspect type}, got #{inspect observed}. Please make sure the " <> - "value you are passing matches the definition in your table or in your " <> - "query or convert the value accordingly." + "type #{inspect(type)}, got #{inspect(observed)}. Please make sure the " <> + "value you are passing matches the definition in your table or in your " <> + "query or convert the value accordingly." end @doc """ Return encode error message. """ def encode_msg(%Date{calendar: calendar} = observed, _expected) when calendar != Calendar.ISO do - "Postgrex expected a %Date{} in the `Calendar.ISO` calendar, got #{inspect observed}. " <> - "Postgrex (and Postgres) support dates in the `Calendar.ISO` calendar only." + "Postgrex expected a %Date{} in the `Calendar.ISO` calendar, got #{inspect(observed)}. " <> + "Postgrex (and Postgres) support dates in the `Calendar.ISO` calendar only." end - def encode_msg(%NaiveDateTime{calendar: calendar} = observed, _expected) when calendar != Calendar.ISO do - "Postgrex expected a %NaiveDateTime{} in the `Calendar.ISO` calendar, got #{inspect observed}. " <> - "Postgrex (and Postgres) support naive datetimes in the `Calendar.ISO` calendar only." + def encode_msg(%NaiveDateTime{calendar: calendar} = observed, _expected) + when calendar != Calendar.ISO do + "Postgrex expected a %NaiveDateTime{} in the `Calendar.ISO` calendar, got #{inspect(observed)}. " <> + "Postgrex (and Postgres) support naive datetimes in the `Calendar.ISO` calendar only." end - def encode_msg(%DateTime{calendar: calendar} = observed, _expected) when calendar != Calendar.ISO do - "Postgrex expected a %DateTime{} in the `Calendar.ISO` calendar, got #{inspect observed}. " <> - "Postgrex (and Postgres) support datetimes in the `Calendar.ISO` calendar only." + def encode_msg(%DateTime{calendar: calendar} = observed, _expected) + when calendar != Calendar.ISO do + "Postgrex expected a %DateTime{} in the `Calendar.ISO` calendar, got #{inspect(observed)}. " <> + "Postgrex (and Postgres) support datetimes in the `Calendar.ISO` calendar only." end def encode_msg(%Time{calendar: calendar} = observed, _expected) when calendar != Calendar.ISO do - "Postgrex expected a %Time{} in the `Calendar.ISO` calendar, got #{inspect observed}. " <> - "Postgrex (and Postgres) support times in the `Calendar.ISO` calendar only." + "Postgrex expected a %Time{} in the `Calendar.ISO` calendar, got #{inspect(observed)}. " <> + "Postgrex (and Postgres) support times in the `Calendar.ISO` calendar only." end def encode_msg(observed, expected) do - "Postgrex expected #{to_desc(expected)}, got #{inspect observed}. " <> - "Please make sure the value you are passing matches the definition in " <> - "your table or in your query or convert the value accordingly." + "Postgrex expected #{to_desc(expected)}, got #{inspect(observed)}. " <> + "Please make sure the value you are passing matches the definition in " <> + "your table or in your query or convert the value accordingly." end @doc """ @@ -137,11 +141,12 @@ defmodule Postgrex.Utils do """ def type_msg(%Postgrex.TypeInfo{type: json}, module) when json in ["json", "jsonb"] do - "type `#{json}` can not be handled by the types module #{inspect module}, " <> - "it must define a `:json` library in its options to support JSON types" + "type `#{json}` can not be handled by the types module #{inspect(module)}, " <> + "it must define a `:json` library in its options to support JSON types" end + def type_msg(%Postgrex.TypeInfo{type: type}, module) do - "type `#{type}` can not be handled by the types module #{inspect module}" + "type `#{type}` can not be handled by the types module #{inspect(module)}" end ## Helpers @@ -151,8 +156,8 @@ defmodule Postgrex.Utils do int end - defp to_desc(struct) when is_atom(struct), do: "%#{inspect struct}{}" - defp to_desc(%Range{} = range), do: "an integer in #{inspect range}" + defp to_desc(struct) when is_atom(struct), do: "%#{inspect(struct)}{}" + defp to_desc(%Range{} = range), do: "an integer in #{inspect(range)}" defp to_desc({a, b}), do: to_desc(a) <> " or " <> to_desc(b) defp to_desc(desc) when is_binary(desc), do: desc end diff --git a/test/alter_test.exs b/test/alter_test.exs index 27c88dd8d..2b3fbdb2f 100644 --- a/test/alter_test.exs +++ b/test/alter_test.exs @@ -5,10 +5,13 @@ defmodule AlterTest do @moduletag :capture_log setup context do - options = [database: "postgrex_test", backoff_type: :stop, - prepare: context[:prepare] || :named] + options = [ + database: "postgrex_test", + backoff_type: :stop, + prepare: context[:prepare] || :named + ] - reset = fn() -> + reset = fn -> {:ok, pid} = Postgrex.start_link(options) Postgrex.query!(pid, "ALTER TABLE altering ALTER a type int2 USING 0", []) Postgrex.query!(pid, "DROP TABLE IF EXISTS missing_enum_table", []) @@ -34,7 +37,11 @@ defmodule AlterTest do test "after alter returns ok for bang queries", context do pid = context[:pid] - query = fn -> Postgrex.query!(pid, "SELECT a FROM altering", [], cache_statement: "select") end + + query = fn -> + Postgrex.query!(pid, "SELECT a FROM altering", [], cache_statement: "select") + end + assert %Postgrex.Result{rows: []} = query.() assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) assert %Postgrex.Result{rows: []} = query.() @@ -42,7 +49,10 @@ defmodule AlterTest do test "after alter returns ok in transaction", context do transaction(fn conn -> - query = fn -> Postgrex.query(conn, "SELECT a FROM altering", [], cache_statement: "select") end + query = fn -> + Postgrex.query(conn, "SELECT a FROM altering", [], cache_statement: "select") + end + assert {:ok, %{rows: []}} = query.() assert {:ok, _} = Postgrex.query(conn, "ALTER TABLE altering ALTER a TYPE int4", []) assert {:error, %Postgrex.Error{postgres: %{code: :feature_not_supported}}} = query.() @@ -112,9 +122,9 @@ defmodule AlterTest do query = prepare("select", "SELECT a FROM altering") assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) - transaction(fn(conn) -> + transaction(fn conn -> assert {:error, %Postgrex.Error{postgres: %{code: :feature_not_supported}}} = - Postgrex.execute(conn, query, []) + Postgrex.execute(conn, query, []) end) end @@ -124,30 +134,31 @@ defmodule AlterTest do assert :ok = query("ALTER TABLE altering ALTER a TYPE timestamp USING CURRENT_TIMESTAMP", []) - transaction(fn(conn) -> + transaction(fn conn -> assert {:error, %Postgrex.Error{postgres: %{code: :undefined_function}}} = - Postgrex.execute(conn, query, [1]) + Postgrex.execute(conn, query, [1]) end) end - test "transaction with prepare query, alter, close and execute with param cast succeeds", context do + test "transaction with prepare query, alter, close and execute with param cast succeeds", + context do query1 = prepare("select", "SELECT a FROM altering WHERE a=$1") query2 = prepare("select", "SELECT a FROM altering") assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) assert :ok = close(query1) assert :ok = close(query2) - assert transaction(fn(conn) -> - %Postgrex.Result{} = Postgrex.execute!(conn, query1, [1]) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + %Postgrex.Result{} = Postgrex.execute!(conn, query1, [1]) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) - assert transaction(fn(conn) -> - %Postgrex.Result{} = Postgrex.execute!(conn, query2, []) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + %Postgrex.Result{} = Postgrex.execute!(conn, query2, []) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) end @@ -156,9 +167,9 @@ defmodule AlterTest do query = prepare("select", "SELECT a FROM altering") assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) - transaction(fn(conn) -> + transaction(fn conn -> assert {:error, %Postgrex.Error{postgres: %{code: :feature_not_supported}}} = - Postgrex.execute(conn, query, [], [mode: :savepoint]) + Postgrex.execute(conn, query, [], mode: :savepoint) assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) end) @@ -172,32 +183,33 @@ defmodule AlterTest do assert :ok = query("ALTER TABLE altering ALTER a TYPE timestamp USING CURRENT_TIMESTAMP", []) - transaction(fn(conn) -> + transaction(fn conn -> assert {:error, %Postgrex.Error{postgres: %{code: :undefined_function}}} = - Postgrex.execute(conn, query, [1], [mode: :savepoint]) + Postgrex.execute(conn, query, [1], mode: :savepoint) assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) end) end - test "transaction with prepare query, close, alter and savepoint execute with param cast succeeds", context do + test "transaction with prepare query, close, alter and savepoint execute with param cast succeeds", + context do query1 = prepare("select", "SELECT a FROM altering WHERE a=$1") query2 = prepare("select", "SELECT a FROM altering") assert :ok = close(query1) assert :ok = close(query2) assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) - assert transaction(fn(conn) -> - %Postgrex.Result{} = Postgrex.execute!(conn, query1, [1], [mode: :savepoint]) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + %Postgrex.Result{} = Postgrex.execute!(conn, query1, [1], mode: :savepoint) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) - assert transaction(fn(conn) -> - %Postgrex.Result{} = Postgrex.execute!(conn, query2, [], [mode: :savepoint]) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + %Postgrex.Result{} = Postgrex.execute!(conn, query2, [], mode: :savepoint) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) end @@ -216,22 +228,23 @@ defmodule AlterTest do end @tag prepare: :unnamed - test "transaction with prepare unnamed query, alter and savepoint execute with param cast succeeds", context do + test "transaction with prepare unnamed query, alter and savepoint execute with param cast succeeds", + context do query1 = prepare("select", "SELECT a FROM altering WHERE a=$1") query2 = prepare("select", "SELECT a FROM altering") assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) - assert transaction(fn(conn) -> - %Postgrex.Result{} = Postgrex.execute!(conn, query1, [1], [mode: :savepoint]) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + %Postgrex.Result{} = Postgrex.execute!(conn, query1, [1], mode: :savepoint) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) - assert transaction(fn(conn) -> - %Postgrex.Result{} = Postgrex.execute!(conn, query2, [], [mode: :savepoint]) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + %Postgrex.Result{} = Postgrex.execute!(conn, query2, [], mode: :savepoint) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) end @@ -240,9 +253,10 @@ defmodule AlterTest do query = prepare("select", "SELECT a FROM altering") assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) - transaction(fn(conn) -> - assert_raise Postgrex.Error, ~r"\(feature_not_supported\)", - fn -> conn |> Postgrex.stream(query, []) |> Enum.to_list() end + transaction(fn conn -> + assert_raise Postgrex.Error, ~r"\(feature_not_supported\)", fn -> + conn |> Postgrex.stream(query, []) |> Enum.to_list() + end end) end @@ -252,32 +266,34 @@ defmodule AlterTest do assert :ok = query("ALTER TABLE altering ALTER a TYPE timestamp USING CURRENT_TIMESTAMP", []) - transaction(fn(conn) -> - assert_raise Postgrex.Error, ~r"\(undefined_function\)", - fn -> conn |> Postgrex.stream(query, [1]) |> Enum.to_list() end + transaction(fn conn -> + assert_raise Postgrex.Error, ~r"\(undefined_function\)", fn -> + conn |> Postgrex.stream(query, [1]) |> Enum.to_list() + end end) end - test "transaction with prepare query, alter, close and stream with param cast succeeds", context do + test "transaction with prepare query, alter, close and stream with param cast succeeds", + context do query1 = prepare("select", "SELECT a FROM altering WHERE a=$1") query2 = prepare("select", "SELECT a FROM altering") assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) assert :ok = close(query1) assert :ok = close(query2) - assert transaction(fn(conn) -> - stream = Postgrex.stream(conn, query1, [1]) - assert [%Postgrex.Result{}] = Enum.to_list(stream) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + stream = Postgrex.stream(conn, query1, [1]) + assert [%Postgrex.Result{}] = Enum.to_list(stream) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) - assert transaction(fn(conn) -> - stream = Postgrex.stream(conn, query2, []) - assert [%Postgrex.Result{}] = Enum.to_list(stream) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + stream = Postgrex.stream(conn, query2, []) + assert [%Postgrex.Result{}] = Enum.to_list(stream) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) end @@ -286,11 +302,12 @@ defmodule AlterTest do query = prepare("select", "SELECT a FROM altering") assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) - transaction(fn(conn) -> - assert_raise Postgrex.Error, ~r"\(feature_not_supported\)", - fn -> conn |> Postgrex.stream(query, [], [mode: :savepoint]) |> Enum.to_list() end + transaction(fn conn -> + assert_raise Postgrex.Error, ~r"\(feature_not_supported\)", fn -> + conn |> Postgrex.stream(query, [], mode: :savepoint) |> Enum.to_list() + end - assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) + assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) end) assert [[42]] = query("SELECT 42", []) @@ -302,34 +319,36 @@ defmodule AlterTest do assert :ok = query("ALTER TABLE altering ALTER a TYPE timestamp USING CURRENT_TIMESTAMP", []) - transaction(fn(conn) -> - assert_raise Postgrex.Error, ~r"\(undefined_function\)", - fn -> conn |> Postgrex.stream(query, [1], [mode: :savepoint]) |> Enum.to_list() end + transaction(fn conn -> + assert_raise Postgrex.Error, ~r"\(undefined_function\)", fn -> + conn |> Postgrex.stream(query, [1], mode: :savepoint) |> Enum.to_list() + end - assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) + assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) end) end - test "transaction with prepare query, close, alter and savepoint stream with param cast succeeds", context do + test "transaction with prepare query, close, alter and savepoint stream with param cast succeeds", + context do query1 = prepare("select", "SELECT a FROM altering WHERE a=$1") query2 = prepare("select", "SELECT a FROM altering") assert :ok = close(query1) assert :ok = close(query2) assert :ok = query("ALTER TABLE altering ALTER a TYPE int4", []) - assert transaction(fn(conn) -> - stream = Postgrex.stream(conn, query1, [1], [mode: :savepoint]) - assert [%Postgrex.Result{}] = Enum.to_list(stream) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + stream = Postgrex.stream(conn, query1, [1], mode: :savepoint) + assert [%Postgrex.Result{}] = Enum.to_list(stream) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) - assert transaction(fn(conn) -> - stream = Postgrex.stream(conn, query2, [], [mode: :savepoint]) - assert [%Postgrex.Result{}] = Enum.to_list(stream) - :done - end) == {:ok, :done} + assert transaction(fn conn -> + stream = Postgrex.stream(conn, query2, [], mode: :savepoint) + assert [%Postgrex.Result{}] = Enum.to_list(stream) + :done + end) == {:ok, :done} assert [[42]] = query("SELECT 42", []) end @@ -345,9 +364,15 @@ defmodule AlterTest do test "new oids in param and result are bootstrapped", context do assert :ok = query("CREATE TYPE missing_enum AS ENUM ('missing')", []) assert :ok = query("CREATE TYPE missing_comp AS (a int, b int)", []) - assert :ok = query("CREATE TABLE missing_comp_table (a missing_comp, b missing_enum DEFAULT 'missing')", []) - assert [["missing"]] = query("INSERT INTO missing_comp_table VALUES ($1, DEFAULT) RETURNING b", [{1, 2}]) + assert :ok = + query( + "CREATE TABLE missing_comp_table (a missing_comp, b missing_enum DEFAULT 'missing')", + [] + ) + + assert [["missing"]] = + query("INSERT INTO missing_comp_table VALUES ($1, DEFAULT) RETURNING b", [{1, 2}]) end test "new oids and their sub-type oids are bootstrapped", context do @@ -389,68 +414,84 @@ defmodule AlterTest do assert :ok = query("CREATE TABLE missing_enum_table (a missing_enum)", []) unnamed = prepare("", "SELECT 42") - assert transaction(fn(conn) -> - named = Postgrex.prepare!(conn, "foo", "INSERT INTO missing_enum_table VALUES ($1)") + assert transaction(fn conn -> + named = Postgrex.prepare!(conn, "foo", "INSERT INTO missing_enum_table VALUES ($1)") - %Postgrex.Result{rows: [[42]]} = Postgrex.execute!(conn, unnamed, []) + %Postgrex.Result{rows: [[42]]} = Postgrex.execute!(conn, unnamed, []) - assert %Postgrex.Result{command: :insert} = Postgrex.execute!(conn, named, ["missing"]) + assert %Postgrex.Result{command: :insert} = + Postgrex.execute!(conn, named, ["missing"]) - Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) - Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) + Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) + Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) - unnamed = Postgrex.prepare!(conn, "", "SELECT 42") - named2 = Postgrex.prepare!(conn, "bar", "INSERT INTO missing_comp_table VALUES ($1)", [mode: :savepoint]) + unnamed = Postgrex.prepare!(conn, "", "SELECT 42") - assert %Postgrex.Result{rows: [[42]]} = Postgrex.execute!(conn, unnamed, [], [mode: :savepoint]) + named2 = + Postgrex.prepare!(conn, "bar", "INSERT INTO missing_comp_table VALUES ($1)", + mode: :savepoint + ) - assert %Postgrex.Result{command: :insert} = Postgrex.execute!(conn, named2, [{1, 2}]) - end) + assert %Postgrex.Result{rows: [[42]]} = + Postgrex.execute!(conn, unnamed, [], mode: :savepoint) + + assert %Postgrex.Result{command: :insert} = Postgrex.execute!(conn, named2, [{1, 2}]) + end) end - @tag prepare: :unnamed - test "new oid is bootstrapped on prepare and prepared executes inside transaction with unnamed", context do + @tag prepare: :unnamed + test "new oid is bootstrapped on prepare and prepared executes inside transaction with unnamed", + context do assert :ok = query("CREATE TYPE missing_enum AS ENUM ('missing')", []) assert :ok = query("CREATE TABLE missing_enum_table (a missing_enum)", []) unnamed = prepare("", "SELECT 42") - assert transaction(fn(conn) -> - named = Postgrex.prepare!(conn, "foo", "INSERT INTO missing_enum_table VALUES ($1)") + assert transaction(fn conn -> + named = Postgrex.prepare!(conn, "foo", "INSERT INTO missing_enum_table VALUES ($1)") - %Postgrex.Result{rows: [[42]]} = Postgrex.execute!(conn, unnamed, []) + %Postgrex.Result{rows: [[42]]} = Postgrex.execute!(conn, unnamed, []) - assert %Postgrex.Result{command: :insert} = Postgrex.execute!(conn, named, ["missing"]) + assert %Postgrex.Result{command: :insert} = + Postgrex.execute!(conn, named, ["missing"]) - Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) - Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) + Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) + Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) - unnamed = Postgrex.prepare!(conn, "", "SELECT 42") - named2 = Postgrex.prepare!(conn, "", "INSERT INTO missing_comp_table VALUES ($1)", [mode: :savepoint]) + unnamed = Postgrex.prepare!(conn, "", "SELECT 42") - assert %Postgrex.Result{rows: [[42]]} = Postgrex.execute!(conn, unnamed, [], [mode: :savepoint]) + named2 = + Postgrex.prepare!(conn, "", "INSERT INTO missing_comp_table VALUES ($1)", + mode: :savepoint + ) - assert %Postgrex.Result{command: :insert} = Postgrex.execute!(conn, named2, [{1, 2}]) - end) + assert %Postgrex.Result{rows: [[42]]} = + Postgrex.execute!(conn, unnamed, [], mode: :savepoint) + + assert %Postgrex.Result{command: :insert} = Postgrex.execute!(conn, named2, [{1, 2}]) + end) end test "new oid is bootstrapped inside transaction", context do assert :ok = query("CREATE TYPE missing_enum AS ENUM ('missing')", []) assert :ok = query("CREATE TABLE missing_enum_table (a missing_enum)", []) - assert transaction(fn(conn) -> - assert {:ok, %Postgrex.Result{num_rows: 1, command: :insert}} = - Postgrex.query(conn, "INSERT INTO missing_enum_table VALUES ($1)", ["missing"]) - assert {:ok, %Postgrex.Result{rows: [["missing"]]}} = - Postgrex.query(conn, "SELECT a FROM missing_enum_table", []) + assert transaction(fn conn -> + assert {:ok, %Postgrex.Result{num_rows: 1, command: :insert}} = + Postgrex.query(conn, "INSERT INTO missing_enum_table VALUES ($1)", [ + "missing" + ]) + + assert {:ok, %Postgrex.Result{rows: [["missing"]]}} = + Postgrex.query(conn, "SELECT a FROM missing_enum_table", []) - Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) - Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) + Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) + Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) - assert {:ok, %Postgrex.Result{num_rows: 1, command: :insert}} = - Postgrex.query(conn, "INSERT INTO missing_comp_table VALUES ($1)", [{1, 2}]) + assert {:ok, %Postgrex.Result{num_rows: 1, command: :insert}} = + Postgrex.query(conn, "INSERT INTO missing_comp_table VALUES ($1)", [{1, 2}]) - :done - end) == {:ok, :done} + :done + end) == {:ok, :done} end @tag prepare: :unnamed @@ -458,59 +499,72 @@ defmodule AlterTest do assert :ok = query("CREATE TYPE missing_enum AS ENUM ('missing')", []) assert :ok = query("CREATE TABLE missing_enum_table (a missing_enum)", []) - assert transaction(fn(conn) -> - assert {:ok, %Postgrex.Result{num_rows: 1, command: :insert}} = - Postgrex.query(conn, "INSERT INTO missing_enum_table VALUES ($1)", ["missing"]) - assert {:ok, %Postgrex.Result{rows: [["missing"]]}} = - Postgrex.query(conn, "SELECT a FROM missing_enum_table", []) + assert transaction(fn conn -> + assert {:ok, %Postgrex.Result{num_rows: 1, command: :insert}} = + Postgrex.query(conn, "INSERT INTO missing_enum_table VALUES ($1)", [ + "missing" + ]) + + assert {:ok, %Postgrex.Result{rows: [["missing"]]}} = + Postgrex.query(conn, "SELECT a FROM missing_enum_table", []) - Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) - Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) + Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) + Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) - assert {:ok, %Postgrex.Result{num_rows: 1, command: :insert}} = - Postgrex.query(conn, "INSERT INTO missing_comp_table VALUES ($1)", [{1, 2}], [mode: :savepoint]) + assert {:ok, %Postgrex.Result{num_rows: 1, command: :insert}} = + Postgrex.query(conn, "INSERT INTO missing_comp_table VALUES ($1)", [{1, 2}], + mode: :savepoint + ) - :done - end) == {:ok, :done} + :done + end) == {:ok, :done} end test "new oid is bootstrapped when preparing enumerable stream", context do assert :ok = query("CREATE TYPE missing_enum AS ENUM ('missing')", []) assert :ok = query("CREATE TABLE missing_enum_table (a missing_enum)", []) - assert transaction(fn(conn) -> - stream = Postgrex.stream(conn, "INSERT INTO missing_enum_table VALUES ($1)", ["missing"]) + assert transaction(fn conn -> + stream = + Postgrex.stream(conn, "INSERT INTO missing_enum_table VALUES ($1)", ["missing"]) - assert [%Postgrex.Result{num_rows: 1, command: :insert}] = Enum.to_list(stream) + assert [%Postgrex.Result{num_rows: 1, command: :insert}] = Enum.to_list(stream) - Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) - Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) + Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) + Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) - stream2 = Postgrex.stream(conn, "INSERT INTO missing_comp_table VALUES ($1)", [{1, 2}], [mode: :savepoint]) + stream2 = + Postgrex.stream(conn, "INSERT INTO missing_comp_table VALUES ($1)", [{1, 2}], + mode: :savepoint + ) - assert [%Postgrex.Result{num_rows: 1, command: :insert}] = Enum.to_list(stream2) + assert [%Postgrex.Result{num_rows: 1, command: :insert}] = Enum.to_list(stream2) - :done - end) == {:ok, :done} + :done + end) == {:ok, :done} end test "new oid is bootstrapped when preparing collectable stream", context do assert :ok = query("CREATE TYPE missing_enum AS ENUM ('missing')", []) assert :ok = query("CREATE TABLE missing_enum_table (a missing_enum)", []) - assert transaction(fn(conn) -> - stream = Postgrex.stream(conn, "INSERT INTO missing_enum_table VALUES ($1)", ["missing"]) + assert transaction(fn conn -> + stream = + Postgrex.stream(conn, "INSERT INTO missing_enum_table VALUES ($1)", ["missing"]) - assert Enum.into(["foo"], stream) == stream + assert Enum.into(["foo"], stream) == stream - Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) - Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) + Postgrex.query!(conn, "CREATE TYPE missing_comp AS (a int, b int)", []) + Postgrex.query!(conn, "CREATE TABLE missing_comp_table (a missing_comp)", []) - stream2 = Postgrex.stream(conn, "INSERT INTO missing_comp_table VALUES ($1)", [{1, 2}], [mode: :savepoint]) + stream2 = + Postgrex.stream(conn, "INSERT INTO missing_comp_table VALUES ($1)", [{1, 2}], + mode: :savepoint + ) - assert Enum.into(["bar"], stream2) == stream2 + assert Enum.into(["bar"], stream2) == stream2 - :done - end) == {:ok, :done} + :done + end) == {:ok, :done} end end diff --git a/test/calendar_test.exs b/test/calendar_test.exs index dd88de39f..bdc1e787d 100644 --- a/test/calendar_test.exs +++ b/test/calendar_test.exs @@ -38,9 +38,11 @@ defmodule CalendarTest do assert [[~T[00:00:00.123456]]] = query("SELECT time with time zone '00:00:00.123456 UTC'", []) assert [[~T[01:02:03.123456]]] = query("SELECT time with time zone '01:02:03.123456'", []) - assert [[~T[01:02:03.123456]]] = query("SELECT time with time zone '16:01:03.123456+1459'", []) + assert [[~T[01:02:03.123456]]] = + query("SELECT time with time zone '16:01:03.123456+1459'", []) - assert [[~T[16:01:03.123456]]] = query("SELECT time with time zone '01:02:03.123456-1459'", []) + assert [[~T[16:01:03.123456]]] = + query("SELECT time with time zone '01:02:03.123456-1459'", []) assert :ok = query("SET SESSION TIME ZONE +1", []) @@ -72,62 +74,144 @@ defmodule CalendarTest do test "decode timestamp", context do assert [[~N[2001-01-01 00:00:00.000000]]] = - query("SELECT timestamp '2001-01-01 00:00:00'", []) + query("SELECT timestamp '2001-01-01 00:00:00'", []) assert :ok = query("SET SESSION TIME ZONE UTC", []) + assert [[~N[2013-09-23 14:04:37.123000]]] = - query("SELECT timestamp '2013-09-23 14:04:37.123'", []) + query("SELECT timestamp '2013-09-23 14:04:37.123'", []) assert [[~N[2013-09-23 14:04:37.000000]]] = - query("SELECT timestamp '2013-09-23 14:04:37 PST'", []) + query("SELECT timestamp '2013-09-23 14:04:37 PST'", []) assert [[~N[2013-09-23 14:04:37.000000]]] = - query("SELECT timestamp '2013-09-23 14:04:37-8'", []) + query("SELECT timestamp '2013-09-23 14:04:37-8'", []) assert :ok = query("SET SESSION TIME ZONE +1", []) + assert [[~N[2013-09-23 14:04:37.000000]]] = - query("SELECT timestamp '2013-09-23 14:04:37 PST'", []) + query("SELECT timestamp '2013-09-23 14:04:37 PST'", []) assert [[~N[1980-01-01 00:00:00.123456]]] = - query("SELECT timestamp '1980-01-01 00:00:00.123456'", []) + query("SELECT timestamp '1980-01-01 00:00:00.123456'", []) end test "decode timestamptz", context do - assert [[%DateTime{year: 2001, month: 1, day: 1, hour: 0, minute: 0, - second: 0, microsecond: {0, 6}, - time_zone: "Etc/UTC", utc_offset: 0}]] = - query("SELECT timestamp with time zone '2001-01-01 00:00:00 UTC'", []) + assert [ + [ + %DateTime{ + year: 2001, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + microsecond: {0, 6}, + time_zone: "Etc/UTC", + utc_offset: 0 + } + ] + ] = query("SELECT timestamp with time zone '2001-01-01 00:00:00 UTC'", []) assert :ok = query("SET SESSION TIME ZONE UTC", []) - assert [[%DateTime{year: 2013, month: 9, day: 23, hour: 14, minute: 4, - second: 37, microsecond: {123000, 6}, - time_zone: "Etc/UTC", utc_offset: 0}]] = - query("SELECT timestamp with time zone '2013-09-23 14:04:37.123'", []) - - assert [[%DateTime{year: 2013, month: 9, day: 23, hour: 22, minute: 4, - second: 37, microsecond: {0, 6}, - time_zone: "Etc/UTC", utc_offset: 0}]] = - query("SELECT timestamp with time zone '2013-09-23 14:04:37 PST'", []) - assert [[%DateTime{year: 2013, month: 9, day: 23, hour: 22, minute: 4, - second: 37, microsecond: {0, 6}, - time_zone: "Etc/UTC", utc_offset: 0}]] = - query("SELECT timestamp with time zone '2013-09-23 14:04:37-8'", []) + + assert [ + [ + %DateTime{ + year: 2013, + month: 9, + day: 23, + hour: 14, + minute: 4, + second: 37, + microsecond: {123_000, 6}, + time_zone: "Etc/UTC", + utc_offset: 0 + } + ] + ] = query("SELECT timestamp with time zone '2013-09-23 14:04:37.123'", []) + + assert [ + [ + %DateTime{ + year: 2013, + month: 9, + day: 23, + hour: 22, + minute: 4, + second: 37, + microsecond: {0, 6}, + time_zone: "Etc/UTC", + utc_offset: 0 + } + ] + ] = query("SELECT timestamp with time zone '2013-09-23 14:04:37 PST'", []) + + assert [ + [ + %DateTime{ + year: 2013, + month: 9, + day: 23, + hour: 22, + minute: 4, + second: 37, + microsecond: {0, 6}, + time_zone: "Etc/UTC", + utc_offset: 0 + } + ] + ] = query("SELECT timestamp with time zone '2013-09-23 14:04:37-8'", []) assert :ok = query("SET SESSION TIME ZONE +1", []) - assert [[%DateTime{year: 2013, month: 9, day: 23, hour: 22, minute: 4, - second: 37, microsecond: {0, 6}, - time_zone: "Etc/UTC", utc_offset: 0}]] = - query("SELECT timestamp with time zone '2013-09-23 14:04:37 PST'", []) - assert [[%DateTime{year: 2013, month: 9, day: 23, hour: 22, minute: 4, - second: 37, microsecond: {0, 6}, - time_zone: "Etc/UTC", utc_offset: 0}]] = - query("SELECT timestamp with time zone '2013-09-23 14:04:37-8'", []) - - assert [[%DateTime{year: 1980, month: 1, day: 1, hour: 0, minute: 0, - second: 0, microsecond: {123456, 6}, - time_zone: "Etc/UTC", utc_offset: 0}]] = - query("SELECT timestamp with time zone '1980-01-01 01:00:00.123456'", []) + assert [ + [ + %DateTime{ + year: 2013, + month: 9, + day: 23, + hour: 22, + minute: 4, + second: 37, + microsecond: {0, 6}, + time_zone: "Etc/UTC", + utc_offset: 0 + } + ] + ] = query("SELECT timestamp with time zone '2013-09-23 14:04:37 PST'", []) + + assert [ + [ + %DateTime{ + year: 2013, + month: 9, + day: 23, + hour: 22, + minute: 4, + second: 37, + microsecond: {0, 6}, + time_zone: "Etc/UTC", + utc_offset: 0 + } + ] + ] = query("SELECT timestamp with time zone '2013-09-23 14:04:37-8'", []) + + assert [ + [ + %DateTime{ + year: 1980, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + microsecond: {123_456, 6}, + time_zone: "Etc/UTC", + utc_offset: 0 + } + ] + ] = query("SELECT timestamp with time zone '1980-01-01 01:00:00.123456'", []) end test "encode time", context do @@ -161,114 +245,216 @@ defmodule CalendarTest do defmodule OtherCalendar do end - assert_raise DBConnection.EncodeError, ~r/Postgrex expected a %Date{} in the `Calendar.ISO` calendar/, fn -> - assert [["1999-12-31"]] = query("SELECT $1::date::text", [%{~D[1999-12-31] | calendar: OtherCalendar}]) - end + assert_raise DBConnection.EncodeError, + ~r/Postgrex expected a %Date{} in the `Calendar.ISO` calendar/, + fn -> + assert [["1999-12-31"]] = + query("SELECT $1::date::text", [ + %{~D[1999-12-31] | calendar: OtherCalendar} + ]) + end # Timestamp - assert_raise DBConnection.EncodeError, ~r/Postgrex expected a %NaiveDateTime{} in the `Calendar.ISO` calendar/, fn -> - assert [["1999-12-31"]] = query("SELECT $1::timestamp::text", - [%{~N[1999-12-31 11:00:00Z] | calendar: OtherCalendar}]) - end + assert_raise DBConnection.EncodeError, + ~r/Postgrex expected a %NaiveDateTime{} in the `Calendar.ISO` calendar/, + fn -> + assert [["1999-12-31"]] = + query( + "SELECT $1::timestamp::text", + [%{~N[1999-12-31 11:00:00Z] | calendar: OtherCalendar}] + ) + end # Timestampz - assert_raise DBConnection.EncodeError, ~r/Postgrex expected a %NaiveDateTime{} in the `Calendar.ISO` calendar/, fn -> - assert [["1999-12-31"]] = query("SELECT $1::timestamp with time zone::text", - [%{~N[1999-12-31 11:00:00Z] | calendar: OtherCalendar}]) - end + assert_raise DBConnection.EncodeError, + ~r/Postgrex expected a %NaiveDateTime{} in the `Calendar.ISO` calendar/, + fn -> + assert [["1999-12-31"]] = + query( + "SELECT $1::timestamp with time zone::text", + [%{~N[1999-12-31 11:00:00Z] | calendar: OtherCalendar}] + ) + end # Time - assert_raise DBConnection.EncodeError, ~r/Postgrex expected a %Time{} in the `Calendar.ISO` calendar/, fn -> - assert [["1999-12-31"]] = query("SELECT $1::time::text", - [%{~T[10:10:10] | calendar: OtherCalendar}]) - end + assert_raise DBConnection.EncodeError, + ~r/Postgrex expected a %Time{} in the `Calendar.ISO` calendar/, + fn -> + assert [["1999-12-31"]] = + query( + "SELECT $1::time::text", + [%{~T[10:10:10] | calendar: OtherCalendar}] + ) + end # Time with zone - assert_raise DBConnection.EncodeError, ~r/Postgrex expected a %Time{} in the `Calendar.ISO` calendar/, fn -> - assert [["1999-12-31"]] = query("SELECT $1::timetz::text", - [%{~T[10:10:10] | calendar: OtherCalendar}]) - end + assert_raise DBConnection.EncodeError, + ~r/Postgrex expected a %Time{} in the `Calendar.ISO` calendar/, + fn -> + assert [["1999-12-31"]] = + query( + "SELECT $1::timetz::text", + [%{~T[10:10:10] | calendar: OtherCalendar}] + ) + end end test "encode timestamp", context do assert [["2001-01-01 00:00:00"]] = - query("SELECT $1::timestamp::text", [~N[2001-01-01 00:00:00.000000]]) + query("SELECT $1::timestamp::text", [~N[2001-01-01 00:00:00.000000]]) assert :ok = query("SET SESSION TIME ZONE UTC", []) + assert [["2013-09-23 14:04:37.123"]] = - query("SELECT $1::timestamp::text", [~N[2013-09-23 14:04:37.123000]]) + query("SELECT $1::timestamp::text", [~N[2013-09-23 14:04:37.123000]]) assert [["2013-09-23 14:04:37"]] = - query("SELECT $1::timestamp::text", [~N[2013-09-23 14:04:37.000000]]) + query("SELECT $1::timestamp::text", [~N[2013-09-23 14:04:37.000000]]) assert :ok = query("SET SESSION TIME ZONE +1", []) assert [["2013-09-23 14:04:37"]] = - query("SELECT $1::timestamp::text", [~N[2013-09-23 14:04:37.000000]]) + query("SELECT $1::timestamp::text", [~N[2013-09-23 14:04:37.000000]]) assert [["1980-01-01 00:00:00.123456"]] = - query("SELECT $1::timestamp::text", [~N[1980-01-01 00:00:00.123456]]) + query("SELECT $1::timestamp::text", [~N[1980-01-01 00:00:00.123456]]) assert [["1980-01-01 00:00:00.123456"]] = - query("SELECT $1::timestamp::text", [DateTime.from_naive!(~N[1980-01-01 00:00:00.123456], "Etc/UTC")]) + query("SELECT $1::timestamp::text", [ + DateTime.from_naive!(~N[1980-01-01 00:00:00.123456], "Etc/UTC") + ]) end test "encode timestamptz", context do assert :ok = query("SET SESSION TIME ZONE UTC", []) assert [["2001-01-01 00:00:00+00"]] = - query("SELECT $1::timestamp with time zone::text", - [%DateTime{year: 2001, month: 1, day: 1, hour: 0, minute: 0, - second: 0, microsecond: {0, 6}, time_zone: "Etc/UTC", - zone_abbr: "UTC", utc_offset: 0, std_offset: 0}]) + query( + "SELECT $1::timestamp with time zone::text", + [ + %DateTime{ + year: 2001, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + microsecond: {0, 6}, + time_zone: "Etc/UTC", + zone_abbr: "UTC", + utc_offset: 0, + std_offset: 0 + } + ] + ) assert [["2013-09-23 14:04:37.123+00"]] = - query("SELECT $1::timestamp with time zone::text", - [%DateTime{year: 2013, month: 9, day: 23, hour: 14, minute: 4, - second: 37, microsecond: {123000, 6}, - time_zone: "Etc/UTC", zone_abbr: "UTC", utc_offset: 0, - std_offset: 0}]) - - assert_raise ArgumentError, ~r"is not in UTC", - fn() -> - query("SELECT $1::timestamp with time zone::text", - [%DateTime{year: 2013, month: 9, day: 23, hour: 14, minute: 4, - second: 37, microsecond: {123000, 6}, - time_zone: "PST", zone_abbr: "PST", utc_offset: -8, - std_offset: 0}]) - end + query( + "SELECT $1::timestamp with time zone::text", + [ + %DateTime{ + year: 2013, + month: 9, + day: 23, + hour: 14, + minute: 4, + second: 37, + microsecond: {123_000, 6}, + time_zone: "Etc/UTC", + zone_abbr: "UTC", + utc_offset: 0, + std_offset: 0 + } + ] + ) + + assert_raise ArgumentError, ~r"is not in UTC", fn -> + query( + "SELECT $1::timestamp with time zone::text", + [ + %DateTime{ + year: 2013, + month: 9, + day: 23, + hour: 14, + minute: 4, + second: 37, + microsecond: {123_000, 6}, + time_zone: "PST", + zone_abbr: "PST", + utc_offset: -8, + std_offset: 0 + } + ] + ) + end assert :ok = query("SET SESSION TIME ZONE +1", []) assert [["2013-09-23 15:04:37.123+01"]] = - query("SELECT $1::timestamp with time zone::text", - [%DateTime{year: 2013, month: 9, day: 23, hour: 14, minute: 4, - second: 37, microsecond: {123000, 6}, - time_zone: "Etc/UTC", zone_abbr: "UTC", utc_offset: 0, - std_offset: 0}]) + query( + "SELECT $1::timestamp with time zone::text", + [ + %DateTime{ + year: 2013, + month: 9, + day: 23, + hour: 14, + minute: 4, + second: 37, + microsecond: {123_000, 6}, + time_zone: "Etc/UTC", + zone_abbr: "UTC", + utc_offset: 0, + std_offset: 0 + } + ] + ) assert [["1980-01-01 01:00:00.123456+01"]] = - query("SELECT $1::timestamp with time zone::text", - [%DateTime{year: 1980, month: 1, day: 1, hour: 0, minute: 0, - second: 0, microsecond: {123456, 6}, - time_zone: "Etc/UTC", zone_abbr: "UTC", utc_offset: 0, - std_offset: 0}]) + query( + "SELECT $1::timestamp with time zone::text", + [ + %DateTime{ + year: 1980, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + microsecond: {123_456, 6}, + time_zone: "Etc/UTC", + zone_abbr: "UTC", + utc_offset: 0, + std_offset: 0 + } + ] + ) end - test "persist timestamp and timestamptz", context do + test "persist timestamp and timestamptz", context do assert :ok = query("SET SESSION TIME ZONE +1", []) - assert :ok = query("INSERT INTO calendar VALUES (timestamp without time zone '2001-01-01 00:00:00', timestamp with time zone '2001-01-01 00:00:00 UTC')", []) - assert [[~N[2001-01-01 00:00:00.000000], - %DateTime{year: 2001, month: 1, day: 1, hour: 0, minute: 0, - second: 0}]] = - query("SELECT a, b FROM calendar", []) + assert :ok = + query( + "INSERT INTO calendar VALUES (timestamp without time zone '2001-01-01 00:00:00', timestamp with time zone '2001-01-01 00:00:00 UTC')", + [] + ) + + assert [ + [ + ~N[2001-01-01 00:00:00.000000], + %DateTime{year: 2001, month: 1, day: 1, hour: 0, minute: 0, second: 0} + ] + ] = query("SELECT a, b FROM calendar", []) assert :ok = query("SET SESSION TIME ZONE +2", []) - assert [[~N[2001-01-01 00:00:00.000000], - %DateTime{year: 2001, month: 1, day: 1, hour: 0, minute: 0, - second: 0}]] = - query("SELECT a, b FROM calendar", []) + assert [ + [ + ~N[2001-01-01 00:00:00.000000], + %DateTime{year: 2001, month: 1, day: 1, hour: 0, minute: 0, second: 0} + ] + ] = query("SELECT a, b FROM calendar", []) end end diff --git a/test/client_test.exs b/test/client_test.exs index 9a6a1badc..366f40844 100644 --- a/test/client_test.exs +++ b/test/client_test.exs @@ -14,34 +14,38 @@ defmodule ClientTest do %Postgrex.Result{connection_id: connection_id} = Postgrex.query!(conn, "SELECT 42", []) Process.flag(:trap_exit, true) + assert capture_log(fn -> - assert [[_]] = query("SELECT pg_stat_get_activity($1)", [connection_id]) + assert [[_]] = query("SELECT pg_stat_get_activity($1)", [connection_id]) - %DBConnection.ConnectionError{message: "tcp recv: closed" <> _} = - query("SELECT pg_sleep(10)", [], [timeout: 50]) + %DBConnection.ConnectionError{message: "tcp recv: closed" <> _} = + query("SELECT pg_sleep(10)", [], timeout: 50) - assert_receive {:EXIT, ^conn, :killed} - end) =~ "disconnected: ** (DBConnection.ConnectionError)" + assert_receive {:EXIT, ^conn, :killed} + end) =~ "disconnected: ** (DBConnection.ConnectionError)" :timer.sleep(500) {:ok, pid} = Postgrex.start_link(context[:options]) + assert %Postgrex.Result{rows: []} = - Postgrex.query!(pid, "SELECT pg_stat_get_activity($1)", [connection_id]) + Postgrex.query!(pid, "SELECT pg_stat_get_activity($1)", [connection_id]) end test "active client DOWN", context do self_pid = self() conn = context[:pid] - pid = spawn fn -> - send self_pid, query("SELECT pg_sleep(0.2)", []) - end + pid = + spawn(fn -> + send(self_pid, query("SELECT pg_sleep(0.2)", [])) + end) :timer.sleep(100) Process.flag(:trap_exit, true) - capture_log fn -> + + capture_log(fn -> Process.exit(pid, :shutdown) assert_receive {:EXIT, ^conn, :killed} - end + end) end end diff --git a/test/custom_extensions_test.exs b/test/custom_extensions_test.exs index 5ac813b51..204633032 100644 --- a/test/custom_extensions_test.exs +++ b/test/custom_extensions_test.exs @@ -21,7 +21,7 @@ defmodule CustomExtensionsTest do def encode([]) do quote do int -> - <<4::int32, int+1::int32>> + <<4::int32, int + 1::int32>> end end @@ -48,7 +48,7 @@ defmodule CustomExtensionsTest do quote do value -> [<> | value] - end + end end def decode({}) do @@ -91,6 +91,7 @@ defmodule CustomExtensionsTest do :code.delete(@types) :code.purge(@types) end) + extensions = [BinaryExtension, TextExtension, BadExtension] opts = [decode_binary: :reference, null: :custom] Postgrex.TypeModule.define(@types, extensions, opts) @@ -109,8 +110,7 @@ defmodule CustomExtensionsTest do end test "encode and decode unknown type", context do - assert [["23"]] = - query("SELECT $1::oid", ["23"]) + assert [["23"]] = query("SELECT $1::oid", ["23"]) end test "encode and decode pushes error to client", context do @@ -120,14 +120,14 @@ defmodule CustomExtensionsTest do query("SELECT $1::boolean", [true]) end - assert capture_log(fn() -> - assert_raise RuntimeError, "decode", fn -> - query("SELECT true", []) - end + assert capture_log(fn -> + assert_raise RuntimeError, "decode", fn -> + query("SELECT true", []) + end - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ "(RuntimeError) decode" + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ "(RuntimeError) decode" end test "raise when executing prepared query on connection with different types", context do @@ -146,9 +146,10 @@ defmodule CustomExtensionsTest do opts = [types: Postgrex.DefaultTypes] ++ context[:options] {:ok, pid2} = Postgrex.start_link(opts) - Postgrex.transaction(pid2, fn(conn) -> - assert_raise Postgrex.QueryError, ~r"invalid types for the connection", - fn() -> stream(query, []) |> Enum.take(1) end + Postgrex.transaction(pid2, fn conn -> + assert_raise Postgrex.QueryError, ~r"invalid types for the connection", fn -> + stream(query, []) |> Enum.take(1) + end end) end @@ -158,9 +159,10 @@ defmodule CustomExtensionsTest do opts = [types: Postgrex.DefaultTypes] ++ context[:options] {:ok, pid2} = Postgrex.start_link(opts) - Postgrex.transaction(pid2, fn(conn) -> - assert_raise Postgrex.QueryError, ~r"invalid types for the connection", - fn() -> Enum.into(["1\n"], stream(query, [])) end + Postgrex.transaction(pid2, fn conn -> + assert_raise Postgrex.QueryError, ~r"invalid types for the connection", fn -> + Enum.into(["1\n"], stream(query, [])) + end end) end diff --git a/test/error_test.exs b/test/error_test.exs index 5a459254e..dbe4cc319 100644 --- a/test/error_test.exs +++ b/test/error_test.exs @@ -50,30 +50,44 @@ defmodule ErrorTest do end test "notices raised by functions do not reset rows", config do - {:ok, _} = P.query(config.pid, """ - CREATE FUNCTION raise_notice_and_return(what integer) RETURNS integer AS $$ - BEGIN - RAISE NOTICE 'notice %', what; - RETURN what; - END; - $$ LANGUAGE plpgsql; - """, []) + {:ok, _} = + P.query( + config.pid, + """ + CREATE FUNCTION raise_notice_and_return(what integer) RETURNS integer AS $$ + BEGIN + RAISE NOTICE 'notice %', what; + RETURN what; + END; + $$ LANGUAGE plpgsql; + """, + [] + ) assert {:ok, result} = - P.query(config.pid, "SELECT raise_notice_and_return(x) FROM generate_series(1, 2) AS x", []) + P.query( + config.pid, + "SELECT raise_notice_and_return(x) FROM generate_series(1, 2) AS x", + [] + ) assert [_, _] = result.messages assert [[1], [2]] = result.rows end test "errors raised by functions disconnect", config do - {:ok, _} = P.query(config.pid, """ - CREATE FUNCTION raise_exception(what integer) RETURNS integer AS $$ - BEGIN - RAISE EXCEPTION 'error %', what; - END; - $$ LANGUAGE plpgsql; - """, []) + {:ok, _} = + P.query( + config.pid, + """ + CREATE FUNCTION raise_exception(what integer) RETURNS integer AS $$ + BEGIN + RAISE EXCEPTION 'error %', what; + END; + $$ LANGUAGE plpgsql; + """, + [] + ) assert {:error, %Postgrex.Error{postgres: %{message: "error 1"}}} = P.query(config.pid, "SELECT raise_exception(1)", []) diff --git a/test/login_test.exs b/test/login_test.exs index 8b30eb6bb..ea2a374e7 100644 --- a/test/login_test.exs +++ b/test/login_test.exs @@ -16,9 +16,10 @@ defmodule LoginTest do test "login cleartext password failure", context do assert capture_log(fn -> - opts = [username: "postgrex_cleartext_pw", password: "wrong_password"] - assert_start_and_killed(opts ++ context[:options]) - end) =~ ~r"\*\* \(Postgrex.Error\) FATAL (28P01 \(invalid_password\)|28000 \(invalid_authorization_specification\))" + opts = [username: "postgrex_cleartext_pw", password: "wrong_password"] + assert_start_and_killed(opts ++ context[:options]) + end) =~ + ~r"\*\* \(Postgrex.Error\) FATAL (28P01 \(invalid_password\)|28000 \(invalid_authorization_specification\))" end test "login md5 password", context do @@ -29,9 +30,10 @@ defmodule LoginTest do test "login md5 password failure", context do assert capture_log(fn -> - opts = [username: "postgrex_md5_pw", password: "wrong_password"] - assert_start_and_killed(opts ++ context[:options]) - end) =~ ~r"\*\* \(Postgrex.Error\) FATAL (28P01 \(invalid_password\)|28000 \(invalid_authorization_specification\))" + opts = [username: "postgrex_md5_pw", password: "wrong_password"] + assert_start_and_killed(opts ++ context[:options]) + end) =~ + ~r"\*\* \(Postgrex.Error\) FATAL (28P01 \(invalid_password\)|28000 \(invalid_authorization_specification\))" end @tag min_pg_version: "10.0" @@ -44,16 +46,17 @@ defmodule LoginTest do @tag min_pg_version: "10.0" test "login scram password failure", context do assert capture_log(fn -> - opts = [username: "postgrex_scram_pw", password: "wrong_password"] - assert_start_and_killed(opts ++ context[:options]) - end) =~ ~r"\*\* \(Postgrex.Error\) FATAL (28P01 \(invalid_password\)|28000 \(invalid_authorization_specification\))" + opts = [username: "postgrex_scram_pw", password: "wrong_password"] + assert_start_and_killed(opts ++ context[:options]) + end) =~ + ~r"\*\* \(Postgrex.Error\) FATAL (28P01 \(invalid_password\)|28000 \(invalid_authorization_specification\))" end test "parameters", context do assert {:ok, pid} = P.start_link(context[:options]) assert {:ok, %Postgrex.Result{}} = P.query(pid, "SELECT 123", []) - assert String.match? P.parameters(pid)["server_version"], ~R"^\d+\.\d+" + assert String.match?(P.parameters(pid)["server_version"], ~R"^\d+\.\d+") end @tag min_pg_version: "9.0" @@ -104,9 +107,9 @@ defmodule LoginTest do set_db_name("doesntexist") assert capture_log(fn -> - opts = Keyword.delete(context[:options], :database) - assert_start_and_killed(opts) - end) + opts = Keyword.delete(context[:options], :database) + assert_start_and_killed(opts) + end) after set_db_name(previous_db_name) end @@ -114,16 +117,16 @@ defmodule LoginTest do test "non-existent database", context do assert capture_log(fn -> - opts = [database: "doesntexist"] - assert_start_and_killed(opts ++ context[:options]) - end) =~ "** (Postgrex.Error) FATAL 3D000 (invalid_catalog_name)" + opts = [database: "doesntexist"] + assert_start_and_killed(opts ++ context[:options]) + end) =~ "** (Postgrex.Error) FATAL 3D000 (invalid_catalog_name)" end test "non-existent domain", context do assert capture_log(fn -> - opts = [hostname: "doesntexist", connect_timeout: 100] - assert_start_and_killed(opts ++ context[:options]) - end) =~ ~r"tcp connect \(doesntexist:\d+\): non-existing domain - :nxdomain" + opts = [hostname: "doesntexist", connect_timeout: 100] + assert_start_and_killed(opts ++ context[:options]) + end) =~ ~r"tcp connect \(doesntexist:\d+\): non-existing domain - :nxdomain" end @tag :unix @@ -131,31 +134,33 @@ defmodule LoginTest do socket = System.get_env("PG_SOCKET_DIR") || "/tmp" opts = [socket_dir: socket] - capture_log fn -> + + capture_log(fn -> assert {:ok, pid} = P.start_link(opts ++ context[:options]) assert {:ok, %Postgrex.Result{}} = P.query(pid, "SELECT 123", []) - end + end) end @tag :unix test "non-existent unix domain socket", context do assert capture_log(fn -> - opts = [socket_dir: "/doesntexist"] - assert_start_and_killed(opts ++ context[:options]) - end) =~ ~r"tcp connect \(/doesntexist/.s.PGSQL.\d+\): no such file or directory - :enoent" + opts = [socket_dir: "/doesntexist"] + assert_start_and_killed(opts ++ context[:options]) + end) =~ + ~r"tcp connect \(/doesntexist/.s.PGSQL.\d+\): no such file or directory - :enoent" end @tag :unix test "socket precedes socket_dir", context do assert capture_log(fn -> - opts = [socket: "/socketfile", socket_dir: "/socketdir"] - assert_start_and_killed(opts ++ context[:options]) - end) =~ ~r"tcp connect \(/socketfile\): no such file or directory - :enoent" + opts = [socket: "/socketfile", socket_dir: "/socketdir"] + assert_start_and_killed(opts ++ context[:options]) + end) =~ ~r"tcp connect \(/socketfile\): no such file or directory - :enoent" end test "after connect function run", context do parent = self() - after_connect = fn(conn) -> send(parent, P.query(conn, "SELECT 42", [])) end + after_connect = fn conn -> send(parent, P.query(conn, "SELECT 42", [])) end opts = [after_connect: after_connect] {:ok, _} = P.start_link(opts ++ context[:options]) @@ -168,6 +173,7 @@ defmodule LoginTest do test "defaults to PGPORT if no port number is provided" do previous_port = System.get_env("PGPORT") + try do set_port_number("12345") assert 12345 == P.Utils.default_opts([])[:port] @@ -179,6 +185,7 @@ defmodule LoginTest do test "ignores PGPORT if non existent" do opts = [] previous_port = System.get_env("PGPORT") + try do System.delete_env("PGPORT") assert nil == P.Utils.default_opts(opts)[:port] diff --git a/test/notification_test.exs b/test/notification_test.exs index 2ed007000..317de91a9 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -29,7 +29,9 @@ defmodule NotificationTest do test "listening, notify, then receive (with payload)", context do assert {:ok, ref} = PN.listen(context.pid_ps, "channel") - assert {:ok, %Postgrex.Result{command: :notify}} = P.query(context.pid, "NOTIFY channel, 'hello'", []) + assert {:ok, %Postgrex.Result{command: :notify}} = + P.query(context.pid, "NOTIFY channel, 'hello'", []) + receiver_pid = context.pid_ps assert_receive {:notification, ^receiver_pid, ^ref, "channel", "hello"} end @@ -75,9 +77,9 @@ defmodule NotificationTest do end test "listen, go away", context do - spawn fn -> + spawn(fn -> assert {:ok, _} = PN.listen(context.pid_ps, "channel") - end + end) assert {:ok, %Postgrex.Result{command: :notify}} = P.query(context.pid, "NOTIFY channel", []) :timer.sleep(300) diff --git a/test/postgrex_test.exs b/test/postgrex_test.exs index 5dd836889..0ade0cbe0 100644 --- a/test/postgrex_test.exs +++ b/test/postgrex_test.exs @@ -7,6 +7,7 @@ defmodule PostgrexTest do end) Application.stop(:ssl) + assert_raise RuntimeError, ~r"SSL connection can not be established", fn -> Postgrex.start_link(ssl: true, database: "postgrex_test") end diff --git a/test/query_test.exs b/test/query_test.exs index c52367de0..56f7c0411 100644 --- a/test/query_test.exs +++ b/test/query_test.exs @@ -17,7 +17,7 @@ defmodule QueryTest do end test "iodata", context do - assert [[123]] = query(["S", ?E, ["LEC"|"T"], " ", '123'], []) + assert [[123]] = query(["S", ?E, ["LEC" | "T"], " ", '123'], []) end test "decode basic types", context do @@ -45,8 +45,13 @@ defmodule QueryTest do assert [[Decimal.new("0.00012345")]] == query("SELECT 0.00012345", []) assert [[Decimal.new("1000000000.0")]] == query("SELECT 1000000000.0", []) assert [[Decimal.new("1000000000.1")]] == query("SELECT 1000000000.1", []) - assert [[Decimal.new("123456789123456789123456789")]] == query("SELECT 123456789123456789123456789::numeric", []) - assert [[Decimal.new("123456789123456789123456789.123456789")]] == query("SELECT 123456789123456789123456789.123456789", []) + + assert [[Decimal.new("123456789123456789123456789")]] == + query("SELECT 123456789123456789123456789::numeric", []) + + assert [[Decimal.new("123456789123456789123456789.123456789")]] == + query("SELECT 123456789123456789123456789.123456789", []) + assert [[Decimal.new("1.1234500000")]] == query("SELECT 1.1234500000", []) assert [[Decimal.new("NaN")]] == query("SELECT 'NaN'::numeric", []) end @@ -58,24 +63,30 @@ defmodule QueryTest do end test "decode uuid", context do - uuid = <<160,238,188,153,156,11,78,248,187,109,107,185,189,56,10,17>> + uuid = <<160, 238, 188, 153, 156, 11, 78, 248, 187, 109, 107, 185, 189, 56, 10, 17>> assert [[^uuid]] = query("SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid", []) end test "decode arrays", context do assert [[[]]] = query("SELECT ARRAY[]::integer[]", []) assert [[[1]]] = query("SELECT ARRAY[1]", []) - assert [[[1,2]]] = query("SELECT ARRAY[1,2]", []) - assert [[[[0],[1]]]] = query("SELECT ARRAY[[0],[1]]", []) + assert [[[1, 2]]] = query("SELECT ARRAY[1,2]", []) + assert [[[[0], [1]]]] = query("SELECT ARRAY[[0],[1]]", []) assert [[[[0]]]] = query("SELECT ARRAY[ARRAY[0]]", []) end test "decode array domain", context do - assert [[[1.0, 2.0, 3.0]]] = - query("SELECT ARRAY[1, 2, 3]::floats_domain", []) + assert [[[1.0, 2.0, 3.0]]] = query("SELECT ARRAY[1, 2, 3]::floats_domain", []) - assert [[[%Postgrex.Point{x: 1.0, y: 1.0}, %Postgrex.Point{x: 2.0, y: 2.0}, %Postgrex.Point{x: 3.0, y: 3.0}]]] = - query("SELECT ARRAY[point '1,1', point '2,2', point '3,3']::points_domain", []) + assert [ + [ + [ + %Postgrex.Point{x: 1.0, y: 1.0}, + %Postgrex.Point{x: 2.0, y: 2.0}, + %Postgrex.Point{x: 3.0, y: 3.0} + ] + ] + ] = query("SELECT ARRAY[point '1,1', point '2,2', point '3,3']::points_domain", []) end test "encode array domain", context do @@ -83,33 +94,43 @@ defmodule QueryTest do floats_string = "{1,2,3}" assert [[^floats_string]] = query("SELECT $1::floats_domain::text", [floats]) - points = [%Postgrex.Point{x: 1.0, y: 1.0}, %Postgrex.Point{x: 2.0, y: 2.0}, %Postgrex.Point{x: 3.0, y: 3.0}] + points = [ + %Postgrex.Point{x: 1.0, y: 1.0}, + %Postgrex.Point{x: 2.0, y: 2.0}, + %Postgrex.Point{x: 3.0, y: 3.0} + ] + points_string = "{\"(1,1)\",\"(2,2)\",\"(3,3)\"}" assert [[^points_string]] = query("SELECT $1::points_domain::text", [points]) end test "decode interval", context do - assert [[%Postgrex.Interval{months: 0, days: 0, secs: 0}]] = - query("SELECT interval '0'", []) + assert [[%Postgrex.Interval{months: 0, days: 0, secs: 0}]] = query("SELECT interval '0'", []) + assert [[%Postgrex.Interval{months: 100, days: 0, secs: 0}]] = - query("SELECT interval '100 months'", []) + query("SELECT interval '100 months'", []) + assert [[%Postgrex.Interval{months: 0, days: 100, secs: 0}]] = - query("SELECT interval '100 days'", []) + query("SELECT interval '100 days'", []) + assert [[%Postgrex.Interval{months: 0, days: 0, secs: 100}]] = - query("SELECT interval '100 secs'", []) + query("SELECT interval '100 secs'", []) + assert [[%Postgrex.Interval{months: 14, days: 40, secs: 10920}]] = - query("SELECT interval '1 year 2 months 40 days 3 hours 2 minutes'", []) + query("SELECT interval '1 year 2 months 40 days 3 hours 2 minutes'", []) end test "decode point", context do - assert [[%Postgrex.Point{x: -97.5, y: 100.1}]] == query("SELECT point(-97.5, 100.1)::point", []) + assert [[%Postgrex.Point{x: -97.5, y: 100.1}]] == + query("SELECT point(-97.5, 100.1)::point", []) end test "encode point", context do - assert [[%Postgrex.Point{x: -97.0, y: 100.0}]] == query("SELECT $1::point", [%Postgrex.Point{x: -97, y: 100}]) + assert [[%Postgrex.Point{x: -97.0, y: 100.0}]] == + query("SELECT $1::point", [%Postgrex.Point{x: -97, y: 100}]) end - test "decode polygon", context do + test "decode polygon", context do p1 = %Postgrex.Point{x: 100.0, y: 101.5} p2 = %Postgrex.Point{x: 100.0, y: -99.1} p3 = %Postgrex.Point{x: -91.1, y: -101.1} @@ -151,28 +172,32 @@ defmodule QueryTest do test "decode line segment", context do segment = %Postgrex.LineSegment{ - point1: %Postgrex.Point{x: 0.0, y: 0.0}, - point2: %Postgrex.Point{x: 1.0, y: 1.0} + point1: %Postgrex.Point{x: 0.0, y: 0.0}, + point2: %Postgrex.Point{x: 1.0, y: 1.0} } + assert [[segment]] == query("SELECT '(0.0,0.0)(1.0,1.0)'::lseg", []) end test "encode line segment", context do segment = %Postgrex.LineSegment{ - point1: %Postgrex.Point{x: 0.0, y: 0.0}, - point2: %Postgrex.Point{x: 1.0, y: 1.0} + point1: %Postgrex.Point{x: 0.0, y: 0.0}, + point2: %Postgrex.Point{x: 1.0, y: 1.0} } + assert [[segment]] == query("SELECT $1::lseg", [segment]) assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::lseg", [1.0])) + assert %DBConnection.EncodeError{} = - catch_error(query("SELECT $1::lseg", [%Postgrex.LineSegment{}])) + catch_error(query("SELECT $1::lseg", [%Postgrex.LineSegment{}])) end test "decode box", context do box = %Postgrex.Box{ - upper_right: %Postgrex.Point{x: 1.0, y: 1.0}, - bottom_left: %Postgrex.Point{x: 0.0, y: 0.0} + upper_right: %Postgrex.Point{x: 1.0, y: 1.0}, + bottom_left: %Postgrex.Point{x: 0.0, y: 0.0} } + # postgres automatically sorts the points so that we get UR/BL assert [[box]] == query("SELECT '(0.0,0.0)(1.0,1.0)'::box", []) assert [[box]] == query("SELECT '(1.0,1.0)(0.0,0.0)'::box", []) @@ -182,13 +207,13 @@ defmodule QueryTest do test "encode box", context do box = %Postgrex.Box{ - upper_right: %Postgrex.Point{x: 1.0, y: 1.0}, - bottom_left: %Postgrex.Point{x: 0.0, y: 0.0} + upper_right: %Postgrex.Point{x: 1.0, y: 1.0}, + bottom_left: %Postgrex.Point{x: 0.0, y: 0.0} } + assert [[box]] == query("SELECT $1::box", [box]) assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::box", [1.0])) - assert %DBConnection.EncodeError{} = - catch_error(query("SELECT $1::box", [%Postgrex.Box{}])) + assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::box", [%Postgrex.Box{}])) end test "decode path", context do @@ -197,7 +222,10 @@ defmodule QueryTest do p3 = %Postgrex.Point{x: -4.0, y: 3.14} path = %Postgrex.Path{points: [p1, p2, p3], open: true} assert [[path]] == query("SELECT '[(0.0,0.0),(1.0,3.0),(-4.0,3.14)]'::path", []) - assert [[%{path | open: false}]] == query("SELECT '((0.0,0.0),(1.0,3.0),(-4.0,3.14))'::path", []) + + assert [[%{path | open: false}]] == + query("SELECT '((0.0,0.0),(1.0,3.0),(-4.0,3.14))'::path", []) + assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::path", [1.0])) bad_path = %Postgrex.Path{points: "foo", open: false} assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::path", [bad_path])) @@ -260,38 +288,120 @@ defmodule QueryTest do @tag min_pg_version: "9.2" test "decode range", context do assert [[%Postgrex.Range{lower: 2, upper: 5, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT '(1,5)'::int4range", []) + query("SELECT '(1,5)'::int4range", []) + assert [[%Postgrex.Range{lower: 1, upper: 7, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT '[1,6]'::int4range", []) - assert [[%Postgrex.Range{lower: :unbound, upper: 5, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT '(,5)'::int4range", []) - assert [[%Postgrex.Range{lower: 1, upper: :unbound, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT '[1,)'::int4range", []) - assert [[%Postgrex.Range{lower: :empty, upper: :empty, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT '(1,1)'::int4range", []) + query("SELECT '[1,6]'::int4range", []) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: 5, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = query("SELECT '(,5)'::int4range", []) + + assert [ + [ + %Postgrex.Range{ + lower: 1, + upper: :unbound, + lower_inclusive: true, + upper_inclusive: false + } + ] + ] = query("SELECT '[1,)'::int4range", []) + + assert [ + [ + %Postgrex.Range{ + lower: :empty, + upper: :empty, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = query("SELECT '(1,1)'::int4range", []) + assert [[%Postgrex.Range{lower: 1, upper: 2, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT '[1,1]'::int4range", []) - assert [[%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT '(,)'::int4range", []) - assert [[%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT '[,]'::int4range", []) + query("SELECT '[1,1]'::int4range", []) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = query("SELECT '(,)'::int4range", []) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = query("SELECT '[,]'::int4range", []) assert [[%Postgrex.Range{lower: 3, upper: 8, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT '(2,8)'::int8range", []) + query("SELECT '(2,8)'::int8range", []) + + assert [ + [ + %Postgrex.Range{ + lower: Decimal.new("1.2"), + upper: Decimal.new("3.4"), + lower_inclusive: false, + upper_inclusive: false + } + ] + ] == + query("SELECT '(1.2,3.4)'::numrange", []) + + assert [ + [ + %Postgrex.Range{ + lower: %Date{year: 2014, month: 1, day: 1}, + upper: %Date{year: 2014, month: 12, day: 31} + } + ] + ] = query("SELECT '[2014-1-1,2014-12-31)'::daterange", []) - assert [[%Postgrex.Range{lower: Decimal.new("1.2"), upper: Decimal.new("3.4"), lower_inclusive: false, upper_inclusive: false}]] == - query("SELECT '(1.2,3.4)'::numrange", []) - - assert [[%Postgrex.Range{lower: %Date{year: 2014, month: 1, day: 1}, upper: %Date{year: 2014, month: 12, day: 31}}]] = - query("SELECT '[2014-1-1,2014-12-31)'::daterange", []) assert [[%Postgrex.Range{lower: :unbound, upper: %Date{year: 2014, month: 12, day: 31}}]] = - query("SELECT '(,2014-12-31)'::daterange", []) + query("SELECT '(,2014-12-31)'::daterange", []) + assert [[%Postgrex.Range{lower: %Date{year: 2014, month: 1, day: 2}, upper: :unbound}]] = - query("SELECT '(2014-1-1,]'::daterange", []) - assert [[%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT '(,)'::daterange", []) - assert [[%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT '[,]'::daterange", []) + query("SELECT '(2014-1-1,]'::daterange", []) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = query("SELECT '(,)'::daterange", []) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = query("SELECT '[,]'::daterange", []) end @tag min_pg_version: "9.0" @@ -358,7 +468,7 @@ defmodule QueryTest do end test "decode oid and its aliases", context do - assert [[4294967295]] = query("select 4294967295::oid;", []) + assert [[4_294_967_295]] = query("select 4294967295::oid;", []) assert [["-"]] = query("select '-'::regproc::text;", []) assert [["sum(integer)"]] = query("select 'sum(int4)'::regprocedure::text;", []) @@ -396,35 +506,48 @@ defmodule QueryTest do end test "decode bit string", context do - assert [[<<1::1,0::1,1::1>>]] == query("SELECT bit '101'", []) - assert [[<<1::1,1::1,0::1>>]] == query("SELECT bit '110'", []) - assert [[<<1::1,1::1,0::1>>]] == query("SELECT bit '110' :: varbit", []) - assert [[<<1::1,0::1,1::1,1::1,0::1>>]] == query("SELECT bit '10110'", []) - assert [[<<1::1,0::1,1::1,0::1,0::1>>]] == - query("SELECT bit '101' :: bit(5)", []) - assert [[<<1::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 1::1,0::1,1::1>>]] == - query("SELECT bit '10000000101'", []) - assert [[<<0::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 0::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 1::1,0::1,1::1>>]] == - query("SELECT bit '0000000000000000101'", []) - assert [[<<1::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 0::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 1::1,0::1,1::1>>]] == - query("SELECT bit '1000000000000000101'", []) - assert [[<<1::1,0::1,0::1,0::1,0::1,0::1,0::1,1::1, - 1::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 1::1,0::1,1::1>>]] == - query("SELECT bit '1000000110000000101'", []) + assert [[<<1::1, 0::1, 1::1>>]] == query("SELECT bit '101'", []) + assert [[<<1::1, 1::1, 0::1>>]] == query("SELECT bit '110'", []) + assert [[<<1::1, 1::1, 0::1>>]] == query("SELECT bit '110' :: varbit", []) + assert [[<<1::1, 0::1, 1::1, 1::1, 0::1>>]] == query("SELECT bit '10110'", []) + + assert [[<<1::1, 0::1, 1::1, 0::1, 0::1>>]] == + query("SELECT bit '101' :: bit(5)", []) + + assert [[<<1::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 1::1, 0::1, 1::1>>]] == + query("SELECT bit '10000000101'", []) + + assert [ + [ + <<0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, + 0::1, 0::1, 0::1, 1::1, 0::1, 1::1>> + ] + ] == + query("SELECT bit '0000000000000000101'", []) + + assert [ + [ + <<1::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, + 0::1, 0::1, 0::1, 1::1, 0::1, 1::1>> + ] + ] == + query("SELECT bit '1000000000000000101'", []) + + assert [ + [ + <<1::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 1::1, 1::1, 0::1, 0::1, 0::1, 0::1, + 0::1, 0::1, 0::1, 1::1, 0::1, 1::1>> + ] + ] == + query("SELECT bit '1000000110000000101'", []) end test "encode oid and its aliases", context do # oid's range is 0 to 4294967295 assert [[0]] = query("select $1::oid;", [0]) - assert [[4294967295]] = query("select $1::oid;", [4294967295]) + assert [[4_294_967_295]] = query("select $1::oid;", [4_294_967_295]) assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::oid", [0 - 1])) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::oid", [4294967295 + 1])) + assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::oid", [4_294_967_295 + 1])) assert [["-"]] = query("select $1::regproc::text;", [0]) assert [["regprocin"]] = query("select $1::regproc::text;", [44]) @@ -450,22 +573,38 @@ defmodule QueryTest do test "encoding oids as binary fails with a helpful error message", context do assert_raise ArgumentError, - ~r"See https://github.com/elixir-ecto/postgrex#oid-type-encoding", - fn() -> query("select $1::regclass;", ["pg_type"]) end + ~r"See https://github.com/elixir-ecto/postgrex#oid-type-encoding", + fn -> query("select $1::regclass;", ["pg_type"]) end end test "fail on encoding wrong value", context do - assert %DBConnection.EncodeError{message: message} = catch_error(query("SELECT $1::integer", ["123"])) + assert %DBConnection.EncodeError{message: message} = + catch_error(query("SELECT $1::integer", ["123"])) + assert message =~ "Postgrex expected an integer in -2147483648..2147483647" end @tag min_pg_version: "9.0" test "decode hstore", context do assert [[%{}]] = query(~s|SELECT ''::hstore|, []) - assert [[%{"Bubbles" => "7", "Name" => "Frank"}]] = query(~s|SELECT '"Name" => "Frank", "Bubbles" => "7"'::hstore|, []) - assert [[%{"non_existant" => nil, "present" => "&accounted_for"}]] = query(~s|SELECT '"non_existant" => NULL, "present" => "&accounted_for"'::hstore|, []) - assert [[%{"spaces in the key" => "are easy!", "floats too" => "66.6"}]] = query(~s|SELECT '"spaces in the key" => "are easy!", "floats too" => "66.6"'::hstore|, []) - assert [[%{"this is true" => "true", "though not this" => "false"}]] = query(~s|SELECT '"this is true" => "true", "though not this" => "false"'::hstore|, []) + + assert [[%{"Bubbles" => "7", "Name" => "Frank"}]] = + query(~s|SELECT '"Name" => "Frank", "Bubbles" => "7"'::hstore|, []) + + assert [[%{"non_existant" => nil, "present" => "&accounted_for"}]] = + query(~s|SELECT '"non_existant" => NULL, "present" => "&accounted_for"'::hstore|, []) + + assert [[%{"spaces in the key" => "are easy!", "floats too" => "66.6"}]] = + query( + ~s|SELECT '"spaces in the key" => "are easy!", "floats too" => "66.6"'::hstore|, + [] + ) + + assert [[%{"this is true" => "true", "though not this" => "false"}]] = + query( + ~s|SELECT '"this is true" => "true", "though not this" => "false"'::hstore|, + [] + ) end test "encode basic types", context do @@ -535,14 +674,29 @@ defmodule QueryTest do end test "encode custom numerics", context do - assert [[%Decimal{sign: 1, coef: 1500, exp: 0}]] == query("SELECT $1::numeric", [Decimal.from_float(1500.0)]) - assert [[%Decimal{sign: 1, coef: 1, exp: 0}]] == query("SELECT $1::numeric", [Decimal.new(1, 1, 0)]) - assert [[%Decimal{sign: 1, coef: 10, exp: 0}]] == query("SELECT $1::numeric", [Decimal.new(1, 1, 1)]) - assert [[%Decimal{sign: 1, coef: 100, exp: 0}]] == query("SELECT $1::numeric", [Decimal.new(1, 1, 2)]) - assert [[%Decimal{sign: 1, coef: 1000, exp: 0}]] == query("SELECT $1::numeric", [Decimal.new(1, 1, 3)]) - assert [[%Decimal{sign: 1, coef: 10000, exp: 0}]] == query("SELECT $1::numeric", [Decimal.new(1, 1, 4)]) - assert [[%Decimal{sign: 1, coef: 100000, exp: 0}]] == query("SELECT $1::numeric", [Decimal.new(1, 1, 5)]) - assert [[%Decimal{sign: 1, coef: 1, exp: -5}]] == query("SELECT $1::numeric", [Decimal.new(1, 1, -5)]) + assert [[%Decimal{sign: 1, coef: 1500, exp: 0}]] == + query("SELECT $1::numeric", [Decimal.from_float(1500.0)]) + + assert [[%Decimal{sign: 1, coef: 1, exp: 0}]] == + query("SELECT $1::numeric", [Decimal.new(1, 1, 0)]) + + assert [[%Decimal{sign: 1, coef: 10, exp: 0}]] == + query("SELECT $1::numeric", [Decimal.new(1, 1, 1)]) + + assert [[%Decimal{sign: 1, coef: 100, exp: 0}]] == + query("SELECT $1::numeric", [Decimal.new(1, 1, 2)]) + + assert [[%Decimal{sign: 1, coef: 1000, exp: 0}]] == + query("SELECT $1::numeric", [Decimal.new(1, 1, 3)]) + + assert [[%Decimal{sign: 1, coef: 10000, exp: 0}]] == + query("SELECT $1::numeric", [Decimal.new(1, 1, 4)]) + + assert [[%Decimal{sign: 1, coef: 100_000, exp: 0}]] == + query("SELECT $1::numeric", [Decimal.new(1, 1, 5)]) + + assert [[%Decimal{sign: 1, coef: 1, exp: -5}]] == + query("SELECT $1::numeric", [Decimal.new(1, 1, -5)]) end test "encode enforces bounds on integers", context do @@ -553,41 +707,53 @@ defmodule QueryTest do assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int2", [-32768 - 1])) # int4's range is -2147483648 to +2147483647 - assert [[-2147483648]] = query("SELECT $1::int4", [-2147483648]) - assert [[2147483647]] = query("SELECT $1::int4", [2147483647]) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int4", [2147483647 + 1])) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int4", [-2147483648 - 1])) + assert [[-2_147_483_648]] = query("SELECT $1::int4", [-2_147_483_648]) + assert [[2_147_483_647]] = query("SELECT $1::int4", [2_147_483_647]) + + assert %DBConnection.EncodeError{} = + catch_error(query("SELECT $1::int4", [2_147_483_647 + 1])) + + assert %DBConnection.EncodeError{} = + catch_error(query("SELECT $1::int4", [-2_147_483_648 - 1])) # int8's range is -9223372036854775808 to 9223372036854775807 - assert [[-9223372036854775808]] = query("SELECT $1::int8", [-9223372036854775808]) - assert [[9223372036854775807]] = query("SELECT $1::int8", [9223372036854775807]) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int8", [9223372036854775807 + 1])) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int8", [-9223372036854775808 - 1])) + assert [[-9_223_372_036_854_775_808]] = query("SELECT $1::int8", [-9_223_372_036_854_775_808]) + assert [[9_223_372_036_854_775_807]] = query("SELECT $1::int8", [9_223_372_036_854_775_807]) + + assert %DBConnection.EncodeError{} = + catch_error(query("SELECT $1::int8", [9_223_372_036_854_775_807 + 1])) + + assert %DBConnection.EncodeError{} = + catch_error(query("SELECT $1::int8", [-9_223_372_036_854_775_808 - 1])) end test "encode uuid", context do - uuid = <<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>> + uuid = <<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15>> assert [[^uuid]] = query("SELECT $1::uuid", [uuid]) end test "encode interval", context do assert [[%Postgrex.Interval{months: 0, days: 0, secs: 0}]] = - query("SELECT $1::interval", [%Postgrex.Interval{months: 0, days: 0, secs: 0}]) + query("SELECT $1::interval", [%Postgrex.Interval{months: 0, days: 0, secs: 0}]) + assert [[%Postgrex.Interval{months: 100, days: 0, secs: 0}]] = - query("SELECT $1::interval", [%Postgrex.Interval{months: 100, days: 0, secs: 0}]) + query("SELECT $1::interval", [%Postgrex.Interval{months: 100, days: 0, secs: 0}]) + assert [[%Postgrex.Interval{months: 0, days: 100, secs: 0}]] = - query("SELECT $1::interval", [%Postgrex.Interval{months: 0, days: 100, secs: 0}]) + query("SELECT $1::interval", [%Postgrex.Interval{months: 0, days: 100, secs: 0}]) + assert [[%Postgrex.Interval{months: 0, days: 0, secs: 100}]] = - query("SELECT $1::interval", [%Postgrex.Interval{months: 0, days: 0, secs: 100}]) + query("SELECT $1::interval", [%Postgrex.Interval{months: 0, days: 0, secs: 100}]) + assert [[%Postgrex.Interval{months: 14, days: 40, secs: 10920}]] = - query("SELECT $1::interval", [%Postgrex.Interval{months: 14, days: 40, secs: 10920}]) + query("SELECT $1::interval", [%Postgrex.Interval{months: 14, days: 40, secs: 10920}]) end test "encode arrays", context do assert [[[]]] = query("SELECT $1::integer[]", [[]]) assert [[[1]]] = query("SELECT $1::integer[]", [[1]]) - assert [[[1,2]]] = query("SELECT $1::integer[]", [[1,2]]) - assert [[[[0],[1]]]] = query("SELECT $1::integer[]", [[[0],[1]]]) + assert [[[1, 2]]] = query("SELECT $1::integer[]", [[1, 2]]) + assert [[[[0], [1]]]] = query("SELECT $1::integer[]", [[[0], [1]]]) assert [[[[0]]]] = query("SELECT $1::integer[]", [[[0]]]) assert [[[1, nil, 3]]] = query("SELECT $1::integer[]", [[1, nil, 3]]) end @@ -606,58 +772,259 @@ defmodule QueryTest do @tag min_pg_version: "9.2" test "encode range", context do assert [[%Postgrex.Range{lower: 1, upper: 4, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT $1::int4range", [%Postgrex.Range{lower: 1, upper: 3, lower_inclusive: true, upper_inclusive: true}]) - assert [[%Postgrex.Range{lower: :unbound, upper: 6, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT $1::int4range", [%Postgrex.Range{lower: :unbound, upper: 5, lower_inclusive: false, upper_inclusive: true}]) - assert [[%Postgrex.Range{lower: 3, upper: :unbound, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT $1::int4range", [%Postgrex.Range{lower: 3, upper: :unbound, lower_inclusive: true, upper_inclusive: true}]) + query("SELECT $1::int4range", [ + %Postgrex.Range{lower: 1, upper: 3, lower_inclusive: true, upper_inclusive: true} + ]) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: 6, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = + query("SELECT $1::int4range", [ + %Postgrex.Range{ + lower: :unbound, + upper: 5, + lower_inclusive: false, + upper_inclusive: true + } + ]) + + assert [ + [ + %Postgrex.Range{ + lower: 3, + upper: :unbound, + lower_inclusive: true, + upper_inclusive: false + } + ] + ] = + query("SELECT $1::int4range", [ + %Postgrex.Range{ + lower: 3, + upper: :unbound, + lower_inclusive: true, + upper_inclusive: true + } + ]) + assert [[%Postgrex.Range{lower: 4, upper: 5, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT $1::int4range", [%Postgrex.Range{lower: 3, upper: 5, lower_inclusive: false, upper_inclusive: false}]) - assert [[%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT $1::int4range", [%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]) - assert [[%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT $1::int4range", [%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: true, upper_inclusive: true}]) + query("SELECT $1::int4range", [ + %Postgrex.Range{lower: 3, upper: 5, lower_inclusive: false, upper_inclusive: false} + ]) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = + query("SELECT $1::int4range", [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ]) - assert [[%Postgrex.Range{lower: :empty, upper: :empty, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT $1::int4range", [%Postgrex.Range{lower: :empty, upper: :empty, lower_inclusive: true, upper_inclusive: true}]) + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = + query("SELECT $1::int4range", [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: true, + upper_inclusive: true + } + ]) + + assert [ + [ + %Postgrex.Range{ + lower: :empty, + upper: :empty, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = + query("SELECT $1::int4range", [ + %Postgrex.Range{ + lower: :empty, + upper: :empty, + lower_inclusive: true, + upper_inclusive: true + } + ]) assert [[%Postgrex.Range{lower: 1, upper: 4, lower_inclusive: true, upper_inclusive: false}]] = - query("SELECT $1::int8range", [%Postgrex.Range{lower: 1, upper: 3, lower_inclusive: true, upper_inclusive: true}]) + query("SELECT $1::int8range", [ + %Postgrex.Range{lower: 1, upper: 3, lower_inclusive: true, upper_inclusive: true} + ]) + + assert [ + [ + %Postgrex.Range{ + lower: Decimal.new("1.2"), + upper: Decimal.new("3.4"), + lower_inclusive: true, + upper_inclusive: true + } + ] + ] == + query("SELECT $1::numrange", [ + %Postgrex.Range{ + lower: Decimal.new("1.2"), + upper: Decimal.new("3.4"), + lower_inclusive: true, + upper_inclusive: true + } + ]) - assert [[%Postgrex.Range{lower: Decimal.new("1.2"), upper: Decimal.new("3.4"), lower_inclusive: true, upper_inclusive: true}]] == - query("SELECT $1::numrange", [%Postgrex.Range{lower: Decimal.new("1.2"), upper: Decimal.new("3.4"), lower_inclusive: true, upper_inclusive: true}]) + assert [ + [ + %Postgrex.Range{ + lower: %Date{year: 2014, month: 1, day: 1}, + upper: %Date{year: 2015, month: 1, day: 1} + } + ] + ] = + query("SELECT $1::daterange", [ + %Postgrex.Range{ + lower: %Date{year: 2014, month: 1, day: 1}, + upper: %Date{year: 2014, month: 12, day: 31} + } + ]) - assert [[%Postgrex.Range{lower: %Date{year: 2014, month: 1, day: 1}, upper: %Date{year: 2015, month: 1, day: 1}}]] = - query("SELECT $1::daterange", [%Postgrex.Range{lower: %Date{year: 2014, month: 1, day: 1}, upper: %Date{year: 2014, month: 12, day: 31}}]) assert [[%Postgrex.Range{lower: :unbound, upper: %Date{year: 2015, month: 1, day: 1}}]] = - query("SELECT $1::daterange", [%Postgrex.Range{lower: :unbound, upper: %Date{year: 2014, month: 12, day: 31}}]) + query("SELECT $1::daterange", [ + %Postgrex.Range{lower: :unbound, upper: %Date{year: 2014, month: 12, day: 31}} + ]) + assert [[%Postgrex.Range{lower: %Date{year: 2014, month: 1, day: 1}, upper: :unbound}]] = - query("SELECT $1::daterange", [%Postgrex.Range{lower: %Date{year: 2014, month: 1, day: 1}, upper: :unbound}]) - assert [[%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT $1::daterange", [%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]) - assert [[%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: false, upper_inclusive: false}]] = - query("SELECT $1::daterange", [%Postgrex.Range{lower: :unbound, upper: :unbound, lower_inclusive: true, upper_inclusive: true}]) + query("SELECT $1::daterange", [ + %Postgrex.Range{lower: %Date{year: 2014, month: 1, day: 1}, upper: :unbound} + ]) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = + query("SELECT $1::daterange", [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ]) + + assert [ + [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: false, + upper_inclusive: false + } + ] + ] = + query("SELECT $1::daterange", [ + %Postgrex.Range{ + lower: :unbound, + upper: :unbound, + lower_inclusive: true, + upper_inclusive: true + } + ]) end @tag min_pg_version: "9.2" test "encode enforces bounds on integer ranges", context do # int4's range is -2147483648 to +2147483647 - assert [[%Postgrex.Range{lower: -2147483648}]] = query("SELECT $1::int4range", [%Postgrex.Range{lower: -2147483648}]) - assert [[%Postgrex.Range{upper: 2147483647}]] = query("SELECT $1::int4range", [%Postgrex.Range{upper: 2147483647, upper_inclusive: false}]) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int4range", [%Postgrex.Range{lower: -2147483649}])) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int4range", [%Postgrex.Range{upper: 2147483648}])) + assert [[%Postgrex.Range{lower: -2_147_483_648}]] = + query("SELECT $1::int4range", [%Postgrex.Range{lower: -2_147_483_648}]) + + assert [[%Postgrex.Range{upper: 2_147_483_647}]] = + query("SELECT $1::int4range", [ + %Postgrex.Range{upper: 2_147_483_647, upper_inclusive: false} + ]) + + assert %DBConnection.EncodeError{} = + catch_error(query("SELECT $1::int4range", [%Postgrex.Range{lower: -2_147_483_649}])) + + assert %DBConnection.EncodeError{} = + catch_error(query("SELECT $1::int4range", [%Postgrex.Range{upper: 2_147_483_648}])) # int8's range is -9223372036854775808 to 9223372036854775807 - assert [[%Postgrex.Range{lower: -9223372036854775807}]] = query("SELECT $1::int8range", [%Postgrex.Range{lower: -9223372036854775807}]) - assert [[%Postgrex.Range{upper: 9223372036854775806}]] = query("SELECT $1::int8range", [%Postgrex.Range{upper: 9223372036854775806, upper_inclusive: false}]) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int8range", [%Postgrex.Range{lower: -9223372036854775809}])) - assert %DBConnection.EncodeError{} = catch_error(query("SELECT $1::int8range", [%Postgrex.Range{upper: 9223372036854775808}])) + assert [[%Postgrex.Range{lower: -9_223_372_036_854_775_807}]] = + query("SELECT $1::int8range", [%Postgrex.Range{lower: -9_223_372_036_854_775_807}]) + + assert [[%Postgrex.Range{upper: 9_223_372_036_854_775_806}]] = + query("SELECT $1::int8range", [ + %Postgrex.Range{upper: 9_223_372_036_854_775_806, upper_inclusive: false} + ]) + + assert %DBConnection.EncodeError{} = + catch_error( + query("SELECT $1::int8range", [%Postgrex.Range{lower: -9_223_372_036_854_775_809}]) + ) + + assert %DBConnection.EncodeError{} = + catch_error( + query("SELECT $1::int8range", [%Postgrex.Range{upper: 9_223_372_036_854_775_808}]) + ) end @tag min_pg_version: "9.0" test "encode hstore", context do - assert [[%{"name" => "Frank", "bubbles" => "7", "limit" => nil, "chillin"=> "true", "fratty"=> "false", "atom" => "bomb"}]] = - query ~s(SELECT $1::hstore), [%{"name" => "Frank", "bubbles" => "7", "limit" => nil, "chillin"=> "true", "fratty"=> "false", "atom" => "bomb"}] + assert [ + [ + %{ + "name" => "Frank", + "bubbles" => "7", + "limit" => nil, + "chillin" => "true", + "fratty" => "false", + "atom" => "bomb" + } + ] + ] = + query(~s(SELECT $1::hstore), [ + %{ + "name" => "Frank", + "bubbles" => "7", + "limit" => nil, + "chillin" => "true", + "fratty" => "false", + "atom" => "bomb" + } + ]) end @tag min_pg_version: "9.0" @@ -742,33 +1109,49 @@ defmodule QueryTest do assert [["110"]] == query("SELECT $1::bit(3)::text", [<<1::1, 1::1, 0::1>>]) assert [["110"]] == query("SELECT $1::varbit::text", [<<1::1, 1::1, 0::1>>]) assert [["101"]] == query("SELECT $1::bit(3)::text", [<<1::1, 0::1, 1::1>>]) + assert [["11010"]] == - query("SELECT $1::bit(5)::text", [<<1::1, 1::1, 0::1, 1::1>>]) + query("SELECT $1::bit(5)::text", [<<1::1, 1::1, 0::1, 1::1>>]) + assert [["10000000101"]] == - query("SELECT $1::bit(11)::text", - [<<1::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 1::1,0::1,1::1>>]) + query( + "SELECT $1::bit(11)::text", + [<<1::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 1::1, 0::1, 1::1>>] + ) + assert [["0000000000000000101"]] == - query("SELECT $1::bit(19)::text", - [<<0::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 0::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 1::1,0::1,1::1>>]) + query( + "SELECT $1::bit(19)::text", + [ + <<0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, + 0::1, 0::1, 0::1, 1::1, 0::1, 1::1>> + ] + ) + assert [["1000000000000000101"]] == - query("SELECT $1::bit(19)::text", - [<<1::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 0::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 1::1,0::1,1::1>>]) + query( + "SELECT $1::bit(19)::text", + [ + <<1::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, + 0::1, 0::1, 0::1, 1::1, 0::1, 1::1>> + ] + ) + assert [["1000000110000000101"]] == - query("SELECT $1::bit(19)::text", - [<<1::1,0::1,0::1,0::1,0::1,0::1,0::1,1::1, - 1::1,0::1,0::1,0::1,0::1,0::1,0::1,0::1, - 1::1,0::1,1::1>>]) + query( + "SELECT $1::bit(19)::text", + [ + <<1::1, 0::1, 0::1, 0::1, 0::1, 0::1, 0::1, 1::1, 1::1, 0::1, 0::1, 0::1, 0::1, + 0::1, 0::1, 0::1, 1::1, 0::1, 1::1>> + ] + ) end test "fail on encode arrays", context do assert_raise ArgumentError, "nested lists must have lists with matching lengths", fn -> - query("SELECT $1::integer[]", [[[1], [1,2]]]) + query("SELECT $1::integer[]", [[[1], [1, 2]]]) end + assert [[42]] = query("SELECT 42", []) end @@ -809,7 +1192,7 @@ defmodule QueryTest do test "multi row result struct with decode mapper", context do map = &Enum.map(&1, fn x -> x * 2 end) - assert [[2,4], [6,8]] = query("VALUES (1, 2), (3, 4)", [], decode_mapper: map) + assert [[2, 4], [6, 8]] = query("VALUES (1, 2), (3, 4)", [], decode_mapper: map) end test "insert", context do @@ -873,7 +1256,7 @@ defmodule QueryTest do assert {:ok, %Postgrex.Result{rows: [[41]]}} = Postgrex.query(pid2, "SELECT 41", []) end - test "error codes are translated", context do + test "error codes are translated", context do assert %Postgrex.Error{postgres: %{code: :syntax_error}} = query("wat", []) end @@ -884,22 +1267,25 @@ defmodule QueryTest do test "connection works after failure in binding state", context do assert %Postgrex.Error{postgres: %{code: :invalid_text_representation}} = - query("insert into uniques values (CAST($1::text AS int))", ["invalid"]) + query("insert into uniques values (CAST($1::text AS int))", ["invalid"]) + assert [[42]] = query("SELECT 42", []) end test "connection works after failure in executing state", context do assert %Postgrex.Error{postgres: %{code: :unique_violation}} = - query("insert into uniques values (1), (1);", []) + query("insert into uniques values (1), (1);", []) + assert [[42]] = query("SELECT 42", []) end test "connection works after failure during transaction", context do assert :ok = query("BEGIN", []) + assert %Postgrex.Error{postgres: %{code: :unique_violation}} = - query("insert into uniques values (1), (1);", []) - assert %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}} = - query("SELECT 42", []) + query("insert into uniques values (1), (1);", []) + + assert %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}} = query("SELECT 42", []) assert :ok = query("ROLLBACK", []) assert [[42]] = query("SELECT 42", []) end @@ -919,10 +1305,8 @@ defmodule QueryTest do test "connection works after failure in execute", context do %Postgrex.Query{} = query = prepare("unique", "insert into uniques values (1), (1);") - assert %Postgrex.Error{postgres: %{code: :unique_violation}} = - execute(query, []) - assert %Postgrex.Error{postgres: %{code: :unique_violation}} = - execute(query, []) + assert %Postgrex.Error{postgres: %{code: :unique_violation}} = execute(query, []) + assert %Postgrex.Error{postgres: %{code: :unique_violation}} = execute(query, []) assert [[42]] = query("SELECT 42", []) end @@ -946,8 +1330,10 @@ defmodule QueryTest do test "connection reuses prepared query after failure in executing state", context do %Postgrex.Query{} = query = prepare("", "SELECT 41") + assert %Postgrex.Error{postgres: %{code: :unique_violation}} = - query("insert into uniques values (1), (1);", []) + query("insert into uniques values (1), (1);", []) + assert [[41]] = execute(query, []) end @@ -967,10 +1353,11 @@ defmodule QueryTest do test "async test", context do self_pid = self() + Enum.each(1..10, fn _ -> - spawn_link fn -> - send self_pid, query("SELECT pg_sleep(0.05)", []) - end + spawn_link(fn -> + send(self_pid, query("SELECT pg_sleep(0.05)", [])) + end) end) assert [[42]] = query("SELECT 42", []) @@ -981,13 +1368,15 @@ defmodule QueryTest do end test "raise when trying to execute unprepared query", context do - assert_raise ArgumentError, ~r/has not been prepared/, - fn -> execute(%Postgrex.Query{name: "hi", statement: "BEGIN"}, []) end + assert_raise ArgumentError, ~r/has not been prepared/, fn -> + execute(%Postgrex.Query{name: "hi", statement: "BEGIN"}, []) + end end test "raise when trying to parse prepared query", context do - assert_raise ArgumentError, ~r/has already been prepared/, - fn -> DBConnection.Query.parse(prepare("SELECT 42", []), []) end + assert_raise ArgumentError, ~r/has already been prepared/, fn -> + DBConnection.Query.parse(prepare("SELECT 42", []), []) + end end test "query struct interpolates to statement" do @@ -996,18 +1385,20 @@ defmodule QueryTest do test "connection_id", context do assert {:ok, %Postgrex.Result{connection_id: connection_id, rows: [[backend_pid]]}} = - Postgrex.query(context[:pid], "SELECT pg_backend_pid()", []) + Postgrex.query(context[:pid], "SELECT pg_backend_pid()", []) + assert is_integer(connection_id) assert connection_id == backend_pid assert {:error, %Postgrex.Error{connection_id: connection_id}} = - Postgrex.query(context[:pid], "FOO BAR", []) + Postgrex.query(context[:pid], "FOO BAR", []) + assert is_integer(connection_id) end test "empty query", context do assert %Postgrex.Result{command: nil, rows: nil, num_rows: 0} = - Postgrex.query!(context[:pid], "", []) + Postgrex.query!(context[:pid], "", []) end test "query from child spec", %{options: opts, test: test} do @@ -1017,7 +1408,7 @@ defmodule QueryTest do end test "query before and after idle ping" do - opts = [ database: "postgrex_test", backoff_type: :stop, idle_interval: 1] + opts = [database: "postgrex_test", backoff_type: :stop, idle_interval: 1] {:ok, pid} = P.start_link(opts) assert {:ok, _} = P.query(pid, "SELECT 42", []) :timer.sleep(20) @@ -1034,10 +1425,10 @@ defmodule QueryTest do message = "postgresql protocol can not handle 65536 parameters, the maximum is 65535" assert capture_log(fn -> - %Postgrex.QueryError{message: ^message} = query(query, params) - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ message + %Postgrex.QueryError{message: ^message} = query(query, params) + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ message end test "COPY FROM STDIN disconnects", context do @@ -1045,11 +1436,11 @@ defmodule QueryTest do message = "trying to copy in but no copy data to send" assert capture_log(fn -> - assert %RuntimeError{message: runtime} = query("COPY uniques FROM STDIN", []) - assert runtime =~ message - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ message + assert %RuntimeError{message: runtime} = query("COPY uniques FROM STDIN", []) + assert runtime =~ message + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ message end test "COPY TO STDOUT", context do @@ -1060,12 +1451,12 @@ defmodule QueryTest do test "COPY TO STDOUT with decoder_mapper", context do opts = [decode_mapper: &String.split/1] - assert [["1","2"], ["3","4"]] = query("COPY (VALUES (1, 2), (3, 4)) TO STDOUT", [], opts) + assert [["1", "2"], ["3", "4"]] = query("COPY (VALUES (1, 2), (3, 4)) TO STDOUT", [], opts) end test "receive packet with remainder greater than 64MB", context do # to ensure remainder is more than 64MB use 64MBx2+1 - big_binary = :binary.copy(<<1>>, 128*1024*1024+1) + big_binary = :binary.copy(<<1>>, 128 * 1024 * 1024 + 1) assert [[binary]] = query("SELECT $1::bytea;", [big_binary]) assert byte_size(binary) == 128 * 1024 * 1024 + 1 end @@ -1074,12 +1465,11 @@ defmodule QueryTest do Process.flag(:trap_exit, true) assert {:ok, pid} = P.start_link([idle_interval: 10] ++ context[:options]) - %Postgrex.Result{connection_id: connection_id} = - Postgrex.query!(pid, "SELECT 42", []) + %Postgrex.Result{connection_id: connection_id} = Postgrex.query!(pid, "SELECT 42", []) assert capture_log(fn -> - assert [[true]] = query("SELECT pg_terminate_backend($1)", [connection_id]) - assert_receive {:EXIT, ^pid, :killed}, 5000 - end) =~ "** (Postgrex.Error) FATAL 57P01 (admin_shutdown)" + assert [[true]] = query("SELECT pg_terminate_backend($1)", [connection_id]) + assert_receive {:EXIT, ^pid, :killed}, 5000 + end) =~ "** (Postgrex.Error) FATAL 57P01 (admin_shutdown)" end end diff --git a/test/schema_test.exs b/test/schema_test.exs index 857337060..f1a8b7181 100644 --- a/test/schema_test.exs +++ b/test/schema_test.exs @@ -4,24 +4,60 @@ defmodule SchemaTest do alias Postgrex, as: P setup do - opts = [ database: "postgrex_test_with_schemas" ] + opts = [database: "postgrex_test_with_schemas"] {:ok, pid} = P.start_link(opts) {:ok, [pid: pid]} end - @tag min_pg_version: "9.1" test "encode hstore", context do - assert [[%{"name" => "Frank", "bubbles" => "7", "limit" => nil, "chillin"=> "true", "fratty"=> "false", "atom" => "bomb"}]] = - query ~s(SELECT $1::test.hstore), [%{"name" => "Frank", "bubbles" => "7", "limit" => nil, "chillin"=> "true", "fratty"=> "false", "atom" => "bomb"}] + assert [ + [ + %{ + "name" => "Frank", + "bubbles" => "7", + "limit" => nil, + "chillin" => "true", + "fratty" => "false", + "atom" => "bomb" + } + ] + ] = + query(~s(SELECT $1::test.hstore), [ + %{ + "name" => "Frank", + "bubbles" => "7", + "limit" => nil, + "chillin" => "true", + "fratty" => "false", + "atom" => "bomb" + } + ]) end @tag min_pg_version: "9.1" test "decode hstore inside a schema", context do assert [[%{}]] = query(~s{SELECT ''::test.hstore}, []) - assert [[%{"Bubbles" => "7", "Name" => "Frank"}]] = query(~s{SELECT '"Name" => "Frank", "Bubbles" => "7"'::test.hstore}, []) - assert [[%{"non_existant" => nil, "present" => "&accounted_for"}]] = query(~s{SELECT '"non_existant" => NULL, "present" => "&accounted_for"'::test.hstore}, []) - assert [[%{"spaces in the key" => "are easy!", "floats too" => "66.6"}]] = query(~s{SELECT '"spaces in the key" => "are easy!", "floats too" => "66.6"'::test.hstore}, []) - assert [[%{"this is true" => "true", "though not this" => "false"}]] = query(~s{SELECT '"this is true" => "true", "though not this" => "false"'::test.hstore}, []) + + assert [[%{"Bubbles" => "7", "Name" => "Frank"}]] = + query(~s{SELECT '"Name" => "Frank", "Bubbles" => "7"'::test.hstore}, []) + + assert [[%{"non_existant" => nil, "present" => "&accounted_for"}]] = + query( + ~s{SELECT '"non_existant" => NULL, "present" => "&accounted_for"'::test.hstore}, + [] + ) + + assert [[%{"spaces in the key" => "are easy!", "floats too" => "66.6"}]] = + query( + ~s{SELECT '"spaces in the key" => "are easy!", "floats too" => "66.6"'::test.hstore}, + [] + ) + + assert [[%{"this is true" => "true", "though not this" => "false"}]] = + query( + ~s{SELECT '"this is true" => "true", "though not this" => "false"'::test.hstore}, + [] + ) end end diff --git a/test/stream_test.exs b/test/stream_test.exs index 6d5819099..9d32431f9 100644 --- a/test/stream_test.exs +++ b/test/stream_test.exs @@ -18,45 +18,55 @@ defmodule StreamTest do test "MAY take part of stream", context do query = prepare("", "SELECT * FROM generate_series(1, 3)") - transaction(fn(conn) -> - assert [[[1]]] = stream(query, [], max_rows: 1) - |> Stream.map(fn(%Result{rows: rows}) -> rows end) - |> Enum.take(1) + + transaction(fn conn -> + assert [[[1]]] = + stream(query, [], max_rows: 1) + |> Stream.map(fn %Result{rows: rows} -> rows end) + |> Enum.take(1) end) end test "streams query in chunks", context do query = prepare("", "SELECT * FROM generate_series(1, 3)") - transaction(fn(conn) -> - assert [[[1], [2]], [[3]]] = stream(query, [], max_rows: 2) - |> Stream.map(fn(%Result{rows: rows}) -> rows end) - |> Enum.to_list() + + transaction(fn conn -> + assert [[[1], [2]], [[3]]] = + stream(query, [], max_rows: 2) + |> Stream.map(fn %Result{rows: rows} -> rows end) + |> Enum.to_list() end) end test "results contain num rows", context do query = prepare("", "SELECT * FROM generate_series(1, 2)") - transaction(fn(conn) -> - assert [%{command: :stream, rows: [[1]], num_rows: 1}, - %{command: :stream, rows: [[2]], num_rows: 1}, - %{command: :select, rows: [], num_rows: 0}] = - stream(query, [], max_rows: 1) - |> Enum.to_list() - - assert [%{command: :stream, rows: [[1], [2]], num_rows: 2}, - %{command: :select, rows: [], num_rows: 0}] = - stream(query, [], max_rows: 2) - |> Enum.to_list() + + transaction(fn conn -> + assert [ + %{command: :stream, rows: [[1]], num_rows: 1}, + %{command: :stream, rows: [[2]], num_rows: 1}, + %{command: :select, rows: [], num_rows: 0} + ] = + stream(query, [], max_rows: 1) + |> Enum.to_list() + + assert [ + %{command: :stream, rows: [[1], [2]], num_rows: 2}, + %{command: :select, rows: [], num_rows: 0} + ] = + stream(query, [], max_rows: 2) + |> Enum.to_list() assert [%{command: :select, rows: [[1], [2]], num_rows: 2}] = - stream(query, [], max_rows: 3) - |> Enum.to_list() + stream(query, [], max_rows: 3) + |> Enum.to_list() end) end test "prepare, stream and close", context do query = prepare("S42", "SELECT 42") - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Result{rows: [[42]]}] = stream(query, []) |> Enum.take(1) assert [%Result{rows: [[42]]}] = stream(query, []) |> Enum.take(1) assert Postgrex.close(conn, query) == :ok @@ -67,7 +77,8 @@ defmodule StreamTest do query42 = prepare("DUPLICATE", "SELECT 42") :ok = close(query42) query41 = prepare("DUPLICATE", "SELECT 41") - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Result{rows: [[42]]}] = stream(query42, []) |> Enum.take(1) assert [%Result{rows: [[41]]}] = stream(query41, []) |> Enum.take(1) @@ -77,7 +88,8 @@ defmodule StreamTest do test "prepare, close and stream", context do query = prepare("S42", "SELECT 42") :ok = close(query) - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Result{rows: [[42]]}] = stream(query, []) |> Enum.take(1) end) end @@ -85,7 +97,8 @@ defmodule StreamTest do @tag prepare: :unnamed test "stream named is unnamed when named not allowed", context do assert (%Postgrex.Query{name: ""} = query) = prepare("42", "SELECT 42") - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Result{rows: [[42]]}] = stream(query, []) |> Enum.take(1) assert [%Result{rows: [[42]]}] = stream(query, []) |> Enum.take(1) assert :ok = Postgrex.close(conn, query) @@ -97,19 +110,20 @@ defmodule StreamTest do query = prepare("S42", "SELECT 42") {:ok, pid2} = Postgrex.start_link(context[:options]) - Postgrex.transaction(pid2, fn(conn) -> + + Postgrex.transaction(pid2, fn conn -> assert [%Result{rows: [[42]]}] = stream(query, []) |> Enum.take(1) assert {:ok, %Result{rows: [[41]]}} = Postgrex.query(conn, "SELECT 41", []) end) end - test "connection works after failure in binding state", context do query = prepare("", "insert into uniques values (CAST($1::text AS int))") - transaction(fn(conn) -> - assert_raise Postgrex.Error, ~r"\(invalid_text_representation\)", - fn -> stream(query, ["EBADF"]) |> Enum.take(1) end + transaction(fn conn -> + assert_raise Postgrex.Error, ~r"\(invalid_text_representation\)", fn -> + stream(query, ["EBADF"]) |> Enum.take(1) + end end) assert [[42]] = query("SELECT 42", []) @@ -118,9 +132,10 @@ defmodule StreamTest do test "connection works after failure in executing state", context do query = prepare("", "insert into uniques values (1), (1)") - transaction(fn(conn) -> - assert_raise Postgrex.Error, ~r"\(unique_violation\)", - fn -> stream(query, []) |> Enum.take(1) end + transaction(fn conn -> + assert_raise Postgrex.Error, ~r"\(unique_violation\)", fn -> + stream(query, []) |> Enum.take(1) + end end) assert [[42]] = query("SELECT 42", []) @@ -128,7 +143,8 @@ defmodule StreamTest do test "connection reuses prepared query after query", context do query = prepare("", "SELECT 41") - transaction(fn(conn) -> + + transaction(fn conn -> assert %Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) assert [%Result{rows: [[41]]}] = stream(query, []) |> Enum.take(1) end) @@ -137,7 +153,8 @@ defmodule StreamTest do test "connection forces prepare on stream after prepare of same name", context do query41 = prepare("", "SELECT 41") query42 = prepare("", "SELECT 42") - transaction(fn(conn) -> + + transaction(fn conn -> assert %Result{rows: [[42]]} = Postgrex.execute!(conn, query42, []) assert [%Result{rows: [[41]]}] = stream(query41, []) |> Enum.take(1) end) @@ -146,26 +163,30 @@ defmodule StreamTest do test "raise when trying to stream unprepared query", context do query = %Postgrex.Query{name: "ENOENT", statement: "SELECT 42"} - transaction(fn(conn) -> - assert_raise ArgumentError, ~r/has not been prepared/, - fn -> stream(query, []) |> Enum.take(1) end + transaction(fn conn -> + assert_raise ArgumentError, ~r/has not been prepared/, fn -> + stream(query, []) |> Enum.take(1) + end end) end test "connection_id", context do query = prepare("", "SELECT pg_backend_pid()") - {:ok, connection_id} = transaction(fn(conn) -> - assert [%Result{connection_id: connection_id, rows: [[backend_pid]]}] = - stream(query, []) |> Enum.take(1) - assert is_integer(connection_id) - assert connection_id == backend_pid - connection_id - end) + + {:ok, connection_id} = + transaction(fn conn -> + assert [%Result{connection_id: connection_id, rows: [[backend_pid]]}] = + stream(query, []) |> Enum.take(1) + + assert is_integer(connection_id) + assert connection_id == backend_pid + connection_id + end) query = prepare("", "insert into uniques values (1), (1)") try do - transaction(fn(conn) -> stream(query, []) |> Enum.take(1) end) + transaction(fn conn -> stream(query, []) |> Enum.take(1) end) rescue err -> assert %Postgrex.Error{connection_id: ^connection_id} = err @@ -174,39 +195,40 @@ defmodule StreamTest do defp range(conn, name, x, y) do {:ok, q} = Postgrex.prepare(conn, name, "SELECT * FROM generate_series(CAST($1 as int), $2)") + stream(q, [x, y], max_rows: 1) - |> Stream.map(fn (res) -> :lists.flatten(res.rows) end) + |> Stream.map(fn res -> :lists.flatten(res.rows) end) end # nest two ranges in a transaction, first query has name1, second has name2 # defp range_x_range(conn, name1, name2, x, y) do - map = - fn - [x] -> range(conn, name2, 1, x) |> Enum.flat_map(&(&1)) - [] -> [] - end + map = fn + [x] -> range(conn, name2, 1, x) |> Enum.flat_map(& &1) + [] -> [] + end + conn |> range(name1, x, y) |> Stream.map(map) - |> Enum.to_list + |> Enum.to_list() end test "streams can be nested using named queries", context do - transaction(fn(conn) -> + transaction(fn conn -> assert [[1], [1, 2], []] = range_x_range(conn, "S1", "S2", 1, 2) end) end test "streams can be nested using unnamed queries", context do - transaction(fn(conn) -> + transaction(fn conn -> assert [[1], [1, 2], []] = range_x_range(conn, "", "", 1, 2) end) end @tag prepare: :unnamed test "streams can be nested using named queries when names not allowed", context do - transaction(fn(conn) -> + transaction(fn conn -> assert [[1], [1, 2], []] = range_x_range(conn, "S1", "S2", 1, 2) end) end @@ -215,18 +237,19 @@ defmodule StreamTest do q1 = Postgrex.prepare!(pid, name1, "SELECT * FROM generate_series(1, 2)") q2 = Postgrex.prepare!(pid, name2, "SELECT CAST($1 as int)") - Postgrex.transaction(pid, fn(conn) -> - map = - fn - %{rows: [[x]]} -> + Postgrex.transaction(pid, fn conn -> + map = fn + %{rows: [[x]]} -> stream(q2, [x], max_rows: 1) |> Enum.flat_map(fn res -> res.rows end) - %{rows: []} -> - [] - end + + %{rows: []} -> + [] + end + stream(q1, [], max_rows: 1) |> Stream.map(map) - |> Enum.to_list - |> :lists.flatten + |> Enum.to_list() + |> :lists.flatten() end) end @@ -245,67 +268,77 @@ defmodule StreamTest do test "COPY empty TO STDOUT", context do query = prepare("", "COPY uniques TO STDOUT") - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Postgrex.Result{command: :copy, rows: [], num_rows: 0}] = - stream(query, []) |> Enum.to_list() + stream(query, []) |> Enum.to_list() end) end test "COPY TO STDOUT", context do query1 = prepare("", "COPY (VALUES (1, 2)) TO STDOUT") query2 = prepare("", "COPY (VALUES (1, 2), (3, 4)) TO STDOUT") - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Postgrex.Result{rows: ["1\t2\n"], num_rows: 1}] = - stream(query1, []) |> Enum.to_list() + stream(query1, []) |> Enum.to_list() assert [%Postgrex.Result{rows: ["1\t2\n", "3\t4\n"], num_rows: 2}] = - stream(query2, [], [max_rows: 0]) |> Enum.to_list() + stream(query2, [], max_rows: 0) |> Enum.to_list() end) end test "COPY TO STDOUT with decoder_mapper", context do query2 = prepare("", "COPY (VALUES (1, 2), (3, 4)) TO STDOUT") - transaction(fn(conn) -> - assert [%Postgrex.Result{rows: [["1","2"], ["3","4"]]}] = - stream(query2, [], [decode_mapper: &String.split/1]) |> Enum.to_list() + + transaction(fn conn -> + assert [%Postgrex.Result{rows: [["1", "2"], ["3", "4"]]}] = + stream(query2, [], decode_mapper: &String.split/1) |> Enum.to_list() end) end test "COPY TO STDOUT with max_rows splitting", context do query1 = prepare("", "COPY (VALUES (1, 2)) TO STDOUT") query2 = prepare("", "COPY (VALUES (1, 2), (3, 4)) TO STDOUT") - transaction(fn(conn) -> - assert [%{command: :copy_stream, rows: ["1\t2\n"], num_rows: 1}, - %{command: :copy, rows: [], num_rows: 0}] = - stream(query1, [], [max_rows: 1]) |> Enum.to_list() - assert [%{command: :copy_stream, rows: ["1\t2\n"], num_rows: 1}, - %{command: :copy_stream, rows: ["3\t4\n"], num_rows: 1}, - %{command: :copy, rows: [], num_rows: 0}] = - stream(query2, [], [max_rows: 1]) |> Enum.to_list() + transaction(fn conn -> + assert [ + %{command: :copy_stream, rows: ["1\t2\n"], num_rows: 1}, + %{command: :copy, rows: [], num_rows: 0} + ] = stream(query1, [], max_rows: 1) |> Enum.to_list() - assert [%{command: :copy_stream, rows: ["1\t2\n", "3\t4\n"], num_rows: 2}, - %{command: :copy, rows: [], num_rows: 0}] = - stream(query2, [], [max_rows: 2]) |> Enum.to_list() + assert [ + %{command: :copy_stream, rows: ["1\t2\n"], num_rows: 1}, + %{command: :copy_stream, rows: ["3\t4\n"], num_rows: 1}, + %{command: :copy, rows: [], num_rows: 0} + ] = stream(query2, [], max_rows: 1) |> Enum.to_list() + + assert [ + %{command: :copy_stream, rows: ["1\t2\n", "3\t4\n"], num_rows: 2}, + %{command: :copy, rows: [], num_rows: 0} + ] = stream(query2, [], max_rows: 2) |> Enum.to_list() assert [%{command: :copy, rows: ["1\t2\n", "3\t4\n"], num_rows: 2}] = - stream(query2, [], [max_rows: 3]) |> Enum.to_list() + stream(query2, [], max_rows: 3) |> Enum.to_list() end) end test "COPY TO STDOUT with stream halting before copy done", context do query = prepare("", "COPY (VALUES (1, 2), (3, 4)) TO STDOUT") - assert transaction(fn(conn) -> - assert [%{command: :copy_stream, rows: ["1\t2\n"], num_rows: 1}] = - stream(query, [], [max_rows: 1]) |> Enum.take(1) - :hi - end) == {:ok, :hi} - - assert transaction(fn(conn) -> - assert [%{command: :copy_stream, rows: ["1\t2\n"], num_rows: 1}] = - stream(query, [], [max_rows: 1, mode: :savepoint]) |> Enum.take(1) - :hi - end) == {:ok, :hi} + + assert transaction(fn conn -> + assert [%{command: :copy_stream, rows: ["1\t2\n"], num_rows: 1}] = + stream(query, [], max_rows: 1) |> Enum.take(1) + + :hi + end) == {:ok, :hi} + + assert transaction(fn conn -> + assert [%{command: :copy_stream, rows: ["1\t2\n"], num_rows: 1}] = + stream(query, [], max_rows: 1, mode: :savepoint) |> Enum.take(1) + + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @@ -314,22 +347,21 @@ defmodule StreamTest do Process.flag(:trap_exit, true) query = prepare("", "COPY (VALUES (1, 2), (3, 4)) TO STDOUT") - transaction(fn(conn) -> - map = - fn _ -> - {:error, %RuntimeError{message: message}} = Postgrex.prepare(conn, "", "BEGIN") - assert message =~ "connection is locked" - true - end + transaction(fn conn -> + map = fn _ -> + {:error, %RuntimeError{message: message}} = Postgrex.prepare(conn, "", "BEGIN") + assert message =~ "connection is locked" + true + end assert capture_log(fn -> - assert_raise DBConnection.ConnectionError, ~r"connection is closed", fn -> - query |> stream([], [max_rows: 1]) |> Enum.map(map) - end + assert_raise DBConnection.ConnectionError, ~r"connection is closed", fn -> + query |> stream([], max_rows: 1) |> Enum.map(map) + end - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ "connection is locked copying to or from the database and can not prepare" + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ "connection is locked copying to or from the database and can not prepare" end) end @@ -337,21 +369,22 @@ defmodule StreamTest do Process.flag(:trap_exit, true) query = prepare("", "COPY uniques FROM STDIN") - transaction(fn(conn) -> + transaction(fn conn -> assert capture_log(fn -> - assert_raise RuntimeError, ~r"trying to copy in but no copy data to send", fn -> - query |> stream([]) |> Enum.to_list() - end + assert_raise RuntimeError, ~r"trying to copy in but no copy data to send", fn -> + query |> stream([]) |> Enum.to_list() + end - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ "is trying to copy in but no copy data to send" + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ "is trying to copy in but no copy data to send" end) end test "COPY empty FROM STDIN", context do query = prepare("", "COPY uniques FROM STDIN") - transaction(fn(conn) -> + + transaction(fn conn -> stream = stream(query, []) assert Enum.into([], stream) == stream assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) @@ -361,11 +394,12 @@ defmodule StreamTest do test "COPY FROM STDIN", context do query = prepare("", "COPY uniques FROM STDIN") - transaction(fn(conn) -> + + transaction(fn conn -> stream = stream(query, []) assert Enum.into(["2\n", "3\n4\n"], stream) == stream - stream = stream(query, [], [log: &send(self(), &1)]) + stream = stream(query, [], log: &send(self(), &1)) assert Enum.into(["5\n"], stream) == stream assert_received %DBConnection.LogEntry{} = entry @@ -373,47 +407,54 @@ defmodule StreamTest do assert {:ok, ^query, %Postgrex.Copy{query: ^query}} = entry.result assert_received %DBConnection.LogEntry{} = entry - assert (entry.query).query == query - assert {:ok, ^query, %{command: :copy_stream, rows: nil, num_rows: :copy_stream}} = entry.result + assert entry.query.query == query + + assert {:ok, ^query, %{command: :copy_stream, rows: nil, num_rows: :copy_stream}} = + entry.result assert_received %DBConnection.LogEntry{} = entry - assert (entry.query).query == query + assert entry.query.query == query assert {:ok, ^query, %{command: :copy, rows: nil, num_rows: 1}} = entry.result assert %Postgrex.Result{rows: [[2], [3], [4], [5]]} = - Postgrex.query!(conn, "SELECT * FROM uniques", []) + Postgrex.query!(conn, "SELECT * FROM uniques", []) Postgrex.rollback(conn, :done) end) - transaction(fn(conn) -> + transaction(fn conn -> stream = stream("COPY uniques FROM STDIN", []) assert Enum.into(["2\n", "3\n4\n"], stream) == stream assert %Postgrex.Result{rows: [[2], [3], [4]]} = - Postgrex.query!(conn, "SELECT * FROM uniques", []) + Postgrex.query!(conn, "SELECT * FROM uniques", []) + Postgrex.rollback(conn, :done) end) end test "COPY FROM STDIN halted", context do query = prepare("", "COPY uniques FROM STDIN") - transaction(fn(conn) -> + + transaction(fn conn -> _ = Postgrex.query!(conn, "SAVEPOINT bad_copy", []) stream = stream(query, []) - map = - fn - "3\n" -> raise "hello" - other -> other - end - assert_raise RuntimeError, "hello", - fn() -> Enum.into(Stream.map(["2\n", "3\n"], map), stream) end - assert_raise RuntimeError, "hello", - fn() -> Enum.into(Stream.map(["3\n", "4\n"], map), stream) end - - assert %Postgrex.Result{rows: [[2]]} = - Postgrex.query!(conn, "SELECT * FROM uniques", []) + + map = fn + "3\n" -> raise "hello" + other -> other + end + + assert_raise RuntimeError, "hello", fn -> + Enum.into(Stream.map(["2\n", "3\n"], map), stream) + end + + assert_raise RuntimeError, "hello", fn -> + Enum.into(Stream.map(["3\n", "4\n"], map), stream) + end + + assert %Postgrex.Result{rows: [[2]]} = Postgrex.query!(conn, "SELECT * FROM uniques", []) Postgrex.rollback(conn, :done) end) @@ -421,12 +462,15 @@ defmodule StreamTest do test "COPY FROM STDIN with savepoint", context do query = prepare("", "COPY uniques FROM STDIN") - transaction(fn(conn) -> - stream = stream(query, [], [mode: :savepoint]) + + transaction(fn conn -> + stream = stream(query, [], mode: :savepoint) assert Enum.into(["2\n", "3\n4\n"], stream) == stream assert Enum.into(["5\n"], stream) == stream + assert %Postgrex.Result{rows: [[2], [3], [4], [5]]} = - Postgrex.query!(conn, "SELECT * FROM uniques", []) + Postgrex.query!(conn, "SELECT * FROM uniques", []) + Postgrex.rollback(conn, :done) end) end @@ -435,7 +479,8 @@ defmodule StreamTest do query42 = prepare("DUPLICATE", "COPY uniques FROM STDIN") :ok = close(query42) query41 = prepare("DUPLICATE", "COPY uniques FROM STDIN WITH DELIMITER AS '\s'") - transaction(fn(conn) -> + + transaction(fn conn -> stream42 = stream(query42, []) assert Enum.into(["1\n"], stream42) == stream42 @@ -450,11 +495,12 @@ defmodule StreamTest do query42 = prepare("DUPLICATE", "COPY uniques FROM STDIN") :ok = close(query42) query41 = prepare("DUPLICATE", "COPY uniques FROM STDIN WITH DELIMITER AS '\s'") - transaction(fn(conn) -> - stream42 = stream(query42, [], [mode: :savepoint]) + + transaction(fn conn -> + stream42 = stream(query42, [], mode: :savepoint) assert Enum.into(["1\n"], stream42) == stream42 - stream41 = stream(query41, [], [mode: :savepoint]) + stream41 = stream(query41, [], mode: :savepoint) assert Enum.into(["2\n"], stream41) == stream41 Postgrex.rollback(conn, :done) @@ -464,25 +510,27 @@ defmodule StreamTest do test "prepare, close and stream into COPY FROM", context do query = prepare("copy", "COPY uniques FROM STDIN") :ok = close(query) - transaction(fn(conn) -> + + transaction(fn conn -> stream = stream(query, []) assert Enum.into(["2\n"], stream) == stream - assert %Postgrex.Result{rows: [[2]]} = - Postgrex.query!(conn, "SELECT * FROM uniques", []) + assert %Postgrex.Result{rows: [[2]]} = Postgrex.query!(conn, "SELECT * FROM uniques", []) Postgrex.rollback(conn, :done) end) end @tag prepare: :unnamed test "stream named COPY FROM is unnamed when named not allowed", context do - assert (%Postgrex.Query{name: ""} = query) = - prepare("copy", "COPY uniques FROM STDIN") - transaction(fn(conn) -> + assert (%Postgrex.Query{name: ""} = query) = prepare("copy", "COPY uniques FROM STDIN") + + transaction(fn conn -> stream = stream(query, []) assert Enum.into(["2\n", "3\n4\n"], stream) == stream assert Enum.into(["5\n"], stream) == stream + assert %Postgrex.Result{rows: [[2], [3], [4], [5]]} = - Postgrex.query!(conn, "SELECT * FROM uniques", []) + Postgrex.query!(conn, "SELECT * FROM uniques", []) + Postgrex.rollback(conn, :done) end) end @@ -490,18 +538,19 @@ defmodule StreamTest do test "COPY FROM prepared query on another connection", context do query = prepare("copy", "COPY uniques FROM STDIN") {:ok, pid2} = Postgrex.start_link(context[:options]) - Postgrex.transaction(pid2, fn(conn) -> + + Postgrex.transaction(pid2, fn conn -> stream = stream(query, []) assert Enum.into(["2\n"], stream) == stream - assert %Postgrex.Result{rows: [[2]]} = - Postgrex.query!(conn, "SELECT * FROM uniques", []) + assert %Postgrex.Result{rows: [[2]]} = Postgrex.query!(conn, "SELECT * FROM uniques", []) Postgrex.rollback(conn, :done) end) end test "connection reuses prepared for COPY FROM after query", context do query = prepare("", "COPY uniques FROM STDIN") - transaction(fn(conn) -> + + transaction(fn conn -> stream = stream(query, []) assert %Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) assert Enum.into(["5\n"], stream) == stream @@ -512,17 +561,17 @@ defmodule StreamTest do test "connection forces prepare on COPY FROM after prepare of same name", context do query_select = prepare("", "SELECT 42") query_copy = prepare("", "COPY uniques FROM STDIN") - transaction(fn(conn) -> + + transaction(fn conn -> stream = stream(query_copy, []) assert %Result{rows: [[42]]} = Postgrex.execute!(conn, query_select, []) assert Enum.into(["5\n"], stream) == stream - stream = stream(query_copy, [], [mode: :savepoint]) + stream = stream(query_copy, [], mode: :savepoint) assert %Result{rows: [[42]]} = Postgrex.execute!(conn, query_select, []) assert Enum.into(["6\n"], stream) == stream - assert %Result{rows: [[5], [6]]} = - Postgrex.query!(conn, "SELECT * FROM uniques", []) + assert %Result{rows: [[5], [6]]} = Postgrex.query!(conn, "SELECT * FROM uniques", []) Postgrex.rollback(conn, :done) end) end @@ -530,21 +579,21 @@ defmodule StreamTest do test "raise when trying to COPY FROM unprepared query", context do query = %Postgrex.Query{name: "ENOENT", statement: "COPY uniques FROM STDIN"} - transaction(fn(conn) -> + transaction(fn conn -> stream = stream(query, []) - assert_raise ArgumentError, ~r/has not been prepared/, - fn -> Enum.into(["5\n"], stream) end + assert_raise ArgumentError, ~r/has not been prepared/, fn -> Enum.into(["5\n"], stream) end end) end test "stream into SELECT ignores data", context do query = prepare("", "SELECT 42") - transaction(fn(conn) -> + + transaction(fn conn -> stream = stream(query, []) assert Enum.into(["42\n", "43\n"], stream) == stream assert %Result{rows: [[41]]} = Postgrex.query!(conn, "SELECT 41", []) - stream = stream(query, [], [mode: :savepoint]) + stream = stream(query, [], mode: :savepoint) assert Enum.into(["42\n", "43\n"], stream) == stream assert %Result{rows: [[41]]} = Postgrex.query!(conn, "SELECT 41", []) Postgrex.rollback(conn, :done) @@ -553,12 +602,13 @@ defmodule StreamTest do test "stream into COPY TO STDOUT ignores data", context do query = prepare("", "COPY (VALUES (1), (2)) TO STDOUT") - transaction(fn(conn) -> + + transaction(fn conn -> stream = stream(query, []) assert Enum.into(["42\n", "43\n"], stream) == stream assert %Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) - stream = stream(query, [], [mode: :savepoint]) + stream = stream(query, [], mode: :savepoint) assert Enum.into(["42\n", "43\n"], stream) == stream assert %Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) Postgrex.rollback(conn, :done) @@ -568,18 +618,22 @@ defmodule StreamTest do test "connection works after stream into with failure in binding state", context do query = prepare("", "insert into uniques values (CAST($1::text AS int))") - transaction(fn(conn) -> + transaction(fn conn -> stream = stream(query, ["EBADF"]) - assert_raise Postgrex.Error, ~r"\(invalid_text_representation\)", - fn() -> Enum.into(["42\n"], stream) end + + assert_raise Postgrex.Error, ~r"\(invalid_text_representation\)", fn -> + Enum.into(["42\n"], stream) + end end) assert [[42]] = query("SELECT 42", []) - transaction(fn(conn) -> - stream = stream(query, ["EBADF"], [mode: :savepoint]) - assert_raise Postgrex.Error, ~r"\(invalid_text_representation\)", - fn() -> Enum.into(["42\n"], stream) end + transaction(fn conn -> + stream = stream(query, ["EBADF"], mode: :savepoint) + + assert_raise Postgrex.Error, ~r"\(invalid_text_representation\)", fn -> + Enum.into(["42\n"], stream) + end assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) end) @@ -590,18 +644,16 @@ defmodule StreamTest do test "connection works after stream into failure in executing state", context do query = prepare("", "insert into uniques values (1), (1)") - transaction(fn(conn) -> + transaction(fn conn -> stream = stream(query, []) - assert_raise Postgrex.Error, ~r"\(unique_violation\)", - fn() -> Enum.into(["42\n"], stream) end + assert_raise Postgrex.Error, ~r"\(unique_violation\)", fn -> Enum.into(["42\n"], stream) end end) assert [[42]] = query("SELECT 42", []) - transaction(fn(conn) -> - stream = stream(query, [], [mode: :savepoint]) - assert_raise Postgrex.Error, ~r"\(unique_violation\)", - fn() -> Enum.into(["42\n"], stream) end + transaction(fn conn -> + stream = stream(query, [], mode: :savepoint) + assert_raise Postgrex.Error, ~r"\(unique_violation\)", fn -> Enum.into(["42\n"], stream) end assert %Postgrex.Result{rows: [[42]]} = Postgrex.query!(conn, "SELECT 42", []) end) @@ -610,11 +662,12 @@ defmodule StreamTest do test "empty query", context do query_out = prepare("out", "") query_in = prepare("in", "") - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Postgrex.Result{command: nil, rows: nil, num_rows: 0}] = - stream(query_out, []) |> Enum.to_list() + stream(query_out, []) |> Enum.to_list() - stream = stream(query_in, [], [log: &send(self(), &1)]) + stream = stream(query_in, [], log: &send(self(), &1)) assert Enum.into(["2\n3\n"], stream) == stream @@ -623,11 +676,13 @@ defmodule StreamTest do assert {:ok, ^query_in, %Postgrex.Copy{query: ^query_in}} = entry.result assert_received %DBConnection.LogEntry{} = entry - assert (entry.query).query == query_in - assert {:ok, ^query_in, %{command: :copy_stream, rows: nil, num_rows: :copy_stream}} = entry.result + assert entry.query.query == query_in + + assert {:ok, ^query_in, %{command: :copy_stream, rows: nil, num_rows: :copy_stream}} = + entry.result assert_received %DBConnection.LogEntry{} = entry - assert (entry.query).query == query_in + assert entry.query.query == query_in assert {:ok, ^query_in, %{command: nil, rows: nil, num_rows: 0}} = entry.result end) end @@ -635,11 +690,12 @@ defmodule StreamTest do test "savepoint query", context do query_out = prepare("out", "SAVEPOINT streaming_test") query_in = prepare("in", query_out.statement) - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Postgrex.Result{command: :savepoint, rows: nil, num_rows: 0}] = - stream(query_out, []) |> Enum.to_list() + stream(query_out, []) |> Enum.to_list() - stream = stream(query_in, [], [log: &send(self(), &1)]) + stream = stream(query_in, [], log: &send(self(), &1)) assert Enum.into(["2\n3\n"], stream) == stream @@ -648,11 +704,13 @@ defmodule StreamTest do assert {:ok, ^query_in, %Postgrex.Copy{query: ^query_in}} = entry.result assert_received %DBConnection.LogEntry{} = entry - assert (entry.query).query == query_in - assert {:ok, ^query_in, %{command: :copy_stream, rows: nil, num_rows: :copy_stream}} = entry.result + assert entry.query.query == query_in + + assert {:ok, ^query_in, %{command: :copy_stream, rows: nil, num_rows: :copy_stream}} = + entry.result assert_received %DBConnection.LogEntry{} = entry - assert (entry.query).query == query_in + assert entry.query.query == query_in assert {:ok, ^query_in, %{command: :savepoint, rows: nil, num_rows: 0}} = entry.result end) end @@ -660,20 +718,24 @@ defmodule StreamTest do test "INSERT .. RETURNING", context do query_out = prepare("out", "INSERT INTO uniques (a) VALUES (2), (3) RETURNING a") query_in = prepare("in", query_out.statement) - transaction(fn(conn) -> + + transaction(fn conn -> assert [%Postgrex.Result{command: :insert, rows: [[2], [3]], num_rows: 2}] = - stream(query_out, [], [max_rows: 3]) |> Enum.to_list() + stream(query_out, [], max_rows: 3) |> Enum.to_list() + Postgrex.rollback(conn, :done) end) - transaction(fn(conn) -> - assert [%{command: :stream, rows: [[2], [3]], num_rows: 2}, - %{command: :insert, rows: [], num_rows: 0}] = - stream(query_out, [], [max_rows: 2]) |> Enum.to_list() + transaction(fn conn -> + assert [ + %{command: :stream, rows: [[2], [3]], num_rows: 2}, + %{command: :insert, rows: [], num_rows: 0} + ] = stream(query_out, [], max_rows: 2) |> Enum.to_list() + Postgrex.rollback(conn, :done) end) - transaction(fn(conn) -> + transaction(fn conn -> stream = stream(query_in, [], log: &send(self(), &1)) assert Enum.into(["2\n3\n"], stream) == stream @@ -682,11 +744,13 @@ defmodule StreamTest do assert {:ok, ^query_in, %Postgrex.Copy{query: ^query_in}} = entry.result assert_received %DBConnection.LogEntry{} = entry - assert (entry.query).query == query_in - assert {:ok, ^query_in, %{command: :copy_stream, rows: nil, num_rows: :copy_stream}} = entry.result + assert entry.query.query == query_in + + assert {:ok, ^query_in, %{command: :copy_stream, rows: nil, num_rows: :copy_stream}} = + entry.result assert_received %DBConnection.LogEntry{} = entry - assert (entry.query).query == query_in + assert entry.query.query == query_in assert {:ok, ^query_in, %{command: :insert, rows: [[2], [3]], num_rows: 2}} = entry.result Postgrex.rollback(conn, :done) @@ -694,19 +758,20 @@ defmodule StreamTest do end test "stream prepares query", context do - assert transaction(fn(conn) -> - assert [%Postgrex.Result{command: :select, rows: [[42]], num_rows: 1}] = - stream("SELECT 42", []) |> Enum.to_list() - :hi - end) == {:ok, :hi} + assert transaction(fn conn -> + assert [%Postgrex.Result{command: :select, rows: [[42]], num_rows: 1}] = + stream("SELECT 42", []) |> Enum.to_list() + + :hi + end) == {:ok, :hi} end test "stream prepares query but fails to encode", context do - assert transaction(fn(conn) -> - stream = stream("SELECT $1::integer", ["not_an_int"]) - assert_raise DBConnection.EncodeError, fn() -> Enum.to_list(stream) end - :hi - end) == {:ok, :hi} + assert transaction(fn conn -> + stream = stream("SELECT $1::integer", ["not_an_int"]) + assert_raise DBConnection.EncodeError, fn -> Enum.to_list(stream) end + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end diff --git a/test/test_helper.exs b/test/test_helper.exs index 45de5907d..720ec82dd 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -5,7 +5,7 @@ defmodule PSQL do {output, status} = System.cmd("psql", args, stderr_to_stdout: true, env: @pg_env) if status != 0 do - IO.puts """ + IO.puts(""" Command: psql #{Enum.join(args, " ")} @@ -18,7 +18,7 @@ defmodule PSQL do create databases and users. If not, you can create a new user with: $ createuser postgres -s --no-password - """ + """) System.halt(1) end @@ -42,7 +42,7 @@ defmodule PSQL do end pg_version = PSQL.vsn() -unix_exclude = if PSQL.supports_sockets?, do: [], else: [unix: true] +unix_exclude = if PSQL.supports_sockets?(), do: [], else: [unix: true] notify_exclude = if pg_version == {8, 4}, do: [requires_notify_payload: true], else: [] version_exclude = @@ -102,14 +102,29 @@ CREATE SCHEMA test; PSQL.cmd(["-c", "DROP DATABASE IF EXISTS postgrex_test;"]) PSQL.cmd(["-c", "DROP DATABASE IF EXISTS postgrex_test_with_schemas;"]) -PSQL.cmd(["-c", "CREATE DATABASE postgrex_test TEMPLATE=template0 ENCODING='UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8';"]) -PSQL.cmd(["-c", "CREATE DATABASE postgrex_test_with_schemas TEMPLATE=template0 ENCODING='UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8';"]) + +PSQL.cmd([ + "-c", + "CREATE DATABASE postgrex_test TEMPLATE=template0 ENCODING='UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8';" +]) + +PSQL.cmd([ + "-c", + "CREATE DATABASE postgrex_test_with_schemas TEMPLATE=template0 ENCODING='UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8';" +]) + PSQL.cmd(["-d", "postgrex_test", "-c", sql_test]) PSQL.cmd(["-d", "postgrex_test_with_schemas", "-c", sql_test_with_schemas]) cond do pg_version >= {9, 1} -> - PSQL.cmd(["-d", "postgrex_test_with_schemas", "-c", "CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA test;"]) + PSQL.cmd([ + "-d", + "postgrex_test_with_schemas", + "-c", + "CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA test;" + ]) + PSQL.cmd(["-d", "postgrex_test", "-c", "CREATE EXTENSION IF NOT EXISTS hstore;"]) pg_version == {9, 0} -> @@ -126,8 +141,7 @@ end defmodule Postgrex.TestHelper do defmacro query(stat, params, opts \\ []) do quote do - case Postgrex.query(var!(context)[:pid], unquote(stat), - unquote(params), unquote(opts)) do + case Postgrex.query(var!(context)[:pid], unquote(stat), unquote(params), unquote(opts)) do {:ok, %Postgrex.Result{rows: nil}} -> :ok {:ok, %Postgrex.Result{rows: rows}} -> rows {:error, err} -> err @@ -137,8 +151,7 @@ defmodule Postgrex.TestHelper do defmacro prepare(name, stat, opts \\ []) do quote do - case Postgrex.prepare(var!(context)[:pid], unquote(name), - unquote(stat), unquote(opts)) do + case Postgrex.prepare(var!(context)[:pid], unquote(name), unquote(stat), unquote(opts)) do {:ok, %Postgrex.Query{} = query} -> query {:error, err} -> err end @@ -147,8 +160,13 @@ defmodule Postgrex.TestHelper do defmacro prepare_execute(name, stat, params, opts \\ []) do quote do - case Postgrex.prepare_execute(var!(context)[:pid], unquote(name), - unquote(stat), unquote(params), unquote(opts)) do + case Postgrex.prepare_execute( + var!(context)[:pid], + unquote(name), + unquote(stat), + unquote(params), + unquote(opts) + ) do {:ok, %Postgrex.Query{} = query, %Postgrex.Result{rows: rows}} -> {query, rows} {:error, err} -> err end @@ -157,8 +175,7 @@ defmodule Postgrex.TestHelper do defmacro execute(query, params, opts \\ []) do quote do - case Postgrex.execute(var!(context)[:pid], unquote(query), - unquote(params), unquote(opts)) do + case Postgrex.execute(var!(context)[:pid], unquote(query), unquote(params), unquote(opts)) do {:ok, %Postgrex.Query{}, %Postgrex.Result{rows: nil}} -> :ok {:ok, %Postgrex.Query{}, %Postgrex.Result{rows: rows}} -> rows {:error, err} -> err diff --git a/test/transaction_test.exs b/test/transaction_test.exs index 7956deac4..9692da062 100644 --- a/test/transaction_test.exs +++ b/test/transaction_test.exs @@ -8,7 +8,7 @@ defmodule TransactionTest do transactions = case context[:mode] do :transaction -> :strict - :savepoint -> :naive + :savepoint -> :naive end opts = [ @@ -27,27 +27,30 @@ defmodule TransactionTest do @tag mode: :transaction test "connection works after failure during commit transaction", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", []) - assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = - P.query(conn, "SELECT 42", []) - :hi - end) == {:error, :rollback} + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", []) + + assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = + P.query(conn, "SELECT 42", []) + + :hi + end) == {:error, :rollback} + assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction test "connection works after failure during rollback transaction", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", []) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", []) - assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = - P.query(conn, "SELECT 42", []) + assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = + P.query(conn, "SELECT 42", []) - P.rollback(conn, :oops) - end) == {:error, :oops} + P.rollback(conn, :oops) + end) == {:error, :oops} assert [[42]] = query("SELECT 42", []) end @@ -57,45 +60,50 @@ defmodule TransactionTest do Process.flag(:trap_exit, true) assert capture_log(fn -> - assert %Postgrex.Error{message: "unexpected postgres status: transaction"} = query("BEGIN", []) - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ "** (Postgrex.Error) unexpected postgres status: transaction" + assert %Postgrex.Error{message: "unexpected postgres status: transaction"} = + query("BEGIN", []) + + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ "** (Postgrex.Error) unexpected postgres status: transaction" end @tag mode: :transaction test "idle status during transaction returns error and disconnects", context do Process.flag(:trap_exit, true) - assert transaction(fn(conn) -> - assert capture_log(fn -> - assert {:error, %Postgrex.Error{message: "unexpected postgres status: idle"}} = - P.query(conn, "ROLLBACK", []) + assert transaction(fn conn -> + assert capture_log(fn -> + assert {:error, + %Postgrex.Error{message: "unexpected postgres status: idle"}} = + P.query(conn, "ROLLBACK", []) - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ "** (Postgrex.Error) unexpected postgres status: idle" + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ "** (Postgrex.Error) unexpected postgres status: idle" - :hi - end) == {:error, :rollback} + :hi + end) == {:error, :rollback} end @tag mode: :transaction @tag prepare: :unnamed test "transaction commits with unnamed queries", context do - assert transaction(fn(conn) -> - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert transaction(fn conn -> + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} + assert query("SELECT 42", []) == [[42]] end @tag mode: :transaction @tag prepare: :unnamed test "transaction rolls back with unnamed queries", context do - assert transaction(fn(conn) -> - P.rollback(conn, :oops) - end) == {:error, :oops} + assert transaction(fn conn -> + P.rollback(conn, :oops) + end) == {:error, :oops} + assert query("SELECT 42", []) == [[42]] end @@ -104,19 +112,20 @@ defmodule TransactionTest do test "transaction read-only only error disconnects with prepare and execute", context do Process.flag(:trap_exit, true) - assert transaction(fn conn -> - P.query!(conn, "SET TRANSACTION READ ONLY", []).connection_id + assert transaction(fn conn -> + P.query!(conn, "SET TRANSACTION READ ONLY", []).connection_id - {:ok, query} = P.prepare(conn, "query_1", "insert into uniques values (1);", []) + {:ok, query} = P.prepare(conn, "query_1", "insert into uniques values (1);", []) - assert capture_log(fn -> - {:error, %Postgrex.Error{postgres: %{code: :read_only_sql_transaction}}} = - P.execute(conn, query, []) + assert capture_log(fn -> + {:error, %Postgrex.Error{postgres: %{code: :read_only_sql_transaction}}} = + P.execute(conn, query, []) - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ "disconnected: ** (Postgrex.Error) ERROR 25006 (read_only_sql_transaction)" - end) + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ + "disconnected: ** (Postgrex.Error) ERROR 25006 (read_only_sql_transaction)" + end) end @tag mode: :transaction @@ -124,43 +133,59 @@ defmodule TransactionTest do test "transaction read-only only error disconnects with prepare, execute, and close", context do Process.flag(:trap_exit, true) - assert transaction(fn conn -> - P.query!(conn, "SET TRANSACTION READ ONLY", []).connection_id + assert transaction(fn conn -> + P.query!(conn, "SET TRANSACTION READ ONLY", []).connection_id - assert capture_log(fn -> - {:error, %Postgrex.Error{postgres: %{code: :read_only_sql_transaction}}} = - P.query(conn, "insert into uniques values (1);", []) + assert capture_log(fn -> + {:error, %Postgrex.Error{postgres: %{code: :read_only_sql_transaction}}} = + P.query(conn, "insert into uniques values (1);", []) - pid = context[:pid] - assert_receive {:EXIT, ^pid, :killed} - end) =~ "disconnected: ** (Postgrex.Error) ERROR 25006 (read_only_sql_transaction)" - end) + pid = context[:pid] + assert_receive {:EXIT, ^pid, :killed} + end) =~ + "disconnected: ** (Postgrex.Error) ERROR 25006 (read_only_sql_transaction)" + end) end @tag mode: :savepoint test "savepoint transaction releases savepoint", context do :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end, [mode: :savepoint]) == {:ok, :hi} + + assert transaction( + fn conn -> + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end, + mode: :savepoint + ) == {:ok, :hi} + assert [[42]] = query("SELECT 42", []) + assert %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}} = - query("RELEASE SAVEPOINT postgrex_savepoint", []) + query("RELEASE SAVEPOINT postgrex_savepoint", []) + assert :ok = query("ROLLBACK", []) end @tag mode: :savepoint test "savepoint transaction rolls back to savepoint and releases", context do assert :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", []) - P.rollback(conn, :oops) - end, [mode: :savepoint]) == {:error, :oops} + + assert transaction( + fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", []) + + P.rollback(conn, :oops) + end, + mode: :savepoint + ) == {:error, :oops} + assert [[42]] = query("SELECT 42", []) + assert %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}} = - query("RELEASE SAVEPOINT postgrex_savepoint", []) + query("RELEASE SAVEPOINT postgrex_savepoint", []) + assert :ok = query("ROLLBACK", []) end @@ -168,13 +193,20 @@ defmodule TransactionTest do @tag prepare: :unnamed test "savepoint transaction releases with unnamed queries", context do assert :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end, [mode: :savepoint]) == {:ok, :hi} + + assert transaction( + fn conn -> + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end, + mode: :savepoint + ) == {:ok, :hi} + assert [[42]] = query("SELECT 42", []) + assert %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}} = - query("RELEASE SAVEPOINT postgrex_savepoint", []) + query("RELEASE SAVEPOINT postgrex_savepoint", []) + assert :ok = query("ROLLBACK", []) end @@ -182,26 +214,39 @@ defmodule TransactionTest do @tag prepare: :unnamed test "savepoint transaction rolls back and releases with unnamed queries", context do assert :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - P.rollback(conn, :oops) - end, [mode: :savepoint]) == {:error, :oops} + + assert transaction( + fn conn -> + P.rollback(conn, :oops) + end, + mode: :savepoint + ) == {:error, :oops} + assert [[42]] = query("SELECT 42", []) + assert %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}} = - query("RELEASE SAVEPOINT postgrex_savepoint", []) + query("RELEASE SAVEPOINT postgrex_savepoint", []) + assert :ok = query("ROLLBACK", []) end @tag mode: :savepoint test "savepoint transaction rollbacks on failed", context do assert :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], []) - - assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = - P.query(conn, "SELECT 42", []) - :hi - end, [mode: :savepoint]) == {:error, :rollback} + + assert transaction( + fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], []) + + assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = + P.query(conn, "SELECT 42", []) + + :hi + end, + mode: :savepoint + ) == {:error, :rollback} + assert [[42]] = query("SELECT 42", []) assert :ok = query("ROLLBACK", []) end @@ -210,126 +255,133 @@ defmodule TransactionTest do @tag prepare: :unnamed test "savepoint transaction rollbacks on failed with unnamed queries", context do assert :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], []) - :hi - end, [mode: :savepoint]) == {:error, :rollback} + + assert transaction( + fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], []) + + :hi + end, + mode: :savepoint + ) == {:error, :rollback} + assert [[42]] = query("SELECT 42", []) assert :ok = query("ROLLBACK", []) end @tag mode: :transaction test "transaction works after encode failure in savepoint query", context do - assert transaction(fn(conn) -> - assert_raise ArgumentError, fn -> - P.query(conn, "SELECT $1::numeric", [Decimal.new("Inf")], [mode: :savepoint]) - end + assert transaction(fn conn -> + assert_raise ArgumentError, fn -> + P.query(conn, "SELECT $1::numeric", [Decimal.new("Inf")], mode: :savepoint) + end - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction - test "transaction works after encode failure in savepoint query with cache_statement", context do - assert transaction(fn(conn) -> - stmt = "SELECT $1::numeric" - opts = [mode: :savepoint, cache_statement: "select_numeric"] - {:ok, _} = P.query(conn, stmt, [1.0], opts) - - assert_raise ArgumentError, fn -> - P.query(conn, stmt, [Decimal.new("Inf")], opts) - end + test "transaction works after encode failure in savepoint query with cache_statement", + context do + assert transaction(fn conn -> + stmt = "SELECT $1::numeric" + opts = [mode: :savepoint, cache_statement: "select_numeric"] + {:ok, _} = P.query(conn, stmt, [1.0], opts) - {:ok, _} = P.query(conn, "SELECT 42", [], []) + assert_raise ArgumentError, fn -> + P.query(conn, stmt, [Decimal.new("Inf")], opts) + end - assert_raise ArgumentError, fn -> - P.query(conn, stmt, [Decimal.new("Inf")], opts) - end + {:ok, _} = P.query(conn, "SELECT 42", [], []) + + assert_raise ArgumentError, fn -> + P.query(conn, stmt, [Decimal.new("Inf")], opts) + end - {:ok, _} = P.query(conn, "SELECT 42", [], []) - :hi - end) == {:ok, :hi} + {:ok, _} = P.query(conn, "SELECT 42", [], []) + :hi + end) == {:ok, :hi} end @tag mode: :transaction test "transaction works after execute failure in savepoint query", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], mode: :savepoint) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction test "transaction works after parse failure in savepoint query", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :syntax_error}}} = - P.query(conn, "NOT SQL", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :syntax_error}}} = + P.query(conn, "NOT SQL", [], mode: :savepoint) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction test "transaction works after parse failure in savepoint prepare", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :syntax_error}}} = - P.prepare(conn, "", "NOT SQL", [mode: :savepoint]) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :syntax_error}}} = + P.prepare(conn, "", "NOT SQL", mode: :savepoint) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - assert {:error, %Postgrex.Error{postgres: %{code: :syntax_error}}} = - P.prepare(conn, "insert", "NOT SQL", [mode: :savepoint]) + assert {:error, %Postgrex.Error{postgres: %{code: :syntax_error}}} = + P.prepare(conn, "insert", "NOT SQL", mode: :savepoint) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction test "savepoint query releases savepoint in transaction", context do - assert transaction(fn(conn) -> - assert {:ok, %Postgrex.Result{rows: [[42]]}} = - P.query(conn, "SELECT 42", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:ok, %Postgrex.Result{rows: [[42]]}} = + P.query(conn, "SELECT 42", [], mode: :savepoint) - assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = - P.query(conn, "RELEASE SAVEPOINT postgrex_query", []) + assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = + P.query(conn, "RELEASE SAVEPOINT postgrex_query", []) - P.rollback(conn, :oops) - end) == {:error, :oops} + P.rollback(conn, :oops) + end) == {:error, :oops} assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction test "savepoint query does not rollback on savepoint error", context do - assert transaction(fn(conn) -> - assert {:ok, _} = P.query(conn, "SAVEPOINT postgrex_query", []) + assert transaction(fn conn -> + assert {:ok, _} = P.query(conn, "SAVEPOINT postgrex_query", []) - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "INSERT INTO uniques VALUES (1), (1)", []) + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "INSERT INTO uniques VALUES (1), (1)", []) - assert {:error, %DBConnection.TransactionError{message: "transaction is aborted"}} = - P.query(conn, "SELECT 42", [], [mode: :savepoint]) + assert {:error, %DBConnection.TransactionError{message: "transaction is aborted"}} = + P.query(conn, "SELECT 42", [], mode: :savepoint) - assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = - P.query(conn, "SELECT 42", []) + assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = + P.query(conn, "SELECT 42", []) - P.rollback(conn, :oops) - end) == {:error, :oops} + P.rollback(conn, :oops) + end) == {:error, :oops} assert [[42]] = query("SELECT 42", []) end @@ -339,27 +391,27 @@ defmodule TransactionTest do Process.flag(:trap_exit, true) assert transaction(fn conn -> - assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = - P.query(conn, "RELEASE SAVEPOINT postgrex_query", [], [mode: :savepoint]) + assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = + P.query(conn, "RELEASE SAVEPOINT postgrex_query", [], mode: :savepoint) - assert {:error, %DBConnection.ConnectionError{message: "connection is closed" <> _}} = - P.query(conn, "SELECT 42", []) + assert {:error, %DBConnection.ConnectionError{message: "connection is closed" <> _}} = + P.query(conn, "SELECT 42", []) - P.rollback(conn, :oops) - end) == {:error, :oops} + P.rollback(conn, :oops) + end) == {:error, :oops} end @tag mode: :transaction test "savepoint query rolls back and releases savepoint in transaction", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], mode: :savepoint) - assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = - P.query(conn, "RELEASE SAVEPOINT postgrex_query", []) + assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = + P.query(conn, "RELEASE SAVEPOINT postgrex_query", []) - P.rollback(conn, :oops) - end) == {:error, :oops} + P.rollback(conn, :oops) + end) == {:error, :oops} assert [[42]] = query("SELECT 42", []) end @@ -367,58 +419,58 @@ defmodule TransactionTest do @tag mode: :transaction @tag prepare: :unnamed test "unnamed savepoint query releases savepoint in transaction", context do - assert transaction(fn(conn) -> - assert {:ok, %Postgrex.Result{rows: [[42]]}} = - P.query(conn, "SELECT 42", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:ok, %Postgrex.Result{rows: [[42]]}} = + P.query(conn, "SELECT 42", [], mode: :savepoint) - assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = - P.query(conn, "RELEASE SAVEPOINT postgrex_query", []) + assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = + P.query(conn, "RELEASE SAVEPOINT postgrex_query", []) - P.rollback(conn, :oops) - end) == {:error, :oops} + P.rollback(conn, :oops) + end) == {:error, :oops} assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction test "unnamed savepoint query rolls back and releases savepoint in transaction", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], mode: :savepoint) - assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = - P.query(conn, "RELEASE SAVEPOINT postgrex_query", []) + assert {:error, %Postgrex.Error{postgres: %{code: :invalid_savepoint_specification}}} = + P.query(conn, "RELEASE SAVEPOINT postgrex_query", []) - P.rollback(conn, :oops) - end) == {:error, :oops} + P.rollback(conn, :oops) + end) == {:error, :oops} assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction test "transaction works after failure in savepoint query binding state", context do - assert transaction(fn(conn) -> - statement = "insert into uniques values (CAST($1::text AS int))" + assert transaction(fn conn -> + statement = "insert into uniques values (CAST($1::text AS int))" - assert {:error, %Postgrex.Error{postgres: %{code: :invalid_text_representation}}} = - P.query(conn, statement, ["invalid"], [mode: :savepoint]) + assert {:error, %Postgrex.Error{postgres: %{code: :invalid_text_representation}}} = + P.query(conn, statement, ["invalid"], mode: :savepoint) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @tag mode: :transaction test "transaction works after failure in savepoint query executing state", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], mode: :savepoint) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @@ -426,13 +478,13 @@ defmodule TransactionTest do @tag mode: :transaction @tag prepare: :unnamed test "transaction works after failure in unammed savepoint query parsing state", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], mode: :savepoint) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @@ -440,14 +492,15 @@ defmodule TransactionTest do @tag mode: :transaction @tag prepare: :unnamed test "transaction works after failure in unnamed savepoint query binding state", context do - assert transaction(fn(conn) -> - statement = "insert into uniques values (CAST($1::text AS int))" - assert {:error, %Postgrex.Error{postgres: %{code: :invalid_text_representation}}} = - P.query(conn, statement, ["invalid"], [mode: :savepoint]) + assert transaction(fn conn -> + statement = "insert into uniques values (CAST($1::text AS int))" - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:error, %Postgrex.Error{postgres: %{code: :invalid_text_representation}}} = + P.query(conn, statement, ["invalid"], mode: :savepoint) + + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @@ -455,13 +508,13 @@ defmodule TransactionTest do @tag mode: :transaction @tag prepare: :unnamed test "transaction works after failure in unnamed savepoint query executing state", context do - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], [mode: :savepoint]) + assert transaction(fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], mode: :savepoint) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end) == {:ok, :hi} + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) end @@ -469,13 +522,17 @@ defmodule TransactionTest do @tag mode: :savepoint test "savepoint transaction works after failure in savepoint query parsing state", context do assert :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], [mode: :savepoint]) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end, [mode: :savepoint]) == {:ok, :hi} + assert transaction( + fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], mode: :savepoint) + + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end, + mode: :savepoint + ) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) assert :ok = query("ROLLBACK", []) @@ -484,14 +541,19 @@ defmodule TransactionTest do @tag mode: :savepoint test "savepoint transaction works after failure in savepoint query binding state", context do assert :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - statement = "insert into uniques values (CAST($1::text AS int))" - assert {:error, %Postgrex.Error{postgres: %{code: :invalid_text_representation}}} = - P.query(conn, statement, ["invalid"], [mode: :savepoint]) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end, [mode: :savepoint]) == {:ok, :hi} + assert transaction( + fn conn -> + statement = "insert into uniques values (CAST($1::text AS int))" + + assert {:error, %Postgrex.Error{postgres: %{code: :invalid_text_representation}}} = + P.query(conn, statement, ["invalid"], mode: :savepoint) + + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end, + mode: :savepoint + ) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) assert :ok = query("ROLLBACK", []) @@ -500,13 +562,17 @@ defmodule TransactionTest do @tag mode: :savepoint test "savepoint transaction works after failure in savepoint query executing state", context do assert :ok = query("BEGIN", []) - assert transaction(fn(conn) -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], [mode: :savepoint]) - assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) - :hi - end, [mode: :savepoint]) == {:ok, :hi} + assert transaction( + fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], mode: :savepoint) + + assert {:ok, %Postgrex.Result{rows: [[42]]}} = P.query(conn, "SELECT 42", []) + :hi + end, + mode: :savepoint + ) == {:ok, :hi} assert [[42]] = query("SELECT 42", []) assert :ok = query("ROLLBACK", []) @@ -521,19 +587,23 @@ defmodule TransactionTest do assert query("SELECT 42", []) == [[42]] assert DBConnection.status(pid, opts) == :idle - assert DBConnection.transaction(pid, fn conn -> - assert DBConnection.status(conn, opts) == :transaction + assert DBConnection.transaction( + pid, + fn conn -> + assert DBConnection.status(conn, opts) == :transaction - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", [], opts) + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", [], opts) - assert DBConnection.status(conn, opts) == :error + assert DBConnection.status(conn, opts) == :error - assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = - P.query(conn, "SELECT 42", [], opts) + assert {:error, %Postgrex.Error{postgres: %{code: :in_failed_sql_transaction}}} = + P.query(conn, "SELECT 42", [], opts) - assert DBConnection.status(conn, opts) == :error - end, opts) == {:error, :rollback} + assert DBConnection.status(conn, opts) == :error + end, + opts + ) == {:error, :rollback} assert DBConnection.status(pid, opts) == :idle assert query("SELECT 42", []) == [[42]] @@ -542,9 +612,12 @@ defmodule TransactionTest do @tag mode: :transaction test "commit log entries", context do - assert transaction(fn conn -> - P.query(conn, "SELECT 42", []) - end, log: &send(self(), &1)) + assert transaction( + fn conn -> + P.query(conn, "SELECT 42", []) + end, + log: &send(self(), &1) + ) assert_receive %DBConnection.LogEntry{} = entry assert {:ok, %DBConnection{}, %Postgrex.Result{command: :begin}} = entry.result @@ -555,9 +628,12 @@ defmodule TransactionTest do @tag mode: :transaction test "rollback log entries", context do - assert transaction(fn conn -> - P.rollback(conn, :hi) - end, log: &send(self(), &1)) == {:error, :hi} + assert transaction( + fn conn -> + P.rollback(conn, :hi) + end, + log: &send(self(), &1) + ) == {:error, :hi} assert_receive %DBConnection.LogEntry{} = entry assert {:ok, %DBConnection{}, %Postgrex.Result{command: :begin}} = entry.result @@ -568,11 +644,15 @@ defmodule TransactionTest do @tag mode: :transaction test "rollback from failed query log entries", context do - assert transaction(fn conn -> - assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = - P.query(conn, "insert into uniques values (1), (1);", []) - :hi - end, log: &send(self(), &1)) == {:error, :rollback} + assert transaction( + fn conn -> + assert {:error, %Postgrex.Error{postgres: %{code: :unique_violation}}} = + P.query(conn, "insert into uniques values (1), (1);", []) + + :hi + end, + log: &send(self(), &1) + ) == {:error, :rollback} assert_receive %DBConnection.LogEntry{} = entry assert {:ok, %DBConnection{}, %Postgrex.Result{command: :begin}} = entry.result diff --git a/test/tsvector_test.exs b/test/tsvector_test.exs index 96ecb00c0..369fd4ea0 100644 --- a/test/tsvector_test.exs +++ b/test/tsvector_test.exs @@ -11,59 +11,75 @@ defmodule TsvectorTest do end test "encode basic tsvector", context do - assert [["'1'"]] = - query("SELECT $1::tsvector::text", [[%Lexeme{positions: [], word: "1"}]]) + assert [["'1'"]] = query("SELECT $1::tsvector::text", [[%Lexeme{positions: [], word: "1"}]]) + assert [["'1' 'hello'"]] = - query("SELECT $1::tsvector::text", [[%Lexeme{positions: [], word: "1"}, %Lexeme{positions: [], word: "hello"}]]) + query("SELECT $1::tsvector::text", [ + [%Lexeme{positions: [], word: "1"}, %Lexeme{positions: [], word: "hello"}] + ]) end test "encode tsvector with positions", context do assert [["'1':1"]] = - query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, nil}], word: "1"}]]) + query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, nil}], word: "1"}]]) end test "encode tsvector with multiple positions", context do assert [["'1':1,2"]] = - query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, nil}, {2, nil}], word: "1"}]]) + query("SELECT $1::tsvector::text", [ + [%Lexeme{positions: [{1, nil}, {2, nil}], word: "1"}] + ]) end test "encode tsvector with position and weight", context do assert [["'car':1A"]] = - query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, :A}], word: "car"}]]) + query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, :A}], word: "car"}]]) + assert [["'car':1B"]] = - query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, :B}], word: "car"}]]) + query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, :B}], word: "car"}]]) + assert [["'car':1C"]] = - query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, :C}], word: "car"}]]) + query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, :C}], word: "car"}]]) end test "encode tsvector with multiple positions and weights", context do - assert [["'car':1A,2,3B"]] = - query("SELECT $1::tsvector::text", [[%Lexeme{positions: [{1, :A}, {2, nil}, {3, :B}], word: "car"}]]) + assert [["'car':1A,2,3B"]] = + query("SELECT $1::tsvector::text", [ + [%Lexeme{positions: [{1, :A}, {2, nil}, {3, :B}], word: "car"}] + ]) end test "decode basic tsvectors", context do - assert [[[%Lexeme{positions: [], word: "1"}]]] = - query("SELECT '1'::tsvector", []) - assert [[[%Lexeme{positions: [], word: "1"}]]] = - query("SELECT '1 '::tsvector", []) - assert [[[%Lexeme{positions: [], word: "1"}]]] = - query("SELECT ' 1'::tsvector", []) - assert [[[%Lexeme{positions: [], word: "1"}]]] = - query("SELECT ' 1 '::tsvector", []) + assert [[[%Lexeme{positions: [], word: "1"}]]] = query("SELECT '1'::tsvector", []) + assert [[[%Lexeme{positions: [], word: "1"}]]] = query("SELECT '1 '::tsvector", []) + assert [[[%Lexeme{positions: [], word: "1"}]]] = query("SELECT ' 1'::tsvector", []) + assert [[[%Lexeme{positions: [], word: "1"}]]] = query("SELECT ' 1 '::tsvector", []) end test "decode tsvectors with multiple elements", context do assert [[[%Lexeme{positions: [], word: "1"}, %Lexeme{positions: [], word: "2"}]]] = - query("SELECT '1 2'::tsvector", []) - assert [[[%Lexeme{positions: [], word: "1 2"}]]] = - query("SELECT '''1 2'''::tsvector", []) + query("SELECT '1 2'::tsvector", []) + + assert [[[%Lexeme{positions: [], word: "1 2"}]]] = query("SELECT '''1 2'''::tsvector", []) end test "decode tsvectors with multiple positions and elements", context do - assert [[[%Lexeme{positions: [{8, nil}], word: "a"}, - %Lexeme{positions: [{1, nil}, {2, :C}, {3, :B}, {4, :A}, {5, nil}], word: "w"}]]] = - query("SELECT '''w'':4A,2C,3B,1D,5 a:8'::tsvector", []) - assert [[[%Lexeme{positions: [{3, :A}], word: "a"}, %Lexeme{positions: [{2, :A}], word: "b"}]]] = - query("SELECT 'a:3A b:2a'::tsvector", []) + assert [ + [ + [ + %Lexeme{positions: [{8, nil}], word: "a"}, + %Lexeme{positions: [{1, nil}, {2, :C}, {3, :B}, {4, :A}, {5, nil}], word: "w"} + ] + ] + ] = query("SELECT '''w'':4A,2C,3B,1D,5 a:8'::tsvector", []) + + assert [ + [ + [ + %Lexeme{positions: [{3, :A}], word: "a"}, + %Lexeme{positions: [{2, :A}], word: "b"} + ] + ] + ] = query("SELECT 'a:3A b:2a'::tsvector", []) end end diff --git a/test/type_module_test.exs b/test/type_module_test.exs index 0e9739eca..245be0b6c 100644 --- a/test/type_module_test.exs +++ b/test/type_module_test.exs @@ -10,6 +10,7 @@ defmodule TypeModuleTest do :code.delete(@types) :code.purge(@types) end) + opts = [decode_binary: :reference, null: :custom, json: :fake_json] Postgrex.TypeModule.define(@types, [], opts) :ok @@ -43,9 +44,16 @@ defmodule TypeModuleTest do test "encode null with custom mapping", context do assert [[:custom, :custom]] = query("SELECT $1::text, $2::int", [:custom, :custom]) - assert [[true, false, :custom]] = query("SELECT $1::bool, $2::bool, $3::bool", [true, false, :custom]) - assert [[true, :custom, false]] = query("SELECT $1::bool, $2::bool, $3::bool", [true, :custom, false]) - assert [[:custom, true, false]] = query("SELECT $1::bool, $2::bool, $3::bool", [:custom, true, false]) + + assert [[true, false, :custom]] = + query("SELECT $1::bool, $2::bool, $3::bool", [true, false, :custom]) + + assert [[true, :custom, false]] = + query("SELECT $1::bool, $2::bool, $3::bool", [true, :custom, false]) + + assert [[:custom, true, false]] = + query("SELECT $1::bool, $2::bool, $3::bool", [:custom, true, false]) + assert [["{NULL,t,f}"]] = query("SELECT ($1::bool[])::text", [[:custom, true, false]]) end diff --git a/test/type_server_test.exs b/test/type_server_test.exs index e9ed1294a..289983a9e 100644 --- a/test/type_server_test.exs +++ b/test/type_server_test.exs @@ -16,7 +16,7 @@ defmodule TypeServerTest do test "fetches and exits" do key = make_ref() server = TM.locate(@types, key) - task = Task.async(fn() -> assert {:lock, _, _} = TS.fetch(server) end) + task = Task.async(fn -> assert {:lock, _, _} = TS.fetch(server) end) {:lock, _, types} = Task.await(task) assert ^server = TM.locate(@types, key) assert {:lock, _, ^types} = TS.fetch(server) @@ -27,7 +27,7 @@ defmodule TypeServerTest do server = TM.locate(@types, key) {:lock, ref, types} = TS.fetch(server) - task = Task.async fn -> TS.fetch(server) end + task = Task.async(fn -> TS.fetch(server) end) :timer.sleep(100) TS.update(server, ref, []) assert {:lock, _, ^types} = Task.await(task) @@ -41,7 +41,7 @@ defmodule TypeServerTest do assert {:lock, ref, ^types} = TS.fetch(server) - task = Task.async fn -> TS.fetch(server) end + task = Task.async(fn -> TS.fetch(server) end) :timer.sleep(100) assert Task.yield(task, 0) == nil TS.update(server, ref, []) @@ -53,7 +53,7 @@ defmodule TypeServerTest do server = TM.locate(@types, key) {:lock, ref, types} = TS.fetch(server) - task = Task.async fn -> TS.fetch(server) end + task = Task.async(fn -> TS.fetch(server) end) :timer.sleep(100) assert Task.yield(task, 0) == nil TS.done(server, ref) @@ -68,7 +68,7 @@ defmodule TypeServerTest do {:lock, ref, ^types} = TS.fetch(server) - task = Task.async fn -> TS.fetch(server) end + task = Task.async(fn -> TS.fetch(server) end) :timer.sleep(100) assert Task.yield(task, 0) == nil TS.done(server, ref) @@ -79,11 +79,13 @@ defmodule TypeServerTest do key = make_ref() server = TM.locate(@types, key) - task = Task.async fn -> - {:lock, ref, types} = TS.fetch(server) - TS.update(server, ref, []) - types - end + task = + Task.async(fn -> + {:lock, ref, types} = TS.fetch(server) + TS.update(server, ref, []) + types + end) + types = Task.await(task) wait_until_dead(task.pid) @@ -99,18 +101,20 @@ defmodule TypeServerTest do TS.update(server, ref, []) parent = self() - task = - fn() -> - result = TS.fetch(server) - send(parent, {self(), result}) - case result do - {:lock, ref2, _} -> - assert_receive {^parent, :go} - TS.update(server, ref2, []) - _ -> - :ok - end + + task = fn -> + result = TS.fetch(server) + send(parent, {self(), result}) + + case result do + {:lock, ref2, _} -> + assert_receive {^parent, :go} + TS.update(server, ref2, []) + + _ -> + :ok end + end {:ok, _} = Task.start_link(task) {:ok, _} = Task.start_link(task) @@ -131,12 +135,14 @@ defmodule TypeServerTest do Application.put_env(:postgrex, :type_server_reap_after, 0) key = make_ref() - task = Task.async fn -> - server = TM.locate(@types, key) - {:lock, ref, types} = TS.fetch(server) - TS.update(server, ref, []) - {server, types} - end + task = + Task.async(fn -> + server = TM.locate(@types, key) + {:lock, ref, types} = TS.fetch(server) + TS.update(server, ref, []) + {server, types} + end) + {server1, types1} = Task.await(task) mref = Process.monitor(server1) assert_receive {:DOWN, ^mref, _, _, _} @@ -162,8 +168,7 @@ defmodule TypeServerTest do assert {:lock, _ref, types} = Task.await(task) wait_until_dead(task.pid) - assert {:lock, _ref, ^types} = - Task.async(fn -> TS.fetch(server) end) |> Task.await + assert {:lock, _ref, ^types} = Task.async(fn -> TS.fetch(server) end) |> Task.await() end test "error waiting process if original holder crashes after fetch" do @@ -171,10 +176,11 @@ defmodule TypeServerTest do server = TM.locate(@types, key) top = self() - {:ok, pid} = Task.start fn -> - send(top, TS.fetch(server)) - :timer.sleep(:infinity) - end + {:ok, pid} = + Task.start(fn -> + send(top, TS.fetch(server)) + :timer.sleep(:infinity) + end) assert_receive {:lock, _, types} task = Task.async(fn -> TS.fetch(server) end)