|
1
|
+ |
defmodule OpenAgents.Machines.IndexReachTest do
|
|
2
|
+ |
@moduledoc """
|
|
3
|
+ |
Issue #184 named three indexes with no reader. Two gained one while it was
|
|
4
|
+ |
open — `inference_grants_machine_id_index` from the revocation sweep in #183,
|
|
5
|
+ |
and `repository_machine_grants_machine_id_index` from the per-computer grant
|
|
6
|
+ |
listing in #182 — and `machine_pairings_expires_at_index` gained one here.
|
|
7
|
+ |
|
|
8
|
+ |
Neither of those lanes asserted that the planner reaches for the index, only
|
|
9
|
+ |
that a query exists, so a reader could narrow again and leave the index dead
|
|
10
|
+ |
exactly as it was found. This is the standing check that it has not.
|
|
11
|
+ |
|
|
12
|
+ |
The plan is read from the SQL the production function emits, captured off
|
|
13
|
+ |
`Ecto.Repo` telemetry while it runs, rather than from a predicate written
|
|
14
|
+ |
here to look like it. A copy would keep passing while the call site changed
|
|
15
|
+ |
underneath it, which is the failure this file exists to prevent.
|
|
16
|
+ |
"""
|
|
17
|
+ |
|
|
18
|
+ |
use OpenAgents.DataCase, async: false
|
|
19
|
+ |
|
|
20
|
+ |
alias OpenAgents.Accounts.User
|
|
21
|
+ |
alias OpenAgents.Inference
|
|
22
|
+ |
alias OpenAgents.Repo
|
|
23
|
+ |
alias OpenAgents.Repositories
|
|
24
|
+ |
|
|
25
|
+ |
# A cold table is small enough that a sequential scan is genuinely the cheaper
|
|
26
|
+ |
# plan, so the planner is asked which index serves the predicate, not whether
|
|
27
|
+ |
# it is worth using yet. `SET LOCAL` dies with the sandbox's transaction and
|
|
28
|
+ |
# cannot follow this connection back into the pool.
|
|
29
|
+ |
defp plan_of(matching, run) do
|
|
30
|
+ |
handler = {__MODULE__, System.unique_integer()}
|
|
31
|
+ |
test = self()
|
|
32
|
+ |
|
|
33
|
+ |
# Derived, not spelled: the prefix is `[:open_agents, :repo]`, and a
|
|
34
|
+ |
# hardcoded guess silently attaches to an event that never fires.
|
|
35
|
+ |
event = Repo.config() |> Keyword.fetch!(:telemetry_prefix) |> Kernel.++([:query])
|
|
36
|
+ |
|
|
37
|
+ |
:telemetry.attach(
|
|
38
|
+ |
handler,
|
|
39
|
+ |
event,
|
|
40
|
+ |
fn _event, _measure, %{query: query, params: params}, _config ->
|
|
41
|
+ |
if String.contains?(query, matching), do: send(test, {:sql, query, params})
|
|
42
|
+ |
end,
|
|
43
|
+ |
nil
|
|
44
|
+ |
)
|
|
45
|
+ |
|
|
46
|
+ |
try do
|
|
47
|
+ |
run.()
|
|
48
|
+ |
after
|
|
49
|
+ |
:telemetry.detach(handler)
|
|
50
|
+ |
end
|
|
51
|
+ |
|
|
52
|
+ |
receive do
|
|
53
|
+ |
{:sql, sql, params} ->
|
|
54
|
+ |
Repo.query!("SET LOCAL enable_seqscan = off")
|
|
55
|
+ |
Repo.query!("EXPLAIN " <> sql, params).rows |> Enum.map_join("\n", &hd/1)
|
|
56
|
+ |
after
|
|
57
|
+ |
0 -> flunk("no query matching #{inspect(matching)} was emitted")
|
|
58
|
+ |
end
|
|
59
|
+ |
end
|
|
60
|
+ |
|
|
61
|
+ |
test "the computer revocation sweep is served by inference_grants_machine_id_index" do
|
|
62
|
+ |
plan =
|
|
63
|
+ |
plan_of("inference_grants", fn ->
|
|
64
|
+ |
Inference.revoke_active_for_machine(Ecto.UUID.generate())
|
|
65
|
+ |
end)
|
|
66
|
+ |
|
|
67
|
+ |
assert plan =~ "inference_grants_machine_id_index", plan
|
|
68
|
+ |
end
|
|
69
|
+ |
|
|
70
|
+ |
test "the per-computer grant listing is served by repository_machine_grants_machine_id_index" do
|
|
71
|
+ |
owner = owner()
|
|
72
|
+ |
machine = computer(owner)
|
|
73
|
+ |
|
|
74
|
+ |
plan =
|
|
75
|
+ |
plan_of("repository_machine_grants", fn ->
|
|
76
|
+ |
Repositories.list_machine_grants(owner, machine.id)
|
|
77
|
+ |
end)
|
|
78
|
+ |
|
|
79
|
+ |
# The composite unique index leads on `repository_id`, which this filter
|
|
80
|
+ |
# does not name, so it cannot serve here — which is why the single-column
|
|
81
|
+ |
# index stopped being redundant the moment the listing landed.
|
|
82
|
+ |
assert plan =~ "repository_machine_grants_machine_id_index", plan
|
|
83
|
+ |
refute plan =~ "repository_machine_grants_repository_id_machine_id_index"
|
|
84
|
+ |
end
|
|
85
|
+ |
|
|
86
|
+ |
test "the pairing sweep is served by machine_pairings_expires_at_index" do
|
|
87
|
+ |
plan =
|
|
88
|
+ |
plan_of("machine_pairings", fn ->
|
|
89
|
+ |
OpenAgents.Machines.expire_elapsed_pairings()
|
|
90
|
+ |
end)
|
|
91
|
+ |
|
|
92
|
+ |
assert plan =~ "machine_pairings_expires_at_index", plan
|
|
93
|
+ |
end
|
|
94
|
+ |
|
|
95
|
+ |
defp owner do
|
|
96
|
+ |
{:ok, %User{} = user} =
|
|
97
|
+ |
OpenAgents.Accounts.upsert_github_user(%{
|
|
98
|
+ |
github_id: :erlang.phash2({__MODULE__, System.unique_integer()}),
|
|
99
|
+ |
github_login: "index-reach-#{System.unique_integer([:positive])}",
|
|
100
|
+ |
github_avatar_url: "https://avatars.githubusercontent.com/u/1?v=4"
|
|
101
|
+ |
})
|
|
102
|
+ |
|
|
103
|
+ |
user
|
|
104
|
+ |
end
|
|
105
|
+ |
|
|
106
|
+ |
defp computer(owner) do
|
|
107
|
+ |
{:ok, %{code: code}} =
|
|
108
|
+ |
OpenAgents.Machines.start_pairing(%{"name" => "box", "tier" => "probe"})
|
|
109
|
+ |
|
|
110
|
+ |
{:ok, machine} = OpenAgents.Machines.approve_pairing(owner, code)
|
|
111
|
+ |
machine
|
|
112
|
+ |
end
|
|
113
|
+ |
end
|