Class: FakeDynamo::Table

Inherits:
Object
  • Object
show all
Includes:
Filter, Validation
Defined in:
lib/fake_dynamo/table.rb

Constant Summary

Constants included from Filter

Filter::INF

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Filter

#comparison_filter, #contains_filter, def_filter, #in_filter, #not_contains_filter, #not_null_filter, #null_filter, #validate_size, #validate_supported_types

Methods included from Validation

#add_errors, #api_config, #api_config_path, #api_input_spec, #available_operations, #param, #validate!, #validate_input, #validate_key_data, #validate_key_schema, #validate_operation, #validate_payload, #validate_spec, #validate_type

Constructor Details

#initialize(data) ⇒ Table

Returns a new instance of Table.



10
11
12
13
# File 'lib/fake_dynamo/table.rb', line 10

def initialize(data)
  extract_values(data)
  init
end

Instance Attribute Details

#creation_date_timeObject

Returns the value of attribute creation_date_time.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def creation_date_time
  @creation_date_time
end

#itemsObject

Returns the value of attribute items.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def items
  @items
end

#key_schemaObject

Returns the value of attribute key_schema.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def key_schema
  @key_schema
end

#last_decreased_timeObject

Returns the value of attribute last_decreased_time.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def last_decreased_time
  @last_decreased_time
end

#last_increased_timeObject

Returns the value of attribute last_increased_time.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def last_increased_time
  @last_increased_time
end

#nameObject

Returns the value of attribute name.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def name
  @name
end

#read_capacity_unitsObject

Returns the value of attribute read_capacity_units.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def read_capacity_units
  @read_capacity_units
end

#size_bytesObject

Returns the value of attribute size_bytes.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def size_bytes
  @size_bytes
end

#statusObject

Returns the value of attribute status.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def status
  @status
end

#write_capacity_unitsObject

Returns the value of attribute write_capacity_units.



6
7
8
# File 'lib/fake_dynamo/table.rb', line 6

def write_capacity_units
  @write_capacity_units
end

Instance Method Details

#activateObject



57
58
59
# File 'lib/fake_dynamo/table.rb', line 57

def activate
  @status = 'ACTIVE'
end

#check_conditions(old_item, conditions) ⇒ Object



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/fake_dynamo/table.rb', line 345

def check_conditions(old_item, conditions)
  return unless conditions

  conditions.each do |name, predicate|
    exist = predicate['Exists']
    value = predicate['Value']

    if not value
      if exist.nil?
        raise ValidationException, "'Exists' is set to null. 'Exists' must be set to false when no Attribute value is specified"
      elsif exist
        raise ValidationException, "'Exists' is set to true. 'Exists' must be set to false when no Attribute value is specified"
      elsif !exist # false
        if old_item and old_item[name]
          raise ConditionalCheckFailedException
        end
      end
    else
      expected_attr = Attribute.from_hash(name, value)

      if exist.nil? or exist
        raise ConditionalCheckFailedException unless (old_item and old_item[name] == expected_attr)
      elsif !exist # false
        raise ValidationException, "Cannot expect an attribute to have a specified value while expecting it to not exist"
      end
    end
  end
end

#consumed_capacityObject



341
342
343
# File 'lib/fake_dynamo/table.rb', line 341

def consumed_capacity
  { 'ConsumedCapacityUnits' => 1 }
end

#count_and_attributes_to_get_present?(data) ⇒ Boolean

Returns:

  • (Boolean)


242
243
244
245
246
# File 'lib/fake_dynamo/table.rb', line 242

def count_and_attributes_to_get_present?(data)
  if data['Count'] and data['AttributesToGet']
    raise ValidationException, "Cannot specify the AttributesToGet when choosing to get only the Count"
  end
end

#create_item?(data) ⇒ Boolean

Returns:

  • (Boolean)


297
298
299
300
301
302
# File 'lib/fake_dynamo/table.rb', line 297

def create_item?(data)
  data['AttributeUpdates'].any? do |name, update_data|
    action = update_data['Action']
    ['PUT', 'ADD', nil].include? action
  end
end

#create_table_dataObject



30
31
32
33
34
35
36
37
38
39
# File 'lib/fake_dynamo/table.rb', line 30

def create_table_data
  {
    'TableName' => name,
    'KeySchema' => key_schema.description,
    'ProvisionedThroughput' => {
      'ReadCapacityUnits' => read_capacity_units,
      'WriteCapacityUnits' => write_capacity_units
    }
  }
end

#deep_copy(x) ⇒ Object



172
173
174
# File 'lib/fake_dynamo/table.rb', line 172

def deep_copy(x)
  Marshal.load(Marshal.dump(x))
end

#deleteObject



61
62
63
64
# File 'lib/fake_dynamo/table.rb', line 61

def delete
  @status = 'DELETING'
  description
end

#delete_item(data) ⇒ Object



