Skip to content

Commit 08cda5c

Browse files
Add query_many (#379)
1 parent 988dddb commit 08cda5c

10 files changed

Lines changed: 137 additions & 5 deletions

File tree

integration_test/myxql/all_test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Code.require_file "../sql/lock.exs", __DIR__
1111
Code.require_file "../sql/logging.exs", __DIR__
1212
Code.require_file "../sql/migration.exs", __DIR__
1313
Code.require_file "../sql/migrator.exs", __DIR__
14+
Code.require_file "../sql/query_many.exs", __DIR__
1415
Code.require_file "../sql/sandbox.exs", __DIR__
1516
Code.require_file "../sql/sql.exs", __DIR__
1617
Code.require_file "../sql/stream.exs", __DIR__
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule Ecto.Integration.QueryManyTest do
2+
use Ecto.Integration.Case, async: true
3+
4+
alias Ecto.Integration.TestRepo
5+
6+
test "query_many!/4" do
7+
results = TestRepo.query_many!("SELECT 1; SELECT 2;")
8+
assert [%{rows: [[1]], num_rows: 1}, %{rows: [[2]], num_rows: 1}] = results
9+
end
10+
11+
test "query_many!/4 with iodata" do
12+
results = TestRepo.query_many!(["SELECT", ?\s, ?1, ";", ?\s, "SELECT", ?\s, ?2, ";"])
13+
assert [%{rows: [[1]], num_rows: 1}, %{rows: [[2]], num_rows: 1}] = results
14+
end
15+
end

lib/ecto/adapters/myxql.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ defmodule Ecto.Adapters.MyXQL do
118118
119119
If your version of MySQL supports microsecond precision, you
120120
will be able to utilize Ecto's usec types.
121+
122+
## Multiple Result Support
123+
124+
MyXQL supports the execution of queries that return multiple
125+
results, such as text queries with multiple statements separated
126+
by semicolons or stored procedures. These can be executed with
127+
`Ecto.Adapters.SQL.query_many/4` or the `YourRepo.query_many/3`
128+
shortcut.
129+
130+
Be default, these queries will be executed with the `:query_type`
131+
option set to `:text`. To take advantage of prepared statements
132+
when executing a stored procedure, set the `:query_type` option
133+
to `:binary`.
121134
"""
122135

123136
# Inherit all behaviour from Ecto.Adapters.SQL

lib/ecto/adapters/myxql/connection.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ if Code.ensure_loaded?(MyXQL) do
2525
MyXQL.query(conn, sql, params, opts)
2626
end
2727

28+
@impl true
29+
def query_many(conn, sql, params, opts) do
30+
opts = Keyword.put_new(opts, :query_type, :text)
31+
MyXQL.query_many(conn, sql, params, opts)
32+
end
33+
2834
@impl true
2935
def execute(conn, query, params, opts) do
3036
case MyXQL.execute(conn, query, params, opts) do

lib/ecto/adapters/postgres/connection.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ if Code.ensure_loaded?(Postgrex) do
9191
Postgrex.query(conn, sql, params, opts)
9292
end
9393

94+
@impl true
95+
def query_many(_conn, _sql, _params, _opts) do
96+
raise RuntimeError, "query_many is not supported in the Postgrex adapter"
97+
end
98+
9499
@impl true
95100
def execute(conn, %{ref: ref} = query, params, opts) do
96101
case Postgrex.execute(conn, query, params, opts) do

lib/ecto/adapters/sql.ex

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ defmodule Ecto.Adapters.SQL do
3030
* `query!(sql, params, options \\ [])` -
3131
shortcut for `Ecto.Adapters.SQL.query!/4`
3232
33+
* `query_many(sql, params, options \\ [])` -
34+
shortcut for `Ecto.Adapters.SQL.query_many/4`
35+
36+
* `query_many!(sql, params, options \\ [])` -
37+
shortcut for `Ecto.Adapters.SQL.query_many!/4`
38+
3339
* `to_sql(type, query, options \\ [])` -
3440
shortcut for `Ecto.Adapters.SQL.to_sql/4`
3541
@@ -429,7 +435,7 @@ defmodule Ecto.Adapters.SQL do
429435
end
430436

431437
@doc """
432-
Runs custom SQL query on given repo.
438+
Runs a custom SQL query on the given repo.
433439
434440
In case of success, it must return an `:ok` tuple containing
435441
a map with at least two keys:
@@ -471,6 +477,63 @@ defmodule Ecto.Adapters.SQL do
471477
sql_call(adapter_meta, :query, [sql], params, opts)
472478
end
473479

480+
@doc """
481+
Same as `query_many/4` but raises on invalid queries.
482+
"""
483+
@spec query_many!(Ecto.Repo.t | Ecto.Adapter.adapter_meta, iodata, [term], Keyword.t) ::
484+
[%{:rows => nil | [[term] | binary],
485+
:num_rows => non_neg_integer,
486+
optional(atom) => any}]
487+
def query_many!(repo, sql, params \\ [], opts \\ []) do
488+
case query_many(repo, sql, params, opts) do
489+
{:ok, result} -> result
490+
{:error, err} -> raise_sql_call_error err
491+
end
492+
end
493+
494+
@doc """
495+
Runs a custom SQL query that returns multiple results on the given repo.
496+
497+
In case of success, it must return an `:ok` tuple containing
498+
a list of maps with at least two keys:
499+
500+
* `:num_rows` - the number of rows affected
501+
502+
* `:rows` - the result set as a list. `nil` may be returned
503+
instead of the list if the command does not yield any row
504+
as result (but still yields the number of affected rows,
505+
like a `delete` command without returning would)
506+
507+
## Options
508+
509+
* `:log` - When false, does not log the query
510+
511+
## Examples
512+
513+
iex> Ecto.Adapters.SQL.query_many(MyRepo, "SELECT $1; SELECT $2;", [40, 2])
514+
{:ok, [%{rows: [[40]], num_rows: 1}, %{rows: [[2]], num_rows: 1}]}
515+
516+
For convenience, this function is also available under the repository:
517+
518+
iex> MyRepo.query_many(SELECT $1; SELECT $2;", [40, 2])
519+
{:ok, [%{rows: [[40]], num_rows: 1}, %{rows: [[2]], num_rows: 1}]}
520+
521+
"""
522+
@spec query_many(pid() | Ecto.Repo.t | Ecto.Adapter.adapter_meta, iodata, [term], Keyword.t) ::
523+
{:ok, [%{:rows => nil | [[term] | binary],
524+
:num_rows => non_neg_integer,
525+
optional(atom) => any}]}
526+
| {:error, Exception.t}
527+
def query_many(repo, sql, params \\ [], opts \\ [])
528+
529+
def query_many(repo, sql, params, opts) when is_atom(repo) or is_pid(repo) do
530+
query_many(Ecto.Adapter.lookup_meta(repo), sql, params, opts)
531+
end
532+
533+
def query_many(adapter_meta, sql, params, opts) do
534+
sql_call(adapter_meta, :query_many, [sql], params, opts)
535+
end
536+
474537
defp sql_call(adapter_meta, callback, args, params, opts) do
475538
%{pid: pool, telemetry: telemetry, sql: sql, opts: default_opts} = adapter_meta
476539
conn = get_conn_or_pool(pool)
@@ -611,6 +674,24 @@ defmodule Ecto.Adapters.SQL do
611674
Ecto.Adapters.SQL.query!(get_dynamic_repo(), sql, params, opts)
612675
end
613676

677+
@doc """
678+
A convenience function for SQL-based repositories that executes the given multi-result query.
679+
680+
See `Ecto.Adapters.SQL.query_many/4` for more information.
681+
"""
682+
def query_many(sql, params \\ [], opts \\ []) do
683+
Ecto.Adapters.SQL.query_many(get_dynamic_repo(), sql, params, opts)
684+
end
685+
686+
@doc """
687+
A convenience function for SQL-based repositories that executes the given multi-result query.
688+
689+
See `Ecto.Adapters.SQL.query_many!/4` for more information.
690+
"""
691+
def query_many!(sql, params \\ [], opts \\ []) do
692+
Ecto.Adapters.SQL.query_many!(get_dynamic_repo(), sql, params, opts)
693+
end
694+
614695
@doc """
615696
A convenience function for SQL-based repositories that translates the given query to SQL.
616697

lib/ecto/adapters/sql/connection.ex

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,17 @@ defmodule Ecto.Adapters.SQL.Connection do
3434
{:ok, cached, term} | {:ok, term} | {:error | :reset, Exception.t}
3535

3636
@doc """
37-
Runs the given statement as query.
37+
Runs the given statement as a query.
3838
"""
3939
@callback query(connection, statement, params, options :: Keyword.t) ::
4040
{:ok, term} | {:error, Exception.t}
4141

42+
@doc """
43+
Runs the given statement as a multi-result query.
44+
"""
45+
@callback query_many(connection, statement, params, options :: Keyword.t) ::
46+
{:ok, term} | {:error, Exception.t}
47+
4248
@doc """
4349
Returns a stream that prepares and executes the given query with
4450
`DBConnection`.

lib/ecto/adapters/tds/connection.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ if Code.ensure_loaded?(Tds) do
5757

5858
@impl true
5959
def stream(_conn, _sql, _params, _opts) do
60-
error!(nil, "Repo.stream is not supported in Tds adapter")
60+
error!(nil, "Repo.stream is not supported in the Tds adapter")
6161
end
6262

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

69+
@impl true
70+
def query_many(_conn, _sql, _params, _opts) do
71+
error!(nil, "query_many is not supported in the Tds adapter")
72+
end
73+
6974
@impl true
7075
def to_constraints(%Tds.Error{mssql: %{number: code, msg_text: message}}, _opts) do
7176
Tds.Error.get_constraint_violations(code, message)

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ defmodule EctoSQL.MixProject do
9393
if path = System.get_env("MYXQL_PATH") do
9494
{:myxql, path: path}
9595
else
96-
{:myxql, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", optional: true}
96+
{:myxql, "~> 0.6.0", optional: true}
9797
end
9898
end
9999

mix.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
1313
"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"},
1414
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
15-
"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"},
15+
"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"},
1616
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
1717
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
1818
"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"},

0 commit comments

Comments
 (0)