Issues (393)

lib/nose/backend/file.rb (9 issues)

1
# frozen_string_literal: true
2
3 1
module NoSE
4 1
  module Backend
5
    # Simple backend which persists data to a file
6 1
    class FileBackend < Backend
7 1
      include Subtype
8
9 1
      def initialize(model, indexes, plans, update_plans, config)
10 8
        super
11
12
        # Try to load data from file or start fresh
13 8
        @index_data = if !config[:file].nil? && File.file?(config[:file])
14
                        Marshal.load File.open(config[:file])
0 ignored issues
show
Avoid using Marshal.load.
Loading history...
15
                      else
16 8
                        {}
17
                      end
18
19
        # Ensure the data is saved when we exit
20 8
        ObjectSpace.define_finalizer self, self.class.finalize(@index_data,
21
                                                               config[:file])
22
      end
23
24
      # Save data when the object is destroyed
25 1
      def self.finalize(index_data, file)
26 8
        proc do
27 8
          if !file.nil?
0 ignored issues
show
Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
Loading history...
Your coding style requires you to favor unless over if for negative conditions.
Loading history...
28
            Marshal.dump(index_data, File.open(file, 'w'))
29
          end
30
        end
31
      end
32 1
33
      # Check for an empty array for the data
34
      def index_empty?(index)
35
        !index_exists?(index) || @index_data[index.key].empty?
36
      end
37 1
38
      # Check if we have prepared space for this index
39
      def index_exists?(index)
40
        @index_data.key? index.key
41
      end
42 1
43
      # @abstract Subclasses implement to allow inserting
44
      def index_insert_chunk(index, chunk)
45
        @index_data[index.key].concat chunk
46
      end
47 1
48
      # Generate a simple UUID
49
      def generate_id
50
        SecureRandom.uuid
51
      end
52 1
53
      # Allocate space for data on the new indexes
54
      def indexes_ddl(execute = false, skip_existing = false,
0 ignored issues
show
Prefer keyword arguments for arguments with a boolean default value; use execute: false instead of execute = false.
Loading history...
Prefer keyword arguments for arguments with a boolean default value; use skip_existing: false instead of skip_existing = false.
Loading history...
55
                      drop_existing = false)
0 ignored issues
show
Prefer keyword arguments for arguments with a boolean default value; use drop_existing: false instead of drop_existing = false.
Loading history...
56
        @indexes.each do |index|
0 ignored issues
show
Favor a normal if-statement over a modifier clause in a multiline statement.
Loading history...
57
          # Do the appropriate behaviour based on the flags passed in
58
          if index_exists?(index)
59
            next if skip_existing
60
            fail unless drop_existing
61
          end
62 1
63
          @index_data[index.key] = []
64
        end if execute
65 1
66
        # We just use the original index definition as DDL
67
        @indexes.map(&:inspect)
68
      end
69 1
70
      # Sample a number of values from the given index
71
      def index_sample(index, count)
72
        data = @index_data[index.key]
73
        data.nil? ? [] : data.sample(count)
74
      end
75
76 1
      # We just produce the data here which can be manipulated as needed
77 15
      # @return [Hash]
78
      def client
79
        @index_data
80
      end
81
82 1
      # Provide some helper functions which allow the matching of rows
83
      # based on a set of list of conditions
84
      module RowMatcher
85 1
        # Check if a row matches the given condition
86 12
        # @return [Boolean]
87
        def row_matches?(row, conditions)
88
          row_matches_eq?(row, conditions) &&
89
            row_matches_range?(row, conditions)
90
        end
91
92 1
        # Check if a row matches the given condition on equality predicates
93 12
        # @return [Boolean]
94 24
        def row_matches_eq?(row, conditions)
95
          @eq_fields.all? do |field|
96
            row[field.id] == conditions.find { |c| c.field == field }.value
97
          end
98
        end
99
100 1
        # Check if a row matches the given condition on the range predicate
101 9
        # @return [Boolean]
102
        def row_matches_range?(row, conditions)
103
          return true if @range_field.nil?
104
105
          range_cond = conditions.find { |c| c.field == @range_field }
106
          row[@range_field.id].send range_cond.operator, range_cond.value
107
        end
108
      end
109 1
110 1
      # Look up data on an index in the backend
111
      class IndexLookupStatementStep < Backend::IndexLookupStatementStep
112
        include RowMatcher
113 1
114
        # Filter all the rows in the specified index to those requested
115 12
        def process(conditions, results)
0 ignored issues
show
The Assignment, Branch, Condition size for process is considered too high. [<9, 20, 8> 23.35/20]. The ABC size is based on assignments, branches (method calls), and conditions.
Loading history...
116 12
          # Get the set of conditions we need to process
117
          results = initial_results(conditions) if results.nil?
118
          condition_list = result_conditions conditions, results
119 12
120 12
          # Loop through all rows to find the matching ones
121 24
          rows = @client[@index.key] || []
122
          selected = condition_list.flat_map do |condition|
123
            rows.select { |row| row_matches? row, condition }
124
          end.compact
125 12
126 12
          # Apply the limit and only return selected fields
127 35
          field_ids = Set.new @step.fields.map(&:id).to_set
128
          selected[0..(@step.limit.nil? ? -1 : @step.limit)].map do |row|
129
            row.select { |k, _| field_ids.include? k }
130
          end
131
        end
132
      end
133 1
134
      # Insert data into an index on the backend
135 1
      class InsertStatementStep < Backend::InsertStatementStep
136 3
        # Add new rows to the index
137
        def process(results)
0 ignored issues
show
The Assignment, Branch, Condition size for process is considered too high. [<8, 26, 10> 28.98/20]. The ABC size is based on assignments, branches (method calls), and conditions.
Loading history...
138 3
          key_ids = (@index.hash_fields + @index.order_fields).map(&:id).to_set
139
140 3
          results.each do |row|
141 7
            # Pick out primary key fields we can use to match
142
            conditions = row.select do |field_id|
143
              key_ids.include? field_id
144
            end
145 3
146
            # If we have all the primary keys, check for a match
147 2
            if conditions.length == key_ids.length
148 1
              # Try to find a row with this ID and update it
149
              matching_row = @client[index.key].find do |index_row|
150
                index_row.merge(conditions) == index_row
151 2
              end
152 1
153 1
              unless matching_row.nil?
154
                matching_row.merge! row
155
                next
156
              end
157
            end
158 2
159 5
            # Populate IDs as needed
160
            key_ids.each do |key_id|
161
              row[key_id] = SecureRandom.uuid if row[key_id].nil?
162 2
            end
163
164
            @client[index.key] << row
165
          end
166
        end
167
      end
168 1
169 1
      # Delete data from an index on the backend
170
      class DeleteStatementStep < Backend::DeleteStatementStep
171
        include RowMatcher
172 1
173
        # Remove rows matching the results from the dataset
174 3
        def process(results)
175
          # Loop over all rows
176 3
          @client[index.key].reject! do |row|
177
            # Check against all results
178 3
            results.any? do |result|
179 11
              # If all fields match, drop the row
180
              result.all? do |field, value|
181
                row[field] == value
182
              end
183
            end
184
          end
185
        end
186
      end
187
    end
188
  end
189
end
190