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
1 change: 1 addition & 0 deletions integration_test/myxql/all_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Code.require_file "../sql/lock.exs", __DIR__
Code.require_file "../sql/logging.exs", __DIR__
Code.require_file "../sql/migration.exs", __DIR__
Code.require_file "../sql/migrator.exs", __DIR__
Code.require_file "../sql/query_many.exs", __DIR__
Code.require_file "../sql/sandbox.exs", __DIR__
Code.require_file "../sql/sql.exs", __DIR__
Code.require_file "../sql/stream.exs", __DIR__
Expand Down
15 changes: 15 additions & 0 deletions integration_test/sql/query_many.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Ecto.Integration.QueryManyTest do
use Ecto.Integration.Case, async: true

alias Ecto.Integration.TestRepo

test "query_many!/4" do
results = TestRepo.query_many!("SELECT 1; SELECT 2;")
assert [%{rows: [[1]], num_rows: 1}, %{rows: [[2]], num_rows: 1}] = results
end

test "query_many!/4 with iodata" do
results = TestRepo.query_many!(["SELECT", ?\s, ?1, ";", ?\s, "SELECT", ?\s, ?2, ";"])
assert [%{rows: [[1]], num_rows: 1}, %{rows: [[2]], num_rows: 1}] = results
end
end
13 changes: 13 additions & 0 deletions lib/ecto/adapters/myxql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ defmodule Ecto.Adapters.MyXQL do

If your version of MySQL supports microsecond precision, you
will be able to utilize Ecto's usec types.

## Multiple Result Support

MyXQL supports the execution of queries that return multiple
results, such as text queries with multiple statements separated
by semicolons or stored procedures. These can be executed with
`Ecto.Adapters.SQL.query_many/4` or the `YourRepo.query_many/3`
shortcut.

Be default, these queries will be executed with the `:query_type`
option set to `:text`. To take advantage of prepared statements
when executing a stored procedure, set the `:query_type` option
to `:binary`.
"""

# Inherit all behaviour from Ecto.Adapters.SQL
Expand Down
6 changes: 6 additions & 0 deletions lib/ecto/adapters/myxql/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ if Code.ensure_loaded?(MyXQL) do
MyXQL.query(conn, sql, params, opts)
end

@impl true
def query_many(conn, sql, params, opts) do
opts = Keyword.put_new(opts, :query_type, :text)
MyXQL.query_many(conn, sql, params, opts)
end

@impl true
def execute(conn, query, params, opts) do
case MyXQL.execute(conn, query, params, opts) do
Expand Down
5 changes: 5 additions & 0 deletions lib/ecto/adapters/postgres/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ if Code.ensure_loaded?(Postgrex) do
Postgrex.query(conn, sql, params, opts)
end

@impl true
def query_many(_conn, _sql, _params, _opts) do
raise RuntimeError, "query_many is not supported in the Postgrex adapter"
end

@impl true
def execute(conn, %{ref: ref} = query, params, opts) do
case Postgrex.execute(conn, query, params, opts) do
Expand Down
83 changes: 82 additions & 1 deletion lib/ecto/adapters/sql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ defmodule Ecto.Adapters.SQL do
* `query!(sql, params, options \\ [])` -
shortcut for `Ecto.Adapters.SQL.query!/4`

* `query_many(sql, params, options \\ [])` -
shortcut for `Ecto.Adapters.SQL.query_many/4`

* `query_many!(sql, params, options \\ [])` -
shortcut for `Ecto.Adapters.SQL.query_many!/4`

* `to_sql(type, query, options \\ [])` -
shortcut for `Ecto.Adapters.SQL.to_sql/4`

Expand Down Expand Up @@ -429,7 +435,7 @@ defmodule Ecto.Adapters.SQL do
end

@doc """
Runs custom SQL query on given repo.
Runs a custom SQL query on the given repo.

In case of success, it must return an `:ok` tuple containing
a map with at least two keys:
Expand Down Expand Up @@ -471,6 +477,63 @@ defmodule Ecto.Adapters.SQL do
sql_call(adapter_meta, :query, [sql], params, opts)
end

@doc """
Same as `query_many/4` but raises on invalid queries.
"""
@spec query_many!(Ecto.Repo.t | Ecto.Adapter.adapter_meta, iodata, [term], Keyword.t) ::
[%{:rows => nil | [[term] | binary],
:num_rows => non_neg_integer,
optional(atom) => any}]
def query_many!(repo, sql, params \\ [], opts \\ []) do
case query_many(repo, sql, params, opts) do
{:ok, result} -> result
{:error, err} -> raise_sql_call_error err
end
end

