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 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