Completed
Push — master ( 1de452...64907a )
by Michael
01:25
created

Results.model()   A

Complexity

Conditions 2

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2.5

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 3
ccs 1
cts 2
cp 0.5
crap 2.5
rs 10
c 0
b 0
f 0
1
# frozen_string_literal: true
2
3 1
module NoSE
4 1
  module Search
5
    # A container for results from a schema search
6 1
    class Results
7 1
      attr_reader :cost_model
8 1
      attr_accessor :enumerated_indexes, :indexes, :total_size, :total_cost,
9
                    :workload, :update_plans, :plans,
10
                    :revision, :time, :command, :by_id_graph
11
12 1
      def initialize(problem = nil, by_id_graph = false)
13 5
        @problem = problem
14 5
        return if problem.nil?
15 5
        @by_id_graph = by_id_graph
16
17
        # Find the indexes the ILP says the query should use
18 8
        @query_indexes = Hash.new { |h, k| h[k] = Set.new }
19 5
        @problem.query_vars.each do |index, query_vars|
20 18
          query_vars.each do |query, var|
21 123
            next unless var.value
22 5
            @query_indexes[query].add index
23
          end
24
        end
25
      end
26
27
      # Provide access to the underlying model in the workload
28
      # @return [Model]
29 1
      def model
30
        @workload.nil? ? @model : @workload.model
31
      end
32
33
      # Assign the model to the workload if it exists, otherwise store it
34
      # @return [void]
35 1
      def model=(model)
36
        if @workload.nil?
37
          @model = model
38
        else
39
          @workload.instance_variable_set :@model, model
40
        end
41
      end
42
43
      # After setting the cost model, recalculate the cost
44
      # @return [void]
45 1
      def cost_model=(new_cost_model)
46 2
        recalculate_cost new_cost_model
47 2
        @cost_model = new_cost_model
48
      end
49
50
      # After setting the cost model, recalculate the cost
51
      # @return [void]
52 1
      def recalculate_cost(new_cost_model = nil)
0 ignored issues
show
Coding Style introduced by
The Assignment, Branch, Condition size for recalculate_cost is considered too high. [25.81/20]. The ABC size is based on assignments, branches (method calls), and conditions.
Loading history...
53 2
        new_cost_model = @cost_model if new_cost_model.nil?
54
55 2
        (@plans || []).each do |plan|
56 8
          plan.each { |s| s.calculate_cost new_cost_model }
57
        end
58 2
        (@update_plans || []).each do |plan|
59
          plan.update_steps.each { |s| s.calculate_cost new_cost_model }
60
          plan.query_plans.each do |query_plan|
61
            query_plan.each { |s| s.calculate_cost new_cost_model }
62
          end
63
        end
64
65
        # Recalculate the total
66 2
        query_cost = (@plans || []).sum_by do |plan|
67 3
          plan.cost * @workload.statement_weights[plan.query]
68
        end
69 2
        update_cost = (@update_plans || []).sum_by do |plan|
70
          plan.cost * @workload.statement_weights[plan.statement]
71
        end
72 2
        @total_cost = query_cost + update_cost
73
      end
74
75
      # Validate that the results of the search are consistent
76
      # @return [void]
77 1
      def validate
78 5
        validate_indexes
79 4
        validate_query_indexes @plans
80 4
        validate_update_indexes
81
82 4
        planned_queries = plans.map(&:query).to_set
83
        fail InvalidResultsException unless \
84 4
          (@workload.queries.to_set - planned_queries).empty?
85 4
        validate_query_plans @plans
86
87 4
        validate_update_plans
88 4
        validate_objective
89
90 2
        freeze
91
      end
92
93
      # Set the query plans which should be used based on the entire tree
94
      # @return [void]
95 1
      def plans_from_trees(trees)
96 2
        @plans = trees.map do |tree|
97
          # Exclude support queries since they will be in update plans
98 9
          query = tree.query
99 9
          next if query.is_a?(SupportQuery)
100
101 3
          select_plan tree
102
        end.compact
103
      end
104
105
      # Select the single query plan from a tree of plans
106
      # @return [Plans::QueryPlan]
107
      # @raise [InvalidResultsException]
108 1
      def select_plan(tree)
109 3
        query = tree.query
110 3
        plan = tree.find do |tree_plan|
111 17
          tree_plan.indexes.to_set == @query_indexes[query]
112
        end
113 3
        plan.instance_variable_set :@workload, @workload
114
115 3
        fail InvalidResultsException if plan.nil?
116 3
        plan
117
      end
118
119 1
      private
120
121
      # Check that the indexes selected were actually enumerated
122
      # @return [void]
123 1
      def validate_indexes
124
        # We may not have enumerated ID graphs
125 5
        check_indexes = @indexes.dup
126
        @indexes.each do |index|
127
          check_indexes.delete index.to_id_graph
128 5
        end if @by_id_graph
129
130 1
        fail InvalidResultsException unless \
131 5
          (check_indexes - @enumerated_indexes).empty?
132
      end
133
134
      # Ensure we only have necessary update plans which use available indexes
135
      # @return [void]
136 1
      def validate_update_indexes
137 4
        @update_plans.each do |plan|
138 1
          validate_query_indexes plan.query_plans
139 1
          valid_plan = @indexes.include?(plan.index)
140 1
          fail InvalidResultsException unless valid_plan
141
        end
142
      end
143
144
      # Check that the objective function has the expected value
145
      # @return [void]
146 1
      def validate_objective
0 ignored issues
show
Coding Style introduced by
The Assignment, Branch, Condition size for validate_objective is considered too high. [28.57/20]. The ABC size is based on assignments, branches (method calls), and conditions.
Loading history...
147 4
        if @problem.objective_type == Objective::COST
148 1
          query_cost = @plans.reduce 0 do |sum, plan|
149
            sum + @workload.statement_weights[plan.query] * plan.cost
150
          end
151 1
          update_cost = @update_plans.reduce 0 do |sum, plan|
152
            sum + @workload.statement_weights[plan.statement] * plan.cost
153
          end
154 1
          cost = query_cost + update_cost
155
156 1
          fail InvalidResultsException unless (cost - @total_cost).abs < 0.001
157 3
        elsif @problem.objective_type == Objective::SPACE
158 3
          size = @indexes.sum_by(&:size)
159 3
          fail InvalidResultsException unless (size - @total_size).abs < 0.001
160
        end
161
      end
162
163
      # Ensure that all the query plans use valid indexes
164
      # @return [void]
165 1
      def validate_query_indexes(plans)
166 5
        plans.each do |plan|
167 3
          plan.each do |step|
168 5
            valid_plan = !step.is_a?(Plans::IndexLookupPlanStep) ||
169
                         @indexes.include?(step.index)
170 5
            fail InvalidResultsException unless valid_plan
171
          end
172
        end
173
      end
174
175
      # Validate the query plans from the original workload
176
      # @return [void]
177 1
      def validate_query_plans(plans)
178
        # Check that these indexes are actually used by the query
179 5
        plans.each do |plan|
180
          fail InvalidResultsException unless \
181 3
            plan.indexes.to_set == @query_indexes[plan.query]
182
        end
183
      end
184
185
      # Validate the support query plans for each update
186
      # @return [void]
187 1
      def validate_update_plans
188 4
        @update_plans.each do |plan|
189 1
          plan.instance_variable_set :@workload, @workload
190
191 1
          validate_query_plans plan.query_plans
192
        end
193
      end
194
    end
195
196
    # Thrown when a search produces invalid results
197 1
    class InvalidResultsException < StandardError
198
    end
199
  end
200
end
201