Skip to content

Commit 4752ad2

Browse files
Add :wrap_in_transaction option to explain (#682)
1 parent cf5080c commit 4752ad2

File tree

4 files changed

+63
-9
lines changed

4 files changed

+63
-9
lines changed

integration_test/myxql/explain_test.exs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule Ecto.Integration.ExplainTest do
1010
explain = TestRepo.explain(:all, from(p in Post, where: p.title == "title"), timeout: 20000)
1111

1212
assert explain =~
13-
"| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |"
13+
"| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |"
1414

1515
assert explain =~ "p0"
1616
assert explain =~ "SIMPLE"
@@ -24,7 +24,9 @@ defmodule Ecto.Integration.ExplainTest do
2424
end
2525

2626
test "update" do
27-
explain = TestRepo.explain(:update_all, from(p in Post, update: [set: [title: "new title"]]))
27+
explain =
28+
TestRepo.explain(:update_all, from(p in Post, update: [set: [title: "new title"]]))
29+
2830
assert explain =~ "UPDATE"
2931
assert explain =~ "p0"
3032
end
@@ -37,10 +39,20 @@ defmodule Ecto.Integration.ExplainTest do
3739

3840
test "map format" do
3941
[explain] = TestRepo.explain(:all, Post, format: :map)
40-
keys = explain["query_block"] |> Map.keys
42+
keys = explain["query_block"] |> Map.keys()
4143
assert Enum.member?(keys, "cost_info")
4244
assert Enum.member?(keys, "select_id")
4345
assert Enum.member?(keys, "table")
4446
end
47+
48+
test "explain without rolling back" do
49+
{:ok, {:ok, explain}} =
50+
TestRepo.transaction(fn ->
51+
TestRepo.explain(:delete_all, Post, wrap_in_transaction: false, timeout: 20000)
52+
end)
53+
54+
assert explain =~ "DELETE"
55+
assert explain =~ "p0"
56+
end
4557
end
4658
end

integration_test/pg/explain_test.exs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,22 @@ defmodule Ecto.Integration.ExplainTest do
8181
assert explain =~ ~r/Node Type:/
8282
assert explain =~ ~r/Relation Name:/
8383
end
84+
85+
test "explain without rolling back" do
86+
TestRepo.insert!(%Post{})
87+
assert [%Post{}] = TestRepo.all(Post)
88+
89+
{:ok, {:ok, explain}} =
90+
TestRepo.transaction(fn ->
91+
TestRepo.explain(:delete_all, Post,
92+
analyze: true,
93+
wrap_in_transaction: false,
94+
timeout: 20000
95+
)
96+
end)
97+
98+
assert explain =~ "Delete on posts p0"
99+
assert explain =~ "cost="
100+
assert TestRepo.all(Post) == []
101+
end
84102
end

integration_test/tds/explain_test.exs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ defmodule Ecto.Integration.ExplainTest do
77

88
describe "explain" do
99
test "select" do
10-
explain = TestRepo.explain(:all, from(p in Post, where: p.title == "explain_test", limit: 1))
10+
explain =
11+
TestRepo.explain(:all, from(p in Post, where: p.title == "explain_test", limit: 1))
12+
1113
assert explain =~ "| Rows | Executes |"
1214
assert explain =~ "| Parallel | EstimateExecutions |"
1315
assert explain =~ "SELECT TOP(1)"
@@ -21,7 +23,9 @@ defmodule Ecto.Integration.ExplainTest do
2123
end
2224

2325
test "update" do
24-
explain = TestRepo.explain(:update_all, from(p in Post, update: [set: [title: "new title"]]))
26+
explain =
27+
TestRepo.explain(:update_all, from(p in Post, update: [set: [title: "new title"]]))
28+
2529
assert explain =~ "UPDATE"
2630
assert explain =~ "p0"
2731
assert explain =~ "new title"
@@ -32,5 +36,15 @@ defmodule Ecto.Integration.ExplainTest do
3236
TestRepo.explain(:all, from(p in "posts", select: p.invalid, where: p.invalid == "title"))
3337
end)
3438
end
39+
40+
test "explain without rolling back" do
41+
{:ok, {:ok, explain}} =
42+
TestRepo.transaction(fn ->
43+
TestRepo.explain(:delete_all, Post, wrap_in_transaction: false, timeout: 20000)
44+
end)
45+
46+
assert explain =~ "DELETE"
47+
assert explain =~ "p0"
48+
end
3549
end
3650
end

lib/ecto/adapters/sql.ex

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,8 @@ defmodule Ecto.Adapters.SQL do
434434
435435
Adapter | Supported opts
436436
---------------- | --------------
437-
Postgrex | `analyze`, `verbose`, `costs`, `settings`, `buffers`, `timing`, `summary`, `format`, `plan`
438-
MyXQL | `format`
437+
Postgrex | `analyze`, `verbose`, `costs`, `settings`, `buffers`, `timing`, `summary`, `format`, `plan`, `wrap_in_transaction`
438+
MyXQL | `format`, `wrap_in_transaction`
439439
440440
All options except `format` are boolean valued and default to `false`.
441441
@@ -447,6 +447,10 @@ defmodule Ecto.Adapters.SQL do
447447
* Postgrex: `:map`, `:yaml` and `:text`
448448
* MyXQL: `:map` and `:text`
449449
450+
The `wrap_in_transaction` option is a boolean that controls whether the command is run inside of a
451+
transaction that is rolled back. This is useful when, for example, you'd like to use `analyze: true`
452+
on an update or delete query without modifying data. Defaults to `true`.
453+
450454
The `:plan` option in Postgrex can take the values `:custom` or `:fallback_generic`. When `:custom`
451455
is specified, the explain plan generated will consider the specific values of the query parameters
452456
that are supplied. When using `:fallback_generic`, the specific values of the query parameters will
@@ -508,10 +512,11 @@ defmodule Ecto.Adapters.SQL do
508512
def explain(repo, operation, queryable, opts \\ [])
509513

510514
def explain(repo, operation, queryable, opts) when is_atom(repo) or is_pid(repo) do
511-
explain(Ecto.Adapter.lookup_meta(repo), operation, queryable, opts)
515+
wrap_in_transaction? = Keyword.get(opts, :wrap_in_transaction, true)
516+
explain(Ecto.Adapter.lookup_meta(repo), operation, queryable, wrap_in_transaction?, opts)
512517
end
513518

514-
def explain(%{repo: repo} = adapter_meta, operation, queryable, opts) do
519+
def explain(%{repo: repo} = adapter_meta, operation, queryable, true, opts) do
515520
Ecto.Multi.new()
516521
|> Ecto.Multi.run(:explain, fn _, _ ->
517522
{prepared, prepared_params} = to_sql(operation, repo, queryable)
@@ -528,6 +533,11 @@ defmodule Ecto.Adapters.SQL do
528533
end
529534
end
530535

536+
def explain(%{repo: repo} = adapter_meta, operation, queryable, false, opts) do
537+
{prepared, prepared_params} = to_sql(operation, repo, queryable)
538+
sql_call(adapter_meta, :explain_query, [prepared], prepared_params, opts)
539+
end
540+
531541
@doc @disconnect_all_doc
532542
@spec disconnect_all(
533543
pid | Ecto.Repo.t() | Ecto.Adapter.adapter_meta(),

0 commit comments

Comments
 (0)