aws-sdk 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +6 -0
- data/LICENSE.txt +171 -0
- data/NOTICE.txt +2 -0
- data/README.rdoc +189 -0
- data/lib/aws-sdk.rb +14 -0
- data/lib/aws.rb +63 -0
- data/lib/aws/api_config.rb +45 -0
- data/lib/aws/api_config/.document +0 -0
- data/lib/aws/api_config/EC2-2011-02-28.yml +2314 -0
- data/lib/aws/api_config/SNS-2010-03-31.yml +171 -0
- data/lib/aws/api_config/SQS-2009-02-01.yml +161 -0
- data/lib/aws/api_config/SimpleDB-2009-04-15.yml +278 -0
- data/lib/aws/api_config/SimpleEmailService-2010-12-01.yml +147 -0
- data/lib/aws/api_config_transform.rb +32 -0
- data/lib/aws/async_handle.rb +90 -0
- data/lib/aws/authorize_v2.rb +37 -0
- data/lib/aws/authorize_v3.rb +37 -0
- data/lib/aws/base_client.rb +524 -0
- data/lib/aws/cacheable.rb +92 -0
- data/lib/aws/common.rb +228 -0
- data/lib/aws/configurable.rb +36 -0
- data/lib/aws/configuration.rb +272 -0
- data/lib/aws/configured_client_methods.rb +81 -0
- data/lib/aws/configured_grammars.rb +65 -0
- data/lib/aws/configured_option_grammars.rb +46 -0
- data/lib/aws/configured_xml_grammars.rb +47 -0
- data/lib/aws/default_signer.rb +38 -0
- data/lib/aws/ec2.rb +321 -0
- data/lib/aws/ec2/attachment.rb +149 -0
- data/lib/aws/ec2/attachment_collection.rb +57 -0
- data/lib/aws/ec2/availability_zone.rb +80 -0
- data/lib/aws/ec2/availability_zone_collection.rb +47 -0
- data/lib/aws/ec2/block_device_mappings.rb +53 -0
- data/lib/aws/ec2/client.rb +54 -0
- data/lib/aws/ec2/client/xml.rb +127 -0
- data/lib/aws/ec2/collection.rb +39 -0
- data/lib/aws/ec2/config_transform.rb +63 -0
- data/lib/aws/ec2/elastic_ip.rb +107 -0
- data/lib/aws/ec2/elastic_ip_collection.rb +85 -0
- data/lib/aws/ec2/errors.rb +29 -0
- data/lib/aws/ec2/filtered_collection.rb +65 -0
- data/lib/aws/ec2/has_permissions.rb +46 -0
- data/lib/aws/ec2/image.rb +245 -0
- data/lib/aws/ec2/image_collection.rb +235 -0
- data/lib/aws/ec2/instance.rb +515 -0
- data/lib/aws/ec2/instance_collection.rb +276 -0
- data/lib/aws/ec2/key_pair.rb +86 -0
- data/lib/aws/ec2/key_pair_collection.rb +102 -0
- data/lib/aws/ec2/permission_collection.rb +177 -0
- data/lib/aws/ec2/region.rb +81 -0
- data/lib/aws/ec2/region_collection.rb +55 -0
- data/lib/aws/ec2/request.rb +27 -0
- data/lib/aws/ec2/reserved_instances.rb +50 -0
- data/lib/aws/ec2/reserved_instances_collection.rb +44 -0
- data/lib/aws/ec2/reserved_instances_offering.rb +55 -0
- data/lib/aws/ec2/reserved_instances_offering_collection.rb +43 -0
- data/lib/aws/ec2/resource.rb +340 -0
- data/lib/aws/ec2/resource_tag_collection.rb +218 -0
- data/lib/aws/ec2/security_group.rb +246 -0
- data/lib/aws/ec2/security_group/ip_permission.rb +70 -0
- data/lib/aws/ec2/security_group/ip_permission_collection.rb +59 -0
- data/lib/aws/ec2/security_group_collection.rb +132 -0
- data/lib/aws/ec2/snapshot.rb +138 -0
- data/lib/aws/ec2/snapshot_collection.rb +90 -0
- data/lib/aws/ec2/tag.rb +88 -0
- data/lib/aws/ec2/tag_collection.rb +114 -0
- data/lib/aws/ec2/tagged_collection.rb +48 -0
- data/lib/aws/ec2/tagged_item.rb +87 -0
- data/lib/aws/ec2/volume.rb +190 -0
- data/lib/aws/ec2/volume_collection.rb +95 -0
- data/lib/aws/errors.rb +129 -0
- data/lib/aws/http/builtin_handler.rb +69 -0
- data/lib/aws/http/curb_handler.rb +123 -0
- data/lib/aws/http/handler.rb +77 -0
- data/lib/aws/http/httparty_handler.rb +61 -0
- data/lib/aws/http/request.rb +136 -0
- data/lib/aws/http/request_param.rb +63 -0
- data/lib/aws/http/response.rb +75 -0
- data/lib/aws/ignore_result_element.rb +38 -0
- data/lib/aws/indifferent_hash.rb +86 -0
- data/lib/aws/inflection.rb +46 -0
- data/lib/aws/lazy_error_classes.rb +64 -0
- data/lib/aws/meta_utils.rb +43 -0
- data/lib/aws/model.rb +57 -0
- data/lib/aws/naming.rb +32 -0
- data/lib/aws/option_grammar.rb +544 -0
- data/lib/aws/policy.rb +912 -0
- data/lib/aws/rails.rb +209 -0
- data/lib/aws/record.rb +79 -0
- data/lib/aws/record/attribute.rb +94 -0
- data/lib/aws/record/attribute_macros.rb +288 -0
- data/lib/aws/record/attributes/boolean.rb +49 -0
- data/lib/aws/record/attributes/datetime.rb +86 -0
- data/lib/aws/record/attributes/float.rb +48 -0
- data/lib/aws/record/attributes/integer.rb +68 -0
- data/lib/aws/record/attributes/sortable_float.rb +60 -0
- data/lib/aws/record/attributes/sortable_integer.rb +95 -0
- data/lib/aws/record/attributes/string.rb +69 -0
- data/lib/aws/record/base.rb +728 -0
- data/lib/aws/record/conversion.rb +38 -0
- data/lib/aws/record/dirty_tracking.rb +286 -0
- data/lib/aws/record/errors.rb +153 -0
- data/lib/aws/record/exceptions.rb +48 -0
- data/lib/aws/record/finder_methods.rb +262 -0
- data/lib/aws/record/naming.rb +31 -0
- data/lib/aws/record/scope.rb +157 -0
- data/lib/aws/record/validations.rb +653 -0
- data/lib/aws/record/validator.rb +237 -0
- data/lib/aws/record/validators/acceptance.rb +51 -0
- data/lib/aws/record/validators/block.rb +38 -0
- data/lib/aws/record/validators/confirmation.rb +43 -0
- data/lib/aws/record/validators/count.rb +108 -0
- data/lib/aws/record/validators/exclusion.rb +43 -0
- data/lib/aws/record/validators/format.rb +57 -0
- data/lib/aws/record/validators/inclusion.rb +56 -0
- data/lib/aws/record/validators/length.rb +107 -0
- data/lib/aws/record/validators/numericality.rb +138 -0
- data/lib/aws/record/validators/presence.rb +45 -0
- data/lib/aws/resource_cache.rb +39 -0
- data/lib/aws/response.rb +113 -0
- data/lib/aws/response_cache.rb +50 -0
- data/lib/aws/s3.rb +109 -0
- data/lib/aws/s3/access_control_list.rb +252 -0
- data/lib/aws/s3/acl_object.rb +266 -0
- data/lib/aws/s3/bucket.rb +320 -0
- data/lib/aws/s3/bucket_collection.rb +122 -0
- data/lib/aws/s3/bucket_version_collection.rb +85 -0
- data/lib/aws/s3/client.rb +999 -0
- data/lib/aws/s3/client/xml.rb +190 -0
- data/lib/aws/s3/data_options.rb +99 -0
- data/lib/aws/s3/errors.rb +43 -0
- data/lib/aws/s3/multipart_upload.rb +318 -0
- data/lib/aws/s3/multipart_upload_collection.rb +78 -0
- data/lib/aws/s3/object_collection.rb +159 -0
- data/lib/aws/s3/object_metadata.rb +67 -0
- data/lib/aws/s3/object_upload_collection.rb +83 -0
- data/lib/aws/s3/object_version.rb +141 -0
- data/lib/aws/s3/object_version_collection.rb +78 -0
- data/lib/aws/s3/paginated_collection.rb +94 -0
- data/lib/aws/s3/policy.rb +76 -0
- data/lib/aws/s3/prefix_and_delimiter_collection.rb +56 -0
- data/lib/aws/s3/prefixed_collection.rb +84 -0
- data/lib/aws/s3/presigned_post.rb +504 -0
- data/lib/aws/s3/request.rb +198 -0
- data/lib/aws/s3/s3_object.rb +794 -0
- data/lib/aws/s3/tree.rb +116 -0
- data/lib/aws/s3/tree/branch_node.rb +71 -0
- data/lib/aws/s3/tree/child_collection.rb +108 -0
- data/lib/aws/s3/tree/leaf_node.rb +99 -0
- data/lib/aws/s3/tree/node.rb +22 -0
- data/lib/aws/s3/tree/parent.rb +90 -0
- data/lib/aws/s3/uploaded_part.rb +82 -0
- data/lib/aws/s3/uploaded_part_collection.rb +86 -0
- data/lib/aws/service_interface.rb +60 -0
- data/lib/aws/simple_db.rb +202 -0
- data/lib/aws/simple_db/attribute.rb +159 -0
- data/lib/aws/simple_db/attribute_collection.rb +227 -0
- data/lib/aws/simple_db/client.rb +52 -0
- data/lib/aws/simple_db/client/options.rb +34 -0
- data/lib/aws/simple_db/client/xml.rb +68 -0
- data/lib/aws/simple_db/consistent_read_option.rb +42 -0
- data/lib/aws/simple_db/delete_attributes.rb +64 -0
- data/lib/aws/simple_db/domain.rb +118 -0
- data/lib/aws/simple_db/domain_collection.rb +116 -0
- data/lib/aws/simple_db/domain_metadata.rb +112 -0
- data/lib/aws/simple_db/errors.rb +46 -0
- data/lib/aws/simple_db/expect_condition_option.rb +45 -0
- data/lib/aws/simple_db/item.rb +84 -0
- data/lib/aws/simple_db/item_collection.rb +594 -0
- data/lib/aws/simple_db/item_data.rb +70 -0
- data/lib/aws/simple_db/put_attributes.rb +62 -0
- data/lib/aws/simple_db/request.rb +27 -0
- data/lib/aws/simple_email_service.rb +373 -0
- data/lib/aws/simple_email_service/client.rb +39 -0
- data/lib/aws/simple_email_service/client/options.rb +24 -0
- data/lib/aws/simple_email_service/client/xml.rb +38 -0
- data/lib/aws/simple_email_service/email_address_collection.rb +66 -0
- data/lib/aws/simple_email_service/errors.rb +29 -0
- data/lib/aws/simple_email_service/quotas.rb +64 -0
- data/lib/aws/simple_email_service/request.rb +27 -0
- data/lib/aws/sns.rb +69 -0
- data/lib/aws/sns/client.rb +37 -0
- data/lib/aws/sns/client/options.rb +24 -0
- data/lib/aws/sns/client/xml.rb +38 -0
- data/lib/aws/sns/errors.rb +29 -0
- data/lib/aws/sns/policy.rb +49 -0
- data/lib/aws/sns/request.rb +27 -0
- data/lib/aws/sns/subscription.rb +100 -0
- data/lib/aws/sns/subscription_collection.rb +84 -0
- data/lib/aws/sns/topic.rb +384 -0
- data/lib/aws/sns/topic_collection.rb +70 -0
- data/lib/aws/sns/topic_subscription_collection.rb +58 -0
- data/lib/aws/sqs.rb +70 -0
- data/lib/aws/sqs/client.rb +38 -0
- data/lib/aws/sqs/client/xml.rb +36 -0
- data/lib/aws/sqs/errors.rb +33 -0
- data/lib/aws/sqs/policy.rb +50 -0
- data/lib/aws/sqs/queue.rb +507 -0
- data/lib/aws/sqs/queue_collection.rb +105 -0
- data/lib/aws/sqs/received_message.rb +184 -0
- data/lib/aws/sqs/received_sns_message.rb +112 -0
- data/lib/aws/sqs/request.rb +44 -0
- data/lib/aws/xml_grammar.rb +923 -0
- data/rails/init.rb +15 -0
- 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
|