module PgHero module Methods module Explain # TODO remove in 4.0 # note: this method is not affected by the explain option def explain(sql) sql = squish(sql) explanation = nil # use transaction for safety with_transaction(statement_timeout: (explain_timeout_sec * 1000).round, rollback: true) do if (sql.sub(/;\z/, "").include?(";") || sql.upcase.include?("COMMIT")) && !explain_safe? raise ActiveRecord::StatementInvalid, "Unsafe statement" end explanation = execute("EXPLAIN #{sql}").map { |v| v["QUERY PLAN"] }.join("\n") end explanation end # TODO rename to explain in 4.0 # note: this method is not affected by the explain option def explain_v2(sql, analyze: nil, verbose: nil, costs: nil, settings: nil, generic_plan: nil, buffers: nil, wal: nil, timing: nil, summary: nil, format: "text") options = [] add_explain_option(options, "ANALYZE", analyze) add_explain_option(options, "VERBOSE", verbose) add_explain_option(options, "SETTINGS", settings) add_explain_option(options, "GENERIC_PLAN", generic_plan) add_explain_option(options, "COSTS", costs) add_explain_option(options, "BUFFERS", buffers) add_explain_option(options, "WAL", wal) add_explain_option(options, "TIMING", timing) add_explain_option(options, "SUMMARY", summary) options << "FORMAT #{explain_format(format)}" explain("(#{options.join(", ")}) #{sql}") end private def explain_safe? select_all("SELECT 1; SELECT 1") false rescue ActiveRecord::StatementInvalid true end def add_explain_option(options, name, value) unless value.nil? options << "#{name}#{value ? "" : " FALSE"}" end end # important! validate format to prevent injection def explain_format(format) if ["text", "xml", "json", "yaml"].include?(format) format.upcase else raise ArgumentError, "Unknown format" end end end end end