dm-adapter-simpledb 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/.gitignore +1 -0
  2. data/History.txt +21 -0
  3. data/README +21 -8
  4. data/Rakefile +35 -23
  5. data/VERSION +1 -1
  6. data/dm-adapter-simpledb.gemspec +44 -24
  7. data/lib/dm-adapter-simpledb.rb +17 -0
  8. data/lib/dm-adapter-simpledb/adapters/simpledb_adapter.rb +339 -0
  9. data/lib/dm-adapter-simpledb/chunked_string.rb +54 -0
  10. data/lib/dm-adapter-simpledb/migrations/simpledb_adapter.rb +45 -0
  11. data/lib/dm-adapter-simpledb/rake.rb +43 -0
  12. data/lib/dm-adapter-simpledb/record.rb +318 -0
  13. data/lib/{simpledb_adapter → dm-adapter-simpledb}/sdb_array.rb +0 -0
  14. data/lib/dm-adapter-simpledb/table.rb +40 -0
  15. data/lib/dm-adapter-simpledb/utils.rb +15 -0
  16. data/lib/simpledb_adapter.rb +2 -469
  17. data/scripts/simple_benchmark.rb +1 -1
  18. data/spec/{associations_spec.rb → integration/associations_spec.rb} +0 -0
  19. data/spec/{compliance_spec.rb → integration/compliance_spec.rb} +0 -0
  20. data/spec/{date_spec.rb → integration/date_spec.rb} +0 -0
  21. data/spec/{limit_and_order_spec.rb → integration/limit_and_order_spec.rb} +0 -0
  22. data/spec/{migrations_spec.rb → integration/migrations_spec.rb} +0 -0
  23. data/spec/{multiple_records_spec.rb → integration/multiple_records_spec.rb} +0 -0
  24. data/spec/{nils_spec.rb → integration/nils_spec.rb} +0 -0
  25. data/spec/{sdb_array_spec.rb → integration/sdb_array_spec.rb} +4 -5
  26. data/spec/{simpledb_adapter_spec.rb → integration/simpledb_adapter_spec.rb} +65 -0
  27. data/spec/{spec_helper.rb → integration/spec_helper.rb} +8 -3
  28. data/spec/unit/record_spec.rb +346 -0
  29. data/spec/unit/simpledb_adapter_spec.rb +80 -0
  30. data/spec/unit/unit_spec_helper.rb +26 -0
  31. metadata +58 -24
  32. data/tasks/devver.rake +0 -167
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ log/**
6
6
  pkg/**
7
7
  /THROW_AWAY_SDB_DOMAIN
8
8
  /TODO
9
+ /TAGS
@@ -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, 'simpledb://ACCESS_KEY:SECRET_KEY@sdb.amazon.com/DOMAIN')
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: simpledb
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: 'my_amazon_sdb_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" # manually setting the domain
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 => [ :spec ]
7
+ task :default => [ 'spec:unit' ]
10
8
 
11
- desc 'Run specifications'
12
- Spec::Rake::SpecTask.new(:spec) do |t|
13
- if File.exists?('spec/spec.opts')
14
- t.spec_opts << '--options' << 'spec/spec.opts'
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 without Rcov'
29
- Spec::Rake::SpecTask.new(:spec_no_rcov) do |t|
30
- if File.exists?('spec/spec.opts')
31
- t.spec_opts << '--options' << 'spec/spec.opts'
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
- t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
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.0.0
1
+ 1.1.0
@@ -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.0.0"
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-16}
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/spec_helper.rb",
57
- "tasks/devver.rake"
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
+