fake_dynamo 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,21 +1,28 @@
1
1
  # FakeDynamo [![Build Status](https://secure.travis-ci.org/ananthakumaran/fake_dynamo.png)](http://travis-ci.org/ananthakumaran/fake_dynamo)
2
2
 
3
- local hosted, inmemory dynamodb emulator.
3
+ local hosted, inmemory Amazon DynamoDB emulator.
4
4
 
5
+ ## Versions
5
6
 
6
- # Caveats
7
+ | Amazon DynamoDB | FakeDynamo |
8
+ | --------------- | ----------- |
9
+ | 2012-08-10 | 0.2.1 |
10
+ | 2011-12-05 | 0.1.3 |
11
+
12
+
13
+ ## Caveats
7
14
 
8
15
  * `ConsumedCapacityUnits` value will be 1 always.
9
16
  * The response size is not constrained by 1mb limit. So operation
10
17
  like `BatchGetItem` will return all items irrespective of the
11
18
  response size
12
19
 
13
- # Usage
20
+ ## Usage
14
21
 
15
22
  __requires ruby >= 1.9__
16
23
 
17
24
  ````
18
- gem install fake_dynamo
25
+ gem install fake_dynamo --version 0.2.0
19
26
 
20
27
  fake_dynamo --port 4567
21
28
  ````
@@ -26,7 +33,7 @@ send a DELETE request to reset the database. eg
26
33
  curl -X DELETE http://localhost:4567
27
34
  ````
28
35
 
29
- # Clients
36
+ ## Clients
30
37
 
31
38
  * aws-sdk
32
39
 
