dm-adapter-simpledb 1.3.0 → 1.4.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/History.txt +59 -0
- data/README +4 -5
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/lib/dm-adapter-simpledb.rb +2 -1
- data/lib/dm-adapter-simpledb/adapters/simpledb_adapter.rb +154 -189
- data/lib/dm-adapter-simpledb/rake.rb +0 -41
- data/lib/dm-adapter-simpledb/record.rb +1 -1
- data/lib/dm-adapter-simpledb/table.rb +2 -1
- data/lib/dm-adapter-simpledb/utils.rb +44 -0
- data/lib/dm-adapter-simpledb/where_expression.rb +180 -0
- data/scripts/console +23 -0
- data/scripts/limits_benchmark +51 -0
- data/scripts/union_benchmark +39 -0
- data/spec/integration/compliance_spec.rb +1 -0
- data/spec/integration/limit_and_order_spec.rb +1 -1
- data/spec/integration/simpledb_adapter_spec.rb +4 -22
- data/spec/integration/spec_helper.rb +1 -0
- data/spec/unit/dm_adapter_simpledb/where_expression_spec.rb +368 -0
- data/spec/unit/simpledb_adapter_spec.rb +3 -2
- data/spec/unit/unit_spec_helper.rb +1 -0
- metadata +9 -4
data/History.txt
CHANGED
@@ -1,3 +1,60 @@
|
|
1
|
+
== 1.4.0 2010-01-24
|
2
|
+
|
3
|
+
* Major enhancements:
|
4
|
+
* Completely rewritten query generation engine queries supports
|
5
|
+
arbitrarily deep nesting of complex AND/OR/NOT operators. This means that
|
6
|
+
code such as:
|
7
|
+
|
8
|
+
Post.all(:title => "foo") | Post.all(:title => "bar", :body => "baz")
|
9
|
+
|
10
|
+
will generate a SELECT statement similar to the following
|
11
|
+
|
12
|
+
SELECT title from mydomain where title = "foo" OR ( title = "bar" AND body = "baz" )
|
13
|
+
|
14
|
+
* New query engine supports range and IN predicates to the extent possible on
|
15
|
+
SimpleDB, including converting empty inclusion predicates to a form that
|
16
|
+
SimpleDB can understand. Exclusive ranges e.g. (0...5) now raise a
|
17
|
+
NotImplementedError instead of being silently flaky.
|
18
|
+
|
19
|
+
* Support for native condition expressions, e.g.:
|
20
|
+
|
21
|
+
Post.all(:conditions => 'title in "%banannas%"')
|
22
|
+
|
23
|
+
This includes support for variable interpolation:
|
24
|
+
|
25
|
+
Post.all(:conditions => ['title = ?', "foo"])
|
26
|
+
|
27
|
+
or:
|
28
|
+
|
29
|
+
post.all(:conditions => ['title = :title', {:title => "foo"}]
|
30
|
+
|
31
|
+
Interpolated values will be quoted according to SimpleDB value quoting
|
32
|
+
rules.
|
33
|
+
|
34
|
+
* Support for arbitrarily large limits. The concept of a query limit and a
|
35
|
+
batch limit have been completely separated in this release. If the batch
|
36
|
+
limit is set to 100 and a query is limited to 201 items, it will generate
|
37
|
+
three selects: two with "LIMIT 100" and one with "LIMIT 1".
|
38
|
+
|
39
|
+
* Vastly improved logging and benchmarking. For a given high-level operation,
|
40
|
+
such as a DataMapper "read", the adapter can output:
|
41
|
+
|
42
|
+
* Number of individual AWS calls made (e.g. individual SELECTs)
|
43
|
+
* Aggregate AWS box usage
|
44
|
+
* User CPU time
|
45
|
+
* System CPU time
|
46
|
+
* Wallclock time
|
47
|
+
|
48
|
+
* Minor enhancements:
|
49
|
+
* Even better quoting. With the new SELECT translator in place, all domain
|
50
|
+
names, attribute names, and values should be quoted properly according to
|
51
|
+
SimpleDB rules.
|
52
|
+
* No direct dependency on RightAws. All operations are performed via SDBTools
|
53
|
+
now.
|
54
|
+
* New "batch_limit" option to configure the maximum results requested per
|
55
|
+
SELECT call. Amazon sets a cap of 250 on this value.
|
56
|
+
|
57
|
+
|
1
58
|
== 1.3.0 2010-01-19
|
2
59
|
|
3
60
|
* 1 major enhancement:
|
@@ -6,6 +63,8 @@
|
|
6
63
|
|
7
64
|
* 1 minor enhancement:
|
8
65
|
* Better quoting of select statements using the 'sdbtools' library.
|
66
|
+
* "Null mode" can be set with :null => true. Null mode logs DB operations but
|
67
|
+
does not actually connect to AWS.
|
9
68
|
|
10
69
|
== 1.2.0 2010-01-13
|
11
70
|
|
data/README
CHANGED
@@ -7,8 +7,8 @@ A DataMapper adapter for Amazon's SimpleDB service.
|
|
7
7
|
Features:
|
8
8
|
* Uses the RightAWS gem for efficient SimpleDB operations.
|
9
9
|
* Full set of CRUD operations
|
10
|
-
* Supports all DataMapper query predicates.
|
11
|
-
*
|
10
|
+
* Supports nearly all DataMapper query predicates.
|
11
|
+
* Full support for complex nested union, intersaction, and negation in queries
|
12
12
|
* Migrations
|
13
13
|
* DataMapper identity map support for record caching
|
14
14
|
* Lazy-loaded attributes
|
@@ -17,6 +17,7 @@ Features:
|
|
17
17
|
* Basic aggregation support (Model.count("..."))
|
18
18
|
* String "chunking" permits attributes to exceed the 1024-byte limit
|
19
19
|
* Support for efficient :limit and :offset, for result set paging
|
20
|
+
* Robust quoting of names in values in selects
|
20
21
|
|
21
22
|
Note: as of version 1.0.0, this gem supports supports the DataMapper 0.10.*
|
22
23
|
series and breaks backwards compatibility with DataMapper 0.9.*.
|
@@ -39,9 +40,6 @@ http://github.com/devver/dm-adapter-simpledb/
|
|
39
40
|
|
40
41
|
== TODO
|
41
42
|
|
42
|
-
* More complete handling of NOT conditions in queries
|
43
|
-
* Support for ORs, parens, and nested queries in general
|
44
|
-
* Robust quoting in SELECT calls
|
45
43
|
* Handle exclusive ranges natively
|
46
44
|
Implement as inclusive range + filter step
|
47
45
|
* Tests for associations
|
@@ -58,6 +56,7 @@ http://github.com/devver/dm-adapter-simpledb/
|
|
58
56
|
* Silence SSL warnings
|
59
57
|
See http://pivotallabs.com/users/carl/blog/articles/1079-standup-blog-11-24-2009-model-validations-without-backing-store-associations-to-array-and-ssl-with-aws
|
60
58
|
* Token cache for reduced requests when given an offset
|
59
|
+
* Optimize key queries
|
61
60
|
|
62
61
|
== Usage
|
63
62
|
|
data/Rakefile
CHANGED
@@ -54,7 +54,6 @@ begin
|
|
54
54
|
A DataMapper adapter for Amazon's SimpleDB service.
|
55
55
|
|
56
56
|
Features:
|
57
|
-
* Uses the RightAWS gem for efficient SimpleDB operations.
|
58
57
|
* Full set of CRUD operations
|
59
58
|
* Supports all DataMapper query predicates.
|
60
59
|
* Can translate many queries into efficient native SELECT operations.
|
@@ -81,7 +80,7 @@ END
|
|
81
80
|
gem.add_dependency('dm-migrations', '~> 0.10.0')
|
82
81
|
gem.add_dependency('dm-types', '~> 0.10.0')
|
83
82
|
gem.add_dependency('uuidtools', '~> 2.0')
|
84
|
-
gem.add_dependency('sdbtools', '~> 0.
|
83
|
+
gem.add_dependency('sdbtools', '~> 0.4')
|
85
84
|
end
|
86
85
|
Jeweler::GemcutterTasks.new
|
87
86
|
rescue LoadError
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.4.0
|
data/lib/dm-adapter-simpledb.rb
CHANGED
@@ -6,13 +6,14 @@ gem 'dm-core', '~> 0.10.0'
|
|
6
6
|
require 'dm-core'
|
7
7
|
require 'dm-aggregates'
|
8
8
|
require 'digest/sha1'
|
9
|
-
require 'right_aws'
|
10
9
|
require 'uuidtools'
|
11
10
|
require 'sdbtools'
|
12
11
|
|
12
|
+
$:.unshift(File.dirname(__FILE__))
|
13
13
|
require 'dm-adapter-simpledb/sdb_array'
|
14
14
|
require 'dm-adapter-simpledb/utils'
|
15
15
|
require 'dm-adapter-simpledb/record'
|
16
16
|
require 'dm-adapter-simpledb/table'
|
17
|
+
require 'dm-adapter-simpledb/where_expression'
|
17
18
|
require 'dm-adapter-simpledb/migrations/simpledb_adapter'
|
18
19
|
require 'dm-adapter-simpledb/adapters/simpledb_adapter'
|
@@ -3,7 +3,9 @@ module DataMapper
|
|
3
3
|
class SimpleDBAdapter < AbstractAdapter
|
4
4
|
include DmAdapterSimpledb::Utils
|
5
5
|
|
6
|
-
attr_reader
|
6
|
+
attr_reader :sdb_options
|
7
|
+
attr_reader :batch_limit
|
8
|
+
attr_accessor :logger
|
7
9
|
|
8
10
|
# For testing purposes ONLY. Seriously, don't enable this for production
|
9
11
|
# code.
|
@@ -18,7 +20,8 @@ module DataMapper
|
|
18
20
|
@sdb_options[:secret_key] = options.fetch(:secret_key) {
|
19
21
|
options[:password]
|
20
22
|
}
|
21
|
-
@
|
23
|
+
@logger = options.fetch(:logger) { DataMapper.logger }
|
24
|
+
@sdb_options[:logger] = @logger
|
22
25
|
@sdb_options[:server] = options.fetch(:host) { 'sdb.amazonaws.com' }
|
23
26
|
@sdb_options[:port] = options[:port] || 443 # port may be set but nil
|
24
27
|
@sdb_options[:domain] = options.fetch(:domain) {
|
@@ -33,18 +36,27 @@ module DataMapper
|
|
33
36
|
# RightAWS's nil-token replacement altogether, but that does not appear
|
34
37
|
# to be an option.
|
35
38
|
@sdb_options[:nil_representation] = "<[<[<NIL>]>]>"
|
39
|
+
@null_mode = options.fetch(:null) { false }
|
40
|
+
@batch_limit = options.fetch(:batch_limit) {
|
41
|
+
SDBTools::Selection::DEFAULT_RESULT_LIMIT
|
42
|
+
}.to_i
|
43
|
+
|
44
|
+
if @null_mode
|
45
|
+
logger.info "SimpleDB adapter for domain #{domain_name} is in null mode"
|
46
|
+
end
|
47
|
+
|
36
48
|
@consistency_policy =
|
37
49
|
normalised_options.fetch(:wait_for_consistency) { false }
|
38
50
|
@sdb = options.fetch(:sdb_interface) { nil }
|
39
|
-
if @sdb_options[:create_domain] && !domains.include?(
|
40
|
-
@sdb_options[:logger].info "Creating domain #{
|
41
|
-
|
51
|
+
if @sdb_options[:create_domain] && !domains.include?(domain_name)
|
52
|
+
@sdb_options[:logger].info "Creating domain #{domain_name}"
|
53
|
+
database.create_domain(domain_name)
|
42
54
|
end
|
43
55
|
end
|
44
56
|
|
45
57
|
def create(resources)
|
46
58
|
created = 0
|
47
|
-
|
59
|
+
transaction("CREATE #{resources.size} objects") do
|
48
60
|
resources.each do |resource|
|
49
61
|
uuid = UUIDTools::UUID.timestamp_create
|
50
62
|
initialize_serial(resource, uuid.to_i)
|
@@ -52,63 +64,63 @@ module DataMapper
|
|
52
64
|
record = DmAdapterSimpledb::Record.from_resource(resource)
|
53
65
|
attributes = record.writable_attributes
|
54
66
|
item_name = record.item_name
|
55
|
-
|
67
|
+
domain.put(item_name, attributes, :replace => true)
|
56
68
|
created += 1
|
57
69
|
end
|
58
70
|
end
|
59
|
-
DataMapper.logger.debug(format_log_entry("(#{created}) INSERT #{resources.inspect}", time))
|
60
71
|
modified!
|
61
72
|
created
|
62
73
|
end
|
63
74
|
|
64
75
|
def delete(collection)
|
65
76
|
deleted = 0
|
66
|
-
|
77
|
+
transaction("DELETE #{collection.query.conditions}") do
|
67
78
|
collection.each do |resource|
|
68
79
|
record = DmAdapterSimpledb::Record.from_resource(resource)
|
69
80
|
item_name = record.item_name
|
70
|
-
|
81
|
+
domain.delete(item_name)
|
71
82
|
deleted += 1
|
72
83
|
end
|
84
|
+
|
85
|
+
# TODO no reason we can't select a bunch of item names with an
|
86
|
+
# arbitrary query and then delete them.
|
73
87
|
raise NotImplementedError.new('Only :eql on delete at the moment') if not_eql_query?(collection.query)
|
74
|
-
end
|
88
|
+
end
|
75
89
|
modified!
|
76
90
|
deleted
|
77
91
|
end
|
78
92
|
|
79
93
|
def read(query)
|
80
94
|
maybe_wait_for_consistency
|
81
|
-
|
82
|
-
|
83
|
-
set_conditions_and_sort_order(query, table.simpledb_type)
|
84
|
-
results = get_results(query, conditions, order)
|
85
|
-
records = results.map{|result|
|
86
|
-
DmAdapterSimpledb::Record.from_simpledb_hash(result)
|
87
|
-
}
|
95
|
+
transaction("READ #{query.model.name} #{query.conditions}") do |t|
|
96
|
+
query = query.dup
|
88
97
|
|
89
|
-
|
90
|
-
record.to_resource_hash(query.fields)
|
91
|
-
}
|
92
|
-
query.conditions.operands.reject!{ |op|
|
93
|
-
!unsupported_conditions.include?(op)
|
94
|
-
}
|
98
|
+
selection = selection_from_query(query)
|
95
99
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
# everything filter_records() does EXCEPT limiting.
|
100
|
-
records = proto_resources
|
101
|
-
records = records.uniq if query.unique?
|
102
|
-
records = query.match_records(records)
|
103
|
-
records = query.sort_records(records)
|
100
|
+
records = selection.map{|name, attributes|
|
101
|
+
DmAdapterSimpledb::Record.from_simpledb_hash(name => attributes)
|
102
|
+
}
|
104
103
|
|
104
|
+
proto_resources = records.map{|record|
|
105
|
+
record.to_resource_hash(query.fields)
|
106
|
+
}
|
107
|
+
|
108
|
+
# This used to be a simple call to Query#filter_records(), but that
|
109
|
+
# caused the result limit to be re-imposed on an already limited result
|
110
|
+
# set, with the upshot that too few records were returned. So here we do
|
111
|
+
# everything filter_records() does EXCEPT limiting.
|
112
|
+
records = proto_resources
|
113
|
+
records = records.uniq if query.unique?
|
114
|
+
records = query.match_records(records)
|
115
|
+
records = query.sort_records(records)
|
105
116
|
|
106
|
-
|
117
|
+
records
|
118
|
+
end
|
107
119
|
end
|
108
120
|
|
109
121
|
def update(attributes, collection)
|
110
122
|
updated = 0
|
111
|
-
|
123
|
+
transaction("UPDATE #{collection.query} with #{attributes.inspect}") do
|
112
124
|
collection.each do |resource|
|
113
125
|
updated_resource = resource.dup
|
114
126
|
updated_resource.attributes = attributes
|
@@ -117,40 +129,24 @@ module DataMapper
|
|
117
129
|
attrs_to_delete = record.deletable_attributes
|
118
130
|
item_name = record.item_name
|
119
131
|
unless attrs_to_update.empty?
|
120
|
-
|
132
|
+
domain.put(item_name, attrs_to_update, :replace => true)
|
121
133
|
end
|
122
134
|
unless attrs_to_delete.empty?
|
123
|
-
|
135
|
+
domain.delete(item_name, attrs_to_delete)
|
124
136
|
end
|
125
137
|
updated += 1
|
126
138
|
end
|
127
139
|
raise NotImplementedError.new('Only :eql on delete at the moment') if not_eql_query?(collection.query)
|
128
140
|
end
|
129
|
-
DataMapper.logger.debug(format_log_entry("UPDATE #{collection.query.conditions.inspect} (#{updated} times)", time))
|
130
141
|
modified!
|
131
142
|
updated
|
132
143
|
end
|
133
144
|
|
134
|
-
def query(query_call, query_limit = 999999999)
|
135
|
-
SDBTools::Operation.new(sdb, :select, query_call).inject([]){
|
136
|
-
|a, results|
|
137
|
-
a.concat(results[:items].map{|i| i.values.first})
|
138
|
-
}[0...query_limit]
|
139
|
-
end
|
140
|
-
|
141
145
|
def aggregate(query)
|
142
|
-
raise
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
query_call = "SELECT count(*) FROM #{domain} "
|
148
|
-
query_call << "WHERE #{conditions.compact.join(' AND ')}" if conditions.length > 0
|
149
|
-
results = nil
|
150
|
-
time = Benchmark.realtime do
|
151
|
-
results = sdb.select(query_call)
|
152
|
-
end; DataMapper.logger.debug(format_log_entry(query_call, time))
|
153
|
-
[results[:items][0].values.first["Count"].first.to_i]
|
146
|
+
raise NotImplementedError, "Only count is supported" unless (query.fields.first.operator == :count)
|
147
|
+
transaction("AGGREGATE") do |t|
|
148
|
+
[selection_from_query(query).count]
|
149
|
+
end
|
154
150
|
end
|
155
151
|
|
156
152
|
# For testing purposes only.
|
@@ -158,142 +154,25 @@ module DataMapper
|
|
158
154
|
return unless @current_consistency_token
|
159
155
|
token = :none
|
160
156
|
begin
|
161
|
-
results =
|
157
|
+
results = domain.get('__dm_consistency_token', '__dm_consistency_token')
|
162
158
|
tokens = Array(results[:attributes]['__dm_consistency_token'])
|
163
159
|
end until tokens.include?(@current_consistency_token)
|
164
160
|
end
|
165
161
|
|
166
162
|
def domains
|
167
|
-
|
168
|
-
token = nil
|
169
|
-
begin
|
170
|
-
response = sdb.list_domains(nil, token)
|
171
|
-
result.concat(response[:domains])
|
172
|
-
token = response[:next_token]
|
173
|
-
end while(token)
|
174
|
-
result
|
163
|
+
database.domains
|
175
164
|
end
|
176
165
|
|
177
166
|
private
|
178
|
-
# Returns the domain for the model
|
179
167
|
def domain
|
180
|
-
@sdb_options[:domain]
|
168
|
+
@domain ||= database.domain(@sdb_options[:domain])
|
181
169
|
end
|
182
170
|
|
183
|
-
#
|
184
|
-
def
|
185
|
-
|
186
|
-
conditions = ["simpledb_type = '#{sdb_type}'"]
|
187
|
-
# look for query.order.first and insure in conditions
|
188
|
-
# raise if order if greater than 1
|
189
|
-
|
190
|
-
if query.order && query.order.length > 0
|
191
|
-
query_object = query.order[0]
|
192
|
-
#anything sorted on must be a condition for SDB
|
193
|
-
conditions << "#{query_object.target.name} IS NOT NULL"
|
194
|
-
order = "ORDER BY #{query_object.target.name} #{query_object.operator}"
|
195
|
-
else
|
196
|
-
order = ""
|
197
|
-
end
|
198
|
-
query.conditions.each do |op|
|
199
|
-
case op.slug
|
200
|
-
when :regexp
|
201
|
-
unsupported_conditions << op
|
202
|
-
when :eql
|
203
|
-
conditions << if op.value.nil?
|
204
|
-
"#{op.subject.name} IS NULL"
|
205
|
-
else
|
206
|
-
"#{op.subject.name} = '#{op.value}'"
|
207
|
-
end
|
208
|
-
when :not then
|
209
|
-
comp = op.operands.first
|
210
|
-
if comp.slug == :like
|
211
|
-
conditions << "#{comp.subject.name} not like '#{comp.value}'"
|
212
|
-
next
|
213
|
-
end
|
214
|
-
case comp.value
|
215
|
-
when Range, Set, Array, Regexp
|
216
|
-
unsupported_conditions << op
|
217
|
-
when nil
|
218
|
-
conditions << "#{comp.subject.name} IS NOT NULL"
|
219
|
-
else
|
220
|
-
conditions << "#{comp.subject.name} != '#{comp.value}'"
|
221
|
-
end
|
222
|
-
when :gt then conditions << "#{op.subject.name} > '#{op.value}'"
|
223
|
-
when :gte then conditions << "#{op.subject.name} >= '#{op.value}'"
|
224
|
-
when :lt then conditions << "#{op.subject.name} < '#{op.value}'"
|
225
|
-
when :lte then conditions << "#{op.subject.name} <= '#{op.value}'"
|
226
|
-
when :like then conditions << "#{op.subject.name} like '#{op.value}'"
|
227
|
-
when :in
|
228
|
-
case op.value
|
229
|
-
when Array, Set
|
230
|
-
values = op.value.collect{|v| "'#{v}'"}.join(',')
|
231
|
-
values = "'__NULL__'" if values.empty?
|
232
|
-
conditions << "#{op.subject.name} IN (#{values})"
|
233
|
-
when Range
|
234
|
-
if op.value.exclude_end?
|
235
|
-
unsupported_conditions << op
|
236
|
-
else
|
237
|
-
conditions << "#{op.subject.name} between '#{op.value.first}' and '#{op.value.last}'"
|
238
|
-
end
|
239
|
-
else
|
240
|
-
raise ArgumentError, "Unsupported inclusion op: #{op.value.inspect}"
|
241
|
-
end
|
242
|
-
when :or
|
243
|
-
# TODO There's no reason not to support OR
|
244
|
-
unsupported_conditions << op
|
245
|
-
else raise "Invalid query op: #{op.inspect}"
|
246
|
-
end
|
247
|
-
end
|
248
|
-
[conditions,order,unsupported_conditions]
|
171
|
+
# Returns the domain for the model
|
172
|
+
def domain_name
|
173
|
+
@sdb_options[:domain]
|
249
174
|
end
|
250
|
-
|
251
|
-
#gets all results or proper number of results depending on the :limit
|
252
|
-
def get_results(query, conditions, order)
|
253
|
-
fields_to_request = query.fields.map{|f| f.field}
|
254
|
-
fields_to_request << DmAdapterSimpledb::Record::METADATA_KEY
|
255
|
-
|
256
|
-
selection = SDBTools::Selection.new(
|
257
|
-
sdb,
|
258
|
-
domain,
|
259
|
-
:attributes => fields_to_request)
|
260
|
-
|
261
|
-
if query.order && query.order.length > 0
|
262
|
-
query_object = query.order[0]
|
263
|
-
#anything sorted on must be a condition for SDB
|
264
|
-
conditions << "#{query_object.target.name} IS NOT NULL"
|
265
|
-
selection.order_by = query_object.target.name
|
266
|
-
selection.order = case query_object.operator
|
267
|
-
when :asc then :ascending
|
268
|
-
when :desc then :descending
|
269
|
-
else raise "Unrecognized sort direction"
|
270
|
-
end
|
271
|
-
end
|
272
|
-
selection.conditions = conditions.compact.inject([]){|conds, cond|
|
273
|
-
conds << "AND" unless conds.empty?
|
274
|
-
conds << cond
|
275
|
-
}
|
276
|
-
if query.limit.nil?
|
277
|
-
selection.limit = :none
|
278
|
-
else
|
279
|
-
selection.limit = query.limit
|
280
|
-
end
|
281
|
-
unless query.offset.nil?
|
282
|
-
selection.offset = query.offset
|
283
|
-
end
|
284
|
-
|
285
|
-
items = []
|
286
|
-
time = Benchmark.realtime do
|
287
|
-
# TODO update Record to be created from name/attributes pair
|
288
|
-
selection.each do |name, value|
|
289
|
-
items << {name => value}
|
290
|
-
end
|
291
|
-
end
|
292
|
-
DataMapper.logger.debug(format_log_entry(selection.to_s, time))
|
293
175
|
|
294
|
-
items
|
295
|
-
end
|
296
|
-
|
297
176
|
# Creates an item name for a query
|
298
177
|
def item_name_for_query(query)
|
299
178
|
sdb_type = simpledb_type(query.model)
|
@@ -313,23 +192,23 @@ module DataMapper
|
|
313
192
|
selectors = [ :gt, :gte, :lt, :lte, :not, :like, :in ]
|
314
193
|
return (selectors - conditions).size != selectors.size
|
315
194
|
end
|
195
|
+
|
196
|
+
def database
|
197
|
+
options = sdb ? {:sdb_interface => sdb} : {}
|
198
|
+
@database ||= SDBTools::Database.new(
|
199
|
+
@sdb_options[:access_key],
|
200
|
+
@sdb_options[:secret_key],
|
201
|
+
options)
|
202
|
+
end
|
316
203
|
|
317
204
|
# Returns an SimpleDB instance to work with
|
318
205
|
def sdb
|
319
|
-
|
320
|
-
secret_key = @sdb_options[:secret_key]
|
321
|
-
@sdb ||= RightAws::SdbInterface.new(access_key,secret_key,@sdb_options)
|
322
|
-
@sdb
|
206
|
+
@sdb ||= (@null_mode ? NullSdbInterface.new(logger) : nil)
|
323
207
|
end
|
324
208
|
|
325
|
-
def format_log_entry(query, ms = 0)
|
326
|
-
'SDB (%.1fs) %s' % [ms, query.squeeze(' ')]
|
327
|
-
end
|
328
|
-
|
329
209
|
def update_consistency_token
|
330
210
|
@current_consistency_token = UUIDTools::UUID.timestamp_create.to_s
|
331
|
-
|
332
|
-
domain,
|
211
|
+
domain.put(
|
333
212
|
'__dm_consistency_token',
|
334
213
|
{'__dm_consistency_token' => [@current_consistency_token]})
|
335
214
|
end
|
@@ -368,6 +247,92 @@ module DataMapper
|
|
368
247
|
end
|
369
248
|
end
|
370
249
|
|
250
|
+
# WARNING This method updates +query+ as a side-effect
|
251
|
+
def selection_from_query(query)
|
252
|
+
query.update(extra_conditions(query))
|
253
|
+
where_expression =
|
254
|
+
DmAdapterSimpledb::WhereExpression.new(query.conditions, :logger => logger)
|
255
|
+
selection_options = {
|
256
|
+
:attributes => fields_to_request(query),
|
257
|
+
:conditions => where_expression,
|
258
|
+
:batch_limit => batch_limit,
|
259
|
+
:limit => query_limit(query),
|
260
|
+
:logger => logger
|
261
|
+
}
|
262
|
+
selection_options.merge!(sort_instructions(query))
|
263
|
+
selection = domain.selection(selection_options)
|
264
|
+
selection.offset = query.offset unless query.offset.nil?
|
265
|
+
query.clear
|
266
|
+
query.update(:conditions => where_expression.unsupported_conditions)
|
267
|
+
selection
|
268
|
+
end
|
269
|
+
|
270
|
+
def transaction(description, &block)
|
271
|
+
on_close = SDBTools::Transaction.log_transaction_close(logger)
|
272
|
+
SDBTools::Transaction.open(description, on_close, &block)
|
273
|
+
end
|
274
|
+
|
275
|
+
def fields_to_request(query)
|
276
|
+
fields = []
|
277
|
+
fields.concat(query.fields.map{|f|
|
278
|
+
f.field if f.respond_to?(:field)
|
279
|
+
}.compact)
|
280
|
+
fields.concat(DmAdapterSimpledb::Record::META_KEYS)
|
281
|
+
fields.uniq!
|
282
|
+
fields
|
283
|
+
end
|
284
|
+
|
285
|
+
def sort_instructions(query)
|
286
|
+
direction = first_order_direction(query)
|
287
|
+
if direction
|
288
|
+
order_by = direction.target.field
|
289
|
+
order = case direction.operator
|
290
|
+
when :asc then :ascending
|
291
|
+
when :desc then :descending
|
292
|
+
else raise "Unrecognized sort direction"
|
293
|
+
end
|
294
|
+
{:order_by => order_by, :order => order}
|
295
|
+
else
|
296
|
+
{}
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def extra_conditions(query)
|
301
|
+
# SimpleDB requires all sort-by attributes to also be included in a
|
302
|
+
# predicate.
|
303
|
+
conditions = if (direction = first_order_direction(query))
|
304
|
+
{ direction.target.field.to_sym.not => nil }
|
305
|
+
else
|
306
|
+
{}
|
307
|
+
end
|
308
|
+
table = DmAdapterSimpledb::Table.new(query.model)
|
309
|
+
meta_key = DmAdapterSimpledb::Record::METADATA_KEY
|
310
|
+
|
311
|
+
# The simpledb_type key is deprecated
|
312
|
+
old_table_key = DmAdapterSimpledb::Record::STORAGE_NAME_KEY
|
313
|
+
|
314
|
+
quoted_table_key = SDBTools::Selection.quote_name(old_table_key)
|
315
|
+
quoted_key = SDBTools::Selection.quote_name(meta_key)
|
316
|
+
conditions.merge!(
|
317
|
+
:conditions => [
|
318
|
+
"( #{quoted_key} = ? OR #{quoted_table_key} = ? )",
|
319
|
+
table.token,
|
320
|
+
table.simpledb_type
|
321
|
+
])
|
322
|
+
conditions
|
323
|
+
end
|
324
|
+
|
325
|
+
def query_limit(query)
|
326
|
+
query.limit.nil? ? :none : query.limit
|
327
|
+
end
|
328
|
+
|
329
|
+
# SimpleDB only supports a single sort-by field. Further sorting has to be
|
330
|
+
# handled locally.
|
331
|
+
def first_order_direction(query)
|
332
|
+
Array(query.order).first
|
333
|
+
end
|
334
|
+
|
335
|
+
|
371
336
|
end # class SimpleDBAdapter
|
372
337
|
|
373
338
|
|