131
132
133
134
135
136
137
138
# File 'lib/fake_dynamo/table.rb', line 131

def delete_item(data)
  key = Key.from_data(data['Key'], key_schema)
  item = @items[key]
  check_conditions(item, data['Expected'])

  @items.delete(key) if item
  consumed_capacity.merge(return_values(data, item))
end

#describe_tableObject



53
54
55
# File 'lib/fake_dynamo/table.rb', line 53

def describe_table
  { 'Table' => description['TableDescription'] }.merge(size_description)
end

#descriptionObject



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/fake_dynamo/table.rb', line 15

def description
  {
    'TableDescription' => {
      'CreationDateTime' => creation_date_time,
      'KeySchema' => key_schema.description,
      'ProvisionedThroughput' => {
        'ReadCapacityUnits' => read_capacity_units,
        'WriteCapacityUnits' => write_capacity_units
      },
      'TableName' => name,
      'TableStatus' => status
    }
  }
end

#drop_till_start(all_items, start_key_hash) ⇒ Object



254
255
256
257
258
259
260
# File 'lib/fake_dynamo/table.rb', line 254

def drop_till_start(all_items, start_key_hash)
  if start_key_hash
    all_items.drop_while { |i| i.key.as_key_hash != start_key_hash }.drop(1)
  else
    all_items
  end
end

#filter(items, conditions, limit, fail_on_type_mismatch) ⇒ Object



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/fake_dynamo/table.rb', line 262

def filter(items, conditions, limit, fail_on_type_mismatch)
  limit ||= -1
  result = []
  last_evaluated_item = nil
  scaned_count = 0
  items.each do |item|
    select = true
    conditions.each do |attribute_name, condition|
      value = condition['AttributeValueList']
      comparison_op = condition['ComparisonOperator']
      unless self.send("#{comparison_op.downcase}_filter", value, item[attribute_name], fail_on_type_mismatch)
        select = false
        break
      end
    end

    if select
      result << item
      if (limit -= 1) == 0
        last_evaluated_item = item
        break
      end
    end

    scaned_count += 1
  end
  [result, last_evaluated_item, scaned_count]
end

#filter_attributes(item, attributes_to_get) ⇒ Object



121
122
123
124
125
126
127
128
129
# File 'lib/fake_dynamo/table.rb', line 121

def filter_attributes(item, attributes_to_get)
  hash = item.as_hash
  if attributes_to_get
    hash.select! do |attribute, value|
      attributes_to_get.include? attribute
    end
  end
  hash
end

#get_item(data) ⇒ Object



104
105
106
107
108
109
110
# File 'lib/fake_dynamo/table.rb', line 104

def get_item(data)
  response = consumed_capacity
  if item_hash = get_raw_item(data['Key'], data['AttributesToGet'])
    response.merge!('Item' => item_hash)
  end
  response
end

#get_items_by_hash_key(hash_key) ⇒ Object



291
292
293
294
295
# File 'lib/fake_dynamo/table.rb', line 291

def get_items_by_hash_key(hash_key)
  items.values.select do |i|
    i.key.primary == hash_key
  end
end

#get_raw_item(key_data, attributes_to_get) ⇒ Object



112
113
114
115
116
117
118
119
# File 'lib/fake_dynamo/table.rb', line 112

def get_raw_item(key_data, attributes_to_get)
  key = Key.from_data(key_data, key_schema)
  item = @items[key]

  if item
    filter_attributes(item, attributes_to_get)
  end
end

#put_item(data) ⇒ Object



95
96
97
98
99
100
101
102
# File 'lib/fake_dynamo/table.rb', line 95

def put_item(data)
  item = Item.from_data(data['Item'], key_schema)
  old_item = @items[item.key]
  check_conditions(old_item, data['Expected'])
  @items[item.key] = item

  consumed_capacity.merge(return_values(data, old_item))
end

#put_item_data(item) ⇒ Object



41
42
43
44
45
46
# File 'lib/fake_dynamo/table.rb', line 41

def put_item_data(item)
  {
    'TableName' => name,
    'Item' => item.as_hash
  }
end

#query(data) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/fake_dynamo/table.rb', line 176

def query(data)
  unless key_schema.range_key
    raise ValidationException, "Query can be performed only on a table with a HASH,RANGE key schema"
  end

  count_and_attributes_to_get_present?(data)
  validate_limit(data)

  hash_attribute = Attribute.from_hash(key_schema.hash_key.name, data['HashKeyValue'])
  matched_items = get_items_by_hash_key(hash_attribute)


  forward = data.has_key?('ScanIndexForward') ? data['ScanIndexForward'] : true

  if forward
    matched_items.sort! { |a, b| a.key.range <=> b.key.range }
  else
    matched_items.sort! { |a, b| b.key.range <=> a.key.range }
  end

  matched_items = drop_till_start(matched_items, data['ExclusiveStartKey'])

  if data['RangeKeyCondition']
    conditions = {key_schema.range_key.name => data['RangeKeyCondition']}
  else
    conditions = {}
  end

  result, last_evaluated_item, _ = filter(matched_items, conditions, data['Limit'], true)

  response = {
    'Count' => result.size,
    'ConsumedCapacityUnits' => 1 }

  unless data['Count']
    response['Items'] = result.map { |r| filter_attributes(r, data['AttributesToGet']) }
  end

  if last_evaluated_item
    response['LastEvaluatedKey'] = last_evaluated_item.key.as_key_hash
  end
  response
