Class: QueryGuard::Explain::QueryPlan

Inherits:
Object
  • Object
show all
Defined in:
lib/query_guard/explain/plan_signals.rb

Overview

Complete query plan and extracted signals

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(explain_output) ⇒ QueryPlan

Returns a new instance of QueryPlan.



127
128
129
130
131
132
133
134
135
136
137
# File 'lib/query_guard/explain/plan_signals.rb', line 127

def initialize(explain_output)
  @raw_output = explain_output

  # Top-level metrics

  @planning_time_ms = explain_output.dig("Planning Time") || 0
  @execution_time_ms = explain_output.dig("Execution Time") || 0
  @triggers = explain_output.dig("Triggers") || []

  # Root plan node

  @root_node = PlanNode.new(explain_output.dig("Plan") || {})
end

Instance Attribute Details

#execution_time_ms ⇒ Object (readonly)

Returns the value of attribute execution_time_ms.



125
126
127
# File 'lib/query_guard/explain/plan_signals.rb', line 125

def execution_time_ms
  @execution_time_ms
end

#planning_time_ms ⇒ Object (readonly)

Returns the value of attribute planning_time_ms.



125
126
127
# File 'lib/query_guard/explain/plan_signals.rb', line 125

def planning_time_ms
  @planning_time_ms
end

#root_node ⇒ Object (readonly)

Returns the value of attribute root_node.



125
126
127
# File 'lib/query_guard/explain/plan_signals.rb', line 125

def root_node
  @root_node
end

#triggers ⇒ Object (readonly)

Returns the value of attribute triggers.



125
126
127
# File 'lib/query_guard/explain/plan_signals.rb', line 125

def triggers
  @triggers
end

Instance Method Details

#scans_for_table(table_name) ⇒ Array<PlanNode>

Find specific relation scans

Parameters:

  • table_name (String) —

    Table to find scans for

Returns:



150
151
152
153
# File 'lib/query_guard/explain/plan_signals.rb', line 150

def scans_for_table(table_name)
  root_node.nodes_of_type("Seq Scan")
    .select { |node| node.relation_name == table_name }
end

#sequential_scans ⇒ Array<PlanNode>

Extract all sequential scans from entire plan tree

Returns:



142
143
144
# File 'lib/query_guard/explain/plan_signals.rb', line 142

def sequential_scans
  root_node.sequential_scans
end

#to_h ⇒ Hash

Convert to hash for serialization

Returns:

  • (Hash)


174
175
176
177
178
179
180
181
182
183
184
# File 'lib/query_guard/explain/plan_signals.rb', line 174

def to_h
  {
    planning_time_ms: planning_time_ms,
    execution_time_ms: execution_time_ms,
    total_estimated_cost: total_estimated_cost,
    has_sequential_scans: sequential_scans.any?,
    sequential_scan_count: sequential_scans.length,
    uses_indexes: uses_indexes?,
    root_node: root_node.to_h
  }
end

#total_estimated_cost ⇒ Float

Total estimated cost of query

Returns:

  • (Float)


167
168
169
# File 'lib/query_guard/explain/plan_signals.rb', line 167

def total_estimated_cost
  root_node.estimated_cost
end

#uses_indexes? ⇒ Boolean

Check if plan uses any indexes

Returns:

  • (Boolean)


158
159
160
161
162
# File 'lib/query_guard/explain/plan_signals.rb', line 158

def uses_indexes?
  root_node.nodes_of_type("Index Scan").any? ||
    root_node.nodes_of_type("Bitmap Index Scan").any? ||
    root_node.nodes_of_type("Index Only Scan").any?
end