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