fake_dynamo 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://secure.travis-ci.org/ananthakumaran/fake_dynamo.png)](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
|