end

#return_values(data, old_item, new_item = {}) ⇒ Object



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/fake_dynamo/table.rb', line 308

def return_values(data, old_item, new_item={})
  old_item ||= {}
  old_hash = old_item.kind_of?(Item) ? old_item.as_hash : old_item

  new_item ||= {}
  new_hash = new_item.kind_of?(Item) ? new_item.as_hash : new_item


  return_value = data['ReturnValues']
  result = case return_value
           when 'ALL_OLD'
             old_hash
           when 'ALL_NEW'
             new_hash
           when 'UPDATED_OLD'
             updated = updated_attributes(data)
             old_hash.select { |name, _| updated.include? name }
           when 'UPDATED_NEW'
             updated = updated_attributes(data)
             new_hash.select { |name, _| updated.include? name }
           when 'NONE', nil
             {}
           else
             raise 'unknown return value'
           end

  unless result.empty?
    { 'Attributes' => result }
  else
    {}
  end
end

#scan(data) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/fake_dynamo/table.rb', line 220

def scan(data)
  count_and_attributes_to_get_present?(data)
  validate_limit(data)
  conditions = data['ScanFilter'] || {}
  all_items = drop_till_start(items.values, data['ExclusiveStartKey'])
  result, last_evaluated_item, scaned_count = filter(all_items, conditions, data['Limit'], false)
  response = {
    'Count' => result.size,
    'ScannedCount' => scaned_count,
    'ConsumedCapacityUnits' => 1 }

  unless data['Count']
    response['Items'] = result.map { |r| filter_attributes(r, data['AttributesToGet']) }
  end

  if last_evaluated_item
    response['LastEvaluatedKey'] = last_evaluated_item.key.as_key_hash
  end

  response
end

#size_descriptionObject



48
49
50
51
# File 'lib/fake_dynamo/table.rb', line 48

def size_description
  { 'ItemCount' => items.count,
    'TableSizeBytes' => size_bytes }
end

#update(read_capacity_units, write_capacity_units) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/fake_dynamo/table.rb', line 66

def update(read_capacity_units, write_capacity_units)
  if @read_capacity_units > read_capacity_units
    @last_decreased_time = Time.now.to_i
  elsif @read_capacity_units < read_capacity_units
    @last_increased_time = Time.now.to_i
  end

  if @write_capacity_units > write_capacity_units
    @last_decreased_time = Time.now.to_i
  elsif @write_capacity_units < write_capacity_units
    @last_increased_time = Time.now.to_i
  end

  @read_capacity_units, @write_capacity_units = read_capacity_units, write_capacity_units

  response = description.merge(size_description)

  if last_increased_time
    response['TableDescription']['ProvisionedThroughput']['LastIncreaseDateTime'] = @last_increased_time
  end

  if last_decreased_time
    response['TableDescription']['ProvisionedThroughput']['LastDecreaseDateTime'] = @last_decreased_time
  end

  response['TableDescription']['TableStatus'] = 'UPDATING'
  response
end

#update_item(data) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/fake_dynamo/table.rb', line 140

def update_item(data)
  key = Key.from_data(data['Key'], key_schema)
  item = @items[key]
  check_conditions(item, data['Expected'])

  unless item
    if create_item?(data)
      item = @items[key] = Item.from_key(key)
    else
      return consumed_capacity
    end
    item_created = true
  end

  old_item = deep_copy(item)
  begin
    old_hash = item.as_hash
    data['AttributeUpdates'].each do |name, update_data|
      item.update(name, update_data)
    end
  rescue => e
    if item_created
      @items.delete(key)
    else
      @items[key] = old_item
    end
    raise e
  end

  consumed_capacity.merge(return_values(data, old_hash, item))
end

#updated_attributes(data) ⇒ Object



304
305
306
# File 'lib/fake_dynamo/table.rb', line 304

def updated_attributes(data)
  data['AttributeUpdates'].map { |name, _| name }
end

#validate_limit(data) ⇒ Object



248
249
250
251
252
# File 'lib/fake_dynamo/table.rb', line 248

def validate_limit(data)
  if data['Limit'] and data['Limit'] <= 0
    raise ValidationException, "Limit failed to satisfy constraint: Member must have value greater than or equal to 1"
  end
end