dm-adapter-simpledb 1.0.0 → 1.1.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/.gitignore +1 -0
- data/History.txt +21 -0
- data/README +21 -8
- data/Rakefile +35 -23
- data/VERSION +1 -1
- data/dm-adapter-simpledb.gemspec +44 -24
- data/lib/dm-adapter-simpledb.rb +17 -0
- data/lib/dm-adapter-simpledb/adapters/simpledb_adapter.rb +339 -0
- data/lib/dm-adapter-simpledb/chunked_string.rb +54 -0
- data/lib/dm-adapter-simpledb/migrations/simpledb_adapter.rb +45 -0
- data/lib/dm-adapter-simpledb/rake.rb +43 -0
- data/lib/dm-adapter-simpledb/record.rb +318 -0
- data/lib/{simpledb_adapter → dm-adapter-simpledb}/sdb_array.rb +0 -0
- data/lib/dm-adapter-simpledb/table.rb +40 -0
- data/lib/dm-adapter-simpledb/utils.rb +15 -0
- data/lib/simpledb_adapter.rb +2 -469
- data/scripts/simple_benchmark.rb +1 -1
- data/spec/{associations_spec.rb → integration/associations_spec.rb} +0 -0
- data/spec/{compliance_spec.rb → integration/compliance_spec.rb} +0 -0
- data/spec/{date_spec.rb → integration/date_spec.rb} +0 -0
- data/spec/{limit_and_order_spec.rb → integration/limit_and_order_spec.rb} +0 -0
- data/spec/{migrations_spec.rb → integration/migrations_spec.rb} +0 -0
- data/spec/{multiple_records_spec.rb → integration/multiple_records_spec.rb} +0 -0
- data/spec/{nils_spec.rb → integration/nils_spec.rb} +0 -0
- data/spec/{sdb_array_spec.rb → integration/sdb_array_spec.rb} +4 -5
- data/spec/{simpledb_adapter_spec.rb → integration/simpledb_adapter_spec.rb} +65 -0
- data/spec/{spec_helper.rb → integration/spec_helper.rb} +8 -3
- data/spec/unit/record_spec.rb +346 -0
- data/spec/unit/simpledb_adapter_spec.rb +80 -0
- data/spec/unit/unit_spec_helper.rb +26 -0
- metadata +58 -24
- data/tasks/devver.rake +0 -167
data/.gitignore
CHANGED
data/History.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== 1.1.0 2009-11-24
|
2
|
+
|
3
|
+
* 2 major enhancements:
|
4
|
+
* Supports legacy (pre-1.0.0) objects
|
5
|
+
|
6
|
+
Prior to version 1.0.0 there were some differences in how this adapter
|
7
|
+
stored nils and strings containing newlines. The adapter will now detect old
|
8
|
+
objects and load them correctly.
|
9
|
+
|
10
|
+
* Adds object versioning
|
11
|
+
|
12
|
+
New records created by the adapter now have version metadata attached to
|
13
|
+
them, making it easier to change how objects are stored in the future.
|
14
|
+
|
15
|
+
* 1 bug fix
|
16
|
+
* Fixed an "odd number of arguments for hash" error when saving objects.
|
17
|
+
|
18
|
+
* Cleanup/refactoring
|
19
|
+
* Work has begun to start splitting out functionality into separate files and
|
20
|
+
classes
|
21
|
+
* Added unit tests to complement the existing integration tests
|
data/README
CHANGED
@@ -38,21 +38,30 @@ http://github.com/devver/dm-adapter-simpledb/
|
|
38
38
|
|
39
39
|
== TODO
|
40
40
|
|
41
|
-
* Backwards-compatibility option for nils stored as "nil" string
|
42
41
|
* More complete handling of NOT conditions in queries
|
43
42
|
* Robust quoting in SELECT calls
|
44
43
|
* Handle exclusive ranges natively
|
45
44
|
Implement as inclusive range + filter step
|
46
45
|
* Tests for associations
|
47
|
-
* Split up into multiple files
|
48
46
|
* Option for smart lexicographical storage for numbers
|
49
47
|
- Zero-pad integers
|
50
48
|
- Store floats using exponential notation
|
51
49
|
* Option to store Date/Time/DateTime as ISO8601
|
52
50
|
* Full aggregate support (min/max/etc)
|
51
|
+
* Offset support
|
52
|
+
Note, from the SimpleDB documentation:
|
53
|
+
|
54
|
+
"The next token returned by count(*) and select are interchangeable as long
|
55
|
+
as the where and order by clauses match. For example, if you want to return
|
56
|
+
the 200 items after the first 10,000 (similar to an offset), you can perform
|
57
|
+
a count with a limit clause of 10,000 and use the next token to return the
|
58
|
+
next 200 items with select."
|
59
|
+
|
53
60
|
* Option to use libxml if available
|
54
61
|
* Parallelized queries for increased throughput
|
55
62
|
* Support of normalized 1:1 table:domain schemes that works with associations
|
63
|
+
* Sharding
|
64
|
+
* Support BatchPutAttributes
|
56
65
|
|
57
66
|
== Usage
|
58
67
|
|
@@ -60,8 +69,14 @@ http://github.com/devver/dm-adapter-simpledb/
|
|
60
69
|
|
61
70
|
require 'rubygems'
|
62
71
|
require 'dm-core'
|
72
|
+
require 'dm-adapter-simpledb'
|
63
73
|
|
64
|
-
DataMapper.setup(:default,
|
74
|
+
DataMapper.setup(:default,
|
75
|
+
:adapter => 'simpledb',
|
76
|
+
:access_key => "ACCESS_KEY",
|
77
|
+
:secret_key => "SECRET_KEY",
|
78
|
+
:domain => "DOMAIN",
|
79
|
+
)
|
65
80
|
|
66
81
|
[Same as the following, but skip the database.yml]
|
67
82
|
|
@@ -71,19 +86,17 @@ http://github.com/devver/dm-adapter-simpledb/
|
|
71
86
|
|
72
87
|
Setup database.yml with the SimpleDB DataMapper adapter:
|
73
88
|
|
74
|
-
adapter:
|
75
|
-
database: 'default'
|
89
|
+
adapter: simpledb
|
76
90
|
access_key: (a 20-character, alphanumeric sequence)
|
77
91
|
secret_key: (a 40-character sequence)
|
78
|
-
domain:
|
79
|
-
base_url: 'http://sdb.amazon.com'
|
92
|
+
domain: 'my_amazon_sdb_domain'
|
80
93
|
|
81
94
|
Create a model
|
82
95
|
|
83
96
|
class Tree
|
84
97
|
include DataMapper::Resource
|
85
98
|
|
86
|
-
storage_name "trees"
|
99
|
+
storage_name[:default] = "trees"
|
87
100
|
|
88
101
|
property :id, Serial
|
89
102
|
property :name, String, :nullable => false
|
data/Rakefile
CHANGED
@@ -1,36 +1,46 @@
|
|
1
1
|
require 'spec'
|
2
2
|
require 'spec/rake/spectask'
|
3
3
|
require 'pathname'
|
4
|
-
load 'tasks/devver.rake'
|
5
4
|
|
6
5
|
ROOT = Pathname(__FILE__).dirname.expand_path
|
7
|
-
require ROOT + 'lib/simpledb_adapter'
|
8
6
|
|
9
|
-
task :default => [ :
|
7
|
+
task :default => [ 'spec:unit' ]
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
namespace :spec do
|
10
|
+
desc 'Run unit-level specifications'
|
11
|
+
Spec::Rake::SpecTask.new(:unit) do |t|
|
12
|
+
if File.exists?('spec/spec.opts')
|
13
|
+
t.spec_opts << '--options' << 'spec/spec.opts'
|
14
|
+
end
|
15
|
+
t.spec_files = Pathname.glob((ROOT + 'spec/unit/**/*_spec.rb').to_s)
|
16
|
+
|
17
|
+
begin
|
18
|
+
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
19
|
+
t.rcov_opts << '--exclude' << 'spec'
|
20
|
+
t.rcov_opts << '--text-summary'
|
21
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
22
|
+
rescue Exception
|
23
|
+
# rcov not installed
|
24
|
+
end
|
15
25
|
end
|
16
|
-
t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
|
17
|
-
|
18
|
-
begin
|
19
|
-
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
20
|
-
t.rcov_opts << '--exclude' << 'spec'
|
21
|
-
t.rcov_opts << '--text-summary'
|
22
|
-
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
23
|
-
rescue Exception
|
24
|
-
# rcov not installed
|
25
|
-
end
|
26
|
-
end
|
27
26
|
|
28
|
-
desc 'Run specifications
|
29
|
-
Spec::Rake::SpecTask.new(:
|
30
|
-
|
31
|
-
|
27
|
+
desc 'Run integration-level specifications'
|
28
|
+
Spec::Rake::SpecTask.new(:integration) do |t|
|
29
|
+
if File.exists?('spec/spec.opts')
|
30
|
+
t.spec_opts << '--options' << 'spec/spec.opts'
|
31
|
+
end
|
32
|
+
t.spec_files = Pathname.glob((ROOT + 'spec/integration/**/*_spec.rb').to_s)
|
33
|
+
|
34
|
+
begin
|
35
|
+
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
36
|
+
t.rcov_opts << '--exclude' << 'spec'
|
37
|
+
t.rcov_opts << '--text-summary'
|
38
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
39
|
+
rescue Exception
|
40
|
+
# rcov not installed
|
41
|
+
end
|
32
42
|
end
|
33
|
-
|
43
|
+
|
34
44
|
end
|
35
45
|
|
36
46
|
begin
|
@@ -68,6 +78,8 @@ END
|
|
68
78
|
]
|
69
79
|
gem.add_dependency('dm-core', '~> 0.10.0')
|
70
80
|
gem.add_dependency('dm-aggregates', '~> 0.10.0')
|
81
|
+
gem.add_dependency('dm-migrations', '~> 0.10.0')
|
82
|
+
gem.add_dependency('dm-types', '~> 0.10.0')
|
71
83
|
gem.add_dependency('uuidtools', '~> 2.0')
|
72
84
|
gem.add_dependency('right_aws', '~> 1.10')
|
73
85
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.1.0
|
data/dm-adapter-simpledb.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{dm-adapter-simpledb}
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jeremy Boles", "Edward Ocampo-Gooding", "Dan Mayer", "Thomas Olausson", "Avdi Grimm"]
|
12
|
-
s.date = %q{2009-11-
|
12
|
+
s.date = %q{2009-11-24}
|
13
13
|
s.description = %q{A DataMapper adapter for Amazon's SimpleDB service.
|
14
14
|
|
15
15
|
Features:
|
@@ -35,26 +35,37 @@ series and breaks backwards compatibility with DataMapper 0.9.*.
|
|
35
35
|
s.files = [
|
36
36
|
".autotest",
|
37
37
|
".gitignore",
|
38
|
+
"History.txt",
|
38
39
|
"README",
|
39
40
|
"Rakefile",
|
40
41
|
"VERSION",
|
41
42
|
"aws_config.sample",
|
42
43
|
"dm-adapter-simpledb.gemspec",
|
44
|
+
"lib/dm-adapter-simpledb.rb",
|
45
|
+
"lib/dm-adapter-simpledb/adapters/simpledb_adapter.rb",
|
46
|
+
"lib/dm-adapter-simpledb/chunked_string.rb",
|
47
|
+
"lib/dm-adapter-simpledb/migrations/simpledb_adapter.rb",
|
48
|
+
"lib/dm-adapter-simpledb/rake.rb",
|
49
|
+
"lib/dm-adapter-simpledb/record.rb",
|
50
|
+
"lib/dm-adapter-simpledb/sdb_array.rb",
|
51
|
+
"lib/dm-adapter-simpledb/table.rb",
|
52
|
+
"lib/dm-adapter-simpledb/utils.rb",
|
43
53
|
"lib/simpledb_adapter.rb",
|
44
|
-
"lib/simpledb_adapter/sdb_array.rb",
|
45
54
|
"scripts/simple_benchmark.rb",
|
46
|
-
"spec/associations_spec.rb",
|
47
|
-
"spec/compliance_spec.rb",
|
48
|
-
"spec/date_spec.rb",
|
49
|
-
"spec/limit_and_order_spec.rb",
|
50
|
-
"spec/migrations_spec.rb",
|
51
|
-
"spec/multiple_records_spec.rb",
|
52
|
-
"spec/nils_spec.rb",
|
53
|
-
"spec/sdb_array_spec.rb",
|
54
|
-
"spec/simpledb_adapter_spec.rb",
|
55
|
+
"spec/integration/associations_spec.rb",
|
56
|
+
"spec/integration/compliance_spec.rb",
|
57
|
+
"spec/integration/date_spec.rb",
|
58
|
+
"spec/integration/limit_and_order_spec.rb",
|
59
|
+
"spec/integration/migrations_spec.rb",
|
60
|
+
"spec/integration/multiple_records_spec.rb",
|
61
|
+
"spec/integration/nils_spec.rb",
|
62
|
+
"spec/integration/sdb_array_spec.rb",
|
63
|
+
"spec/integration/simpledb_adapter_spec.rb",
|
64
|
+
"spec/integration/spec_helper.rb",
|
55
65
|
"spec/spec.opts",
|
56
|
-
"spec/
|
57
|
-
"
|
66
|
+
"spec/unit/record_spec.rb",
|
67
|
+
"spec/unit/simpledb_adapter_spec.rb",
|
68
|
+
"spec/unit/unit_spec_helper.rb"
|
58
69
|
]
|
59
70
|
s.homepage = %q{http://github.com/devver/dm-adapter-simpledb}
|
60
71
|
s.rdoc_options = ["--charset=UTF-8"]
|
@@ -62,16 +73,19 @@ series and breaks backwards compatibility with DataMapper 0.9.*.
|
|
62
73
|
s.rubygems_version = %q{1.3.5}
|
63
74
|
s.summary = %q{DataMapper adapter for Amazon SimpleDB}
|
64
75
|
s.test_files = [
|
65
|
-
"spec/nils_spec.rb",
|
66
|
-
"spec/limit_and_order_spec.rb",
|
67
|
-
"spec/compliance_spec.rb",
|
68
|
-
"spec/simpledb_adapter_spec.rb",
|
69
|
-
"spec/date_spec.rb",
|
70
|
-
"spec/sdb_array_spec.rb",
|
71
|
-
"spec/migrations_spec.rb",
|
72
|
-
"spec/spec_helper.rb",
|
73
|
-
"spec/multiple_records_spec.rb",
|
74
|
-
"spec/associations_spec.rb"
|
76
|
+
"spec/integration/nils_spec.rb",
|
77
|
+
"spec/integration/limit_and_order_spec.rb",
|
78
|
+
"spec/integration/compliance_spec.rb",
|
79
|
+
"spec/integration/simpledb_adapter_spec.rb",
|
80
|
+
"spec/integration/date_spec.rb",
|
81
|
+
"spec/integration/sdb_array_spec.rb",
|
82
|
+
"spec/integration/migrations_spec.rb",
|
83
|
+
"spec/integration/spec_helper.rb",
|
84
|
+
"spec/integration/multiple_records_spec.rb",
|
85
|
+
"spec/integration/associations_spec.rb",
|
86
|
+
"spec/unit/simpledb_adapter_spec.rb",
|
87
|
+
"spec/unit/unit_spec_helper.rb",
|
88
|
+
"spec/unit/record_spec.rb"
|
75
89
|
]
|
76
90
|
|
77
91
|
if s.respond_to? :specification_version then
|
@@ -81,17 +95,23 @@ series and breaks backwards compatibility with DataMapper 0.9.*.
|
|
81
95
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
82
96
|
s.add_runtime_dependency(%q<dm-core>, ["~> 0.10.0"])
|
83
97
|
s.add_runtime_dependency(%q<dm-aggregates>, ["~> 0.10.0"])
|
98
|
+
s.add_runtime_dependency(%q<dm-migrations>, ["~> 0.10.0"])
|
99
|
+
s.add_runtime_dependency(%q<dm-types>, ["~> 0.10.0"])
|
84
100
|
s.add_runtime_dependency(%q<uuidtools>, ["~> 2.0"])
|
85
101
|
s.add_runtime_dependency(%q<right_aws>, ["~> 1.10"])
|
86
102
|
else
|
87
103
|
s.add_dependency(%q<dm-core>, ["~> 0.10.0"])
|
88
104
|
s.add_dependency(%q<dm-aggregates>, ["~> 0.10.0"])
|
105
|
+
s.add_dependency(%q<dm-migrations>, ["~> 0.10.0"])
|
106
|
+
s.add_dependency(%q<dm-types>, ["~> 0.10.0"])
|
89
107
|
s.add_dependency(%q<uuidtools>, ["~> 2.0"])
|
90
108
|
s.add_dependency(%q<right_aws>, ["~> 1.10"])
|
91
109
|
end
|
92
110
|
else
|
93
111
|
s.add_dependency(%q<dm-core>, ["~> 0.10.0"])
|
94
112
|
s.add_dependency(%q<dm-aggregates>, ["~> 0.10.0"])
|
113
|
+
s.add_dependency(%q<dm-migrations>, ["~> 0.10.0"])
|
114
|
+
s.add_dependency(%q<dm-types>, ["~> 0.10.0"])
|
95
115
|
s.add_dependency(%q<uuidtools>, ["~> 2.0"])
|
96
116
|
s.add_dependency(%q<right_aws>, ["~> 1.10"])
|
97
117
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
gem 'dm-migrations', '~> 0.10.0'
|
2
|
+
gem 'dm-types', '~> 0.10.0'
|
3
|
+
gem 'dm-aggregates', '~> 0.10.0'
|
4
|
+
gem 'dm-core', '~> 0.10.0'
|
5
|
+
|
6
|
+
require 'dm-core'
|
7
|
+
require 'dm-aggregates'
|
8
|
+
require 'digest/sha1'
|
9
|
+
require 'right_aws'
|
10
|
+
require 'uuidtools'
|
11
|
+
|
12
|
+
require 'dm-adapter-simpledb/sdb_array'
|
13
|
+
require 'dm-adapter-simpledb/utils'
|
14
|
+
require 'dm-adapter-simpledb/record'
|
15
|
+
require 'dm-adapter-simpledb/table'
|
16
|
+
require 'dm-adapter-simpledb/migrations/simpledb_adapter'
|
17
|
+
require 'dm-adapter-simpledb/adapters/simpledb_adapter'
|
@@ -0,0 +1,339 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
class SimpleDBAdapter < AbstractAdapter
|
4
|
+
include DmAdapterSimpledb::Utils
|
5
|
+
|
6
|
+
attr_reader :sdb_options
|
7
|
+
|
8
|
+
# For testing purposes ONLY. Seriously, don't enable this for production
|
9
|
+
# code.
|
10
|
+
attr_accessor :consistency_policy
|
11
|
+
|
12
|
+
def initialize(name, normalised_options)
|
13
|
+
super
|
14
|
+
@sdb_options = {}
|
15
|
+
@sdb_options[:access_key] = options.fetch(:access_key) {
|
16
|
+
options[:user]
|
17
|
+
}
|
18
|
+
@sdb_options[:secret_key] = options.fetch(:secret_key) {
|
19
|
+
options[:password]
|
20
|
+
}
|
21
|
+
@sdb_options[:logger] = options.fetch(:logger) { DataMapper.logger }
|
22
|
+
@sdb_options[:server] = options.fetch(:host) { 'sdb.amazonaws.com' }
|
23
|
+
@sdb_options[:port] = options[:port] || 443 # port may be set but nil
|
24
|
+
@sdb_options[:domain] = options.fetch(:domain) {
|
25
|
+
options[:path].to_s.gsub(%r{(^/+)|(/+$)},"") # remove slashes
|
26
|
+
}
|
27
|
+
# We do not expect to be saving any nils in future, because now we
|
28
|
+
# represent null values by removing the attributes. The representation
|
29
|
+
# here is chosen on the basis of it being unlikely to match any strings
|
30
|
+
# found in real-world records, as well as being eye-catching in case any
|
31
|
+
# nils DO manage to sneak in. It would be preferable if we could disable
|
32
|
+
# RightAWS's nil-token replacement altogether, but that does not appear
|
33
|
+
# to be an option.
|
34
|
+
@sdb_options[:nil_representation] = "<[<[<NIL>]>]>"
|
35
|
+
@consistency_policy =
|
36
|
+
normalised_options.fetch(:wait_for_consistency) { false }
|
37
|
+
@sdb = options.fetch(:sdb_interface) { nil }
|
38
|
+
end
|
39
|
+
|
40
|
+
def create(resources)
|
41
|
+
created = 0
|
42
|
+
time = Benchmark.realtime do
|
43
|
+
resources.each do |resource|
|
44
|
+
uuid = UUIDTools::UUID.timestamp_create
|
45
|
+
initialize_serial(resource, uuid.to_i)
|
46
|
+
|
47
|
+
record = DmAdapterSimpledb::Record.from_resource(resource)
|
48
|
+
attributes = record.writable_attributes
|
49
|
+
item_name = record.item_name
|
50
|
+
sdb.put_attributes(domain, item_name, attributes)
|
51
|
+
created += 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
DataMapper.logger.debug(format_log_entry("(#{created}) INSERT #{resources.inspect}", time))
|
55
|
+
modified!
|
56
|
+
created
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete(collection)
|
60
|
+
deleted = 0
|
61
|
+
time = Benchmark.realtime do
|
62
|
+
collection.each do |resource|
|
63
|
+
record = DmAdapterSimpledb::Record.from_resource(resource)
|
64
|
+
item_name = record.item_name
|
65
|
+
sdb.delete_attributes(domain, item_name)
|
66
|
+
deleted += 1
|
67
|
+
end
|
68
|
+
raise NotImplementedError.new('Only :eql on delete at the moment') if not_eql_query?(collection.query)
|
69
|
+
end; DataMapper.logger.debug(format_log_entry("(#{deleted}) DELETE #{collection.query.conditions.inspect}", time))
|
70
|
+
modified!
|
71
|
+
deleted
|
72
|
+
end
|
73
|
+
|
74
|
+
def read(query)
|
75
|
+
maybe_wait_for_consistency
|
76
|
+
table = DmAdapterSimpledb::Table.new(query.model)
|
77
|
+
conditions, order, unsupported_conditions =
|
78
|
+
set_conditions_and_sort_order(query, table.simpledb_type)
|
79
|
+
results = get_results(query, conditions, order)
|
80
|
+
records = results.map{|result|
|
81
|
+
DmAdapterSimpledb::Record.from_simpledb_hash(result)
|
82
|
+
}
|
83
|
+
|
84
|
+
proto_resources = records.map{|record|
|
85
|
+
record.to_resource_hash(query.fields)
|
86
|
+
}
|
87
|
+
query.conditions.operands.reject!{ |op|
|
88
|
+
!unsupported_conditions.include?(op)
|
89
|
+
}
|
90
|
+
records = query.filter_records(proto_resources)
|
91
|
+
|
92
|
+
records
|
93
|
+
end
|
94
|
+
|
95
|
+
def update(attributes, collection)
|
96
|
+
updated = 0
|
97
|
+
time = Benchmark.realtime do
|
98
|
+
collection.each do |resource|
|
99
|
+
updated_resource = resource.dup
|
100
|
+
updated_resource.attributes = attributes
|
101
|
+
record = DmAdapterSimpledb::Record.from_resource(updated_resource)
|
102
|
+
attrs_to_update = record.writable_attributes
|
103
|
+
attrs_to_delete = record.deletable_attributes
|
104
|
+
item_name = record.item_name
|
105
|
+
unless attrs_to_update.empty?
|
106
|
+
sdb.put_attributes(domain, item_name, attrs_to_update, :replace)
|
107
|
+
end
|
108
|
+
unless attrs_to_delete.empty?
|
109
|
+
sdb.delete_attributes(domain, item_name, attrs_to_delete)
|
110
|
+
end
|
111
|
+
updated += 1
|
112
|
+
end
|
113
|
+
raise NotImplementedError.new('Only :eql on delete at the moment') if not_eql_query?(collection.query)
|
114
|
+
end
|
115
|
+
DataMapper.logger.debug(format_log_entry("UPDATE #{collection.query.conditions.inspect} (#{updated} times)", time))
|
116
|
+
modified!
|
117
|
+
updated
|
118
|
+
end
|
119
|
+
|
120
|
+
def query(query_call, query_limit = 999999999)
|
121
|
+
select(query_call, query_limit).collect{|x| x.values[0]}
|
122
|
+
end
|
123
|
+
|
124
|
+
def aggregate(query)
|
125
|
+
raise ArgumentError.new("Only count is supported") unless (query.fields.first.operator == :count)
|
126
|
+
table = DmAdapterSimpledb::Table.new(query.model)
|
127
|
+
sdb_type = table.simpledb_type
|
128
|
+
conditions, order, unsupported_conditions = set_conditions_and_sort_order(query, sdb_type)
|
129
|
+
|
130
|
+
query_call = "SELECT count(*) FROM #{domain} "
|
131
|
+
query_call << "WHERE #{conditions.compact.join(' AND ')}" if conditions.length > 0
|
132
|
+
results = nil
|
133
|
+
time = Benchmark.realtime do
|
134
|
+
results = sdb.select(query_call)
|
135
|
+
end; DataMapper.logger.debug(format_log_entry(query_call, time))
|
136
|
+
[results[:items][0].values.first["Count"].first.to_i]
|
137
|
+
end
|
138
|
+
|
139
|
+
# For testing purposes only.
|
140
|
+
def wait_for_consistency
|
141
|
+
return unless @current_consistency_token
|
142
|
+
token = :none
|
143
|
+
begin
|
144
|
+
results = sdb.get_attributes(domain, '__dm_consistency_token', '__dm_consistency_token')
|
145
|
+
tokens = results[:attributes]['__dm_consistency_token']
|
146
|
+
end until tokens.include?(@current_consistency_token)
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
# Returns the domain for the model
|
151
|
+
def domain
|
152
|
+
@sdb_options[:domain]
|
153
|
+
end
|
154
|
+
|
155
|
+
#sets the conditions and order for the SDB query
|
156
|
+
def set_conditions_and_sort_order(query, sdb_type)
|
157
|
+
unsupported_conditions = []
|
158
|
+
conditions = ["simpledb_type = '#{sdb_type}'"]
|
159
|
+
# look for query.order.first and insure in conditions
|
160
|
+
# raise if order if greater than 1
|
161
|
+
|
162
|
+
if query.order && query.order.length > 0
|
163
|
+
query_object = query.order[0]
|
164
|
+
#anything sorted on must be a condition for SDB
|
165
|
+
conditions << "#{query_object.target.name} IS NOT NULL"
|
166
|
+
order = "ORDER BY #{query_object.target.name} #{query_object.operator}"
|
167
|
+
else
|
168
|
+
order = ""
|
169
|
+
end
|
170
|
+
query.conditions.each do |op|
|
171
|
+
case op.slug
|
172
|
+
when :regexp
|
173
|
+
unsupported_conditions << op
|
174
|
+
when :eql
|
175
|
+
conditions << if op.value.nil?
|
176
|
+
"#{op.subject.name} IS NULL"
|
177
|
+
else
|
178
|
+
"#{op.subject.name} = '#{op.value}'"
|
179
|
+
end
|
180
|
+
when :not then
|
181
|
+
comp = op.operands.first
|
182
|
+
if comp.slug == :like
|
183
|
+
conditions << "#{comp.subject.name} not like '#{comp.value}'"
|
184
|
+
next
|
185
|
+
end
|
186
|
+
case comp.value
|
187
|
+
when Range, Set, Array, Regexp
|
188
|
+
unsupported_conditions << op
|
189
|
+
when nil
|
190
|
+
conditions << "#{comp.subject.name} IS NOT NULL"
|
191
|
+
else
|
192
|
+
conditions << "#{comp.subject.name} != '#{comp.value}'"
|
193
|
+
end
|
194
|
+
when :gt then conditions << "#{op.subject.name} > '#{op.value}'"
|
195
|
+
when :gte then conditions << "#{op.subject.name} >= '#{op.value}'"
|
196
|
+
when :lt then conditions << "#{op.subject.name} < '#{op.value}'"
|
197
|
+
when :lte then conditions << "#{op.subject.name} <= '#{op.value}'"
|
198
|
+
when :like then conditions << "#{op.subject.name} like '#{op.value}'"
|
199
|
+
when :in
|
200
|
+
case op.value
|
201
|
+
when Array, Set
|
202
|
+
values = op.value.collect{|v| "'#{v}'"}.join(',')
|
203
|
+
values = "'__NULL__'" if values.empty?
|
204
|
+
conditions << "#{op.subject.name} IN (#{values})"
|
205
|
+
when Range
|
206
|
+
if op.value.exclude_end?
|
207
|
+
unsupported_conditions << op
|
208
|
+
else
|
209
|
+
conditions << "#{op.subject.name} between '#{op.value.first}' and '#{op.value.last}'"
|
210
|
+
end
|
211
|
+
else
|
212
|
+
raise ArgumentError, "Unsupported inclusion op: #{op.value.inspect}"
|
213
|
+
end
|
214
|
+
else raise "Invalid query op: #{op.inspect}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
[conditions,order,unsupported_conditions]
|
218
|
+
end
|
219
|
+
|
220
|
+
def select(query_call, query_limit)
|
221
|
+
items = []
|
222
|
+
time = Benchmark.realtime do
|
223
|
+
sdb_continuation_key = nil
|
224
|
+
while (results = sdb.select(query_call, sdb_continuation_key)) do
|
225
|
+
sdb_continuation_key = results[:next_token]
|
226
|
+
items += results[:items]
|
227
|
+
break if items.length > query_limit
|
228
|
+
break if sdb_continuation_key.nil?
|
229
|
+
end
|
230
|
+
end; DataMapper.logger.debug(format_log_entry(query_call, time))
|
231
|
+
items[0...query_limit]
|
232
|
+
end
|
233
|
+
|
234
|
+
#gets all results or proper number of results depending on the :limit
|
235
|
+
def get_results(query, conditions, order)
|
236
|
+
fields_to_request = query.fields.map{|f| f.field}
|
237
|
+
fields_to_request << DmAdapterSimpledb::Record::METADATA_KEY
|
238
|
+
output_list = fields_to_request.join(', ')
|
239
|
+
query_call = "SELECT #{output_list} FROM #{domain} "
|
240
|
+
query_call << "WHERE #{conditions.compact.join(' AND ')}" if conditions.length > 0
|
241
|
+
query_call << " #{order}"
|
242
|
+
if query.limit!=nil
|
243
|
+
query_limit = query.limit
|
244
|
+
query_call << " LIMIT #{query.limit}"
|
245
|
+
else
|
246
|
+
#on large items force the max limit
|
247
|
+
query_limit = 999999999 #TODO hack for query.limit being nil
|
248
|
+
#query_call << " limit 2500" #this doesn't work with continuation keys as it halts at the limit passed not just a limit per query.
|
249
|
+
end
|
250
|
+
records = select(query_call, query_limit)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Creates an item name for a query
|
254
|
+
def item_name_for_query(query)
|
255
|
+
sdb_type = simpledb_type(query.model)
|
256
|
+
|
257
|
+
item_name = "#{sdb_type}+"
|
258
|
+
keys = keys_for_model(query.model)
|
259
|
+
conditions = query.conditions.sort {|a,b| a[1].name.to_s <=> b[1].name.to_s }
|
260
|
+
item_name += conditions.map do |property|
|
261
|
+
property[2].to_s
|
262
|
+
end.join('-')
|
263
|
+
Digest::SHA1.hexdigest(item_name)
|
264
|
+
end
|
265
|
+
|
266
|
+
def not_eql_query?(query)
|
267
|
+
# Curosity check to make sure we are only dealing with a delete
|
268
|
+
conditions = query.conditions.map {|c| c.slug }.uniq
|
269
|
+
selectors = [ :gt, :gte, :lt, :lte, :not, :like, :in ]
|
270
|
+
return (selectors - conditions).size != selectors.size
|
271
|
+
end
|
272
|
+
|
273
|
+
# Returns an SimpleDB instance to work with
|
274
|
+
def sdb
|
275
|
+
access_key = @sdb_options[:access_key]
|
276
|
+
secret_key = @sdb_options[:secret_key]
|
277
|
+
@sdb ||= RightAws::SdbInterface.new(access_key,secret_key,@sdb_options)
|
278
|
+
@sdb
|
279
|
+
end
|
280
|
+
|
281
|
+
def format_log_entry(query, ms = 0)
|
282
|
+
'SDB (%.1fs) %s' % [ms, query.squeeze(' ')]
|
283
|
+
end
|
284
|
+
|
285
|
+
def update_consistency_token
|
286
|
+
@current_consistency_token = UUIDTools::UUID.timestamp_create.to_s
|
287
|
+
sdb.put_attributes(
|
288
|
+
domain,
|
289
|
+
'__dm_consistency_token',
|
290
|
+
{'__dm_consistency_token' => [@current_consistency_token]})
|
291
|
+
end
|
292
|
+
|
293
|
+
def maybe_wait_for_consistency
|
294
|
+
if consistency_policy == :automatic && @current_consistency_token
|
295
|
+
wait_for_consistency
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# SimpleDB supports "eventual consistency", which mean your data will be
|
300
|
+
# there... eventually. Obviously this can make tests a little flaky. One
|
301
|
+
# option is to just wait a fixed amount of time after every write, but
|
302
|
+
# this can quickly add up to a lot of waiting. The strategy implemented
|
303
|
+
# here is based on the theory that while consistency is only eventual,
|
304
|
+
# chances are writes will at least be linear. That is, once the results of
|
305
|
+
# write #2 show up we can probably assume that the results of write #1 are
|
306
|
+
# in as well.
|
307
|
+
#
|
308
|
+
# When a consistency policy is enabled, the adapter writes a new unique
|
309
|
+
# "consistency token" to the database after every write (i.e. every
|
310
|
+
# create, update, or delete). If the policy is :manual, it only writes the
|
311
|
+
# consistency token. If the policy is :automatic, writes will not return
|
312
|
+
# until the token has been successfully read back.
|
313
|
+
#
|
314
|
+
# When waiting for the consistency token to show up, we use progressively
|
315
|
+
# longer timeouts until finally giving up and raising an exception.
|
316
|
+
def modified!
|
317
|
+
case @consistency_policy
|
318
|
+
when :manual, :automatic then
|
319
|
+
update_consistency_token
|
320
|
+
when false then
|
321
|
+
# do nothing
|
322
|
+
else
|
323
|
+
raise "Invalid :wait_for_consistency option: #{@consistency_policy.inspect}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
end # class SimpleDBAdapter
|
328
|
+
|
329
|
+
|
330
|
+
# Required naming scheme.
|
331
|
+
SimpledbAdapter = SimpleDBAdapter
|
332
|
+
|
333
|
+
const_added(:SimpledbAdapter)
|
334
|
+
|
335
|
+
end # module Adapters
|
336
|
+
|
337
|
+
|
338
|
+
end # module DataMapper
|
339
|
+
|