aws-sdk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. data/.yardopts +6 -0
  2. data/LICENSE.txt +171 -0
  3. data/NOTICE.txt +2 -0
  4. data/README.rdoc +189 -0
  5. data/lib/aws-sdk.rb +14 -0
  6. data/lib/aws.rb +63 -0
  7. data/lib/aws/api_config.rb +45 -0
  8. data/lib/aws/api_config/.document +0 -0
  9. data/lib/aws/api_config/EC2-2011-02-28.yml +2314 -0
  10. data/lib/aws/api_config/SNS-2010-03-31.yml +171 -0
  11. data/lib/aws/api_config/SQS-2009-02-01.yml +161 -0
  12. data/lib/aws/api_config/SimpleDB-2009-04-15.yml +278 -0
  13. data/lib/aws/api_config/SimpleEmailService-2010-12-01.yml +147 -0
  14. data/lib/aws/api_config_transform.rb +32 -0
  15. data/lib/aws/async_handle.rb +90 -0
  16. data/lib/aws/authorize_v2.rb +37 -0
  17. data/lib/aws/authorize_v3.rb +37 -0
  18. data/lib/aws/base_client.rb +524 -0
  19. data/lib/aws/cacheable.rb +92 -0
  20. data/lib/aws/common.rb +228 -0
  21. data/lib/aws/configurable.rb +36 -0
  22. data/lib/aws/configuration.rb +272 -0
  23. data/lib/aws/configured_client_methods.rb +81 -0
  24. data/lib/aws/configured_grammars.rb +65 -0
  25. data/lib/aws/configured_option_grammars.rb +46 -0
  26. data/lib/aws/configured_xml_grammars.rb +47 -0
  27. data/lib/aws/default_signer.rb +38 -0
  28. data/lib/aws/ec2.rb +321 -0
  29. data/lib/aws/ec2/attachment.rb +149 -0
  30. data/lib/aws/ec2/attachment_collection.rb +57 -0
  31. data/lib/aws/ec2/availability_zone.rb +80 -0
  32. data/lib/aws/ec2/availability_zone_collection.rb +47 -0
  33. data/lib/aws/ec2/block_device_mappings.rb +53 -0
  34. data/lib/aws/ec2/client.rb +54 -0
  35. data/lib/aws/ec2/client/xml.rb +127 -0
  36. data/lib/aws/ec2/collection.rb +39 -0
  37. data/lib/aws/ec2/config_transform.rb +63 -0
  38. data/lib/aws/ec2/elastic_ip.rb +107 -0
  39. data/lib/aws/ec2/elastic_ip_collection.rb +85 -0
  40. data/lib/aws/ec2/errors.rb +29 -0
  41. data/lib/aws/ec2/filtered_collection.rb +65 -0
  42. data/lib/aws/ec2/has_permissions.rb +46 -0
  43. data/lib/aws/ec2/image.rb +245 -0
  44. data/lib/aws/ec2/image_collection.rb +235 -0
  45. data/lib/aws/ec2/instance.rb +515 -0
  46. data/lib/aws/ec2/instance_collection.rb +276 -0
  47. data/lib/aws/ec2/key_pair.rb +86 -0
  48. data/lib/aws/ec2/key_pair_collection.rb +102 -0
  49. data/lib/aws/ec2/permission_collection.rb +177 -0
  50. data/lib/aws/ec2/region.rb +81 -0
  51. data/lib/aws/ec2/region_collection.rb +55 -0
  52. data/lib/aws/ec2/request.rb +27 -0
  53. data/lib/aws/ec2/reserved_instances.rb +50 -0
  54. data/lib/aws/ec2/reserved_instances_collection.rb +44 -0
  55. data/lib/aws/ec2/reserved_instances_offering.rb +55 -0
  56. data/lib/aws/ec2/reserved_instances_offering_collection.rb +43 -0
  57. data/lib/aws/ec2/resource.rb +340 -0
  58. data/lib/aws/ec2/resource_tag_collection.rb +218 -0
  59. data/lib/aws/ec2/security_group.rb +246 -0
  60. data/lib/aws/ec2/security_group/ip_permission.rb +70 -0
  61. data/lib/aws/ec2/security_group/ip_permission_collection.rb +59 -0
  62. data/lib/aws/ec2/security_group_collection.rb +132 -0
  63. data/lib/aws/ec2/snapshot.rb +138 -0
  64. data/lib/aws/ec2/snapshot_collection.rb +90 -0
  65. data/lib/aws/ec2/tag.rb +88 -0
  66. data/lib/aws/ec2/tag_collection.rb +114 -0
  67. data/lib/aws/ec2/tagged_collection.rb +48 -0
  68. data/lib/aws/ec2/tagged_item.rb +87 -0
  69. data/lib/aws/ec2/volume.rb +190 -0
  70. data/lib/aws/ec2/volume_collection.rb +95 -0
  71. data/lib/aws/errors.rb +129 -0
  72. data/lib/aws/http/builtin_handler.rb +69 -0
  73. data/lib/aws/http/curb_handler.rb +123 -0
  74. data/lib/aws/http/handler.rb +77 -0
  75. data/lib/aws/http/httparty_handler.rb +61 -0
  76. data/lib/aws/http/request.rb +136 -0
  77. data/lib/aws/http/request_param.rb +63 -0
  78. data/lib/aws/http/response.rb +75 -0
  79. data/lib/aws/ignore_result_element.rb +38 -0
  80. data/lib/aws/indifferent_hash.rb +86 -0
  81. data/lib/aws/inflection.rb +46 -0
  82. data/lib/aws/lazy_error_classes.rb +64 -0
  83. data/lib/aws/meta_utils.rb +43 -0
  84. data/lib/aws/model.rb +57 -0
  85. data/lib/aws/naming.rb +32 -0
  86. data/lib/aws/option_grammar.rb +544 -0
  87. data/lib/aws/policy.rb +912 -0
  88. data/lib/aws/rails.rb +209 -0
  89. data/lib/aws/record.rb +79 -0
  90. data/lib/aws/record/attribute.rb +94 -0
  91. data/lib/aws/record/attribute_macros.rb +288 -0
  92. data/lib/aws/record/attributes/boolean.rb +49 -0
  93. data/lib/aws/record/attributes/datetime.rb +86 -0
  94. data/lib/aws/record/attributes/float.rb +48 -0
  95. data/lib/aws/record/attributes/integer.rb +68 -0
  96. data/lib/aws/record/attributes/sortable_float.rb +60 -0
  97. data/lib/aws/record/attributes/sortable_integer.rb +95 -0
  98. data/lib/aws/record/attributes/string.rb +69 -0
  99. data/lib/aws/record/base.rb +728 -0
  100. data/lib/aws/record/conversion.rb +38 -0
  101. data/lib/aws/record/dirty_tracking.rb +286 -0
  102. data/lib/aws/record/errors.rb +153 -0
  103. data/lib/aws/record/exceptions.rb +48 -0
  104. data/lib/aws/record/finder_methods.rb +262 -0
  105. data/lib/aws/record/naming.rb +31 -0
  106. data/lib/aws/record/scope.rb +157 -0
  107. data/lib/aws/record/validations.rb +653 -0
  108. data/lib/aws/record/validator.rb +237 -0
  109. data/lib/aws/record/validators/acceptance.rb +51 -0
  110. data/lib/aws/record/validators/block.rb +38 -0
  111. data/lib/aws/record/validators/confirmation.rb +43 -0
  112. data/lib/aws/record/validators/count.rb +108 -0
  113. data/lib/aws/record/validators/exclusion.rb +43 -0
  114. data/lib/aws/record/validators/format.rb +57 -0
  115. data/lib/aws/record/validators/inclusion.rb +56 -0
  116. data/lib/aws/record/validators/length.rb +107 -0
  117. data/lib/aws/record/validators/numericality.rb +138 -0
  118. data/lib/aws/record/validators/presence.rb +45 -0
  119. data/lib/aws/resource_cache.rb +39 -0
  120. data/lib/aws/response.rb +113 -0
  121. data/lib/aws/response_cache.rb +50 -0
  122. data/lib/aws/s3.rb +109 -0
  123. data/lib/aws/s3/access_control_list.rb +252 -0
  124. data/lib/aws/s3/acl_object.rb +266 -0
  125. data/lib/aws/s3/bucket.rb +320 -0
  126. data/lib/aws/s3/bucket_collection.rb +122 -0
  127. data/lib/aws/s3/bucket_version_collection.rb +85 -0
  128. data/lib/aws/s3/client.rb +999 -0
  129. data/lib/aws/s3/client/xml.rb +190 -0
  130. data/lib/aws/s3/data_options.rb +99 -0
  131. data/lib/aws/s3/errors.rb +43 -0
  132. data/lib/aws/s3/multipart_upload.rb +318 -0
  133. data/lib/aws/s3/multipart_upload_collection.rb +78 -0
  134. data/lib/aws/s3/object_collection.rb +159 -0
  135. data/lib/aws/s3/object_metadata.rb +67 -0
  136. data/lib/aws/s3/object_upload_collection.rb +83 -0
  137. data/lib/aws/s3/object_version.rb +141 -0
  138. data/lib/aws/s3/object_version_collection.rb +78 -0
  139. data/lib/aws/s3/paginated_collection.rb +94 -0
  140. data/lib/aws/s3/policy.rb +76 -0
  141. data/lib/aws/s3/prefix_and_delimiter_collection.rb +56 -0
  142. data/lib/aws/s3/prefixed_collection.rb +84 -0
  143. data/lib/aws/s3/presigned_post.rb +504 -0
  144. data/lib/aws/s3/request.rb +198 -0
  145. data/lib/aws/s3/s3_object.rb +794 -0
  146. data/lib/aws/s3/tree.rb +116 -0
  147. data/lib/aws/s3/tree/branch_node.rb +71 -0
  148. data/lib/aws/s3/tree/child_collection.rb +108 -0
  149. data/lib/aws/s3/tree/leaf_node.rb +99 -0
  150. data/lib/aws/s3/tree/node.rb +22 -0
  151. data/lib/aws/s3/tree/parent.rb +90 -0
  152. data/lib/aws/s3/uploaded_part.rb +82 -0
  153. data/lib/aws/s3/uploaded_part_collection.rb +86 -0
  154. data/lib/aws/service_interface.rb +60 -0
  155. data/lib/aws/simple_db.rb +202 -0
  156. data/lib/aws/simple_db/attribute.rb +159 -0
  157. data/lib/aws/simple_db/attribute_collection.rb +227 -0
  158. data/lib/aws/simple_db/client.rb +52 -0
  159. data/lib/aws/simple_db/client/options.rb +34 -0
  160. data/lib/aws/simple_db/client/xml.rb +68 -0
  161. data/lib/aws/simple_db/consistent_read_option.rb +42 -0
  162. data/lib/aws/simple_db/delete_attributes.rb +64 -0
  163. data/lib/aws/simple_db/domain.rb +118 -0
  164. data/lib/aws/simple_db/domain_collection.rb +116 -0
  165. data/lib/aws/simple_db/domain_metadata.rb +112 -0
  166. data/lib/aws/simple_db/errors.rb +46 -0
  167. data/lib/aws/simple_db/expect_condition_option.rb +45 -0
  168. data/lib/aws/simple_db/item.rb +84 -0
  169. data/lib/aws/simple_db/item_collection.rb +594 -0
  170. data/lib/aws/simple_db/item_data.rb +70 -0
  171. data/lib/aws/simple_db/put_attributes.rb +62 -0
  172. data/lib/aws/simple_db/request.rb +27 -0
  173. data/lib/aws/simple_email_service.rb +373 -0
  174. data/lib/aws/simple_email_service/client.rb +39 -0
  175. data/lib/aws/simple_email_service/client/options.rb +24 -0
  176. data/lib/aws/simple_email_service/client/xml.rb +38 -0
  177. data/lib/aws/simple_email_service/email_address_collection.rb +66 -0
  178. data/lib/aws/simple_email_service/errors.rb +29 -0
  179. data/lib/aws/simple_email_service/quotas.rb +64 -0
  180. data/lib/aws/simple_email_service/request.rb +27 -0
  181. data/lib/aws/sns.rb +69 -0
  182. data/lib/aws/sns/client.rb +37 -0
  183. data/lib/aws/sns/client/options.rb +24 -0
  184. data/lib/aws/sns/client/xml.rb +38 -0
  185. data/lib/aws/sns/errors.rb +29 -0
  186. data/lib/aws/sns/policy.rb +49 -0
  187. data/lib/aws/sns/request.rb +27 -0
  188. data/lib/aws/sns/subscription.rb +100 -0
  189. data/lib/aws/sns/subscription_collection.rb +84 -0
  190. data/lib/aws/sns/topic.rb +384 -0
  191. data/lib/aws/sns/topic_collection.rb +70 -0
  192. data/lib/aws/sns/topic_subscription_collection.rb +58 -0
  193. data/lib/aws/sqs.rb +70 -0
  194. data/lib/aws/sqs/client.rb +38 -0
  195. data/lib/aws/sqs/client/xml.rb +36 -0
  196. data/lib/aws/sqs/errors.rb +33 -0
  197. data/lib/aws/sqs/policy.rb +50 -0
  198. data/lib/aws/sqs/queue.rb +507 -0
  199. data/lib/aws/sqs/queue_collection.rb +105 -0
  200. data/lib/aws/sqs/received_message.rb +184 -0
  201. data/lib/aws/sqs/received_sns_message.rb +112 -0
  202. data/lib/aws/sqs/request.rb +44 -0
  203. data/lib/aws/xml_grammar.rb +923 -0
  204. data/rails/init.rb +15 -0
  205. metadata +298 -0
@@ -0,0 +1,594 @@
1
+ # Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'aws/model'
15
+ require 'aws/simple_db/item'
16
+ require 'aws/simple_db/item_data'
17
+ require 'aws/simple_db/consistent_read_option'
18
+
19
+ # for 1.8.6
20
+ require 'enumerator'
21
+
22
+ module AWS
23
+ class SimpleDB
24
+
25
+ # Represents a collection of items in a SimpleDB domain.
26
+ class ItemCollection
27
+
28
+ include Model
29
+ include Enumerable
30
+ include ConsistentReadOption
31
+
32
+ # @private
33
+ attr_reader :conditions
34
+
35
+ # @private
36
+ attr_reader :sort_instructions
37
+
38
+ # @param [Domain] domain The domain that you want an item collection for.
39
+ # @return [ItemCollection]
40
+ def initialize domain, options = {}
41
+ @domain = domain
42
+ @conditions = []
43
+ @conditions += options[:conditions] if options[:conditions]
44
+ @sort_instructions = options[:sort_instructions] if options[:sort_instructions]
45
+ @not_null_attribute = options[:not_null_attribute]
46
+ @limit = options[:limit] if options[:limit]
47
+ super
48
+ end
49
+
50
+ # @return [Domain] The domain the items belong to.
51
+ attr_reader :domain
52
+
53
+ # Creates a new item in SimpleDB with the given attributes:
54
+ #
55
+ # domain.items.create('shirt', {
56
+ # 'colors' => ['red', 'blue'],
57
+ # 'category' => 'clearance'})
58
+ #
59
+ # @overload create(item_name, attribute_hash)
60
+ # @param [String] item_name The name of the item as you want it stored
61
+ # in SimpleDB.
62
+ # @param [Hash] attribute_hash A hash of attribute names and values
63
+ # you want to store in SimpleDB.
64
+ # @return [Item] Returns a reference to the object that was created.
65
+ def create item_name, *args
66
+ item = self[item_name]
67
+ item.attributes.replace(*args)
68
+ item
69
+ end
70
+
71
+ # Retuns an item with the given name.
72
+ #
73
+ # @note This does not make a request to SimpleDB.
74
+ #
75
+ # You can ask for any item. The named item may or may not actually
76
+ # exist in SimpleDB.
77
+ #
78
+ # @example Get an item by symbol or string name
79
+ #
80
+ # item = domain.items[:itemname]
81
+ # item = domain.items['itemname']
82
+ #
83
+ # @param [String, Symbol] item_name name of the item to get.
84
+ # @return [Item] Returns an item with the given name.
85
+ def [] item_name
86
+ Item.new(domain, item_name.to_s)
87
+ end
88
+
89
+ # Yields to the block once for each item in the domain.
90
+ #
91
+ # @example using each to fetch every item in the domain.
92
+ #
93
+ # domain.items.each do |item|
94
+ # puts item.name
95
+ # end
96
+ #
97
+ # @yield [item] Yields once for every item in the {#domain}.
98
+ # @yieldparam [Item] item
99
+ # @param options (see #select)
100
+ # @option options (see #select)
101
+ # @option options [Symbol or Array] :select Causes this method
102
+ # to behave like {#select} and yield {ItemData} instead of
103
+ # {Item} instances.
104
+ # @option options :batch_size Specifies a maximum number of records
105
+ # to fetch from SimpleDB in a single request. SimpleDB may return
106
+ # fewer items than :batch_size per request, but never more.
107
+ # @return [nil]
108
+ def each options = {}, &block
109
+
110
+ return if handle_query_options(:each, options, &block)
111
+
112
+ if attributes = options.delete(:select)
113
+ return select(attributes, options, &block)
114
+ end
115
+
116
+ perform_select(options) do |response|
117
+ response.items.each do |item|
118
+ yield(self[item.name])
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ # Retrieves data from each item in the domain.
125
+ #
126
+ # domain.items.select('size', 'color')
127
+ #
128
+ # You may optionally filter by a set of conditions. For example,
129
+ # to retrieve the attributes of each of the top 100 items in order of
130
+ # descending popularity as an array of hashes, you could do:
131
+ #
132
+ # items.order(:popularity, :desc).limit(100).select do |data|
133
+ # puts data.to_yaml
134
+ # end
135
+ #
136
+ # You can select specific attributes; for example, to get
137
+ # all the unique colors in the collection you could do:
138
+ #
139
+ # colors = Set.new
140
+ # items.select(:color) {|data| colors += data.attributes["color"] }
141
+ #
142
+ # Finally, you can specify conditions, sort instructions, and
143
+ # a limit in the same method call:
144
+ #
145
+ # items.select(:color,
146
+ # :where => "rating > 4",
147
+ # :order => [:popularity, :desc],
148
+ # :limit => 100) do |data|
149
+ # puts "Data for #{data.name}: #{data.attributes.inspect}"
150
+ # end
151
+ #
152
+ # @overload select(*attribute_names, options = {}) &block
153
+ # @param *attributes [Symbol, String, or Array]
154
+ # The attributes to retrieve. This can be:
155
+ #
156
+ # * +:all+ to retrieve all attributes (the default).
157
+ # * a Symbol or String to retrieve a single attribute.
158
+ # * an array of Symbols or Strings to retrieve multiple attributes.
159
+ #
160
+ # For single attributes or arrays of attributes, the
161
+ # attribute name may contain any characters that are valid
162
+ # in a SimpleDB attribute name; this method will handle
163
+ # escaping them for inclusion in the query. Note that you
164
+ # cannot use this method to select the number of items; use
165
+ # {#count} instead.
166
+ #
167
+ # @param [Hash] options Options for querying the domain.
168
+ # @option options [Boolean] :consistent_read (false) Causes this
169
+ # method to yield the most current data in the domain.
170
+ # @option options :where Restricts the item collection using
171
+ # {#where} before querying.
172
+ # @option options :order Changes the order in which the items
173
+ # will be yielded (see {#order}).
174
+ # @option options :limit [Integer] The maximum number of
175
+ # items to fetch from SimpleDB. More than one request may be
176
+ # required to satisfy the limit.
177
+ # @option options :batch_size Specifies a maximum number of records
178
+ # to fetch from SimpleDB in a single request. SimpleDB may return
179
+ # fewer items than :batch_size per request, but never more.
180
+ # @return If no block is given, an enumerator is returned. If a block
181
+ # was passed then nil is returned.
182
+ def select *attributes, &block
183
+
184
+ options = attributes.last.is_a?(Hash) ? attributes.pop : {}
185
+
186
+ args = attributes + [options]
187
+
188
+ return if handle_query_options(:select, *args, &block)
189
+
190
+ unless block_given?
191
+ return Enumerator.new(self, :select, *args)
192
+ end
193
+
194
+ if attributes.empty?
195
+ output_list = '*'
196
+ #elsif attributes == ['*']
197
+ # output_list = '*'
198
+ else
199
+ output_list = [attributes].flatten.collect do |attr|
200
+ coerce_attribute(attr)
201
+ end.join(', ')
202
+ end
203
+
204
+ perform_select(options.merge(:output_list => output_list)) do |response|
205
+ response.items.each do |item|
206
+ yield(ItemData.new(:domain => domain, :response_object => item))
207
+ end
208
+ end
209
+
210
+ nil
211
+
212
+ end
213
+
214
+ # Counts the items in the collection.
215
+ #
216
+ # domain.items.count
217
+ #
218
+ # You can use this method to get the total number of items in
219
+ # the domain, or you can use it with {#where} to count a subset
220
+ # of items. For example, to count the items where the "color"
221
+ # attribute is "red":
222
+ #
223
+ # domain.items.where("color" => "red").count
224
+ #
225
+ # You can also limit the number of items searched using the
226
+ # {#limit} method. For example, to count the number of items up
227
+ # to 500:
228
+ #
229
+ # domain.items.limit(500).count
230
+ #
231
+ # @param [Hash] options Options for counting items.
232
+ #
233
+ # @option options [Boolean] :consistent_read (false) Causes this
234
+ # method to yield the most current data in the domain.
235
+ # @option options :where Restricts the item collection using
236
+ # {#where} before querying.
237
+ # @option options :limit [Integer] The maximum number of
238
+ # items to fetch from SimpleDB. More than one request may be
239
+ # required to satisfy the limit.
240
+ def count options = {}, &block
241
+ return if handle_query_options(:count, options, &block)
242
+
243
+ options = options.merge(:output_list => "count(*)")
244
+
245
+ count = 0
246
+ next_token = nil
247
+
248
+ while limit.nil? || count < limit and
249
+ response = select_request(options, next_token)
250
+
251
+ if domain_item = response.items.first and
252
+ count_attribute = domain_item.attributes.first
253
+ count += count_attribute.value.to_i
254
+ end
255
+
256
+ next_token = response.next_token
257
+ break unless next_token
258
+
259
+ end
260
+
261
+ count
262
+ end
263
+ alias_method :size, :count
264
+
265
+ # Identifies quoted regions in the string, giving access to
266
+ # the regions before and after each quoted region, for example:
267
+ # "? ? `foo?``bar?` ? 'foo?' ?".scan(OUTSIDE_QUOTES_REGEX)
268
+ # # => [["? ? ", "`foo?``bar?`", " ? "], ["", "'foo?'", " ?"]]
269
+ OUTSIDE_QUOTES_REGEX = Regexp.compile('([^\'"`]*)(`(?:[^`]*(?:``))*[^`]*`|'+
270
+ '\'(?:[^\']*(?:\'\'))*[^\']*\'|'+
271
+ '"(?:[^"]*(?:""))*[^"]*")([^\'`"]*)')
272
+
273
+ # Returns an item collection defined by the given conditions
274
+ # in addition to any conditions defined on this collection.
275
+ # For example:
276
+ #
277
+ # items = domain.items.where(:color => 'blue').
278
+ # where('engine_type is not null')
279
+ #
280
+ # # does SELECT itemName() FROM `mydomain`
281
+ # # WHERE color = "blue" AND engine_type is not null
282
+ # items.each { |i| ... }
283
+ #
284
+ # == Hash Conditions
285
+ #
286
+ # When +conditions+ is a hash, each entry produces a condition
287
+ # on the attribute named in the hash key. For example:
288
+ #
289
+ # # produces "WHERE `foo` = 'bar'"
290
+ # domain.items.where(:foo => 'bar')
291
+ #
292
+ # You can pass an array value to use an "IN" operator instead
293
+ # of "=":
294
+ #
295
+ # # produces "WHERE `foo` IN ('bar', 'baz')"
296
+ # domain.items.where(:foo => ['bar', 'baz'])
297
+ #
298
+ # You can also pass a range value to use a "BETWEEN" operator:
299
+ #
300
+ # # produces "WHERE `foo` BETWEEN 'bar' AND 'baz'
301
+ # domain.items.where(:foo => 'bar'..'baz')
302
+ #
303
+ # # produces "WHERE (`foo` >= 'bar' AND `foo` < 'baz')"
304
+ # domain.items.where(:foo => 'bar'...'baz')
305
+ #
306
+ # == Placeholders
307
+ #
308
+ # If +conditions+ is a string and "?" appears outside of any
309
+ # quoted part of the expression, +placeholers+ is expected to
310
+ # contain a value for each of the "?" characters in the
311
+ # expression. For example:
312
+ #
313
+ # # produces "WHERE foo like 'fred''s % value'"
314
+ # domain.items.where("foo like ?", "fred's % value")
315
+ #
316
+ # Array values are surrounded with parentheses when they are
317
+ # substituted for a placeholder:
318
+ #
319
+ # # produces "WHERE foo in ('1', '2')"
320
+ # domain.items.where("foo in ?", [1, 2])
321
+ #
322
+ # Note that no substitutions are made within a quoted region
323
+ # of the query:
324
+ #
325
+ # # produces "WHERE `foo?` = 'red'"
326
+ # domain.items.where("`foo?` = ?", "red")
327
+ #
328
+ # # produces "WHERE foo = 'fuzz?' AND bar = 'zap'"
329
+ # domain.items.where("foo = 'fuzz?' AND bar = ?", "zap")
330
+ #
331
+ # Also note that no attempt is made to correct for syntax:
332
+ #
333
+ # # produces "WHERE 'foo' = 'bar'", which is invalid
334
+ # domain.items.where("? = 'bar'", "foo")
335
+ #
336
+ # @return [ItemCollection] Returns a new item collection with the
337
+ # additional conditions.
338
+ def where(conditions, *substitutions)
339
+ case conditions
340
+ when String
341
+ conditions = [replace_placeholders(conditions, *substitutions)]
342
+ when Hash
343
+ conditions = conditions.map do |name, value|
344
+ name = coerce_attribute(name)
345
+ case value
346
+ when Array
347
+ "#{name} IN " + coerce_substitution(value)
348
+ when Range
349
+ if value.exclude_end?
350
+ "(#{name} >= #{coerce_substitution(value.begin)} AND " +
351
+ "#{name} < #{coerce_substitution(value.end)})"
352
+ else
353
+ "#{name} BETWEEN #{coerce_substitution(value.begin)} AND " +
354
+ coerce_substitution(value.end)
355
+ end
356
+ else
357
+ "#{name} = " + coerce_substitution(value)
358
+ end
359
+ end
360
+ end
361
+
362
+ collection_with(:conditions => self.conditions + conditions)
363
+ end
364
+
365
+ # Changes the order in which results are returned or yielded.
366
+ # For example, to get item names in descending order of
367
+ # popularity, you can do:
368
+ #
369
+ # domain.items.order(:popularity, :desc).map(&:name)
370
+ #
371
+ # @param attribute [String or Symbol] The attribute name to
372
+ # order by.
373
+ # @param order [String or Symbol] The desired order, which may be:
374
+ # * +asc+ or +ascending+ (the default)
375
+ # * +desc+ or +descending+
376
+ # @return [ItemCollection] Returns a new item collection with the
377
+ # given ordering logic.
378
+ def order(attribute, order = nil)
379
+ sort = coerce_attribute(attribute)
380
+ sort += " DESC" if order.to_s =~ /^desc(ending)?$/
381
+ sort += " ASC" if order.to_s =~ /^asc(ending)?$/
382
+ collection_with(:sort_instructions => sort,
383
+ :not_null_attribute => attribute.to_s)
384
+ end
385
+
386
+ # Limits the number of items that are returned or yielded.
387
+ # For example, to get the 100 most popular item names:
388
+ #
389
+ # domain.items.
390
+ # order(:popularity, :desc).
391
+ # limit(100).
392
+ # map(&:name)
393
+ #
394
+ # @overload limit
395
+ # @return [Integer] Returns the current limit for the collection.
396
+ # @overload limit(value)
397
+ # @return [ItemCollection] Returns a collection with the given limit.
398
+ def limit(*args)
399
+ return @limit if args.empty?
400
+ collection_with(:limit => Integer(args.first))
401
+ end
402
+
403
+ # turns e.g. each(:where => 'foo', ...) into where('foo').each(...)
404
+ # @private
405
+ protected
406
+ def handle_query_options(method, *args, &block)
407
+ options = args.pop if args.last.kind_of?(Hash)
408
+ if query_option = (options.keys & [:where, :order, :limit]).first
409
+ option_args = options[query_option]
410
+ option_args = [option_args] unless option_args.kind_of?(Array)
411
+ options.delete(query_option)
412
+ send(query_option, *option_args).
413
+ send(method, *(args + [options]), &block)
414
+ true
415
+ else
416
+ false
417
+ end
418
+ end
419
+
420
+ # @private
421
+ protected
422
+ def perform_select(options = {})
423
+
424
+ next_token = options[:next_token]
425
+ batch_size = options[:batch_size] ? Integer(options[:batch_size]) : nil
426
+ total = 0
427
+
428
+ begin
429
+
430
+ # if the user provided a batch size we need to rewrite the
431
+ # select expression's LIMIT clause.
432
+ if batch_size
433
+ max = limit ? [limit - total, batch_size].min : batch_size
434
+ else
435
+ max = nil
436
+ end
437
+
438
+ response = select_request(options, next_token, max)
439
+
440
+ yield(response)
441
+
442
+ next_token = response.next_token
443
+
444
+ total += response.items.size
445
+
446
+ end while next_token && (limit.nil? || total < limit)
447
+ end
448
+
449
+ protected
450
+ def select_request(options, next_token = nil, limit = nil)
451
+ opts = {}
452
+ opts[:select_expression] = select_expression(options[:output_list])
453
+ opts[:consistent_read] = consistent_read(options)
454
+ opts[:next_token] = next_token if next_token
455
+
456
+ if limit
457
+ unless opts[:select_expression].gsub!(/LIMIT \d+/, "LIMIT #{limit}")
458
+ opts[:select_expression] << " LIMIT #{limit}"
459
+ end
460
+ end
461
+
462
+ client.select(opts)
463
+ end
464
+
465
+ # @private
466
+ protected
467
+ def select_expression(output_list = nil)
468
+ output_list ||= "itemName()"
469
+ "SELECT #{output_list} FROM `#{domain.name}`" +
470
+ where_clause + order_by_clause + limit_clause
471
+ end
472
+
473
+ # @private
474
+ protected
475
+ def limit_clause
476
+ if limit
477
+ " LIMIT #{limit}"
478
+ else
479
+ ""
480
+ end
481
+ end
482
+
483
+ # @private
484
+ protected
485
+ def where_clause
486
+ all_conditions = conditions.dup
487
+ if @not_null_attribute
488
+ all_conditions << coerce_attribute(@not_null_attribute) + " IS NOT NULL"
489
+ end
490
+ if all_conditions.empty?
491
+ ""
492
+ else
493
+ " WHERE " + all_conditions.join(" AND ")
494
+ end
495
+ end
496
+
497
+ # @private
498
+ protected
499
+ def order_by_clause
500
+ if sort_instructions
501
+ " ORDER BY " + sort_instructions
502
+ else
503
+ ""
504
+ end
505
+ end
506
+
507
+ # @private
508
+ protected
509
+ def collection_with(opts)
510
+ ItemCollection.new(domain, {
511
+ :limit => limit,
512
+ :conditions => conditions,
513
+ :sort_instructions => sort_instructions }.merge(opts))
514
+ end
515
+
516
+ # @private
517
+ protected
518
+ def replace_placeholders(str, *substitutions)
519
+ named = {}
520
+ named = substitutions.pop if substitutions.last.kind_of?(Hash)
521
+ if str =~ /['"`]/
522
+ count = 0
523
+ str = str.scan(OUTSIDE_QUOTES_REGEX).
524
+ map do |(before, quoted, after)|
525
+
526
+ (before, after) = [before, after].map do |s|
527
+ s, count =
528
+ replace_placeholders_outside_quotes(s, count, substitutions, named)
529
+ s
530
+ end
531
+ [before, quoted, after].join
532
+ end.join
533
+ else
534
+ # no quotes
535
+ str, count =
536
+ replace_placeholders_outside_quotes(str, 0, substitutions, named)
537
+ end
538
+ raise ArgumentError.new("extra value(s): #{substitutions.inspect}") unless
539
+ substitutions.empty?
540
+ str
541
+ end
542
+
543
+ # @private
544
+ protected
545
+ def replace_placeholders_outside_quotes(str, count, substitutions, named = {})
546
+ str, count = replace_positional_placeders(str, count, substitutions)
547
+ str = replace_named_placeholders(str, named)
548
+ [str, count]
549
+ end
550
+
551
+ # @private
552
+ protected
553
+ def replace_positional_placeders(str, count, substitutions)
554
+ str = str.gsub("?") do |placeholder|
555
+ count += 1
556
+ raise ArgumentError.new("missing value for placeholder #{count}") if
557
+ substitutions.empty?
558
+ coerce_substitution(substitutions.shift)
559
+ end
560
+ [str, count]
561
+ end
562
+
563
+ # @private
564
+ protected
565
+ def replace_named_placeholders(str, named)
566
+ named.each do |name, value|
567
+ str = str.gsub(name.to_sym.inspect, coerce_substitution(value))
568
+ end
569
+ str.scan(/:\S+/) do |missing|
570
+ raise ArgumentError.new("missing value for placeholder #{missing}")
571
+ end
572
+ str
573
+ end
574
+
575
+ # @private
576
+ protected
577
+ def coerce_substitution(subst)
578
+ if subst.kind_of?(Array)
579
+ "(" +
580
+ subst.flatten.map { |s| coerce_substitution(s) }.join(", ") + ")"
581
+ else
582
+ "'" + subst.to_s.gsub("'", "''") + "'"
583
+ end
584
+ end
585
+
586
+ # @private
587
+ protected
588
+ def coerce_attribute(name)
589
+ '`' + name.to_s.gsub('`', '``') + '`'
590
+ end
591
+
592
+ end
593
+ end
594
+ end