monkeyhelper-right_aws 1.10.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.
@@ -0,0 +1,703 @@
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
+ request_info_impl(thread[:sdb_connection], @@bench, request, parser)
114
+ end
115
+
116
+ # Prepare attributes for putting.
117
+ # (used by put_attributes)
118
+ def pack_attributes(attributes, replace = false) #:nodoc:
119
+ result = {}
120
+ if attributes
121
+ idx = 0
122
+ skip_values = attributes.is_a?(Array)
123
+ attributes.each do |attribute, values|
124
+ # set replacement attribute
125
+ result["Attribute.#{idx}.Replace"] = 'true' if replace
126
+ # pack Name/Value
127
+ unless values.nil?
128
+ Array(values).each do |value|
129
+ result["Attribute.#{idx}.Name"] = attribute
130
+ result["Attribute.#{idx}.Value"] = ruby_to_sdb(value) unless skip_values
131
+ idx += 1
132
+ end
133
+ else
134
+ result["Attribute.#{idx}.Name"] = attribute
135
+ result["Attribute.#{idx}.Value"] = ruby_to_sdb(nil) unless skip_values
136
+ idx += 1
137
+ end
138
+ end
139
+ end
140
+ result
141
+ end
142
+
143
+ # Use this helper to manually escape the fields in the query expressions.
144
+ # To escape the single quotes and backslashes and to wrap the string into the single quotes.
145
+ #
146
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API.html
147
+ #
148
+ def escape(value)
149
+ %Q{'#{value.to_s.gsub(/(['\\])/){ "\\#{$1}" }}'} if value
150
+ end
151
+
152
+ # Convert a Ruby language value to a SDB value by replacing Ruby nil with the user's chosen string representation of nil.
153
+ # Non-nil values are unaffected by this filter.
154
+ #def ruby_to_sdb(value)
155
+ # value.nil? ? @nil_rep : value
156
+ #end
157
+ # http://code.google.com/p/ruby-aws/issues/detail?id=1#c0
158
+ def ruby_to_sdb(value)
159
+ value.nil? ? @nil_rep : value.is_a?(String) ?
160
+ RightAws::AwsUtils.URLencode(value) : value
161
+ end
162
+
163
+ # Convert a SDB value to a Ruby language value by replacing the user's chosen string representation of nil with Ruby nil.
164
+ # Values are unaffected by this filter unless they match the nil representation exactly.
165
+ def sdb_to_ruby(value)
166
+ value.eql?(@nil_rep) ? nil : value
167
+ end
168
+
169
+ # 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.
170
+ # (This method affects on a passed response value)
171
+ def select_response_to_ruby(response) #:nodoc:
172
+ response[:items].each_with_index do |item, idx|
173
+ item.each do |key, attributes|
174
+ attributes.each do |name, values|
175
+ values.collect! { |value| sdb_to_ruby(value) }
176
+ end
177
+ end
178
+ end
179
+ response
180
+ end
181
+
182
+ # Create query expression from an array.
183
+ # (similar to ActiveRecord::Base#find using :conditions => ['query', param1, .., paramN])
184
+ #
185
+ def query_expression_from_array(params) #:nodoc:
186
+ return '' if params.blank?
187
+ query = params.shift.to_s
188
+ query.gsub(/(\\)?(\?)/) do
189
+ if $1 # if escaped '\?' is found - replace it by '?' without backslash
190
+ "?"
191
+ else # well, if no backslash precedes '?' then replace it by next param from the list
192
+ escape(params.shift)
193
+ end
194
+ end
195
+ end
196
+
197
+ def query_expression_from_hash(hash)
198
+ return '' if hash.blank?
199
+ expression = []
200
+ hash.each do |key, value|
201
+ expression << "#{key}=#{escape(value)}"
202
+ end
203
+ expression.join(' AND ')
204
+ end
205
+
206
+ # Retrieve a list of SDB domains from Amazon.
207
+ #
208
+ # Returns a hash:
209
+ # { :domains => [domain1, ..., domainN],
210
+ # :next_token => string || nil,
211
+ # :box_usage => string,
212
+ # :request_id => string }
213
+ #
214
+ # Example:
215
+ #
216
+ # sdb = RightAws::SdbInterface.new
217
+ # sdb.list_domains #=> { :box_usage => "0.0000071759",
218
+ # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982",
219
+ # :domains => ["toys", "dolls"]}
220
+ #
221
+ # 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,
222
+ # list_domains will end.
223
+ #
224
+ # sdb.list_domains(10) do |result| # list by 10 domains per iteration
225
+ # puts result.inspect
226
+ # true
227
+ # end
228
+ #
229
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_ListDomains.html
230
+ #
231
+ def list_domains(max_number_of_domains = nil, next_token = nil )
232
+ request_params = { 'MaxNumberOfDomains' => max_number_of_domains,
233
+ 'NextToken' => next_token }
234
+ link = generate_request("ListDomains", request_params)
235
+ result = request_info(link, QSdbListDomainParser.new)
236
+ # return result if no block given
237
+ return result unless block_given?
238
+ # loop if block if given
239
+ begin
240
+ # the block must return true if it wanna continue
241
+ break unless yield(result) && result[:next_token]
242
+ # make new request
243
+ request_params['NextToken'] = result[:next_token]
244
+ link = generate_request("ListDomains", request_params)
245
+ result = request_info(link, QSdbListDomainParser.new)
246
+ end while true
247
+ rescue Exception
248
+ on_exception
249
+ end
250
+
251
+ # Create new SDB domain at Amazon.
252
+ #
253
+ # Returns a hash: { :box_usage, :request_id } on success or an exception on error.
254
+ # (Amazon raises no errors if the domain already exists).
255
+ #
256
+ # Example:
257
+ #
258
+ # sdb = RightAws::SdbInterface.new
259
+ # sdb.create_domain('toys') # => { :box_usage => "0.0000071759",
260
+ # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982" }
261
+ #
262
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_CreateDomain.html
263
+ def create_domain(domain_name)
264
+ link = generate_request("CreateDomain",
265
+ 'DomainName' => domain_name)
266
+ request_info(link, QSdbSimpleParser.new)
267
+ rescue Exception
268
+ on_exception
269
+ end
270
+
271
+ # Delete SDB domain at Amazon.
272
+ #
273
+ # Returns a hash: { :box_usage, :request_id } on success or an exception on error.
274
+ # (Amazon raises no errors if the domain does not exist).
275
+ #
276
+ # Example:
277
+ #
278
+ # sdb = RightAws::SdbInterface.new
279
+ # sdb.delete_domain('toys') # => { :box_usage => "0.0000071759",
280
+ # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982" }
281
+ #
282
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_DeleteDomain.html
283
+ #
284
+ def delete_domain(domain_name)
285
+ link = generate_request("DeleteDomain",
286
+ 'DomainName' => domain_name)
287
+ request_info(link, QSdbSimpleParser.new)
288
+ rescue Exception
289
+ on_exception
290
+ end
291
+
292
+ # Add/Replace item attributes.
293
+ #
294
+ # Params:
295
+ # domain_name = DomainName
296
+ # item_name = ItemName
297
+ # attributes = {
298
+ # 'nameA' => [valueA1,..., valueAN],
299
+ # ...
300
+ # 'nameZ' => [valueZ1,..., valueZN]
301
+ # }
302
+ # replace = :replace | any other value to skip replacement
303
+ #
304
+ # Returns a hash: { :box_usage, :request_id } on success or an exception on error.
305
+ # (Amazon raises no errors if the attribute was not overridden, as when the :replace param is unset).
306
+ #
307
+ # Example:
308
+ #
309
+ # sdb = RightAws::SdbInterface.new
310
+ # sdb.create_domain 'family'
311
+ #
312
+ # attributes = {}
313
+ # # create attributes for Jon and Silvia
314
+ # attributes['Jon'] = %w{ car beer }
315
+ # attributes['Silvia'] = %w{ beetle rolling_pin kids }
316
+ # sdb.put_attributes 'family', 'toys', attributes #=> ok
317
+ # # now: Jon=>[car, beer], Silvia=>[beetle, rolling_pin, kids]
318
+ #
319
+ # # add attributes to Jon
320
+ # attributes.delete('Silvia')
321
+ # attributes['Jon'] = %w{ girls pub }
322
+ # sdb.put_attributes 'family', 'toys', attributes #=> ok
323
+ # # now: Jon=>[car, beer, girls, pub], Silvia=>[beetle, rolling_pin, kids]
324
+ #
325
+ # # replace attributes for Jon and add to a cat (the cat had no attributes before)
326
+ # attributes['Jon'] = %w{ vacuum_cleaner hammer spade }
327
+ # attributes['cat'] = %w{ mouse clew Jons_socks }
328
+ # sdb.put_attributes 'family', 'toys', attributes, :replace #=> ok
329
+ # # now: Jon=>[vacuum_cleaner, hammer, spade], Silvia=>[beetle, rolling_pin, kids], cat=>[mouse, clew, Jons_socks]
330
+ #
331
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_PutAttributes.html
332
+ #
333
+ def put_attributes(domain_name, item_name, attributes, replace = false)
334
+ params = { 'DomainName' => domain_name,
335
+ 'ItemName' => item_name }.merge(pack_attributes(attributes, replace))
336
+ link = generate_request("PutAttributes", params)
337
+ request_info( link, QSdbSimpleParser.new )
338
+ rescue Exception
339
+ on_exception
340
+ end
341
+
342
+ # Retrieve SDB item's attribute(s).
343
+ #
344
+ # Returns a hash:
345
+ # { :box_usage => string,
346
+ # :request_id => string,
347
+ # :attributes => { 'nameA' => [valueA1,..., valueAN],
348
+ # ... ,
349
+ # 'nameZ' => [valueZ1,..., valueZN] } }
350
+ #
351
+ # Example:
352
+ # # request all attributes
353
+ # sdb.get_attributes('family', 'toys') # => { :attributes => {"cat" => ["clew", "Jons_socks", "mouse"] },
354
+ # "Silvia" => ["beetle", "rolling_pin", "kids"],
355
+ # "Jon" => ["vacuum_cleaner", "hammer", "spade"]},
356
+ # :box_usage => "0.0000093222",
357
+ # :request_id => "81273d21-000-1111-b3f9-512d91d29ac8" }
358
+ #
359
+ # # request cat's attributes only
360
+ # sdb.get_attributes('family', 'toys', 'cat') # => { :attributes => {"cat" => ["clew", "Jons_socks", "mouse"] },
361
+ # :box_usage => "0.0000093222",
362
+ # :request_id => "81273d21-001-1111-b3f9-512d91d29ac8" }
363
+ #
364
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_GetAttributes.html
365
+ #
366
+ def get_attributes(domain_name, item_name, attribute_name=nil)
367
+ link = generate_request("GetAttributes", 'DomainName' => domain_name,
368
+ 'ItemName' => item_name,
369
+ 'AttributeName' => attribute_name )
370
+ res = request_info(link, QSdbGetAttributesParser.new)
371
+ res[:attributes].each_value do |values|
372
+ values.collect! { |e| sdb_to_ruby(e) }
373
+ end
374
+ res
375
+ rescue Exception
376
+ on_exception
377
+ end
378
+
379
+ # Delete value, attribute or item.
380
+ #
381
+ # Example:
382
+ # # delete 'vodka' and 'girls' from 'Jon' and 'mice' from 'cat'.
383
+ # sdb.delete_attributes 'family', 'toys', { 'Jon' => ['vodka', 'girls'], 'cat' => ['mice'] }
384
+ #
385
+ # # delete the all the values from attributes (i.e. delete the attributes)
386
+ # sdb.delete_attributes 'family', 'toys', { 'Jon' => [], 'cat' => [] }
387
+ # # or
388
+ # sdb.delete_attributes 'family', 'toys', [ 'Jon', 'cat' ]
389
+ #
390
+ # # delete all the attributes from item 'toys' (i.e. delete the item)
391
+ # sdb.delete_attributes 'family', 'toys'
392
+ #
393
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_DeleteAttributes.html
394
+ #
395
+ def delete_attributes(domain_name, item_name, attributes = nil)
396
+ params = { 'DomainName' => domain_name,
397
+ 'ItemName' => item_name }.merge(pack_attributes(attributes))
398
+ link = generate_request("DeleteAttributes", params)
399
+ request_info( link, QSdbSimpleParser.new )
400
+ rescue Exception
401
+ on_exception
402
+ end
403
+
404
+
405
+ # QUERY:
406
+
407
+ # Perform a query on SDB.
408
+ #
409
+ # Returns a hash:
410
+ # { :box_usage => string,
411
+ # :request_id => string,
412
+ # :next_token => string,
413
+ # :items => [ItemName1,..., ItemNameN] }
414
+ #
415
+ # Example:
416
+ #
417
+ # query = "['cat' = 'clew']"
418
+ # sdb.query('family', query) #=> hash of data
419
+ # sdb.query('family', query, 10) #=> hash of data with max of 10 items
420
+ #
421
+ # If a block is given, query will iteratively yield results to it as long as the block continues to return true.
422
+ #
423
+ # # List 10 items per iteration. Don't
424
+ # # forget to escape single quotes and backslashes and wrap all the items in single quotes.
425
+ # query = "['cat'='clew'] union ['dog'='Jon\\'s boot']"
426
+ # sdb.query('family', query, 10) do |result|
427
+ # puts result.inspect
428
+ # true
429
+ # end
430
+ #
431
+ # # Same query using automatic escaping...to use the auto escape, pass the query and its params as an array:
432
+ # query = [ "['cat'=?] union ['dog'=?]", "clew", "Jon's boot" ]
433
+ # sdb.query('family', query)
434
+ #
435
+ # query = [ "['cat'=?] union ['dog'=?] sort 'cat' desc", "clew", "Jon's boot" ]
436
+ # sdb.query('family', query)
437
+ #
438
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_Query.html
439
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SortingData.html
440
+ #
441
+ def query(domain_name, query_expression = nil, max_number_of_items = nil, next_token = nil)
442
+ query_expression = query_expression_from_array(query_expression) if query_expression.is_a?(Array)
443
+ @last_query_expression = query_expression
444
+ #
445
+ request_params = { 'DomainName' => domain_name,
446
+ 'QueryExpression' => query_expression,
447
+ 'MaxNumberOfItems' => max_number_of_items,
448
+ 'NextToken' => next_token }
449
+ link = generate_request("Query", request_params)
450
+ result = request_info( link, QSdbQueryParser.new )
451
+ # return result if no block given
452
+ return result unless block_given?
453
+ # loop if block if given
454
+ begin
455
+ # the block must return true if it wanna continue
456
+ break unless yield(result) && result[:next_token]
457
+ # make new request
458
+ request_params['NextToken'] = result[:next_token]
459
+ link = generate_request("Query", request_params)
460
+ result = request_info( link, QSdbQueryParser.new )
461
+ end while true
462
+ rescue Exception
463
+ on_exception
464
+ end
465
+
466
+ # Perform a query and fetch specified attributes.
467
+ # If attributes are not specified then fetches the whole list of attributes.
468
+ #
469
+ #
470
+ # Returns a hash:
471
+ # { :box_usage => string,
472
+ # :request_id => string,
473
+ # :next_token => string,
474
+ # :items => [ { ItemName1 => { attribute1 => value1, ... attributeM => valueM } },
475
+ # { ItemName2 => {...}}, ... ]
476
+ #
477
+ # Example:
478
+ #
479
+ # sdb.query_with_attributes(domain, ['hobby', 'country'], "['gender'='female'] intersection ['name' starts-with ''] sort 'name'") #=>
480
+ # { :request_id => "06057228-70d0-4487-89fb-fd9c028580d3",
481
+ # :items =>
482
+ # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=>
483
+ # { "hobby" => ["cooking", "flowers", "cats"],
484
+ # "country" => ["Russia"]}},
485
+ # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=>
486
+ # { "hobby" => ["patchwork", "bundle jumping"],
487
+ # "country" => ["USA"]}}, ... ],
488
+ # :box_usage=>"0.0000504786"}
489
+ #
490
+ # sdb.query_with_attributes(domain, [], "['gender'='female'] intersection ['name' starts-with ''] sort 'name'") #=>
491
+ # { :request_id => "75bb19db-a529-4f69-b86f-5e3800f79a45",
492
+ # :items =>
493
+ # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=>
494
+ # { "hobby" => ["cooking", "flowers", "cats"],
495
+ # "name" => ["Mary"],
496
+ # "country" => ["Russia"],
497
+ # "gender" => ["female"],
498
+ # "id" => ["035f1ba8-dbd8-11dd-80bd-001bfc466dd7"]}},
499
+ # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=>
500
+ # { "hobby" => ["patchwork", "bundle jumping"],
501
+ # "name" => ["Mary"],
502
+ # "country" => ["USA"],
503
+ # "gender" => ["female"],
504
+ # "id" => ["0327614a-dbd8-11dd-80bd-001bfc466dd7"]}}, ... ],
505
+ # :box_usage=>"0.0000506668"}
506
+ #
507
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDB_API_QueryWithAttributes.html
508
+ #
509
+ def query_with_attributes(domain_name, attributes=[], query_expression = nil, max_number_of_items = nil, next_token = nil)
510
+ attributes = attributes.to_a
511
+ query_expression = query_expression_from_array(query_expression) if query_expression.is_a?(Array)
512
+ @last_query_expression = query_expression
513
+ #
514
+ request_params = { 'DomainName' => domain_name,
515
+ 'QueryExpression' => query_expression,
516
+ 'MaxNumberOfItems' => max_number_of_items,
517
+ 'NextToken' => next_token }
518
+ attributes.each_with_index do |attribute, idx|
519
+ request_params["AttributeName.#{idx+1}"] = attribute
520
+ end
521
+ link = generate_request("QueryWithAttributes", request_params)
522
+ result = select_response_to_ruby(request_info( link, QSdbQueryWithAttributesParser.new ))
523
+ # return result if no block given
524
+ return result unless block_given?
525
+ # loop if block if given
526
+ begin
527
+ # the block must return true if it wanna continue
528
+ break unless yield(result) && result[:next_token]
529
+ # make new request
530
+ request_params['NextToken'] = result[:next_token]
531
+ link = generate_request("QueryWithAttributes", request_params)
532
+ result = select_response_to_ruby(request_info( link, QSdbQueryWithAttributesParser.new ))
533
+ end while true
534
+ rescue Exception
535
+ on_exception
536
+ end
537
+
538
+ # Perform SQL-like select and fetch attributes.
539
+ # 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.
540
+ # (Use array to pass select_expression params to avoid manual escaping).
541
+ #
542
+ # sdb.select(["select * from my_domain where gender=?", 'female']) #=>
543
+ # {:request_id =>"8241b843-0fb9-4d66-9100-effae12249ec",
544
+ # :items =>
545
+ # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=>
546
+ # {"hobby" => ["cooking", "flowers", "cats"],
547
+ # "name" => ["Mary"],
548
+ # "country" => ["Russia"],
549
+ # "gender" => ["female"],
550
+ # "id" => ["035f1ba8-dbd8-11dd-80bd-001bfc466dd7"]}},
551
+ # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=>
552
+ # {"hobby" => ["patchwork", "bundle jumping"],
553
+ # "name" => ["Mary"],
554
+ # "country" => ["USA"],
555
+ # "gender" => ["female"],
556
+ # "id" => ["0327614a-dbd8-11dd-80bd-001bfc466dd7"]}}, ... ]
557
+ # :box_usage =>"0.0000506197"}
558
+ #
559
+ # sdb.select('select country, name from my_domain') #=>
560
+ # {:request_id=>"b1600198-c317-413f-a8dc-4e7f864a940a",
561
+ # :items=>
562
+ # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["Russia"]} },
563
+ # { "376d2e00-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Putin"], "country"=>["Russia"]} },
564
+ # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["USA"]} },
565
+ # { "372ebbd4-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Bush"], "country"=>["USA"]} },
566
+ # { "37a4e552-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Medvedev"], "country"=>["Russia"]} },
567
+ # { "38278dfe-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["Russia"]} },
568
+ # { "37df6c36-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["USA"]} } ],
569
+ # :box_usage=>"0.0000777663"}
570
+ #
571
+ # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDB_API_Select.html
572
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingSelect.html
573
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDBLimits.html
574
+ #
575
+ def select(select_expression, next_token = nil)
576
+ select_expression = query_expression_from_array(select_expression) if select_expression.is_a?(Array)
577
+ @last_query_expression = select_expression
578
+ #
579
+ request_params = { 'SelectExpression' => select_expression,
580
+ 'NextToken' => next_token }
581
+ link = generate_request("Select", request_params)
582
+ result = select_response_to_ruby(request_info( link, QSdbSelectParser.new ))
583
+ return result unless block_given?
584
+ # loop if block if given
585
+ begin
586
+ # the block must return true if it wanna continue
587
+ break unless yield(result) && result[:next_token]
588
+ # make new request
589
+ request_params['NextToken'] = result[:next_token]
590
+ link = generate_request("Select", request_params)
591
+ result = select_response_to_ruby(request_info( link, QSdbSelectParser.new ))
592
+ end while true
593
+ rescue Exception
594
+ on_exception
595
+ end
596
+
597
+ #-----------------------------------------------------------------
598
+ # PARSERS:
599
+ #-----------------------------------------------------------------
600
+ class QSdbListDomainParser < RightAWSParser #:nodoc:
601
+ def reset
602
+ @result = { :domains => [] }
603
+ end
604
+ def tagend(name)
605
+ case name
606
+ when 'NextToken' then @result[:next_token] = @text
607
+ when 'DomainName' then @result[:domains] << @text
608
+ when 'BoxUsage' then @result[:box_usage] = @text
609
+ when 'RequestId' then @result[:request_id] = @text
610
+ end
611
+ end
612
+ end
613
+
614
+ class QSdbSimpleParser < RightAWSParser #:nodoc:
615
+ def reset
616
+ @result = {}
617
+ end
618
+ def tagend(name)
619
+ case name
620
+ when 'BoxUsage' then @result[:box_usage] = @text
621
+ when 'RequestId' then @result[:request_id] = @text
622
+ end
623
+ end
624
+ end
625
+
626
+ class QSdbGetAttributesParser < RightAWSParser #:nodoc:
627
+ def reset
628
+ @last_attribute_name = nil
629
+ @result = { :attributes => {} }
630
+ end
631
+ def tagend(name)
632
+ case name
633
+ when 'Name' then @last_attribute_name = @text
634
+ when 'Value' then (@result[:attributes][@last_attribute_name] ||= []) << @text
635
+ when 'BoxUsage' then @result[:box_usage] = @text
636
+ when 'RequestId' then @result[:request_id] = @text
637
+ end
638
+ end
639
+ end
640
+
641
+ class QSdbQueryParser < RightAWSParser #:nodoc:
642
+ def reset
643
+ @result = { :items => [] }
644
+ end
645
+ def tagend(name)
646
+ case name
647
+ when 'ItemName' then @result[:items] << @text
648
+ when 'BoxUsage' then @result[:box_usage] = @text
649
+ when 'RequestId' then @result[:request_id] = @text
650
+ when 'NextToken' then @result[:next_token] = @text
651
+ end
652
+ end
653
+ end
654
+
655
+ class QSdbQueryWithAttributesParser < RightAWSParser #:nodoc:
656
+ def reset
657
+ @result = { :items => [] }
658
+ end
659
+ def tagend(name)
660
+ case name
661
+ when 'Name'
662
+ case @xmlpath
663
+ when 'QueryWithAttributesResponse/QueryWithAttributesResult/Item'
664
+ @item = @text
665
+ @result[:items] << { @item => {} }
666
+ when 'QueryWithAttributesResponse/QueryWithAttributesResult/Item/Attribute'
667
+ @attribute = @text
668
+ @result[:items].last[@item][@attribute] ||= []
669
+ end
670
+ when 'RequestId' then @result[:request_id] = @text
671
+ when 'BoxUsage' then @result[:box_usage] = @text
672
+ when 'NextToken' then @result[:next_token] = @text
673
+ when 'Value' then @result[:items].last[@item][@attribute] << @text
674
+ end
675
+ end
676
+ end
677
+
678
+ class QSdbSelectParser < RightAWSParser #:nodoc:
679
+ def reset
680
+ @result = { :items => [] }
681
+ end
682
+ def tagend(name)
683
+ case name
684
+ when 'Name'
685
+ case @xmlpath
686
+ when 'SelectResponse/SelectResult/Item'
687
+ @item = @text
688
+ @result[:items] << { @item => {} }
689
+ when 'SelectResponse/SelectResult/Item/Attribute'
690
+ @attribute = @text
691
+ @result[:items].last[@item][@attribute] ||= []
692
+ end
693
+ when 'RequestId' then @result[:request_id] = @text
694
+ when 'BoxUsage' then @result[:box_usage] = @text
695
+ when 'NextToken' then @result[:next_token] = @text
696
+ when 'Value' then @result[:items].last[@item][@attribute] << @text
697
+ end
698
+ end
699
+ end
700
+
701
+ end
702
+
703
+ end