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
![]() |
|||
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 |