aws 1.10.1

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.
@@ -0,0 +1,701 @@
1
+ #
2
+ # Copyright (c) 2008 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ require "right_aws"
25
+
26
+ module RightAws
27
+
28
+ class SdbInterface < RightAwsBase
29
+
30
+ include RightAwsBaseInterface
31
+
32
+ DEFAULT_HOST = 'sdb.amazonaws.com'
33
+ DEFAULT_PORT = 443
34
+ DEFAULT_PROTOCOL = 'https'
35
+ API_VERSION = '2007-11-07'
36
+ DEFAULT_NIL_REPRESENTATION = 'nil'
37
+
38
+ @@bench = AwsBenchmarkingBlock.new
39
+ def self.bench_xml; @@bench.xml; end
40
+ def self.bench_sdb; @@bench.service; end
41
+
42
+ attr_reader :last_query_expression
43
+
44
+ # Creates new RightSdb instance.
45
+ #
46
+ # Params:
47
+ # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
48
+ # :port => 443 # Amazon service port: 80 or 443(default)
49
+ # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
50
+ # :signature_version => '0' # The signature version : '0' or '1'(default)
51
+ # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
52
+ # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
53
+ # :nil_representation => 'mynil'} # interpret Ruby nil as this string value; i.e. use this string in SDB to represent Ruby nils (default is the string 'nil')
54
+ #
55
+ # Example:
56
+ #
57
+ # sdb = RightAws::SdbInterface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX', {:multi_thread => true, :logger => Logger.new('/tmp/x.log')}) #=> #<RightSdb:0xa6b8c27c>
58
+ #
59
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/
60
+ #
61
+ def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
62
+ @nil_rep = params[:nil_representation] ? params[:nil_representation] : DEFAULT_NIL_REPRESENTATION
63
+ params.delete(:nil_representation)
64
+ init({ :name => 'SDB',
65
+ :default_host => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).host : DEFAULT_HOST,
66
+ :default_port => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).port : DEFAULT_PORT,
67
+ :default_protocol => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).scheme : DEFAULT_PROTOCOL },
68
+ aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'],
69
+ aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'],
70
+ params)
71
+ end
72
+
73
+ #-----------------------------------------------------------------
74
+ # Requests
75
+ #-----------------------------------------------------------------
76
+ def generate_request(action, params={}) #:nodoc:
77
+ # remove empty params from request
78
+ params.delete_if {|key,value| value.nil? }
79
+ #params_string = params.to_a.collect{|key,val| key + "=#{CGI::escape(val.to_s)}" }.join("&")
80
+ # prepare service data
81
+ service = '/'
82
+ service_hash = {"Action" => action,
83
+ "AWSAccessKeyId" => @aws_access_key_id,
84
+ "Version" => API_VERSION }
85
+ service_hash.update(params)
86
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, :get, @params[:server], service)
87
+ #
88
+ # use POST method if the length of the query string is too large
89
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/MakingRESTRequests.html
90
+ if service_params.size > 2000
91
+ if signature_version == '2'
92
+ # resign the request because HTTP verb is included into signature
93
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, :post, @params[:server], service)
94
+ end
95
+ request = Net::HTTP::Post.new(service)
96
+ request.body = service_params
97
+ request['Content-Type'] = 'application/x-www-form-urlencoded'
98
+ else
99
+ request = Net::HTTP::Get.new("#{service}?#{service_params}")
100
+ end
101
+ # prepare output hash
102
+ { :request => request,
103
+ :server => @params[:server],
104
+ :port => @params[:port],
105
+ :protocol => @params[:protocol] }
106
+ end
107
+
108
+ # Sends request to Amazon and parses the response
109
+ # Raises AwsError if any banana happened
110
+ def request_info(request, parser) #:nodoc:
111
+ # thread = @params[:multi_thread] ? Thread.current : Thread.main
112
+ # thread[:sdb_connection] ||= Rightscale::HttpConnection.new(:exception => AwsError, :logger => @logger)
113
+ http_conn = Rightscale::HttpConnection.new(:exception => AwsError, :logger => @logger)
114
+ ret = request_info_impl(http_conn, @@bench, request, parser)
115
+ http_conn.finish
116
+ ret
117
+ end
118
+
119
+ # Prepare attributes for putting.
120
+ # (used by put_attributes)
121
+ def pack_attributes(attributes, replace = false) #:nodoc:
122
+ result = {}
123
+ if attributes
124
+ idx = 0
125
+ skip_values = attributes.is_a?(Array)
126
+ attributes.each do |attribute, values|
127
+ # set replacement attribute
128
+ result["Attribute.#{idx}.Replace"] = 'true' if replace
129
+ # pack Name/Value
130
+ unless values.nil?
131
+ Array(values).each do |value|
132
+ result["Attribute.#{idx}.Name"] = attribute
133
+ result["Attribute.#{idx}.Value"] = ruby_to_sdb(value) unless skip_values
134
+ idx += 1
135
+ end
136
+ else
137
+ result["Attribute.#{idx}.Name"] = attribute
138
+ result["Attribute.#{idx}.Value"] = ruby_to_sdb(nil) unless skip_values
139
+ idx += 1
140
+ end
141
+ end
142
+ end
143
+ result
144
+ end
145
+
146
+ # Use this helper to manually escape the fields in the query expressions.
147
+ # To escape the single quotes and backslashes and to wrap the string into the single quotes.
148
+ #
149
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API.html
150
+ #
151
+ def escape(value)
152
+ %Q{'#{value.to_s.gsub(/(['\\])/){ "\\#{$1}" }}'} if value
153
+ end
154
+
155
+ # Convert a Ruby language value to a SDB value by replacing Ruby nil with the user's chosen string representation of nil.
156
+ # Non-nil values are unaffected by this filter.
157
+ def ruby_to_sdb(value)
158
+ value.nil? ? @nil_rep : value
159
+ end
160
+
161
+ # Convert a SDB value to a Ruby language value by replacing the user's chosen string representation of nil with Ruby nil.
162
+ # Values are unaffected by this filter unless they match the nil representation exactly.
163
+ def sdb_to_ruby(value)
164
+ value.eql?(@nil_rep) ? nil : value
165
+ end
166
+
167
+ # Convert select and query_with_attributes responses to a Ruby language values by replacing the user's chosen string representation of nil with Ruby nil.
168
+ # (This method affects on a passed response value)
169
+ def select_response_to_ruby(response) #:nodoc:
170
+ response[:items].each_with_index do |item, idx|
171
+ item.each do |key, attributes|
172
+ attributes.each do |name, values|
173
+ values.collect! { |value| sdb_to_ruby(value) }
174
+ end
175
+ end
176
+ end
177
+ response
178
+ end
179
+
180
+ # Create query expression from an array.
181
+ # (similar to ActiveRecord::Base#find using :conditions => ['query', param1, .., paramN])
182
+ #
183
+ def query_expression_from_array(params) #:nodoc:
184
+ return '' if params.blank?
185
+ query = params.shift.to_s
186
+ query.gsub(/(\\)?(\?)/) do
187
+ if $1 # if escaped '\?' is found - replace it by '?' without backslash
188
+ "?"
189
+ else # well, if no backslash precedes '?' then replace it by next param from the list
190
+ escape(params.shift)
191
+ end
192
+ end
193
+ end
194
+
195
+ def query_expression_from_hash(hash)
196
+ return '' if hash.blank?
197
+ expression = []
198
+ hash.each do |key, value|
199
+ expression << "#{key}=#{escape(value)}"
200
+ end
201
+ expression.join(' AND ')
202
+ end
203
+
204
+ # Retrieve a list of SDB domains from Amazon.
205
+ #
206
+ # Returns a hash:
207
+ # { :domains => [domain1, ..., domainN],
208
+ # :next_token => string || nil,
209
+ # :box_usage => string,
210
+ # :request_id => string }
211
+ #
212
+ # Example:
213
+ #
214
+ # sdb = RightAws::SdbInterface.new
215
+ # sdb.list_domains #=> { :box_usage => "0.0000071759",
216
+ # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982",
217
+ # :domains => ["toys", "dolls"]}
218
+ #
219
+ # If a block is given, this method yields to it. If the block returns true, list_domains will continue looping the request. If the block returns false,
220
+ # list_domains will end.
221
+ #
222
+ # sdb.list_domains(10) do |result| # list by 10 domains per iteration
223
+ # puts result.inspect
224
+ # true
225
+ # end
226
+ #
227
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_ListDomains.html
228
+ #
229
+ def list_domains(max_number_of_domains = nil, next_token = nil )
230
+ request_params = { 'MaxNumberOfDomains' => max_number_of_domains,
231
+ 'NextToken' => next_token }
232
+ link = generate_request("ListDomains", request_params)
233
+ result = request_info(link, QSdbListDomainParser.new)
234
+ # return result if no block given
235
+ return result unless block_given?
236
+ # loop if block if given
237
+ begin
238
+ # the block must return true if it wanna continue
239
+ break unless yield(result) && result[:next_token]
240
+ # make new request
241
+ request_params['NextToken'] = result[:next_token]
242
+ link = generate_request("ListDomains", request_params)
243
+ result = request_info(link, QSdbListDomainParser.new)
244
+ end while true
245
+ rescue Exception
246
+ on_exception
247
+ end
248
+
249
+ # Create new SDB domain at Amazon.
250
+ #
251
+ # Returns a hash: { :box_usage, :request_id } on success or an exception on error.
252
+ # (Amazon raises no errors if the domain already exists).
253
+ #
254
+ # Example:
255
+ #
256
+ # sdb = RightAws::SdbInterface.new
257
+ # sdb.create_domain('toys') # => { :box_usage => "0.0000071759",
258
+ # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982" }
259
+ #
260
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_CreateDomain.html
261
+ def create_domain(domain_name)
262
+ link = generate_request("CreateDomain",
263
+ 'DomainName' => domain_name)
264
+ request_info(link, QSdbSimpleParser.new)
265
+ rescue Exception
266
+ on_exception
267
+ end
268
+
269
+ # Delete SDB domain at Amazon.
270
+ #
271
+ # Returns a hash: { :box_usage, :request_id } on success or an exception on error.
272
+ # (Amazon raises no errors if the domain does not exist).
273
+ #
274
+ # Example:
275
+ #
276
+ # sdb = RightAws::SdbInterface.new
277
+ # sdb.delete_domain('toys') # => { :box_usage => "0.0000071759",
278
+ # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982" }
279
+ #
280
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_DeleteDomain.html
281
+ #
282
+ def delete_domain(domain_name)
283
+ link = generate_request("DeleteDomain",
284
+ 'DomainName' => domain_name)
285
+ request_info(link, QSdbSimpleParser.new)
286
+ rescue Exception
287
+ on_exception
288
+ end
289
+
290
+ # Add/Replace item attributes.
291
+ #
292
+ # Params:
293
+ # domain_name = DomainName
294
+ # item_name = ItemName
295
+ # attributes = {
296
+ # 'nameA' => [valueA1,..., valueAN],
297
+ # ...
298
+ # 'nameZ' => [valueZ1,..., valueZN]
299
+ # }
300
+ # replace = :replace | any other value to skip replacement
301
+ #
302
+ # Returns a hash: { :box_usage, :request_id } on success or an exception on error.
303
+ # (Amazon raises no errors if the attribute was not overridden, as when the :replace param is unset).
304
+ #
305
+ # Example:
306
+ #
307
+ # sdb = RightAws::SdbInterface.new
308
+ # sdb.create_domain 'family'
309
+ #
310
+ # attributes = {}
311
+ # # create attributes for Jon and Silvia
312
+ # attributes['Jon'] = %w{ car beer }
313
+ # attributes['Silvia'] = %w{ beetle rolling_pin kids }
314
+ # sdb.put_attributes 'family', 'toys', attributes #=> ok
315
+ # # now: Jon=>[car, beer], Silvia=>[beetle, rolling_pin, kids]
316
+ #
317
+ # # add attributes to Jon
318
+ # attributes.delete('Silvia')
319
+ # attributes['Jon'] = %w{ girls pub }
320
+ # sdb.put_attributes 'family', 'toys', attributes #=> ok
321
+ # # now: Jon=>[car, beer, girls, pub], Silvia=>[beetle, rolling_pin, kids]
322
+ #
323
+ # # replace attributes for Jon and add to a cat (the cat had no attributes before)
324
+ # attributes['Jon'] = %w{ vacuum_cleaner hammer spade }
325
+ # attributes['cat'] = %w{ mouse clew Jons_socks }
326
+ # sdb.put_attributes 'family', 'toys', attributes, :replace #=> ok
327
+ # # now: Jon=>[vacuum_cleaner, hammer, spade], Silvia=>[beetle, rolling_pin, kids], cat=>[mouse, clew, Jons_socks]
328
+ #
329
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_PutAttributes.html
330
+ #
331
+ def put_attributes(domain_name, item_name, attributes, replace = false)
332
+ params = { 'DomainName' => domain_name,
333
+ 'ItemName' => item_name }.merge(pack_attributes(attributes, replace))
334
+ link = generate_request("PutAttributes", params)
335
+ request_info( link, QSdbSimpleParser.new )
336
+ rescue Exception
337
+ on_exception
338
+ end
339
+
340
+ # Retrieve SDB item's attribute(s).
341
+ #
342
+ # Returns a hash:
343
+ # { :box_usage => string,
344
+ # :request_id => string,
345
+ # :attributes => { 'nameA' => [valueA1,..., valueAN],
346
+ # ... ,
347
+ # 'nameZ' => [valueZ1,..., valueZN] } }
348
+ #
349
+ # Example:
350
+ # # request all attributes
351
+ # sdb.get_attributes('family', 'toys') # => { :attributes => {"cat" => ["clew", "Jons_socks", "mouse"] },
352
+ # "Silvia" => ["beetle", "rolling_pin", "kids"],
353
+ # "Jon" => ["vacuum_cleaner", "hammer", "spade"]},
354
+ # :box_usage => "0.0000093222",
355
+ # :request_id => "81273d21-000-1111-b3f9-512d91d29ac8" }
356
+ #
357
+ # # request cat's attributes only
358
+ # sdb.get_attributes('family', 'toys', 'cat') # => { :attributes => {"cat" => ["clew", "Jons_socks", "mouse"] },
359
+ # :box_usage => "0.0000093222",
360
+ # :request_id => "81273d21-001-1111-b3f9-512d91d29ac8" }
361
+ #
362
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_GetAttributes.html
363
+ #
364
+ def get_attributes(domain_name, item_name, attribute_name=nil)
365
+ link = generate_request("GetAttributes", 'DomainName' => domain_name,
366
+ 'ItemName' => item_name,
367
+ 'AttributeName' => attribute_name )
368
+ res = request_info(link, QSdbGetAttributesParser.new)
369
+ res[:attributes].each_value do |values|
370
+ values.collect! { |e| sdb_to_ruby(e) }
371
+ end
372
+ res
373
+ rescue Exception
374
+ on_exception
375
+ end
376
+
377
+ # Delete value, attribute or item.
378
+ #
379
+ # Example:
380
+ # # delete 'vodka' and 'girls' from 'Jon' and 'mice' from 'cat'.
381
+ # sdb.delete_attributes 'family', 'toys', { 'Jon' => ['vodka', 'girls'], 'cat' => ['mice'] }
382
+ #
383
+ # # delete the all the values from attributes (i.e. delete the attributes)
384
+ # sdb.delete_attributes 'family', 'toys', { 'Jon' => [], 'cat' => [] }
385
+ # # or
386
+ # sdb.delete_attributes 'family', 'toys', [ 'Jon', 'cat' ]
387
+ #
388
+ # # delete all the attributes from item 'toys' (i.e. delete the item)
389
+ # sdb.delete_attributes 'family', 'toys'
390
+ #
391
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_DeleteAttributes.html
392
+ #
393
+ def delete_attributes(domain_name, item_name, attributes = nil)
394
+ params = { 'DomainName' => domain_name,
395
+ 'ItemName' => item_name }.merge(pack_attributes(attributes))
396
+ link = generate_request("DeleteAttributes", params)
397
+ request_info( link, QSdbSimpleParser.new )
398
+ rescue Exception
399
+ on_exception
400
+ end
401
+
402
+
403
+ # QUERY:
404
+
405
+ # Perform a query on SDB.
406
+ #
407
+ # Returns a hash:
408
+ # { :box_usage => string,
409
+ # :request_id => string,
410
+ # :next_token => string,
411
+ # :items => [ItemName1,..., ItemNameN] }
412
+ #
413
+ # Example:
414
+ #
415
+ # query = "['cat' = 'clew']"
416
+ # sdb.query('family', query) #=> hash of data
417
+ # sdb.query('family', query, 10) #=> hash of data with max of 10 items
418
+ #
419
+ # If a block is given, query will iteratively yield results to it as long as the block continues to return true.
420
+ #
421
+ # # List 10 items per iteration. Don't
422
+ # # forget to escape single quotes and backslashes and wrap all the items in single quotes.
423
+ # query = "['cat'='clew'] union ['dog'='Jon\\'s boot']"
424
+ # sdb.query('family', query, 10) do |result|
425
+ # puts result.inspect
426
+ # true
427
+ # end
428
+ #
429
+ # # Same query using automatic escaping...to use the auto escape, pass the query and its params as an array:
430
+ # query = [ "['cat'=?] union ['dog'=?]", "clew", "Jon's boot" ]
431
+ # sdb.query('family', query)
432
+ #
433
+ # query = [ "['cat'=?] union ['dog'=?] sort 'cat' desc", "clew", "Jon's boot" ]
434
+ # sdb.query('family', query)
435
+ #
436
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_Query.html
437
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SortingData.html
438
+ #
439
+ def query(domain_name, query_expression = nil, max_number_of_items = nil, next_token = nil)
440
+ query_expression = query_expression_from_array(query_expression) if query_expression.is_a?(Array)
441
+ @last_query_expression = query_expression
442
+ #
443
+ request_params = { 'DomainName' => domain_name,
444
+ 'QueryExpression' => query_expression,
445
+ 'MaxNumberOfItems' => max_number_of_items,
446
+ 'NextToken' => next_token }
447
+ link = generate_request("Query", request_params)
448
+ result = request_info( link, QSdbQueryParser.new )
449
+ # return result if no block given
450
+ return result unless block_given?
451
+ # loop if block if given
452
+ begin
453
+ # the block must return true if it wanna continue
454
+ break unless yield(result) && result[:next_token]
455
+ # make new request
456
+ request_params['NextToken'] = result[:next_token]
457
+ link = generate_request("Query", request_params)
458
+ result = request_info( link, QSdbQueryParser.new )
459
+ end while true
460
+ rescue Exception
461
+ on_exception
462
+ end
463
+
464
+ # Perform a query and fetch specified attributes.
465
+ # If attributes are not specified then fetches the whole list of attributes.
466
+ #
467
+ #
468
+ # Returns a hash:
469
+ # { :box_usage => string,
470
+ # :request_id => string,
471
+ # :next_token => string,
472
+ # :items => [ { ItemName1 => { attribute1 => value1, ... attributeM => valueM } },
473
+ # { ItemName2 => {...}}, ... ]
474
+ #
475
+ # Example:
476
+ #
477
+ # sdb.query_with_attributes(domain, ['hobby', 'country'], "['gender'='female'] intersection ['name' starts-with ''] sort 'name'") #=>
478
+ # { :request_id => "06057228-70d0-4487-89fb-fd9c028580d3",
479
+ # :items =>
480
+ # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=>
481
+ # { "hobby" => ["cooking", "flowers", "cats"],
482
+ # "country" => ["Russia"]}},
483
+ # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=>
484
+ # { "hobby" => ["patchwork", "bundle jumping"],
485
+ # "country" => ["USA"]}}, ... ],
486
+ # :box_usage=>"0.0000504786"}
487
+ #
488
+ # sdb.query_with_attributes(domain, [], "['gender'='female'] intersection ['name' starts-with ''] sort 'name'") #=>
489
+ # { :request_id => "75bb19db-a529-4f69-b86f-5e3800f79a45",
490
+ # :items =>
491
+ # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=>
492
+ # { "hobby" => ["cooking", "flowers", "cats"],
493
+ # "name" => ["Mary"],
494
+ # "country" => ["Russia"],
495
+ # "gender" => ["female"],
496
+ # "id" => ["035f1ba8-dbd8-11dd-80bd-001bfc466dd7"]}},
497
+ # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=>
498
+ # { "hobby" => ["patchwork", "bundle jumping"],
499
+ # "name" => ["Mary"],
500
+ # "country" => ["USA"],
501
+ # "gender" => ["female"],
502
+ # "id" => ["0327614a-dbd8-11dd-80bd-001bfc466dd7"]}}, ... ],
503
+ # :box_usage=>"0.0000506668"}
504
+ #
505
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDB_API_QueryWithAttributes.html
506
+ #
507
+ def query_with_attributes(domain_name, attributes=[], query_expression = nil, max_number_of_items = nil, next_token = nil)
508
+ attributes = attributes.to_a
509
+ query_expression = query_expression_from_array(query_expression) if query_expression.is_a?(Array)
510
+ @last_query_expression = query_expression
511
+ #
512
+ request_params = { 'DomainName' => domain_name,
513
+ 'QueryExpression' => query_expression,
514
+ 'MaxNumberOfItems' => max_number_of_items,
515
+ 'NextToken' => next_token }
516
+ attributes.each_with_index do |attribute, idx|
517
+ request_params["AttributeName.#{idx+1}"] = attribute
518
+ end
519
+ link = generate_request("QueryWithAttributes", request_params)
520
+ result = select_response_to_ruby(request_info( link, QSdbQueryWithAttributesParser.new ))
521
+ # return result if no block given
522
+ return result unless block_given?
523
+ # loop if block if given
524
+ begin
525
+ # the block must return true if it wanna continue
526
+ break unless yield(result) && result[:next_token]
527
+ # make new request
528
+ request_params['NextToken'] = result[:next_token]
529
+ link = generate_request("QueryWithAttributes", request_params)
530
+ result = select_response_to_ruby(request_info( link, QSdbQueryWithAttributesParser.new ))
531
+ end while true
532
+ rescue Exception
533
+ on_exception
534
+ end
535
+
536
+ # Perform SQL-like select and fetch attributes.
537
+ # Attribute values must be quoted with a single or double quote. If a quote appears within the attribute value, it must be escaped with the same quote symbol as shown in the following example.
538
+ # (Use array to pass select_expression params to avoid manual escaping).
539
+ #
540
+ # sdb.select(["select * from my_domain where gender=?", 'female']) #=>
541
+ # {:request_id =>"8241b843-0fb9-4d66-9100-effae12249ec",
542
+ # :items =>
543
+ # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=>
544
+ # {"hobby" => ["cooking", "flowers", "cats"],
545
+ # "name" => ["Mary"],
546
+ # "country" => ["Russia"],
547
+ # "gender" => ["female"],
548
+ # "id" => ["035f1ba8-dbd8-11dd-80bd-001bfc466dd7"]}},
549
+ # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=>
550
+ # {"hobby" => ["patchwork", "bundle jumping"],
551
+ # "name" => ["Mary"],
552
+ # "country" => ["USA"],
553
+ # "gender" => ["female"],
554
+ # "id" => ["0327614a-dbd8-11dd-80bd-001bfc466dd7"]}}, ... ]
555
+ # :box_usage =>"0.0000506197"}
556
+ #
557
+ # sdb.select('select country, name from my_domain') #=>
558
+ # {:request_id=>"b1600198-c317-413f-a8dc-4e7f864a940a",
559
+ # :items=>
560
+ # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["Russia"]} },
561
+ # { "376d2e00-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Putin"], "country"=>["Russia"]} },
562
+ # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["USA"]} },
563
+ # { "372ebbd4-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Bush"], "country"=>["USA"]} },
564
+ # { "37a4e552-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Medvedev"], "country"=>["Russia"]} },
565
+ # { "38278dfe-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["Russia"]} },
566
+ # { "37df6c36-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["USA"]} } ],
567
+ # :box_usage=>"0.0000777663"}
568
+ #
569
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDB_API_Select.html
570
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingSelect.html
571
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDBLimits.html
572
+ #
573
+ def select(select_expression, next_token = nil)
574
+ select_expression = query_expression_from_array(select_expression) if select_expression.is_a?(Array)
575
+ @last_query_expression = select_expression
576
+ #
577
+ request_params = { 'SelectExpression' => select_expression,
578
+ 'NextToken' => next_token }
579
+ link = generate_request("Select", request_params)
580
+ result = select_response_to_ruby(request_info( link, QSdbSelectParser.new ))
581
+ return result unless block_given?
582
+ # loop if block if given
583
+ begin
584
+ # the block must return true if it wanna continue
585
+ break unless yield(result) && result[:next_token]
586
+ # make new request
587
+ request_params['NextToken'] = result[:next_token]
588
+ link = generate_request("Select", request_params)
589
+ result = select_response_to_ruby(request_info( link, QSdbSelectParser.new ))
590
+ end while true
591
+ rescue Exception
592
+ on_exception
593
+ end
594
+
595
+ #-----------------------------------------------------------------
596
+ # PARSERS:
597
+ #-----------------------------------------------------------------
598
+ class QSdbListDomainParser < RightAWSParser #:nodoc:
599
+ def reset
600
+ @result = { :domains => [] }
601
+ end
602
+ def tagend(name)
603
+ case name
604
+ when 'NextToken' then @result[:next_token] = @text
605
+ when 'DomainName' then @result[:domains] << @text
606
+ when 'BoxUsage' then @result[:box_usage] = @text
607
+ when 'RequestId' then @result[:request_id] = @text
608
+ end
609
+ end
610
+ end
611
+
612
+ class QSdbSimpleParser < RightAWSParser #:nodoc:
613
+ def reset
614
+ @result = {}
615
+ end
616
+ def tagend(name)
617
+ case name
618
+ when 'BoxUsage' then @result[:box_usage] = @text
619
+ when 'RequestId' then @result[:request_id] = @text
620
+ end
621
+ end
622
+ end
623
+
624
+ class QSdbGetAttributesParser < RightAWSParser #:nodoc:
625
+ def reset
626
+ @last_attribute_name = nil
627
+ @result = { :attributes => {} }
628
+ end
629
+ def tagend(name)
630
+ case name
631
+ when 'Name' then @last_attribute_name = @text
632
+ when 'Value' then (@result[:attributes][@last_attribute_name] ||= []) << @text
633
+ when 'BoxUsage' then @result[:box_usage] = @text
634
+ when 'RequestId' then @result[:request_id] = @text
635
+ end
636
+ end
637
+ end
638
+
639
+ class QSdbQueryParser < RightAWSParser #:nodoc:
640
+ def reset
641
+ @result = { :items => [] }
642
+ end
643
+ def tagend(name)
644
+ case name
645
+ when 'ItemName' then @result[:items] << @text
646
+ when 'BoxUsage' then @result[:box_usage] = @text
647
+ when 'RequestId' then @result[:request_id] = @text
648
+ when 'NextToken' then @result[:next_token] = @text
649
+ end
650
+ end
651
+ end
652
+
653
+ class QSdbQueryWithAttributesParser < RightAWSParser #:nodoc:
654
+ def reset
655
+ @result = { :items => [] }
656
+ end
657
+ def tagend(name)
658
+ case name
659
+ when 'Name'
660
+ case @xmlpath
661
+ when 'QueryWithAttributesResponse/QueryWithAttributesResult/Item'
662
+ @item = @text
663
+ @result[:items] << { @item => {} }
664
+ when 'QueryWithAttributesResponse/QueryWithAttributesResult/Item/Attribute'
665
+ @attribute = @text
666
+ @result[:items].last[@item][@attribute] ||= []
667
+ end
668
+ when 'RequestId' then @result[:request_id] = @text
669
+ when 'BoxUsage' then @result[:box_usage] = @text
670
+ when 'NextToken' then @result[:next_token] = @text
671
+ when 'Value' then @result[:items].last[@item][@attribute] << @text
672
+ end
673
+ end
674
+ end
675
+
676
+ class QSdbSelectParser < RightAWSParser #:nodoc:
677
+ def reset
678
+ @result = { :items => [] }
679
+ end
680
+ def tagend(name)
681
+ case name
682
+ when 'Name'
683
+ case @xmlpath
684
+ when 'SelectResponse/SelectResult/Item'
685
+ @item = @text
686
+ @result[:items] << { @item => {} }
687
+ when 'SelectResponse/SelectResult/Item/Attribute'
688
+ @attribute = @text
689
+ @result[:items].last[@item][@attribute] ||= []
690
+ end
691
+ when 'RequestId' then @result[:request_id] = @text
692
+ when 'BoxUsage' then @result[:box_usage] = @text
693
+ when 'NextToken' then @result[:next_token] = @text
694
+ when 'Value' then @result[:items].last[@item][@attribute] << @text
695
+ end
696
+ end
697
+ end
698
+
699
+ end
700
+
701
+ end