michaelmior /
NoSE
| 1 | # frozen_string_literal: true |
||
| 2 | |||
| 3 | 1 | module NoSE |
|
| 4 | # Statement planning and abstract models of execution steps |
||
| 5 | 1 | module Plans |
|
| 6 | # A single step in a statement plan |
||
| 7 | 1 | class PlanStep |
|
| 8 | 1 | include Supertype |
|
| 9 | |||
| 10 | 1 | attr_accessor :state, :parent |
|
| 11 | 1 | attr_reader :children, :cost, :fields |
|
| 12 | |||
| 13 | 1 | def initialize |
|
| 14 | 510 | @children = Set.new |
|
| 15 | 510 | @parent = nil |
|
| 16 | 510 | @fields = Set.new |
|
| 17 | end |
||
| 18 | |||
| 19 | # :nocov: |
||
| 20 | 1 | def to_color |
|
| 21 | # Split on capital letters and remove the last two parts (PlanStep) |
||
| 22 | 5 | self.class.name.split('::').last.split(/(?=[A-Z])/)[0..-3] \ |
|
| 23 | .map(&:downcase).join(' ').capitalize |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 24 | end |
||
| 25 | # :nocov: |
||
| 26 | |||
| 27 | # Set the children of the current plan step |
||
| 28 | # @return [void] |
||
| 29 | 1 | def children=(children) |
|
| 30 | 111 | @children = children.to_set |
|
| 31 | |||
| 32 | # Track the parent step of each step |
||
| 33 | 111 | children.each do |child| |
|
| 34 | 306 | child.instance_variable_set(:@parent, self) |
|
| 35 | 306 | fields = child.instance_variable_get(:@fields) + self.fields |
|
| 36 | 306 | child.instance_variable_set(:@fields, fields) |
|
| 37 | end |
||
| 38 | end |
||
| 39 | |||
| 40 | # Mark the fields in this index as fetched |
||
| 41 | # @return [void] |
||
| 42 | 1 | def add_fields_from_index(index) |
|
| 43 | 288 | @fields += index.all_fields |
|
| 44 | end |
||
| 45 | |||
| 46 | # Get the list of steps which led us here |
||
| 47 | # If a cost model is not provided, statement plans using |
||
| 48 | # this step cannot be evaluated on the basis of cost |
||
| 49 | # |
||
| 50 | # (this is to support PlanStep#parent_index which does not need cost) |
||
| 51 | # @return [QueryPlan] |
||
| 52 | 1 | def parent_steps(cost_model = nil) |
|
| 53 | 3853 | steps = nil |
|
| 54 | |||
| 55 | 3853 | if @parent.nil? |
|
| 56 | 2054 | steps = QueryPlan.new state.query, cost_model |
|
| 57 | else |
||
| 58 | 1799 | steps = @parent.parent_steps cost_model |
|
| 59 | 1799 | steps << self |
|
| 60 | end |
||
| 61 | |||
| 62 | 3853 | steps |
|
| 63 | end |
||
| 64 | |||
| 65 | # Find the closest index to this step |
||
| 66 | # @return [PlanStep] |
||
| 67 | 1 | def parent_index |
|
| 68 | 1579 | step = parent_steps.to_a.reverse_each.find do |parent_step| |
|
| 69 | 1046 | parent_step.is_a? IndexLookupPlanStep |
|
| 70 | end |
||
| 71 | 1579 | step.index unless step.nil? |
|
|
0 ignored issues
–
show
|
|||
| 72 | end |
||
| 73 | |||
| 74 | # Calculate the cost of executing this step in the plan |
||
| 75 | # @return [Integer] |
||
| 76 | 1 | def calculate_cost(cost_model) |
|
| 77 | 363 | @cost = cost_model.method((subtype_name + '_cost').to_sym).call self |
|
|
0 ignored issues
–
show
|
|||
| 78 | end |
||
| 79 | |||
| 80 | # Add the Subtype module to all step classes |
||
| 81 | # @return [void] |
||
| 82 | 1 | def self.inherited(child_class) |
|
|
0 ignored issues
–
show
|
|||
| 83 | 9 | child_class.send(:include, Subtype) |
|
| 84 | end |
||
| 85 | end |
||
| 86 | |||
| 87 | # A dummy step used to inspect failed statement plans |
||
| 88 | 1 | class PrunedPlanStep < PlanStep |
|
| 89 | 1 | def state |
|
| 90 | OpenStruct.new answered?: true |
||
| 91 | end |
||
| 92 | end |
||
| 93 | |||
| 94 | # The root of a tree of statement plans used as a placeholder |
||
| 95 | 1 | class RootPlanStep < PlanStep |
|
| 96 | 1 | def initialize(state) |
|
| 97 | 70 | super() |
|
| 98 | 70 | @state = state |
|
| 99 | 70 | @cost = 0 |
|
| 100 | end |
||
| 101 | end |
||
| 102 | |||
| 103 | # This superclass defines what is necessary for manually defined |
||
| 104 | # and automatically generated plans to provide for execution |
||
| 105 | 1 | class AbstractPlan |
|
| 106 | 1 | attr_reader :group, :name, :weight |
|
| 107 | |||
| 108 | # @abstract Subclasses should produce the steps for executing this query |
||
| 109 | 1 | def steps |
|
| 110 | fail NotImplementedError |
||
| 111 | end |
||
| 112 | |||
| 113 | # @abstract Subclasses should produce the fields selected by this plan |
||
| 114 | 1 | def select_fields |
|
| 115 | [] |
||
| 116 | end |
||
| 117 | |||
| 118 | # @abstract Subclasses should produce the parameters |
||
| 119 | # necessary for this plan |
||
| 120 | 1 | def params |
|
| 121 | fail NotImplementedError |
||
| 122 | end |
||
| 123 | end |
||
| 124 | end |
||
| 125 | end |
||
| 126 | |||
| 127 | 1 | require_relative 'plans/filter' |
|
| 128 | 1 | require_relative 'plans/index_lookup' |
|
| 129 | 1 | require_relative 'plans/limit' |
|
| 130 | 1 | require_relative 'plans/sort' |
|
| 131 | 1 | require_relative 'plans/update' |
|
| 132 | |||
| 133 | 1 | require_relative 'plans/query_planner' |
|
| 134 | 1 | require_relative 'plans/update_planner' |
|
| 135 | require_relative 'plans/execution_plan' |
||
| 136 |