Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions lib/postgrex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule Postgrex do
you need to start a separate (notifications) connection.
"""

alias Postgrex.Query
alias Postgrex.{Query, TextQuery}

@typedoc """
A connection process name, pid or reference.
Expand Down Expand Up @@ -252,12 +252,18 @@ defmodule Postgrex do
end

@doc """
Runs an (extended) query and returns the result as `{:ok, %Postgrex.Result{}}`
or `{:error, %Postgrex.Error{}}` if there was a database error. Parameters can
be set in the query as `$1` embedded in the query string. Parameters are given
as a list of elixir values. See the README for information on how Postgrex
encodes and decodes Elixir values by default. See `Postgrex.Result` for the
result data.
Runs a query and returns the result as `{:ok, %Postgrex.Result{}}` or
`{:error, %Postgrex.Error{}}` if there was a database error.

Queries can be run using both the extended query protocol (binary format)
or the simple query protocol (text format). This can be controlled using
the `:query_type` option.

If using the extended query protocol, parameters can be set as `$1` embedded
in the query string and results are encoded and decoded according to the
[data representation chart](readme.html#data-representation). If using the
simple query protocol, queries cannot be parameterized and results are encoded
and decoded in text format. See `Postgrex.Result` for the result data.

This function may still raise an exception if there is an issue with types
(`ArgumentError`), connection (`DBConnection.ConnectionError`), ownership
Expand All @@ -272,6 +278,9 @@ defmodule Postgrex do
* `:mode` - set to `:savepoint` to use a savepoint to rollback to before the
query on error, otherwise set to `:transaction` (default: `:transaction`);
* `:cache_statement` - Caches the query with the given name
* `:query_type` - Either `:binary` or `:text`. If `:binary` then the
extended query protocol is used. If `:text` then the simple protocol
is used. Defaults to `:binary`.

## Examples

Expand All @@ -290,6 +299,22 @@ defmodule Postgrex do
@spec query(conn, iodata, list, [execute_option]) ::
{:ok, Postgrex.Result.t()} | {:error, Exception.t()}
def query(conn, statement, params \\ [], opts \\ []) when is_list(params) and is_list(opts) do
query_type = Keyword.get(opts, :query_type, :binary)

case query_type do
:binary ->
binary_query(conn, statement, params, opts)

:text ->
text_query(conn, statement, params, opts)

_ ->
raise ArgumentError,
"allowed query types are `:binary` and `:text`, got: #{inspect(query_type)}"
end
end

defp binary_query(conn, statement, params, opts) do
name = Keyword.get(opts, :cache_statement)

if comment_not_present!(opts) && name do
Expand All @@ -315,6 +340,15 @@ defmodule Postgrex do
end
end

defp text_query(conn, statement, params, opts) do
query = %TextQuery{statement: statement}

case DBConnection.execute(conn, query, params, opts) do
{:ok, _, result} -> {:ok, result}
{:error, _} = error -> error
end
end

defp query_prepare_execute(conn, query, params, opts) do
case DBConnection.prepare_execute(conn, query, params, opts) do
{:ok, _, result} -> {:ok, result}
Expand Down
19 changes: 16 additions & 3 deletions lib/postgrex/protocol.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Postgrex.Protocol do
@moduledoc false

alias Postgrex.{Types, TypeServer, Query, Cursor, Copy}
alias Postgrex.{Types, TypeServer, Query, TextQuery, Cursor, Copy}
import Postgrex.{Messages, BinaryUtils}
require Logger
use DBConnection
Expand Down Expand Up @@ -55,6 +55,8 @@ defmodule Postgrex.Protocol do

@type notify :: (binary, binary -> any)

@type binary_or_text_query :: Postgrex.Query.t() | Postgrex.TextQuery.t()

defmacrop new_status(opts, fields \\ []) do
defaults =
quote(
Expand Down Expand Up @@ -421,8 +423,9 @@ defmodule Postgrex.Protocol do
end
end

@spec handle_execute(Postgrex.Query.t(), list, Keyword.t(), state) ::
{:ok, Postgrex.Query.t(), Postgrex.Result.t() | Postgrex.Copy.t(), state}
@spec handle_execute(binary_or_text_query, list, Keyword.t(), state) ::
{:ok, binary_or_text_query,
Postgrex.Result.t() | [Postgrex.Result.t()] | Postgrex.Copy.t(), state}
| {:error, %ArgumentError{} | Postgrex.Error.t(), state}
| {:error, %DBConnection.TransactionError{}, state}
| {:disconnect, %RuntimeError{}, state}
Expand All @@ -437,6 +440,16 @@ defmodule Postgrex.Protocol do
end
end

def handle_execute(%TextQuery{statement: statement} = query, [], opts, s) do
case handle_simple(statement, opts, s) do
{:ok, [first_result | _], s} ->
{:ok, query, first_result, s}

{error, _, _} = other when error in [:error, :disconnect] ->
other
end
end

@spec handle_execute(Postgrex.Copy.t(), {:copy_data, iodata} | :copy_done, Keyword.t(), state) ::
{:ok, Postgrex.Query.t(), Postgrex.Result.t(), state}
| {:error, %ArgumentError{} | Postgrex.Error.t(), state}
Expand Down
25 changes: 25 additions & 0 deletions lib/postgrex/text_query.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Postgrex.TextQuery do
@moduledoc false

defstruct [:statement]
end

defimpl DBConnection.Query, for: Postgrex.TextQuery do
def parse(query, _opts), do: query

def describe(query, _opts), do: query

def encode(_query, [], _opts), do: []

def encode(_query, params, _opts) do
raise ArgumentError, "text queries cannot use parameters, got: #{inspect(params)}"
end

def decode(_query, result, _opts), do: result
end

defimpl String.Chars, for: Postgrex.TextQuery do
def to_string(%{statement: statement}) do
IO.iodata_to_binary(statement)
end
end
32 changes: 32 additions & 0 deletions test/query_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,38 @@ defmodule QueryTest do
assert {:ok, _, _} = P.execute(pid, query, [])
end

test "query with query_type: text", context do
{:ok, %{rows: result}} =
P.query(context[:pid], "SELECT * FROM UNNEST(ARRAY[1, 2], ARRAY[3, 4])", [],
query_type: :text
)

assert result == [["1", "3"], ["2", "4"]]

%{rows: result} =
P.query!(context[:pid], "SELECT * FROM UNNEST(ARRAY[1, 2], ARRAY[3, 4])", [],
query_type: :text
)

assert result == [["1", "3"], ["2", "4"]]
end

test "query with query_type: :text only returns first result", context do
{:ok, %{rows: result}} =
P.query(context[:pid], "SELECT 1; SELECT 2", [], query_type: :text)

assert result == [["1"]]
end

test "query with query_type: :text handles errors properly", context do
{:error, %Postgrex.Error{}} =
P.query(context[:pid], "SELEC;", [], query_type: :text)

assert_raise Postgrex.Error, fn ->
P.query!(context[:pid], "SELEC;", [], query_type: :text)
end
end

defp disconnect(pid) do
sock = DBConnection.run(pid, &get_socket/1)
:gen_tcp.shutdown(sock, :read_write)
Expand Down
Loading