aws-sdk 1.2.6 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/lib/aws.rb +2 -0
  2. data/lib/aws/api_config/DynamoDB-2011-12-05.yml +721 -0
  3. data/lib/aws/core.rb +10 -1
  4. data/lib/aws/core/client.rb +17 -12
  5. data/lib/aws/core/configuration.rb +13 -3
  6. data/lib/aws/core/configured_json_client_methods.rb +71 -0
  7. data/lib/aws/core/lazy_error_classes.rb +7 -2
  8. data/lib/aws/core/option_grammar.rb +67 -13
  9. data/lib/aws/core/resource.rb +9 -1
  10. data/lib/aws/core/session_signer.rb +95 -0
  11. data/lib/aws/dynamo_db.rb +169 -0
  12. data/lib/aws/dynamo_db/attribute_collection.rb +460 -0
  13. data/lib/aws/dynamo_db/batch_get.rb +206 -0
  14. data/lib/aws/dynamo_db/client.rb +119 -0
  15. data/lib/aws/dynamo_db/config.rb +20 -0
  16. data/lib/aws/dynamo_db/errors.rb +57 -0
  17. data/lib/aws/dynamo_db/expectations.rb +40 -0
  18. data/lib/aws/dynamo_db/item.rb +130 -0
  19. data/lib/aws/dynamo_db/item_collection.rb +837 -0
  20. data/lib/aws/{record/optimistic_locking.rb → dynamo_db/item_data.rb} +9 -12
  21. data/lib/aws/{record/attributes/boolean.rb → dynamo_db/keys.rb} +15 -23
  22. data/lib/aws/dynamo_db/primary_key_element.rb +47 -0
  23. data/lib/aws/dynamo_db/request.rb +78 -0
  24. data/lib/aws/{record/attributes/float.rb → dynamo_db/resource.rb} +10 -25
  25. data/lib/aws/dynamo_db/table.rb +418 -0
  26. data/lib/aws/dynamo_db/table_collection.rb +165 -0
  27. data/lib/aws/dynamo_db/types.rb +86 -0
  28. data/lib/aws/ec2/resource_tag_collection.rb +3 -1
  29. data/lib/aws/record.rb +36 -8
  30. data/lib/aws/record/abstract_base.rb +642 -0
  31. data/lib/aws/record/attributes.rb +384 -0
  32. data/lib/aws/record/dirty_tracking.rb +0 -1
  33. data/lib/aws/record/errors.rb +0 -8
  34. data/lib/aws/record/hash_model.rb +163 -0
  35. data/lib/aws/record/hash_model/attributes.rb +182 -0
  36. data/lib/aws/record/hash_model/finder_methods.rb +178 -0
  37. data/lib/aws/record/hash_model/scope.rb +108 -0
  38. data/lib/aws/record/model.rb +429 -0
  39. data/lib/aws/record/model/attributes.rb +377 -0
  40. data/lib/aws/record/model/finder_methods.rb +232 -0
  41. data/lib/aws/record/model/scope.rb +213 -0
  42. data/lib/aws/record/scope.rb +43 -169
  43. data/lib/aws/record/validations.rb +11 -11
  44. data/lib/aws/s3/client.rb +9 -6
  45. data/lib/aws/s3/object_collection.rb +1 -1
  46. data/lib/aws/simple_db/expect_condition_option.rb +1 -1
  47. data/lib/aws/simple_db/item_collection.rb +5 -3
  48. data/lib/aws/sts/client.rb +9 -0
  49. metadata +73 -30
  50. data/lib/aws/record/attribute.rb +0 -94
  51. data/lib/aws/record/attribute_macros.rb +0 -312
  52. data/lib/aws/record/attributes/date.rb +0 -89
  53. data/lib/aws/record/attributes/datetime.rb +0 -86
  54. data/lib/aws/record/attributes/integer.rb +0 -68
  55. data/lib/aws/record/attributes/sortable_float.rb +0 -60
  56. data/lib/aws/record/attributes/sortable_integer.rb +0 -95
  57. data/lib/aws/record/attributes/string.rb +0 -69
  58. data/lib/aws/record/base.rb +0 -828
  59. data/lib/aws/record/finder_methods.rb +0 -230
  60. data/lib/aws/record/scopes.rb +0 -55
