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