Class: Dynamini::TestClient

Inherits:
Object
  • Object
show all
Defined in:
lib/dynamini/test_client.rb

Overview

In-memory database client for test purposes.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash_key_attr, range_key_attr = nil, secondary_index = nil) ⇒ TestClient

Returns a new instance of TestClient.



10
11
12
13
14
15
# File 'lib/dynamini/test_client.rb', line 10

def initialize(hash_key_attr, range_key_attr = nil, secondary_index=nil)
  @data = {}
  @hash_key_attr = hash_key_attr
  @range_key_attr = range_key_attr
  @secondary_index = secondary_index
end

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



8
9
10
# File 'lib/dynamini/test_client.rb', line 8

def data
  @data
end

#hash_key_attrObject (readonly)

Returns the value of attribute hash_key_attr.



8
9
10
# File 'lib/dynamini/test_client.rb', line 8

def hash_key_attr
  @hash_key_attr
end

#range_key_attrObject (readonly)

Returns the value of attribute range_key_attr.



8
9
10
# File 'lib/dynamini/test_client.rb', line 8

def range_key_attr
  @range_key_attr
end

#secondary_indexObject (readonly)

Returns the value of attribute secondary_index.



8
9
10
# File 'lib/dynamini/test_client.rb', line 8

def secondary_index
  @secondary_index
end

Instance Method Details

#apply_filter_options(parent, args, start_val, end_val) ⇒ Object



203
204
205
206
207
208
209
210
# File 'lib/dynamini/test_client.rb', line 203

def apply_filter_options(parent, args, start_val, end_val)
  records = parent.values
  records = records.select { |record| record[@range_key_attr] >= start_val.to_f } if start_val
  records = records.select { |record| record[@range_key_attr] <= end_val.to_f } if end_val
  records = records.sort! { |a, b| b[@range_key_attr] <=> a[@range_key_attr] } if args[:scan_index_forward] == false
  records = records[0...args[:limit]] if args[:limit]
  records
end

#batch_get_item(args = {}) ⇒ Object

No range key support - use query instead.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/dynamini/test_client.rb', line 71

def batch_get_item(args = {})
  responses = {}

  args[:request_items].each do |table_name, get_request|
    responses[table_name] = []
    ids = get_request[:keys].flat_map(&:values)
    raise 'Aws::DynamoDB::Errors::ValidationException: Provided list of item keys contains duplicates' if ids.length != ids.uniq.length
    get_request[:keys].each do |key_hash|
      item = get_table(table_name)[key_hash.values.first]
      responses[table_name] << item unless item.nil?
    end
  end

  OpenStruct.new(responses: responses)
end

#batch_write_item(request_options) ⇒ Object

TODO add range key support for delete, not currently implemented batch_operations.batch_delete



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/dynamini/test_client.rb', line 136

def batch_write_item(request_options)
  request_options[:request_items].each do |table_name, requests|
    table = get_table(table_name)

    requests.each do |request_hash|
      if request_hash[:put_request]
        item = request_hash[:put_request][:item].symbolize_keys
        hash_key_value = item[hash_key_attr]
        if range_key_attr.present?
          range_key_value = item[range_key_attr]
          table[hash_key_value] = {} if table[hash_key_value].nil?
          table[hash_key_value][range_key_value] = item
        else
          table[hash_key_value] = item
        end
      else
        item = request_hash[:delete_request][:key]
        id = item[hash_key_attr]
        table.delete(id)
      end
    end
  end
end

#delete_item(args = {}) ⇒ Object



161
162
163
# File 'lib/dynamini/test_client.rb', line 161

def delete_item(args = {})
  get_table(args[:table_name]).delete(args[:key][hash_key_attr])
end

#determine_hash_and_range(args) ⇒ Object



185
186
187
188
189
190
191
192
# File 'lib/dynamini/test_client.rb', line 185

def determine_hash_and_range(args)
  if args[:index_name]
    index = secondary_index[args[:index_name].to_s]
    [index[:hash_key_name].to_s, index[:range_key_name].to_s]
  else
    [@hash_key_attr.to_s, @range_key_attr.to_s]
  end
end

#get_item(args = {}) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/dynamini/test_client.rb', line 58

