dm-adapter-simpledb 1.0.0
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/.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
|