@@ -0,0 +1,206 @@
1
+ # Copyright 2011-2012 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
+ module AWS
15
+ class DynamoDB
16
+
17
+ # A utility class for configuring a list of tables, attributes and
18
+ # items to request information for.
19
+ #
20
+ # @see DynamoDB#batch_get
21
+ # @see Table#batch_get
22
+ #
23
+ class BatchGet
24
+
25
+ include Keys
26
+ include Enumerable
27
+ include Core::Model
28
+
29
+ def initialize options = {}
30
+ super(options)
31
+ @request_items = {}
32
+ end
33
+
34
+ # Add a list of items to fetch in this batch.
35
+ #
36
+ # @param [String] table_name The name of the table to fetch attributes
37
+ # from.
38
+ #
39
+ # @param [Symbol, String, Array<String>] attributes The list of attributes
40
+ # to fetch. If you want to load *ALL* attributes for the named items,
41
+ # then pass the symbol +:all+.
42
+ #
43
+ # # get all attributes
44
+ # batch_get.table('mytable', :all, items)
45
+ #
46
+ # # get one attribute for each item
47
+ # batch_get.table('mytable', ['name'], items)
48
+ #
49
+ # # get a list of attributes for each item
50
+ # batch_get.table('mytable', ['name', 'size'], items)
51
+ #
52
+ # @param [Array<Item,Array>] items One or more items to fetch attributes
53
+ # for. Each attribute should be one of the following:
54
+ #
55
+ # * an {Item} object
56
+ # * a hash key value
57
+ # * a hash key value and a range key value
58
+ #
59
+ # You must provide both the hash key and range key values if the table
60
+ # schema has both.
61
+ #
62
+ # batch_get.table('mytable', :all, [%w(hk1 rk1), %w(hk1 rk2), ...])
63
+ #
64
+ # @return [nil]
65
+ #
66
+ def table table, attributes, items
67
+
68
+ table = table.is_a?(Table) ? table.name : table.to_s
69
+
70
+ attributes = attributes == :all ? nil : [attributes].flatten
71
+
72
+ keys = items.collect do |item|
73
+ case item
74
+ when Item then item_key_hash(item)
75
+ when Array
76
+ {
77
+ :hash_key_element => format_attribute_value(item[0]),
78
+ :range_key_element => format_attribute_value(item[1]),
79
+ }
80
+ else
81
+ { :hash_key_element => format_attribute_value(item) }
82
+ end
83
+ end
84
+
85
+ ## ensure we don't receive 2 different lists of attributes for
86
+ ## the same table
87
+
88
+ if
89
+ @request_items.has_key?(table) and
90
+ @request_items[table][:attributes_to_get] != attributes
91
+ then
92
+ msg = "When batch getting attributes, you may only provide " +
93
+ "1 list of attributes per table, but the `#{table}` table " +
94
+ "has received reqeusts for 2 different sets of attributes"
95
+ raise ArgumentError, msg
96
+ end
97
+
98
+ ## merge attributes and items with the request items
99
+
100
+ @request_items[table] ||= { :keys => [] }
101
+ @request_items[table][:attributes_to_get] = attributes if attributes
102
+ @request_items[table][:keys] += keys
103
+
104
+ nil
105
+
106
+ end
107
+
108
+ # Specify a list of {Item} objects to batch fetch attributes for.
109
+ # The table name is retrieved from the items objects, this means
110
+ # the items do not need to belong to the same table.
111
+ #
112
+ # @param [Symbol, String, Array<String>] attributes The list of attributes
113
+ # to fetch. If you want to load *ALL* attributes for the named items,
114
+ # then pass the symbol +:all+.
115
+ #
116
+ # # get all attributes
117
+ # batch_get.table('mytable', :all, items)
118
+ #
119
+ # # get one attribute for each item
120
+ # batch_get.table('mytable', ['name'], items)
121
+ #
122
+ # # get a list of attributes for each item
123
+ # batch_get.table('mytable', ['name', 'size'], items)
124
+ #
125
+ # @param [Item] items One or more {Item} objects to fetch attributes
126
+ # for. These items may come from any number of different tables.
127
+ #
128
+ def items attributes, *items
129
+ [items].flatten.each do |item|
130
+ self.table(item.table, attributes, [item])
131
+ end
132
+ end
133
+
134
+ # @return [nil]
135
+ def each &block
136
+
137
+ options = { :request_items => @request_items }
138
+
139
+ begin
140
+
141
+ response = client.batch_get_item(options)
142
+
143
+ response.data['Responses'].each_pair do |table_name,details|
144
+ details['Items'].each do |hash_data|
145
+ attributes = values_from_response_hash(hash_data)
146
+ yield(table_name, attributes)
147
+ end
148
+ end
149
+
150
+ options[:request_items] = convert_unprocessed_keys(response)
151
+
152
+ end while options[:request_items]
153
+
154
+ nil
155
+
156
+ end
157
+
158
+ # Yields only attribute hashes. This removes the outer hash that
159
+ # normally provides the :table_name and :attributes keys. This is
160
+ # useful when your batch get requested items from a single table.
161
+ def each_attributes
162
+ each do |table_name, attributes|
163
+ yield(attributes)
164
+ end
165
+ end
166
+
167
+ protected
168
+ def convert_unprocessed_keys response
169
+
170
+ return nil if response.data['UnprocessedKeys'].empty?
171
+
172
+ # convert the json response keys into symbolized keys
173
+ str2sym = lambda do |key_desc|
174
+ type, value = key_desc.to_a.flatten
175
+ case type
176
+ when "S" then { :s => value }
177
+ when "N" then { :n => value }
178
+ else
179
+ raise "unhandled key type: #{type}"
180
+ end
181
+ end
182
+
183
+ request_items = {}
184
+ response.data['UnprocessedKeys'].each_pair do |table,keys|
185
+
186
+ request_items[table] = {}
187
+
188
+ request_items[table][:attributes_to_get] = keys['AttributesToGet'] if
189
+ keys['AttributesToGet']
190
+
191
+ request_items[table][:keys] = keys['Keys'].collect do |desc|
192
+ key = {}
193
+ key[:hash_key_element] = str2sym.call(desc['HashKeyElement'])
194
+ key[:range_key_element] = str2sym.call(desc['RangeKeyElement']) if
195
+ desc['RangeKeyElement']
196
+ key
197
+ end
198
+
199
+ end
200
+ request_items
201
+
202
+ end
203
+
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,119 @@
1
+ # Copyright 2011-2012 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
+ module AWS
15
+ class DynamoDB
16
+
17
+ # @private
18
+ class Client < Core::Client
19
+
20
+ API_VERSION = '2011-06-01'
21
+
22
+ REGION_US_E1 = 'dynamodb.us-east-1.amazonaws.com'
23
+
24
+ TARGET_PREFIX = "DynamoDB_20111205."
25
+
26
+ REQUEST_CLASS = DynamoDB::Request
27
+
28
+ CACHEABLE_REQUESTS = Set[:list_tables, :describe_table]
29
+
30
+ include Core::ConfiguredJsonClientMethods
31
+
32
+ configure_client
33
+
34
+ def initialize *args
35
+
36
+ super
37
+
38
+ # If the signer does not provide a session token, then we will
39
+ # replace it with another signer that manages an AWS STS session.
40
+ # This session will auto renew whenever it has expired and will
41
+ # be shared across threads.
42
+ if config.signer.session_token.nil?
43
+ @signer = Core::SessionSigner.for(config)
44
+ end
45
+
46
+ end
47
+
48
+ protected
49
+ def new_request
50
+ req = super
51
+ req.headers["content-type"] = "application/x-amz-json-1.0"
52
+ req
53
+ end
54
+
55
+ protected
56
+ def extract_error_code response
57
+ if response.http_response.status == 413
58
+ 'RequestEntityTooLarge'
59
+ elsif response.http_response.status >= 300 and
60
+ body = response.http_response.body and
61
+ json = (JSON.load(body) rescue nil) and
62
+ type = json["__type"] and
63
+ type =~ /\#(.*)$/
64
+ $1
65
+ end
66
+ end
67
+
68
+ private
69
+ def should_retry? response
70
+ if possible_expired_credentials?(response)
71
+ true
72
+ elsif response.error.is_a?(Errors::ProvisionedThroughputExceededException)
73
+ config.dynamo_db_retry_throughput_errors?
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ private
80
+ def rebuild_http_request response
81
+ # called when a request is going to be retried, in case of
82
+ # expired credentials we should refresh the session
83
+ signer.refresh_session if possible_expired_credentials?(response)
84
+ super
85
+ end
86
+
87
+ private
88
+ def sleep_durations response
89
+
90
+ retry_count =
91
+ if possible_expired_credentials?(response)
92
+ config.max_retries == 0 ? 0 : 1
93
+ else
94
+ config.max_retries { 10 }
95
+ end
96
+
97
+ # given a retry_count of 10, the sleep durations will look like:
98
+ # 0, 50, 100, 200, 400, 800, 1600, 3200, 6400, 12800 (milliseconds)
99
+ (0...retry_count).map do |n|
100
+ if n == 0
101
+ 0
102
+ else
103
+ 50 * (2 ** (n - 1)) / 1000.0
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ private
110
+ # Returns true if we get an access denied error from the service AND
111
+ # our signer is capible of getting new short-term credentials
112
+ def possible_expired_credentials? response
113
+ signer.respond_to?(:refresh_session) and
114
+ response.error.is_a?(Errors::AccessDeniedException)
115
+ end
116
+
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,20 @@
1
+ # Copyright 2011-2012 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
+ AWS::Core::Configuration.module_eval do
15
+
16
+ add_service 'DynamoDB', 'dynamo_db', 'dynamodb.us-east-1.amazonaws.com'
17
+
18
+ add_option :dynamo_db_retry_throughput_errors, true, :boolean => true
19
+
20
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright 2011-2012 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
+ module AWS
15
+ class DynamoDB
16
+
17
+ module Errors
18
+
19
+ module ModeledError
20
+
21
+ def initialize(request = nil, response = nil)
22
+ message = extract_message(response)
23
+ include_error_type(response) if response
24
+ super(request, response, message)
25
+ end
26
+
27
+ def extract_message(response)
28
+ if response and response.body != ''
29
+ JSON.load(response.body)["message"] || code
30
+ elsif code == 'RequestEntityTooLarge'
31
+ 'Request body must be less than 1 MB'
32
+ else
33
+ code
34
+ end
35
+ end
36
+
37
+ def include_error_type(response)
38
+ if response.status >= 500
39
+ extend Errors::ServerError
40
+ else
41
+ extend Errors::ClientError
42
+ end
43
+ end
44
+
45
+ def code
46
+ self.class.name =~ /(::)?([^:]+)$/
47
+ $2
48
+ end
49
+
50
+ end
51
+
52
+ include Core::LazyErrorClasses
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ # Copyright 2011-2012 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
+ module AWS
15
+ class DynamoDB
16
+
17
+ module Expectations
18
+
19
+ private
20
+ def expect_conditions(options)
21
+ expected = {}
22
+
23
+ options[:if].each do |name, value|
24
+ context = "expected value for attribute #{name}"
25
+ expected[name.to_s] = {
26
+ :value => format_attribute_value(value, context)
27
+ }
28
+ end if options[:if]
29
+
30
+ [options[:unless_exists]].flatten.each do |name|
31
+ expected[name.to_s] = { :exists => false }
32
+ end if options[:unless_exists]
33
+
34
+ expected
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,130 @@
1
+ # Copyright 2011-2012 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
+ module AWS
15
+ class DynamoDB
16
+
17
+ # Represents a DynamoDB item. An item is identified by simple or
18
+ # complex primary key (according to the table schema) and consists
19
+ # of a collection of attributes. Attributes are name/value pairs
20
+ # where the value may be a string, number, string set, or number
21
+ # set.
22
+ #
23
+ # Getting an item by hash key value:
24
+ #
25
+ # item = table.items['hash-key-value']
26
+ #
27
+ # Getting an item from a table with both hash and range keys:
28
+ #
29
+ # item = table.items['hash-key','range-key']
30
+ #
31
+ class Item < Core::Resource
32
+
33
+ extend Types
34
+ include Keys
35
+ include Expectations
36
+
37
+ # @return [Table] The table in which the item is stored.
38
+ attr_reader :table
39
+
40
+ # @return [String, Numeric] The hash key value of the item.
41
+ attr_reader :hash_value
42
+
43
+ # @return [String, Numeric, nil] The range key value of the
44
+ # item, or +nil+ if the table has a simple primary key.
45
+ attr_reader :range_value
46
+
47
+ # @private
48
+ def initialize(table, *args)
49
+ opts = args.pop if args.last.kind_of?(Hash)
50
+ (@hash_value, @range_value) = args
51
+ @table = table
52
+ super(table, opts)
53
+ end
54
+
55
+ # Deletes the item.
56
+ #
57
+ # @param [Hash] options Options for deleting the item.
58
+ #
59
+ # @option options [Hash] :if Designates a conditional delete.
60
+ # The operation will fail unless the item exists and has the
61
+ # attributes in the value for this option. For example:
62
+ #
63
+ # # throws DynamoDB::Errors::ConditionalCheckFailedException
64
+ # # unless the item has "color" set to "red"
65
+ # item.delete(:if => { :color => "red" })
66
+ #
67
+ # @option options [String, Symbol, Array] :unless_exists A name
68
+ # or collection of attribute names; if the item has a value
69
+ # for any of these attributes, this method will raise
70
+ # +DynamoDB::Errors::ConditionalCheckFailedException+. For
71
+ # example:
72
+ #
73
+ # item.delete(:unless_exists => "version")
74
+ def delete(options = {})
75
+ client_opts = item_key_options(self)
76
+
77
+ expected = expect_conditions(options)
78
+ client_opts[:expected] = expected unless expected.empty?
79
+
80
+ client_opts[:return_values] = options[:return].to_s.upcase if
81
+ options[:return]
82
+
83
+ resp = client.delete_item(client_opts)
84
+
85
+ values_from_response_hash(resp.data["Attributes"]) if
86
+ options[:return] and resp.data["Attributes"]
87
+ end
88
+
89
+ # @return [Boolean] True if the item exists.
90
+ def exists?(options = {})
91
+ client_opts = item_key_options(self, options)
92
+ client_opts[:attributes_to_get] = [table.hash_key.name]
93
+ resp = client.get_item(client_opts)
94
+ resp.data.key?("Item")
95
+ end
96
+
97
+ # @return [AttributeCollection] An object representing the
98
+ # attributes of the item.
99
+ def attributes
100
+ AttributeCollection.new(self)
101
+ end
102
+
103
+ # @private
104
+ def self.new_from(op, response_object, table, *args)
105
+ table.assert_schema!
106
+ hash_value =
107
+ value_from_response(response_object[table.hash_key.name])
108
+ range_value =
109
+ value_from_response(response_object[table.range_key.name]) if
110
+ table.range_key
111
+
112
+ raise "missing hash key value in put_item response" unless hash_value
113
+ raise "missing range key value in put_item response" unless
114
+ range_value || !table.range_key
115
+
116
+ super(op, response_object,
117
+ table, hash_value, range_value, *args)
118
+ end
119
+
120
+ protected
121
+ def resource_identifiers
122
+ [[:table_name, table.name],
123
+ [:hash_value, hash_value],
124
+ [:range_value, range_value]]
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end