def get_item(args = {})
  table = get_table(args[:table_name])

  hash_key_value = args[:key][hash_key_attr]
  range_key_value = args[:key][range_key_attr]

  attributes_hash = table[hash_key_value]
  attributes_hash = attributes_hash[range_key_value] if attributes_hash && range_key_value

  OpenStruct.new(item: (attributes_hash ? attributes_hash.deep_dup : nil))
end

#get_last_evaluated_key(secondary_index_name, items, records) ⇒ Object



124
125
126
127
128
129
130
131
132
133
# File 'lib/dynamini/test_client.rb', line 124

def get_last_evaluated_key(secondary_index_name, items, records)
  if records.length > 0 && items.last != records.last
    index = secondary_index[secondary_index_name]
    if index
      { get_secondary_hash_key(index).to_s => items.last[get_secondary_hash_key(index)] }
    else
      { hash_key_attr.to_s => items.last[hash_key_attr] }
    end
  end
end

#get_secondary_hash_key(index) ⇒ Object



243
244
245
# File 'lib/dynamini/test_client.rb', line 243

def get_secondary_hash_key(index)
  index[:hash_key_name] == @hash_key_attr ? index[:hash_key_name] : index[:hash_key_name].to_s
end

#get_secondary_range_key(index) ⇒ Object



247
248
249
# File 'lib/dynamini/test_client.rb', line 247

def get_secondary_range_key(index)
  index[:range_key_name] == @range_key_attr ? index[:range_key_name] : index[:range_key_name].to_s
end

#get_table(table_name) ⇒ Object



17
18
19
# File 'lib/dynamini/test_client.rb', line 17

def get_table(table_name)
  @data[table_name] ||= {}
end

#index_of_start_key(args, records) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/dynamini/test_client.rb', line 103

def index_of_start_key(args, records)
  if args[:exclusive_start_key]
    sec_index = secondary_index && secondary_index[args[:secondary_index_name]]
    start_index = records.index do |r|
      if sec_index
        r[get_secondary_hash_key(sec_index)] == args[:exclusive_start_key].values[0]
      else
        r[hash_key_attr] == args[:exclusive_start_key].values[0]
      end
    end
    start_index || 0
  else
    0
  end
end

#limit_scanned_records(limit, records, start_index) ⇒ Object



119
120
121
122
# File 'lib/dynamini/test_client.rb', line 119

def limit_scanned_records(limit, records, start_index)
  end_index = limit ? start_index + limit - 1 : -1
  records[start_index..end_index]
end

#primary_index_insertion(hash_key_value, range_key_value, updates, table) ⇒ Object



36
37
38
39
40
41
42
# File 'lib/dynamini/test_client.rb', line 36

def primary_index_insertion(hash_key_value, range_key_value, updates, table)
  if range_key_value
    primary_with_range_insertion(hash_key_value, range_key_value, updates, table)
  else
    primary_only_hash_insertion(hash_key_value, updates, table)
  end
end

#primary_only_hash_insertion(hash_key_value, updates, table) ⇒ Object



54
55
56
# File 'lib/dynamini/test_client.rb', line 54

def primary_only_hash_insertion(hash_key_value, updates, table)
  table[hash_key_value] ? table[hash_key_value].merge!(updates) : table[hash_key_value] = updates
end

#primary_with_range_insertion(hash_key_value, range_key_value, updates, table) ⇒ Object



44
45
46
47
48
49
50
51
52
# File 'lib/dynamini/test_client.rb', line 44

def primary_with_range_insertion(hash_key_value, range_key_value, updates, table)
  updates.merge!(range_key_attr => range_key_value)
  if table[hash_key_value] && table[hash_key_value][range_key_value]
    table[hash_key_value][range_key_value].merge! updates
  else
    table[hash_key_value] ||= {}
    table[hash_key_value][range_key_value] = updates
  end
end

#query(args = {}) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/dynamini/test_client.rb', line 165

