Class: QueryGuard::Explain::PostgreSQLAdapter
- Inherits:
-
AdapterInterface
- Object
- AdapterInterface
- QueryGuard::Explain::PostgreSQLAdapter
- Defined in:
- lib/query_guard/explain/postgresql_adapter.rb
Overview
PostgreSQL-specific EXPLAIN plan adapter. Executes EXPLAIN (FORMAT JSON, ANALYZE) for PostgreSQL queries.
Features:
- Safely executes EXPLAIN with ANALYZE for actual metrics
- Returns well-structured JSON plan data
- Fails gracefully if connection unavailable
- Filters out unsafe query patterns
- Configurable timeout and ANALYZE flag
Example:
adapter = PostgreSQLAdapter.new(ActiveRecord::Base.connection)
plan = adapter.get_plan("SELECT * FROM users WHERE status = 'active'")
Note: Requires postgres adapter (pg gem) with access to query execution
Constant Summary collapse
- DEFAULT_EXPLAIN_TIMEOUT =
seconds
5.0
Instance Method Summary collapse
-
#can_explain?(sql) ⇒ Boolean
Check if query can be safely explained.
-
#engine_name ⇒ Symbol
Engine identifier.
-
#get_plan(sql, options = {}) ⇒ Hash
Execute EXPLAIN and return parsed JSON plan.
-
#initialize(connection, options = {}) ⇒ PostgreSQLAdapter
constructor
Initialize PostgreSQL adapter.
-
#server_version ⇒ String
Get version of PostgreSQL server.
Constructor Details
#initialize(connection, options = {}) ⇒ PostgreSQLAdapter
Initialize PostgreSQL adapter
33 34 35 36 37 38 39 40 41 |
# File 'lib/query_guard/explain/postgresql_adapter.rb', line 33 def initialize(connection, = {}) super(connection) @timeout = [:timeout] || DEFAULT_EXPLAIN_TIMEOUT @use_analyze = .fetch(:use_analyze, false) @logger = [:logger] @validate_connection = .fetch(:validate_connection, true) validate_connection! if @validate_connection end |
Instance Method Details
#can_explain?(sql) ⇒ Boolean
Check if query can be safely explained
79 80 81 82 83 84 85 86 87 |
# File 'lib/query_guard/explain/postgresql_adapter.rb', line 79 def can_explain?(sql) normalized = sql.strip.upcase # Only EXPLAIN SELECT, WITH (CTE), and simple UPDATE/DELETE return false if normalized.start_with?("PRAGMA", "BEGIN", "COMMIT") return false if normalized.start_with?("DROP", "ALTER", "CREATE", "TRUNCATE") return false if normalized.include?("RETURNING") && !normalized.start_with?("SELECT") true end |
#engine_name ⇒ Symbol
Engine identifier
92 93 94 |
# File 'lib/query_guard/explain/postgresql_adapter.rb', line 92 def engine_name :postgresql end |
#get_plan(sql, options = {}) ⇒ Hash
Execute EXPLAIN and return parsed JSON plan
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/query_guard/explain/postgresql_adapter.rb', line 49 def get_plan(sql, = {}) unless can_explain?(sql) error_msg = "Cannot EXPLAIN this query type: #{sql.strip[0..50]}..." log_warn(error_msg) raise UnsupportedQueryError, error_msg end use_analyze = .fetch(:use_analyze, @use_analyze) explain_sql = build_explain_query(sql, use_analyze) log_debug("Executing EXPLAIN query", use_analyze: use_analyze) plan_json = execute_explain(explain_sql) JSON.parse(plan_json) rescue JSON::ParserError => e error_msg = "Failed to parse EXPLAIN output: #{e.}" log_warn(error_msg) raise PlanParseError, error_msg rescue Timeout::Error => e error_msg = "EXPLAIN query timed out after #{@timeout}s" log_warn(error_msg) raise TimeoutError, error_msg rescue StandardError => e log_warn("EXPLAIN execution failed: #{e.}") raise AdapterError, "EXPLAIN execution failed: #{e.}" end |
#server_version ⇒ String
Get version of PostgreSQL server
99 100 101 102 103 |
# File 'lib/query_guard/explain/postgresql_adapter.rb', line 99 def server_version execute_query("SELECT version()").first.first rescue StandardError => e raise ConnectionError, "Cannot connect to PostgreSQL: #{e.}" end |