dm-adapter-simpledb 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +0 -0
- data/.gitignore +8 -0
- data/README +156 -0
- data/Rakefile +77 -0
- data/VERSION +1 -0
- data/aws_config.sample +3 -0
- data/dm-adapter-simpledb.gemspec +99 -0
- data/lib/simpledb_adapter.rb +469 -0
- data/lib/simpledb_adapter/sdb_array.rb +52 -0
- data/scripts/simple_benchmark.rb +84 -0
- data/spec/associations_spec.rb +15 -0
- data/spec/compliance_spec.rb +18 -0
- data/spec/date_spec.rb +51 -0
- data/spec/limit_and_order_spec.rb +110 -0
- data/spec/migrations_spec.rb +41 -0
- data/spec/multiple_records_spec.rb +111 -0
- data/spec/nils_spec.rb +45 -0
- data/spec/sdb_array_spec.rb +71 -0
- data/spec/simpledb_adapter_spec.rb +162 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +67 -0
- data/tasks/devver.rake +167 -0
- metadata +146 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'dm-types'
|
2
|
+
|
3
|
+
# NOTE: Do not try to clear SdbArray properties by assigning nil. Instead,
|
4
|
+
# assign an empty array:
|
5
|
+
#
|
6
|
+
# resource.array_prop = []
|
7
|
+
# resource.save
|
8
|
+
#
|
9
|
+
# The reason has to do with DataMapper's lazy-load handling - a lazy-loaded
|
10
|
+
# property has a value of nil until it is loaded. If you assign nil, DM thinks
|
11
|
+
# that
|
12
|
+
module DataMapper
|
13
|
+
module Types
|
14
|
+
class SdbArray < DataMapper::Type
|
15
|
+
primitive ::Object
|
16
|
+
lazy true
|
17
|
+
|
18
|
+
def self.load(value, property)
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.dump(value, property)
|
23
|
+
dumped = ::Object.new
|
24
|
+
# This is a little screwy. DataMapper has a fixed list of values it
|
25
|
+
# considers primitives, and it insists that the value that comes out of
|
26
|
+
# a type's .dump() method MUST match one of these types. For SimpleDB
|
27
|
+
# Array is effectively a primitive because of the way it stores values,
|
28
|
+
# but DM doesn't include Array in it's list of valid primtive types. So
|
29
|
+
# we need to return an object which IS considered a primitive - in this
|
30
|
+
# case a plain 'ole Ruby Object. In order to convey the actual array
|
31
|
+
# value to the backend, we tack on a #to_ary method which returns the
|
32
|
+
# array data. RightAws calls Array() on all values before writing them,
|
33
|
+
# which in turn calls #to_ary(), and winds up with the correct data. In
|
34
|
+
# effect we are sneaking the array data through DataMapper inside a
|
35
|
+
# singleton method.
|
36
|
+
singleton_class = (class << dumped; self; end)
|
37
|
+
singleton_class.send(:define_method, :to_ary) do
|
38
|
+
value
|
39
|
+
end
|
40
|
+
singleton_class.send(:define_method, :to_s) do
|
41
|
+
value.to_s
|
42
|
+
end
|
43
|
+
dumped
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.typecast(value, property)
|
47
|
+
value
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.parent.expand_path + 'lib/simpledb_adapter'
|
3
|
+
require 'ruby-debug'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
access_key = ENV['AMAZON_ACCESS_KEY_ID']
|
7
|
+
secret_key = ENV['AMAZON_SECRET_ACCESS_KEY']
|
8
|
+
|
9
|
+
#For those that don't like to mess up their ENV
|
10
|
+
if access_key==nil && secret_key==nil
|
11
|
+
lines = File.readlines(File.join(File.dirname(__FILE__),'..','aws_config'))
|
12
|
+
access_key = lines[0].strip
|
13
|
+
secret_key = lines[1].strip
|
14
|
+
end
|
15
|
+
|
16
|
+
DataMapper.setup(:default, {
|
17
|
+
:adapter => 'simpledb',
|
18
|
+
:access_key => access_key,
|
19
|
+
:secret_key => secret_key,
|
20
|
+
:domain => 'benchmark'
|
21
|
+
})
|
22
|
+
|
23
|
+
class Person
|
24
|
+
include DataMapper::Resource
|
25
|
+
|
26
|
+
property :id, String, :key => true
|
27
|
+
property :name, String, :key => true
|
28
|
+
property :age, Integer
|
29
|
+
property :wealth, Float
|
30
|
+
property :birthday, Date
|
31
|
+
property :created_at, DateTime
|
32
|
+
end
|
33
|
+
|
34
|
+
@adapter = repository(:default).adapter
|
35
|
+
|
36
|
+
|
37
|
+
def rand_str(length = 10)
|
38
|
+
(0...length).map{65.+(rand(25)).chr}.join
|
39
|
+
end
|
40
|
+
|
41
|
+
def clean_up
|
42
|
+
#clean up by removing the benchmark storage model
|
43
|
+
ENV['destroy']='true'
|
44
|
+
@adapter.destroy_model_storage(repository(:default), Person)
|
45
|
+
ENV['destroy']='false'
|
46
|
+
end
|
47
|
+
|
48
|
+
#clean_up
|
49
|
+
#sleep(5)
|
50
|
+
|
51
|
+
Person.auto_migrate!
|
52
|
+
sleep(1)
|
53
|
+
|
54
|
+
Benchmark.bm do|b|
|
55
|
+
friends = []
|
56
|
+
number = 200
|
57
|
+
|
58
|
+
b.report("creating #{number} users") do
|
59
|
+
number.times do
|
60
|
+
friend_attrs = { :id => "person-#{rand_str}-#{Time.now.to_f.to_s}", :name => "name #{Time.now.to_f.to_s} #{rand_str}", :age => 25, :wealth => 25.00, :birthday => Date.today }
|
61
|
+
friend = Person.create(friend_attrs)
|
62
|
+
friends << friend
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
sleep(5.5) #let everything distribute through SDB
|
67
|
+
Person.all(:age => 25) #seems to make sure the first one gets the right amount
|
68
|
+
|
69
|
+
b.report("Finding all users age 25, 100 Times") do
|
70
|
+
100.times do
|
71
|
+
people = Person.all(:age => 25, :limit => number)
|
72
|
+
if people.length!=number
|
73
|
+
puts "warning wrong number or users #{people.length}"
|
74
|
+
#clean_up
|
75
|
+
#raise "wrong amount of peoplefound"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
clean_up
|
83
|
+
sleep(2)
|
84
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
|
3
|
+
|
4
|
+
describe 'associations' do
|
5
|
+
it 'should work with belongs_to associations'
|
6
|
+
it 'should work with has n associations'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'STI' do
|
10
|
+
it 'should override default type'
|
11
|
+
it 'should load descendents on parent.all'
|
12
|
+
it 'should raise an error if you have a column named couchdb_type'
|
13
|
+
end
|
14
|
+
|
15
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
|
3
|
+
|
4
|
+
require 'dm-core/spec/adapter_shared_spec'
|
5
|
+
|
6
|
+
describe DataMapper::Adapters::SimpleDBAdapter do
|
7
|
+
before :all do
|
8
|
+
@adapter = DataMapper::Repository.adapters[:default]
|
9
|
+
@old_consistency_policy = @adapter.consistency_policy
|
10
|
+
@adapter.consistency_policy = :automatic
|
11
|
+
end
|
12
|
+
|
13
|
+
after :all do
|
14
|
+
@adapter.consistency_policy = @old_consistency_policy
|
15
|
+
end
|
16
|
+
|
17
|
+
it_should_behave_like 'An Adapter'
|
18
|
+
end
|
data/spec/date_spec.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
|
3
|
+
|
4
|
+
class Professor
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, String, :key => true
|
8
|
+
property :name, String, :key => true
|
9
|
+
property :age, Integer
|
10
|
+
property :wealth, Float
|
11
|
+
property :birthday, Date
|
12
|
+
property :created_at, DateTime
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'with multiple records saved' do
|
17
|
+
before(:each) do
|
18
|
+
@adapter.wait_for_consistency
|
19
|
+
@person_attrs = { :id => "person-#{Time.now.to_f.to_s}", :name => 'Jeremy Boles', :age => 25, :wealth => 25.00, :birthday => Date.today }
|
20
|
+
@jeremy = Professor.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Jeremy Boles", :age => 25))
|
21
|
+
@danielle = Professor.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Danille Boles", :age => 26))
|
22
|
+
@keegan = Professor.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Keegan Jones", :age => 20))
|
23
|
+
@adapter.wait_for_consistency
|
24
|
+
end
|
25
|
+
|
26
|
+
after(:each) do
|
27
|
+
@jeremy.destroy
|
28
|
+
@danielle.destroy
|
29
|
+
@keegan.destroy
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should handle DateTime' do
|
33
|
+
time = DateTime.civil(1970,1,1)
|
34
|
+
@jeremy.created_at = time
|
35
|
+
@jeremy.save
|
36
|
+
@adapter.wait_for_consistency
|
37
|
+
person = Professor.get!(@jeremy.id, @jeremy.name)
|
38
|
+
person.created_at.should == time
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should handle Date' do
|
42
|
+
person = Professor.get!(@jeremy.id, @jeremy.name)
|
43
|
+
person.birthday.should == @jeremy.birthday
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should match with Data' do
|
47
|
+
people = Professor.all(:birthday => Date.today)
|
48
|
+
people.length.should == 3
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
|
3
|
+
|
4
|
+
class Hero
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, String, :key => true
|
8
|
+
property :name, String, :key => true
|
9
|
+
property :age, Integer
|
10
|
+
property :wealth, Float
|
11
|
+
property :birthday, Date
|
12
|
+
property :created_at, DateTime
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'with multiple records saved' do
|
17
|
+
before(:each) do
|
18
|
+
@person_attrs = { :id => "person-#{Time.now.to_f.to_s}", :name => 'Jeremy Boles', :age => 25, :wealth => 25.00, :birthday => Date.today }
|
19
|
+
@jeremy = Hero.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Jeremy Boles", :age => 25))
|
20
|
+
@danielle = Hero.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Danille Boles", :age => 26))
|
21
|
+
@keegan = Hero.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Keegan Jones", :age => 20, :wealth => 15.00))
|
22
|
+
@adapter.wait_for_consistency
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:each) do
|
26
|
+
@jeremy.destroy
|
27
|
+
@danielle.destroy
|
28
|
+
@keegan.destroy
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should handle limit one case' do
|
32
|
+
persons = Hero.all(:limit => 1)
|
33
|
+
persons.length.should ==1
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should handle max item limit case' do
|
37
|
+
persons = Hero.all(:limit => 3)
|
38
|
+
persons.length.should ==3
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should handle max item if limit is large case' do
|
42
|
+
persons = Hero.all(:limit => 150)
|
43
|
+
persons.length.should ==3
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should handle ordering asc results with a limit' do
|
47
|
+
persons = Hero.all(:order => [:age.asc], :limit => 2)
|
48
|
+
persons.inspect #can't access via array until loaded? Weird
|
49
|
+
persons.length.should ==2
|
50
|
+
persons[0].should == @keegan
|
51
|
+
persons[1].should == @jeremy
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should handle ordering asc results' do
|
55
|
+
persons = Hero.all(:order => [:age.asc])
|
56
|
+
persons.inspect #can't access via array until loaded? Weird
|
57
|
+
persons[0].should == @keegan
|
58
|
+
persons[1].should == @jeremy
|
59
|
+
persons[2].should == @danielle
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should handle ordering desc results' do
|
63
|
+
persons = Hero.all(:order => [:age.desc])
|
64
|
+
persons.inspect #can't access via array until loaded? Weird
|
65
|
+
persons[0].should == @danielle
|
66
|
+
persons[1].should == @jeremy
|
67
|
+
persons[2].should == @keegan
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should handle ordering results with multiple conditionss' do
|
71
|
+
persons = Hero.all(:age.gt => 20, :wealth.gt => 20, :order => [:age.desc])
|
72
|
+
persons.inspect #can't access via array until loaded? Weird
|
73
|
+
persons.length.should ==2
|
74
|
+
persons[0].should == @danielle
|
75
|
+
persons[1].should == @jeremy
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should handle ordering results with ordered by conditions' do
|
79
|
+
persons = Hero.all(:age.gt => 20, :order => [:age.desc])
|
80
|
+
persons.inspect #can't access via array until loaded? Weird
|
81
|
+
persons.length.should ==2
|
82
|
+
persons[0].should == @danielle
|
83
|
+
persons[1].should == @jeremy
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should handle ordering results with unorder by conditions' do
|
87
|
+
persons = Hero.all(:wealth.gt => 20.00, :order => [:age.desc])
|
88
|
+
persons.inspect #can't access via array until loaded? Weird
|
89
|
+
persons.length.should ==2
|
90
|
+
persons[0].should == @danielle
|
91
|
+
persons[1].should == @jeremy
|
92
|
+
end
|
93
|
+
|
94
|
+
context "with many entries" do
|
95
|
+
before :each do
|
96
|
+
resources = []
|
97
|
+
111.times do |i|
|
98
|
+
resources << Hero.new(:id => i, :name => "Hero#{i}")
|
99
|
+
end
|
100
|
+
DataMapper.repository(:default).create(resources)
|
101
|
+
@adapter.wait_for_consistency
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should support limits over 100" do
|
105
|
+
results = Hero.all(:limit => 110)
|
106
|
+
results.should have(110).entries
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
|
3
|
+
require 'dm-migrations'
|
4
|
+
|
5
|
+
describe 'support migrations' do
|
6
|
+
|
7
|
+
#TODO do this on different storage
|
8
|
+
class Person
|
9
|
+
include DataMapper::Resource
|
10
|
+
|
11
|
+
property :id, String, :key => true
|
12
|
+
property :name, String, :key => true
|
13
|
+
property :age, Integer
|
14
|
+
property :wealth, Float
|
15
|
+
property :birthday, Date
|
16
|
+
property :created_at, DateTime
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
# test can't be run simultanious make it delete a throwawaable storage model
|
21
|
+
# instead of the one used by all the tests
|
22
|
+
# it "should destroy model storage" do
|
23
|
+
# ENV['destroy']='true'
|
24
|
+
# @adapter.destroy_model_storage(repository(:default), Person)
|
25
|
+
# @adapter.storage_exists?("missionaries").should == false
|
26
|
+
# ENV['destroy']='false'
|
27
|
+
# @adapter.create_model_storage(repository(:default), Person)
|
28
|
+
# @adapter.storage_exists?("missionaries").should == true
|
29
|
+
# end
|
30
|
+
|
31
|
+
before :all do
|
32
|
+
@sdb.delete_domain(@domain)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should create model storage" do
|
36
|
+
DataMapper.auto_migrate!
|
37
|
+
@adapter.storage_exists?(@domain).should == true
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
|
3
|
+
|
4
|
+
class Person
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, String, :key => true
|
8
|
+
property :name, String, :key => true
|
9
|
+
property :age, Integer
|
10
|
+
property :wealth, Float
|
11
|
+
property :birthday, Date
|
12
|
+
property :created_at, DateTime
|
13
|
+
|
14
|
+
belongs_to :company
|
15
|
+
end
|
16
|
+
|
17
|
+
#TODO write some tests with company or drop this
|
18
|
+
class Company
|
19
|
+
include DataMapper::Resource
|
20
|
+
|
21
|
+
property :id, String, :key => true
|
22
|
+
property :name, String, :key => true
|
23
|
+
|
24
|
+
has n, :people
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'with multiple records saved' do
|
28
|
+
before(:all) do
|
29
|
+
@person_attrs = { :id => "person-#{Time.now.to_f.to_s}", :name => 'Jeremy Boles', :age => 25, :wealth => 25.00, :birthday => Date.today }
|
30
|
+
@jeremy = Person.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Jeremy Boles", :age => 25))
|
31
|
+
@danielle = Person.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Danille Boles", :age => 26))
|
32
|
+
@keegan = Person.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Keegan Jones", :age => 20))
|
33
|
+
@adapter.wait_for_consistency
|
34
|
+
end
|
35
|
+
|
36
|
+
after(:all) do
|
37
|
+
@jeremy.destroy
|
38
|
+
@danielle.destroy
|
39
|
+
@keegan.destroy
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should get all records' do
|
43
|
+
Person.all.length.should == 3
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should get records by eql matcher' do
|
47
|
+
people = Person.all(:age => 25)
|
48
|
+
people.length.should == 1
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should get record by eql matcher' do
|
52
|
+
person = Person.first(:conditions => {:age => 25})
|
53
|
+
person.should_not be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should get records by not matcher' do
|
57
|
+
people = Person.all(:age.not => 25)
|
58
|
+
people.should have(2).entries
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should get record by not matcher' do
|
62
|
+
person = Person.first(:age.not => 25)
|
63
|
+
person.should_not be_nil
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should get records by gt matcher' do
|
67
|
+
people = Person.all(:age.gt => 25)
|
68
|
+
people.length.should == 1
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should get records by gte matcher' do
|
72
|
+
people = Person.all(:age.gte => 25)
|
73
|
+
people.length.should == 2
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should get records by lt matcher' do
|
77
|
+
people = Person.all(:age.lt => 25)
|
78
|
+
people.length.should == 1
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should get records by lte matcher' do
|
82
|
+
people = Person.all(:age.lte => 25)
|
83
|
+
people.length.should == 2
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should get record by lte matcher' do
|
87
|
+
person = Person.first(:age.lte => 25)
|
88
|
+
person.should_not be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should get records with multiple matchers' do
|
92
|
+
people = Person.all(:birthday => Date.today, :age.lte => 25)
|
93
|
+
people.length.should == 2
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should get records by the like matcher' do
|
97
|
+
people = Person.all(:name.like => 'Jeremy%')
|
98
|
+
people.should == [@jeremy]
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should get records by the IN matcher' do
|
102
|
+
people = Person.all(:id => [@jeremy.id, @danielle.id])
|
103
|
+
people.should include(@jeremy)
|
104
|
+
people.should include(@danielle)
|
105
|
+
people.should_not include(@keegan)
|
106
|
+
end
|
107
|
+
it "should get no records if IN array is empty" do
|
108
|
+
people = Person.all(:id => [])
|
109
|
+
people.should be_empty
|
110
|
+
end
|
111
|
+
end
|