def query(args = {})
  # Possible key condition structures:
  # "foo = val"
  # "foo = val AND bar <= val2"
  # "foo = val AND bar >= val2"
  # "foo = val AND bar BETWEEN val2 AND val3"

  attr_placeholders = args[:expression_attribute_values].merge(args[:expression_attribute_names])
  attr_placeholders.each { |symbol, value| args[:key_condition_expression].gsub!(symbol, value.to_s) }

  tokens = args[:key_condition_expression].split(/\s+/)

  hash_key_name, range_key_name = determine_hash_and_range(args)

  inspect_for_correct_keys?(tokens, hash_key_name, range_key_name)

  args[:index_name] ?  secondary_index_query(args, tokens) : range_key_query(args, tokens)

end

#range_key_limits(tokens) ⇒ Object



194
195
196
197
198
199
200
201
# File 'lib/dynamini/test_client.rb', line 194

def range_key_limits(tokens)
  case tokens[5]
    when ">=" then [tokens[6], nil]
    when "<=" then [nil, tokens[6]]
    when "BETWEEN" then [tokens[6], tokens[8]]
    else [nil, nil]
  end
end

#range_key_query(args, tokens) ⇒ Object



212
213
214
215
216
217
218
219
220
221
# File 'lib/dynamini/test_client.rb', line 212

def range_key_query(args, tokens)
  start_val, end_val = range_key_limits(tokens)
  hash_key = hash_key_value(args).is_a?(Integer) ? tokens[2].to_i : tokens[2]
  parent = get_table(args[:table_name])[hash_key]

  return OpenStruct.new(items: []) unless parent

  selected = apply_filter_options(parent, args, start_val, end_val)
  OpenStruct.new(items: selected)
end

#resetObject



251
252
253
# File 'lib/dynamini/test_client.rb', line 251

def reset
  @data = {}
end

#scan(args = {}) ⇒ Object



87
88
89
90
91
92
93
94
# File 'lib/dynamini/test_client.rb', line 87

def scan(args = {})
  records = get_table(args[:table_name]).values
  sort_scanned_records!(records, args[:secondary_index_name]) if args[:secondary_index_name]
  start_index = index_of_start_key(args, records)
  items = limit_scanned_records(args[:limit], records, start_index)
  last_evaluated_key = get_last_evaluated_key(args[:secondary_index_name], items, records)
  OpenStruct.new({items: items, last_evaluated_key: last_evaluated_key})
end

#secondary_index_query(args = {}, tokens) ⇒ Object



223
224
225
226
227
228
229
230
231
# File 'lib/dynamini/test_client.rb', line 223

def secondary_index_query(args = {}, tokens)
  start_val, end_val = range_key_limits(tokens)
  index = secondary_index[args[:index_name].to_s]
  table = get_table(args[:table_name])

  records = @range_key_attr ? get_values(table) : table.values
  selected = sort_records(records, index, args, start_val, end_val)
  OpenStruct.new(items: selected)
end

#sort_records(records, index, args, start_val, end_val) ⇒ Object



233
234
235
236
237
238
239
240
241
# File 'lib/dynamini/test_client.rb', line 233

def sort_records(records, index, args, start_val, end_val)
  records = records.select { |record| record[get_secondary_hash_key(index)] == hash_key_value(args) }
  records = records.select { |record| record[get_secondary_range_key(index)] >= start_val.to_f } if start_val
  records = records.select { |record| record[get_secondary_range_key(index)] <= end_val.to_f } if end_val
  records = records.sort { |a, b| a[get_secondary_range_key(index)] <=> b[get_secondary_range_key(index)] }
  records = records.reverse if args[:scan_index_forward] == false
  records = records[0...args[:limit]] if args[:limit]
  records
end

#sort_scanned_records!(records, secondary_index_name) ⇒ Object



96
97
98
99
100
101
# File 'lib/dynamini/test_client.rb', line 96

def sort_scanned_records!(records, secondary_index_name)
  index = secondary_index[secondary_index_name]
  records.sort! do |a, b|
    a[get_secondary_hash_key(index)] <=> b[get_secondary_hash_key(index)]
  end
end

#update_item(args = {}) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/dynamini/test_client.rb', line 21

def update_item(args = {})
  table = get_table(args[:table_name])

  keys = args[:key]

  hash_key_value = keys[hash_key_attr]
  range_key_value = keys[range_key_attr]

  updates = flatten_attribute_updates(args).merge(
      hash_key_attr => hash_key_value
  )

  primary_index_insertion(hash_key_value, range_key_value, updates, table) if hash_key_value
end