├── .coveralls.yml ├── .formatter.exs ├── .gitignore ├── .tool-versions ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config ├── config.exs ├── dev.exs └── test.exs ├── lib └── ecto │ └── explain.ex ├── mix.exs ├── mix.lock └── test ├── ecto_explain_test.exs ├── support ├── ecto_explain_test_repo.ex └── postgres_types.ex └── test_helper.exs /.coveralls.yml: -------------------------------------------------------------------------------- 1 | multi: 2 | excoveralls: cover/excoveralls.json 3 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | ecto_explain-*.tar 24 | 25 | .elixir_ls 26 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 21.3.8 2 | elixir 1.8.1-otp-21 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | dist: trusty 3 | elixir: 4 | - 1.8 5 | otp_release: 6 | - 21.1 7 | addons: 8 | postgresql: '9.5' 9 | services: 10 | - postgresql 11 | before_script: 12 | - gem install coveralls-multi --no-document 13 | - MIX_ENV=test mix ecto.create 14 | script: 15 | - MIX_ENV=test mix coveralls.json 16 | - coveralls-multi 17 | deploy: 18 | skip_cleanup: true 19 | provider: script 20 | script: mix hex.publish --yes 21 | on: 22 | tags: true 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ### Added 11 | 12 | - Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Revelry Labs LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 5 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 6 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 9 | of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 12 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 13 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 14 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 15 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/revelrylabs/ecto_soft_delete.svg?branch=master)](https://travis-ci.org/revelrylabs/ecto_explain) 2 | [![Hex.pm](https://img.shields.io/hexpm/dt/ecto_explain.svg)](https://hex.pm/packages/ecto_explain) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![Coverage Status](https://opencov.prod.revelry.net/projects/34/badge.svg)](https://opencov.prod.revelry.net/projects/34) 5 | 6 | # Explain 7 | 8 | Adds explain function to Ecto.Repo 9 | 10 | ## Usage 11 | 12 | To include the explain function in repos, just add use Ecto.Explain to your repo. After that, the function explain/1 will be available for you. 13 | 14 | ```elixir 15 | # repo.ex 16 | defmodule Ecto.ExplainTest.Repo do 17 | use Ecto.Repo, 18 | otp_app: :my_project, 19 | adapter: Ecto.Adapters.Postgres 20 | use Ecto.Explain 21 | end 22 | ``` 23 | 24 | ```elixir 25 | # posts.ex 26 | Repo.explain(from(p in Post)) 27 | 28 | Update on posts p0 (cost=0.00..10.70 rows=70 width=1046) 29 | -> Seq Scan on posts p0 (cost=0.00..10.70 rows=70 width=1046) 30 | ``` 31 | 32 | ```elixir 33 | Repo.explain(from(posts in Post), format: :json, analyze: true) 34 | 35 | [ 36 | [ 37 | { 38 | "Execution Time": 0.084, 39 | "Plan": { 40 | "Actual Loops": 1, 41 | "Actual Rows": 0, 42 | "Actual Startup Time": 0.027, 43 | "Actual Total Time": 0.027, 44 | "Alias": "p0", 45 | "Node Type": "Seq Scan", 46 | "Parallel Aware": false, 47 | "Plan Rows": 70, 48 | "Plan Width": 1040, 49 | "Relation Name": "posts", 50 | "Startup Cost": 0.0, 51 | "Total Cost": 10.7 52 | }, 53 | "Planning Time": 0.585, 54 | "Triggers": [] 55 | } 56 | ] 57 | ] 58 | ``` 59 | 60 | 61 | ## Installation 62 | 63 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 64 | by adding `ecto_explain` to your list of dependencies in `mix.exs`: 65 | 66 | ```elixir 67 | def deps do 68 | [ 69 | {:ecto_explain, "~> 0.1.2"} 70 | ] 71 | end 72 | ``` 73 | 74 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 75 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 76 | be found at [https://hexdocs.pm/ecto_explain](https://hexdocs.pm/ecto_explain). 77 | 78 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :ecto_explain, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:ecto_explain, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | import_config "#{Mix.env()}.exs" 31 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :ecto_explain, ecto_repos: [Ecto.ExplainTest.Repo] 4 | 5 | config :ecto_explain, Ecto.ExplainTest.Repo, 6 | database: "ecto_explain_test", 7 | hostname: "localhost", 8 | port: 5432, 9 | pool: Ecto.Adapters.SQL.Sandbox, 10 | types: EctoExplain.PostgresTypes 11 | -------------------------------------------------------------------------------- /lib/ecto/explain.ex: -------------------------------------------------------------------------------- 1 | defmodule Ecto.Explain do 2 | @moduledoc """ 3 | Explain function for Ecto.Repo 4 | """ 5 | defmacro __using__(_) do 6 | quote location: :keep do 7 | require Logger 8 | 9 | def explain(query, opts \\ []) do 10 | opts = put_defaults(opts) 11 | 12 | {sql, params} = Ecto.Adapters.SQL.to_sql(opts[:op], __MODULE__, query) 13 | 14 | sql = "EXPLAIN (#{analyze_to_sql(opts[:analyze])}, #{format_to_sql(opts[:format])}) #{sql}" 15 | 16 | {:error, explain} = 17 | __MODULE__.transaction(fn -> 18 | __MODULE__ 19 | |> Ecto.Adapters.SQL.query!(sql, params) 20 | |> __MODULE__.rollback() 21 | end) 22 | 23 | if opts[:log_output] do 24 | log_output(explain, opts[:format]) 25 | query 26 | else 27 | explain 28 | end 29 | end 30 | 31 | defp put_defaults(opts) do 32 | opts 33 | |> Keyword.put_new(:op, :all) 34 | |> Keyword.put_new(:format, :json) 35 | |> Keyword.put_new(:analyze, false) 36 | |> Keyword.put_new(:log_output, true) 37 | end 38 | 39 | defp log_output(results, :text) do 40 | results 41 | |> Map.get(:rows) 42 | |> Enum.join("\n") 43 | |> Logger.warn() 44 | end 45 | 46 | defp log_output(results, :json) do 47 | results 48 | |> Map.get(:rows) 49 | |> List.first() 50 | |> Jason.encode!(pretty: true) 51 | |> Logger.warn() 52 | end 53 | 54 | defp log_output(results, :yaml) do 55 | results 56 | |> Map.get(:rows) 57 | |> List.first() 58 | |> Logger.warn() 59 | end 60 | 61 | defp format_to_sql(:text), do: "FORMAT TEXT" 62 | defp format_to_sql(:json), do: "FORMAT JSON" 63 | defp format_to_sql(:yaml), do: "FORMAT YAML" 64 | 65 | defp analyze_to_sql(true), do: "ANALYZE true" 66 | defp analyze_to_sql(false), do: "ANALYZE false" 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Explain.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :ecto_explain, 7 | version: "0.1.3", 8 | elixir: "~> 1.7", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | build_embedded: Mix.env() == :prod, 11 | start_permanent: Mix.env() == :prod, 12 | test_coverage: [tool: ExCoveralls], 13 | preferred_cli_env: [ 14 | coveralls: :test, 15 | "coveralls.detail": :test, 16 | "coveralls.post": :test, 17 | "coveralls.html": :test 18 | ], 19 | deps: deps(), 20 | description: description(), 21 | package: package(), 22 | # Docs 23 | name: "Ecto Explain", 24 | source_url: "https://github.com/revelrylabs/ecto_explain", 25 | homepage_url: "https://github.com/revelrylabs/ecto_explain", 26 | docs: [main: "readme", extras: ["README.md"]], 27 | ] 28 | end 29 | 30 | def application do 31 | [extra_applications: [:logger]] 32 | end 33 | 34 | defp elixirc_paths(:test), do: ["lib", "test/support"] 35 | defp elixirc_paths(_), do: ["lib"] 36 | 37 | defp deps do 38 | [ 39 | {:ecto_sql, "~> 3.1.3", only: [:test]}, 40 | {:postgrex, "~> 0.14.3", only: [:test]}, 41 | {:jason, "~> 1.0", only: [:dev, :test]}, 42 | {:ex_doc, ">= 0.0.0", only: [:dev, :test]}, 43 | {:excoveralls, "~> 0.8", only: [:dev, :test]} 44 | ] 45 | end 46 | 47 | defp description do 48 | """ 49 | Explain with Ecto. 50 | """ 51 | end 52 | 53 | defp package do 54 | [ 55 | files: ["lib", "mix.exs", "README.md", "LICENSE", "CHANGELOG.md"], 56 | maintainers: ["Revelry Labs"], 57 | licenses: ["MIT"], 58 | links: %{ 59 | "GitHub" => "https://github.com/revelrylabs/ecto_explain" 60 | }, 61 | build_tools: ["mix"] 62 | ] 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, 4 | "db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"}, 6 | "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, 7 | "ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, 8 | "ecto_sql": {:hex, :ecto_sql, "3.1.3", "2c536139190492d9de33c5fefac7323c5eaaa82e1b9bf93482a14649042f7cd9", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, 9 | "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 10 | "excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, 11 | "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, 12 | "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, 13 | "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, 14 | "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 15 | "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, 16 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, 17 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, 18 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, 19 | "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, 20 | "postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [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"}, 21 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, 22 | "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, 23 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, 24 | } 25 | -------------------------------------------------------------------------------- /test/ecto_explain_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Ecto.ExplainTest do 2 | use ExUnit.Case 3 | alias Ecto.ExplainTest.Repo 4 | import Ecto.Query, warn: false 5 | 6 | defmodule Post do 7 | use Ecto.Schema 8 | 9 | schema "posts" do 10 | field(:title, :string) 11 | field(:body, :string) 12 | end 13 | end 14 | 15 | setup do 16 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(Ecto.ExplainTest.Repo) 17 | end 18 | 19 | test "explain all" do 20 | query = from(posts in Post) 21 | assert Repo.explain(query) == query 22 | end 23 | 24 | test "explain update_all" do 25 | post = Repo.insert!(%Post{title: "original title"}) 26 | query = from(posts in Post, update: [set: [title: ^"hello"]]) 27 | assert Repo.explain(query, op: :update_all) == query 28 | assert %Post{title: "original title"} = Repo.get!(Post, post.id) 29 | end 30 | 31 | test "explain format text " do 32 | query = from(posts in Post) 33 | assert Repo.explain(query, format: :json) == query 34 | end 35 | 36 | test "explain format json" do 37 | query = from(posts in Post) 38 | assert Repo.explain(query, format: :json) == query 39 | end 40 | 41 | test "explain format yaml" do 42 | query = from(posts in Post) 43 | assert Repo.explain(query, format: :yaml) == query 44 | end 45 | 46 | test "log output false" do 47 | query = from(posts in Post) 48 | refute Repo.explain(query, log_output: false) == query 49 | assert %Postgrex.Result{ 50 | rows: [[[%{"Plan" => %{}}]]] 51 | } = Repo.explain(query, log_output: false) 52 | end 53 | 54 | test "explain analyze" do 55 | query = from(posts in Post) 56 | 57 | assert %Postgrex.Result{ 58 | rows: [[[%{ 59 | "Execution Time" => execution_time, 60 | "Planning Time" => planning_time, 61 | "Triggers" => [], 62 | "Plan" => %{ 63 | "Actual Loops" => actual_loops, 64 | "Actual Rows" => actual_rows, 65 | "Actual Startup Time" => actual_startup_time, 66 | "Actual Total Time" => actual_total_time, 67 | } 68 | }]]] 69 | } = Repo.explain(query, analyze: true, log_output: false) 70 | 71 | assert is_float(execution_time) 72 | assert is_float(planning_time) 73 | assert is_float(actual_startup_time) 74 | assert is_float(actual_total_time) 75 | assert is_integer(actual_loops) 76 | assert is_integer(actual_rows) 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /test/support/ecto_explain_test_repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Ecto.ExplainTest.Repo do 2 | use Ecto.Repo, 3 | otp_app: :ecto_explain, 4 | adapter: Ecto.Adapters.Postgres 5 | use Ecto.Explain 6 | end 7 | -------------------------------------------------------------------------------- /test/support/postgres_types.ex: -------------------------------------------------------------------------------- 1 | Postgrex.Types.define(EctoExplain.PostgresTypes, [], json: Jason) 2 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | {:ok, _} = Application.ensure_all_started(:postgrex) 2 | {:ok, _pid} = Ecto.ExplainTest.Repo.start_link 3 | 4 | defmodule Ecto.ExplainTest.Migrations do 5 | use Ecto.Migration 6 | 7 | def change do 8 | drop_if_exists table(:posts) 9 | create table(:posts) do 10 | add :title, :string 11 | add :body, :string 12 | end 13 | end 14 | end 15 | 16 | _ = Ecto.Migrator.up(Ecto.ExplainTest.Repo, 0, Ecto.ExplainTest.Migrations, log: false) 17 | ExUnit.start() 18 | 19 | Ecto.Adapters.SQL.Sandbox.mode(Ecto.ExplainTest.Repo, :manual) 20 | --------------------------------------------------------------------------------