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 +13 -6
- data/bin/fake_dynamo +6 -2
- data/lib/fake_dynamo/key.rb +30 -4
- data/lib/fake_dynamo/key_schema.rb +1 -1
- data/lib/fake_dynamo/table.rb +31 -2
- data/lib/fake_dynamo/validation.rb +2 -2
- data/lib/fake_dynamo/version.rb +1 -1
- data/spec/fake_dynamo/table_spec.rb +8 -2
- metadata +8 -8
data/README.md
CHANGED
@@ -1,21 +1,28 @@
|
|
1
1
|
# FakeDynamo [](http://travis-ci.org/ananthakumaran/fake_dynamo)
|
2
2
|
|
3
|
-
local hosted, inmemory
|
3
|
+
local hosted, inmemory Amazon DynamoDB emulator.
|
4
4
|
|
5
|
+
## Versions
|
5
6
|
|
6
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/fake_dynamo/key.rb
CHANGED
@@ -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 <<
|
13
|
+
description << {'AttributeName' => range_key.name, 'KeyType' => 'RANGE'}
|
14
14
|
end
|
15
15
|
description
|
16
16
|
end
|
data/lib/fake_dynamo/table.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/fake_dynamo/version.rb
CHANGED
@@ -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
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *70292157821000
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activesupport
|
27
|
-
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: *
|
35
|
+
version_requirements: *70292157820120
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: json
|
38
|
-
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: *
|
46
|
+
version_requirements: *70292157812900
|
47
47
|
description:
|
48
48
|
email:
|
49
49
|
- ananthakumaran@gmail.com
|