dm-adapter-simpledb 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|