@doc """
Runs a custom SQL query that returns multiple results on the given repo.

In case of success, it must return an `:ok` tuple containing
a list of maps with at least two keys:

* `:num_rows` - the number of rows affected

* `:rows` - the result set as a list. `nil` may be returned
instead of the list if the command does not yield any row
as result (but still yields the number of affected rows,
like a `delete` command without returning would)

## Options

* `:log` - When false, does not log the query

## Examples

iex> Ecto.Adapters.SQL.query_many(MyRepo, "SELECT $1; SELECT $2;", [40, 2])
{:ok, [%{rows: [[40]], num_rows: 1}, %{rows: [[2]], num_rows: 1}]}

For convenience, this function is also available under the repository:

iex> MyRepo.query_many(SELECT $1; SELECT $2;", [40, 2])
{:ok, [%{rows: [[40]], num_rows: 1}, %{rows: [[2]], num_rows: 1}]}

"""
@spec query_many(pid() | Ecto.Repo.t | Ecto.Adapter.adapter_meta, iodata, [term], Keyword.t) ::
{:ok, [%{:rows => nil | [[term] | binary],
:num_rows => non_neg_integer,
optional(atom) => any}]}
| {:error, Exception.t}
def query_many(repo, sql, params \\ [], opts \\ [])

def query_many(repo, sql, params, opts) when is_atom(repo) or is_pid(repo) do
query_many(Ecto.Adapter.lookup_meta(repo), sql, params, opts)
end

def query_many(adapter_meta, sql, params, opts) do
sql_call(adapter_meta, :query_many, [sql], params, opts)
end

defp sql_call(adapter_meta, callback, args, params, opts) do
%{pid: pool, telemetry: telemetry, sql: sql, opts: default_opts} = adapter_meta
conn = get_conn_or_pool(pool)
Expand Down Expand Up @@ -611,6 +674,24 @@ defmodule Ecto.Adapters.SQL do
Ecto.Adapters.SQL.query!(get_dynamic_repo(), sql, params, opts)
end

@doc """
A convenience function for SQL-based repositories that executes the given multi-result query.

See `Ecto.Adapters.SQL.query_many/4` for more information.
"""
def query_many(sql, params \\ [], opts \\ []) do
Ecto.Adapters.SQL.query_many(get_dynamic_repo(), sql, params, opts)
end

@doc """
A convenience function for SQL-based repositories that executes the given multi-result query.

See `Ecto.Adapters.SQL.query_many!/4` for more information.
"""
def query_many!(sql, params \\ [], opts \\ []) do
Ecto.Adapters.SQL.query_many!(get_dynamic_repo(), sql, params, opts)
end

@doc """
A convenience function for SQL-based repositories that translates the given query to SQL.

Expand Down
8 changes: 7 additions & 1 deletion lib/ecto/adapters/sql/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ defmodule Ecto.Adapters.SQL.Connection do
{:ok, cached, term} | {:ok, term} | {:error | :reset, Exception.t}

@doc """
Runs the given statement as query.
Runs the given statement as a query.
"""
@callback query(connection, statement, params, options :: Keyword.t) ::
{:ok, term} | {:error, Exception.t}

@doc """
Runs the given statement as a multi-result query.
"""
@callback query_many(connection, statement, params, options :: Keyword.t) ::
{:ok, term} | {:error, Exception.t}

@doc """
Returns a stream that prepares and executes the given query with
`DBConnection`.
Expand Down
7 changes: 6 additions & 1 deletion lib/ecto/adapters/tds/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ if Code.ensure_loaded?(Tds) do

@impl true
def stream(_conn, _sql, _params, _opts) do
error!(nil, "Repo.stream is not supported in Tds adapter")
error!(nil, "Repo.stream is not supported in the Tds adapter")
end

@impl true
Expand All @@ -66,6 +66,11 @@ if Code.ensure_loaded?(Tds) do
Tds.query(conn, sql, params, opts)
end

@impl true
def query_many(_conn, _sql, _params, _opts) do
error!(nil, "query_many is not supported in the Tds adapter")
end

@impl true
def to_constraints(%Tds.Error{mssql: %{number: code, msg_text: message}}, _opts) do
Tds.Error.get_constraint_violations(code, message)
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ defmodule EctoSQL.MixProject do
if path = System.get_env("MYXQL_PATH") do
{:myxql, path: path}
else
{:myxql, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", optional: true}
{:myxql, "~> 0.6.0", optional: true}
end
end

Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"myxql": {:hex, :myxql, "0.5.1", "42cc502f9f373eeebfe6753266c0b601c01a6a96e4d861d429a4952ffb396689", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.3", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "73c6b684ae119ef9707a755f185f1410ec611ee748e54b9b1b1ff4aab4bc48d7"},
"myxql": {:hex, :myxql, "0.6.0", "71ca53ee2fe6dd7d395476c8bb49a451b30c626a8a627bc3f21485e6c78c14ae", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "e13198a65d868da4a16cf0d5277024ac3db071162d6dc8e641f279f4c9755d58"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"},
Expand Down