@@ -41,7 +48,7 @@ AWS.config(:use_ssl => false,
41
48
  __please open a pull request with your configuration if you are using
42
49
  fake_dynamo with clients other than the ones mentioned above__.
43
50
 
44
- # Storage
51
+ ## Storage
45
52
  fake_dynamo stores the `write operations` (request that changes the
46
53
  data) in `/usr/local/var/fake_dynamo/db.fdb` and replays it before
47
54
  starting the server. Because of the way fake_dynamo stores the data,
data/bin/fake_dynamo CHANGED
@@ -4,7 +4,7 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
4
4
  require 'fake_dynamo'
5
5
  require 'optparse'
6
6
 
7
- options = { :port => 4567, :compact => false, :db => '/usr/local/var/fake_dynamo/db.fdb' }
7
+ options = { :port => 4567, :bind => '127.0.0.1', :compact => false, :db => '/usr/local/var/fake_dynamo/db.fdb' }
8
8
  OptionParser.new do |opts|
9
9
  opts.banner = "Usage: fake_dynamo [options]"
10
10
 
@@ -12,6 +12,10 @@ OptionParser.new do |opts|
12
12
  options[:port] = v
13
13
  end
14
14
 
15
+ opts.on("-b", "--bind ADDR", "Default: #{options[:bind]}") do |v|
16
+ options[:bind] = v
17
+ end
18
+
15
19
  opts.on("-d", "--db PATH", "Default: #{options[:db]}") do |v|
16
20
  options[:db] = v
17
21
  end
@@ -56,7 +60,7 @@ if options[:compact]
56
60
  end
57
61
 
58
62
  FakeDynamo::Storage.instance.load_aof
59
- FakeDynamo::Server.run!(:port => options[:port])
63
+ FakeDynamo::Server.run!(:port => options[:port], :bind => options[:bind])
60
64
 
61
65
  at_exit {
62
66
  FakeDynamo::Storage.instance.shutdown
@@ -3,7 +3,7 @@ module FakeDynamo
3
3
  include Comparable
4
4
  extend Validation
5
5
 
6
- attr_accessor :primary, :range
6
+ attr_accessor :primary, :range, :tertiary
7
7
 
8
8
  class << self
9
9
  def from_data(key_data, key_schema)
@@ -28,6 +28,28 @@ module FakeDynamo
28
28
  key
29
29
  end
30
30
 
31
+ # local secondary indexes have a three part key:
32
+ # hash key -> primary
33
+ # index range key -> range
34
+ # table range key -> tertiary
35
+ def from_index_schema(data, index_schema, table_schema)
36
+ key = Key.new
37
+ validate_key_schema(data, index_schema)
38
+ validate_key_schema(data, table_schema)
39
+ key.primary = create_attribute(index_schema.hash_key, data)
40
+ key.range = create_attribute(index_schema.range_key, data)
41
+ key.tertiary = create_attribute(table_schema.range_key, data)
42
+ key
43
+ end
44
+
45
+ def from_index_item(item, key_schema)
46
+ key = Key.new
47
+ key.primary = item.key.primary
48
+ key.range = item[key_schema.range_key.name]
49
+ key.tertiary = item.key.range
50
+ key
51
+ end
52
+
31
53
  def create_attribute(key, data)
32
54
  name = key.name
33
55
  attr = Attribute.from_hash(name, data[name])
@@ -45,11 +67,12 @@ module FakeDynamo
45
67
  return false unless key.kind_of? Key
46
68
 
47
69
  @primary == key.primary &&
48
- @range == key.range
70
+ @range == key.range &&
71
+ @tertiary == key.tertiary
49
72
  end
50
73
 
51
74
  def hash
52
- primary.hash ^ range.hash
75
+ primary.hash ^ range.hash ^ tertiary.hash
53
76
  end
54
77
 
55
78
  def as_hash
@@ -57,11 +80,14 @@ module FakeDynamo
57
80
  if @range
58
81
  result.merge!(@range.as_hash)
59
82
  end
83
+ if @tertiary
84
+ result.merge!(@tertiary.as_hash)
85
+ end
60
86
  result
61
87
  end
62
88
 
63
89
  def <=>(other)
64
- [primary, range] <=> [other.primary, other.range]
90
+ [primary, range, tertiary] <=> [other.primary, other.range, other.tertiary]
65
91
  end
66
92
 
67
93
  end
@@ -10,7 +10,7 @@ module FakeDynamo
10
10
  def description
11
11
  description = [{'AttributeName' => hash_key.name, 'KeyType' => 'HASH'}]
12
12
  if range_key
13
- description << [{'AttributeName' => range_key.name, 'KeyType' => 'RANGE'}]
13
+ description << {'AttributeName' => range_key.name, 'KeyType' => 'RANGE'}
14
14
  end
15
15
  description
16
16
  end
@@ -231,7 +231,11 @@ module FakeDynamo
231
231
  matched_items = get_items_by_hash_key(hash_attribute)
232
232
 
233
233
  forward = data.has_key?('ScanIndexForward') ? data['ScanIndexForward'] : true
234
- matched_items = drop_till_start(matched_items, data['ExclusiveStartKey'], forward, schema)
234
+ if index
235
+ matched_items = drop_till_start_index(matched_items, data['ExclusiveStartKey'], forward, schema)
236
+ else
237
+ matched_items = drop_till_start(matched_items, data['ExclusiveStartKey'], forward, schema)
238
+ end
235
239
 
236
240
  if !(range_condition = data['KeyConditions'].clone.tap { |h| h.delete(schema.hash_key.name) }).empty?
237
241
  validate_range_condition(range_condition, schema)
@@ -246,7 +250,11 @@ module FakeDynamo
246
250
  merge_items(response, data, results, index)
247
251
 
248
252
  if last_evaluated_item
249
- response['LastEvaluatedKey'] = last_evaluated_item.key.as_hash
253
+ if index
254
+ response['LastEvaluatedKey'] = Key.from_index_item(last_evaluated_item, schema).as_hash
255
+ else
256
+ response['LastEvaluatedKey'] = last_evaluated_item.key.as_hash
257
+ end
250
258
  end
251
259
  response
252
260
  end
@@ -335,6 +343,27 @@ module FakeDynamo
335
343
  end
336
344
  end
337
345
 
346
+ def drop_till_start_index(all_items, start_key_hash, forward, schema)
347
+ all_items = all_items.sort_by { |item| Key.from_index_item(item, schema) }
348
+
349
+ unless forward
350
+ all_items = all_items.reverse
351
+ end
352
+
353
+ if start_key_hash
354
+ start_key = Key.from_index_schema(start_key_hash, schema, key_schema)
355
+ all_items.drop_while do |item|
356
+ if forward
357
+ Key.from_index_item(item, schema) <= start_key
358
+ else
359
+ Key.from_index_item(item, schema) >= start_key
360
+ end
361
+ end
362
+ else
363
+ all_items
364
+ end
365
+ end
366
+
338
367
  def filter(items, conditions, limit, fail_on_type_mismatch)
339
368
  limit ||= -1
340
369
  result = []
@@ -81,13 +81,13 @@ module FakeDynamo
81
81
  end
82
82
  when :map
83
83
  map = constrain[:map]
84
- raise "#{param(attribute, parents)} must be a Hash" unless data.kind_of? Hash
84
+ raise ValidationException, "#{param(attribute, parents)} must be a Hash" unless data.kind_of? Hash
85
85
  data.each do |key, value|
86
86
  validate_spec(key, key, map[:key], new_parents)
87
87
  validate_spec(key, value, map[:value], new_parents)
88
88
  end
89
89
  when :list
90
- raise "#{param(attribute, parents)} must be a Array" unless data.kind_of? Array
90
+ raise ValidationException, "#{param(attribute, parents)} must be a Array" unless data.kind_of? Array
91
91
  data.each_with_index do |element, i|
92
92
  validate_spec(element, element, constrain[:list], new_parents + [(i+1).to_s])
93
93
  end
@@ -1,3 +1,3 @@
1
1
  module FakeDynamo
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -405,7 +405,7 @@ module FakeDynamo
405
405
  next if j.even?
406
406
  item['Item']['AttributeName1']['S'] = "att#{i}"
407
407
  item['Item']['AttributeName2']['N'] = j.to_s
408
- item['Item']['AttributeName3'] = {'N' => j.to_s}
408
+ item['Item']['AttributeName3'] = {'N' => ((j % 3) + 2).to_s}
409
409
  t.put_item(item)
410
410
  end
411
411
  end
@@ -543,6 +543,12 @@ module FakeDynamo
543
543
  result['Count'].should eq(5)
544
544
  end
545
545
 
546
+ it 'should sort based on lsi range key' do
547
+ index_query.delete('Limit')
548
+ result = subject.query(index_query)
549
+ keys = result['Items'].map { |i| [i['AttributeName3']['N'].to_i, i['AttributeName2']['N'].to_i] }
550
+ keys.should eq(keys.sort)
551
+ end
546
552
 
547
553
  it 'should handle scanindexforward' do
548
554
  result = subject.query(query)
@@ -593,7 +599,7 @@ module FakeDynamo
593
599
  end
594
600
 
595
601
 
596
- it 'should return all elements if not rangekeycondition is given' do
602
+ it 'should return all elements if rangekeycondition is not given' do
597
603
  query['KeyConditions'].delete('AttributeName2')
598
604
  result = subject.query(query)
599
605
  result['Count'].should eq(5)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fake_dynamo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-22 00:00:00.000000000 Z
12
+ date: 2013-05-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
16
- requirement: &70301515417000 !ruby/object:Gem::Requirement
16
+ requirement: &70292157821000 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70301515417000
24
+ version_requirements: *70292157821000
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activesupport
27
- requirement: &70301515416500 !ruby/object:Gem::Requirement
27
+ requirement: &70292157820120 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70301515416500
35
+ version_requirements: *70292157820120
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: json
38
- requirement: &70301515416080 !ruby/object:Gem::Requirement
38
+ requirement: &70292157812900 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70301515416080
46
+ version_requirements: *70292157812900
47
47
  description:
48
48
  email:
49
49
  - ananthakumaran@gmail.com