Skip to content

Instantly share code, notes, and snippets.

@gmcabrita
Created April 8, 2024 10:28
Show Gist options
  • Save gmcabrita/35f4555b5560ce904cf9cf6ed4a7f0a2 to your computer and use it in GitHub Desktop.
Save gmcabrita/35f4555b5560ce904cf9cf6ed4a7f0a2 to your computer and use it in GitHub Desktop.
# config/initializers/active_record.rb
Rails.application.reloader.to_prepare do
ActiveRecord::Relation.include(ExplainAnalyze::Relation)
end
> User.where(id: User.select(:id)).select(:email).explain_analyze
https://explain.dalibo.com/plan/XXXXXXXXXXXXXX
# lib/explain_analyze.rb
module ExplainAnalyze
module Relation
extend ActiveSupport::Concern
def explain_analyze
query = to_sql + ";"
explain = "EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) #{query}"
plan = connection.execute(explain)
.values
.first
.first
.gsub(/\s\s\s*/, "") # cleanup useless indentation
.delete("\n") # cleanup useless newlines
response = HTTP
.headers(content_type: "application/json")
.post(
"https://explain.dalibo.com/new",
json: {
plan: plan,
query: query,
title: Time.now.to_s
}
)
if response.status == 302
puts "https://explain.dalibo.com" + response.headers["Location"]
else
raise "Failed to upload explain analyze and query successfully"
end
end
end
end
@gmcabrita
Copy link
Author

Elixir version:

defmodule MyApp.Repo do
  use Ecto.Repo,
    otp_app: :my_app,
    adapter: Ecto.Adapters.Postgres

  def explain_analyze(fragment) do
    {query, _} = Ecto.Adapters.SQL.to_sql(:all, __MODULE__, fragment)

    explain_query = "EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) " <> query

    {:ok, %Postgrex.Result{rows: [[[json]]]}} =
      Ecto.Adapters.SQL.query(__MODULE__, explain_query, [])

    plan = Jason.encode!(json)

    response =
      Req.post!(
        "https://explain.dalibo.com/new",
        json: %{
          plan: plan,
          query: query,
          title: DateTime.utc_now() |> DateTime.to_string()
        },
        headers: [{"Content-Type", "application/json"}],
        redirect: false
      )

    case response.status do
      302 ->
        [location | _] = response.headers["location"]
        IO.puts("https://explain.dalibo.com" <> location)

      _ ->
        raise "Failed to upload to Dalibo!"
    end
  end
end

Usage:

iex(1)> import Ecto.Query
Ecto.Query
iex(1)> MyApp.Repo.explain_analyze(from(u in MyApp.User, limit: 1))
[debug] QUERY OK db=8.3ms queue=9.1ms idle=1259.3ms
EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) SELECT u0."id", u0."name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 LIMIT 1 []
https://explain.dalibo.com/plan/ec6fgde92341b50d
:ok

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment