aws-sdk 1.0.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.
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