dynamoid 3.2.0 → 3.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +111 -1
- data/README.md +580 -241
- data/lib/dynamoid.rb +2 -0
- data/lib/dynamoid/adapter.rb +15 -15
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +82 -102
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +108 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +29 -16
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +3 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/backoff.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +2 -3
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/start_key.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +15 -6
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +15 -5
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb +1 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/until_past_table_status.rb +5 -3
- data/lib/dynamoid/application_time_zone.rb +1 -0
- data/lib/dynamoid/associations.rb +182 -19
- data/lib/dynamoid/associations/association.rb +4 -2
- data/lib/dynamoid/associations/belongs_to.rb +2 -1
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +2 -1
- data/lib/dynamoid/associations/has_many.rb +2 -1
- data/lib/dynamoid/associations/has_one.rb +2 -1
- data/lib/dynamoid/associations/many_association.rb +65 -22
- data/lib/dynamoid/associations/single_association.rb +28 -1
- data/lib/dynamoid/components.rb +8 -3
- data/lib/dynamoid/config.rb +16 -3
- data/lib/dynamoid/config/backoff_strategies/constant_backoff.rb +1 -0
- data/lib/dynamoid/config/backoff_strategies/exponential_backoff.rb +1 -0
- data/lib/dynamoid/config/options.rb +1 -0
- data/lib/dynamoid/criteria.rb +2 -1
- data/lib/dynamoid/criteria/chain.rb +418 -46
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +3 -3
- data/lib/dynamoid/criteria/key_fields_detector.rb +109 -32
- data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +3 -2
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +1 -1
- data/lib/dynamoid/dirty.rb +239 -32
- data/lib/dynamoid/document.rb +130 -251
- data/lib/dynamoid/dumping.rb +9 -0
- data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
- data/lib/dynamoid/fields.rb +246 -20
- data/lib/dynamoid/finders.rb +69 -32
- data/lib/dynamoid/identity_map.rb +6 -0
- data/lib/dynamoid/indexes.rb +76 -17
- data/lib/dynamoid/loadable.rb +31 -0
- data/lib/dynamoid/log/formatter.rb +26 -0
- data/lib/dynamoid/middleware/identity_map.rb +1 -0
- data/lib/dynamoid/persistence.rb +592 -122
- data/lib/dynamoid/persistence/import.rb +73 -0
- data/lib/dynamoid/persistence/save.rb +64 -0
- data/lib/dynamoid/persistence/update_fields.rb +63 -0
- data/lib/dynamoid/persistence/upsert.rb +60 -0
- data/lib/dynamoid/primary_key_type_mapping.rb +1 -0
- data/lib/dynamoid/railtie.rb +1 -0
- data/lib/dynamoid/tasks.rb +3 -1
- data/lib/dynamoid/tasks/database.rb +1 -0
- data/lib/dynamoid/type_casting.rb +12 -2
- data/lib/dynamoid/undumping.rb +8 -0
- data/lib/dynamoid/validations.rb +2 -0
- data/lib/dynamoid/version.rb +1 -1
- metadata +49 -71
- data/.coveralls.yml +0 -1
- data/.document +0 -5
- data/.gitignore +0 -74
- data/.rspec +0 -2
- data/.rubocop.yml +0 -71
- data/.rubocop_todo.yml +0 -55
- data/.travis.yml +0 -41
- data/Appraisals +0 -28
- data/Gemfile +0 -8
- data/Rakefile +0 -46
- data/Vagrantfile +0 -29
- data/docker-compose.yml +0 -7
- data/dynamoid.gemspec +0 -57
- data/gemfiles/rails_4_2.gemfile +0 -11
- data/gemfiles/rails_5_0.gemfile +0 -10
- data/gemfiles/rails_5_1.gemfile +0 -10
- data/gemfiles/rails_5_2.gemfile +0 -10
data/lib/dynamoid.rb
CHANGED
@@ -30,6 +30,7 @@ require 'dynamoid/criteria'
|
|
30
30
|
require 'dynamoid/finders'
|
31
31
|
require 'dynamoid/identity_map'
|
32
32
|
require 'dynamoid/config'
|
33
|
+
require 'dynamoid/loadable'
|
33
34
|
require 'dynamoid/components'
|
34
35
|
require 'dynamoid/document'
|
35
36
|
require 'dynamoid/adapter'
|
@@ -56,6 +57,7 @@ module Dynamoid
|
|
56
57
|
@included_models ||= []
|
57
58
|
end
|
58
59
|
|
60
|
+
# @private
|
59
61
|
def adapter
|
60
62
|
@adapter ||= Adapter.new
|
61
63
|
end
|
data/lib/dynamoid/adapter.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# require only 'concurrent/atom' once this issue is resolved:
|
4
4
|
# https://github.com/ruby-concurrency/concurrent-ruby/pull/377
|
5
5
|
require 'concurrent'
|
6
|
+
require 'dynamoid/adapter_plugin/aws_sdk_v3'
|
6
7
|
|
7
8
|
# encoding: utf-8
|
8
9
|
module Dynamoid
|
@@ -10,6 +11,7 @@ module Dynamoid
|
|
10
11
|
# 1) For the rest of Dynamoid, the gateway to DynamoDB.
|
11
12
|
# 2) Allows switching `config.adapter` to ease development of a new adapter.
|
12
13
|
# 3) Caches the list of tables Dynamoid knows about.
|
14
|
+
# @private
|
13
15
|
class Adapter
|
14
16
|
def initialize
|
15
17
|
@adapter_ = Concurrent::Atom.new(nil)
|
@@ -29,7 +31,7 @@ module Dynamoid
|
|
29
31
|
def adapter
|
30
32
|
unless @adapter_.value
|
31
33
|
adapter = self.class.adapter_plugin_class.new
|
32
|
-
adapter.connect!
|
34
|
+
adapter.connect!
|
33
35
|
@adapter_.compare_and_set(nil, adapter)
|
34
36
|
clear_cache!
|
35
37
|
end
|
@@ -77,8 +79,8 @@ module Dynamoid
|
|
77
79
|
#
|
78
80
|
# @param [String] table the name of the table to write the object to
|
79
81
|
# @param [Array] ids to fetch, can also be a string of just one id
|
80
|
-
# @param [Hash] options
|
81
|
-
#
|
82
|
+
# @param [Hash] options Passed to the underlying query. The :range_key option is required whenever the table has a range key,
|
83
|
+
# unless multiple ids are passed in.
|
82
84
|
#
|
83
85
|
# @since 0.2.0
|
84
86
|
def read(table, ids, options = {}, &blk)
|
@@ -93,7 +95,9 @@ module Dynamoid
|
|
93
95
|
#
|
94
96
|
# @param [String] table the name of the table to write the object to
|
95
97
|
# @param [Array] ids to delete, can also be a string of just one id
|
96
|
-
# @param [
|
98
|
+
# @param [Hash] options allowed only +range_key+ - range key or array of
|
99
|
+
# range keys of the record to delete, can also be
|
100
|
+
# a string of just one range_key, and +conditions+
|
97
101
|
#
|
98
102
|
def delete(table, ids, options = {})
|
99
103
|
range_key = options[:range_key] # array of range keys that matches the ids passed in
|
@@ -114,7 +118,7 @@ module Dynamoid
|
|
114
118
|
# Scans a table. Generally quite slow; try to avoid using scan if at all possible.
|
115
119
|
#
|
116
120
|
# @param [String] table the name of the table to write the object to
|
117
|
-
# @param [Hash]
|
121
|
+
# @param [Hash] query a hash of attributes: matching records will be returned by the scan
|
118
122
|
#
|
119
123
|
# @since 0.2.0
|
120
124
|
def scan(table, query = {}, opts = {})
|
@@ -123,8 +127,12 @@ module Dynamoid
|
|
123
127
|
|
124
128
|
def create_table(table_name, key, options = {})
|
125
129
|
unless tables.include?(table_name)
|
126
|
-
|
130
|
+
result = nil
|
131
|
+
benchmark('Create Table') { result = adapter.create_table(table_name, key, options) }
|
127
132
|
tables << table_name
|
133
|
+
result
|
134
|
+
else
|
135
|
+
false
|
128
136
|
end
|
129
137
|
end
|
130
138
|
|
@@ -142,11 +150,7 @@ module Dynamoid
|
|
142
150
|
#
|
143
151
|
# @since 0.2.0
|
144
152
|
define_method(m) do |*args, &blk|
|
145
|
-
|
146
|
-
benchmark(m.to_s, *args) { adapter.send(m, *args, &blk) }
|
147
|
-
else
|
148
|
-
benchmark(m.to_s, *args) { adapter.send(m, *args) }
|
149
|
-
end
|
153
|
+
benchmark(m.to_s, *args) { adapter.send(m, *args, &blk) }
|
150
154
|
end
|
151
155
|
end
|
152
156
|
|
@@ -179,10 +183,6 @@ module Dynamoid
|
|
179
183
|
end
|
180
184
|
|
181
185
|
def self.adapter_plugin_class
|
182
|
-
unless Dynamoid.const_defined?(:AdapterPlugin) && Dynamoid::AdapterPlugin.const_defined?(Dynamoid::Config.adapter.camelcase)
|
183
|
-
require "dynamoid/adapter_plugin/#{Dynamoid::Config.adapter}"
|
184
|
-
end
|
185
|
-
|
186
186
|
Dynamoid::AdapterPlugin.const_get(Dynamoid::Config.adapter.camelcase)
|
187
187
|
end
|
188
188
|
end
|
@@ -3,11 +3,13 @@
|
|
3
3
|
require_relative 'aws_sdk_v3/query'
|
4
4
|
require_relative 'aws_sdk_v3/scan'
|
5
5
|
require_relative 'aws_sdk_v3/create_table'
|
6
|
+
require_relative 'aws_sdk_v3/batch_get_item'
|
6
7
|
require_relative 'aws_sdk_v3/item_updater'
|
7
8
|
require_relative 'aws_sdk_v3/table'
|
8
9
|
require_relative 'aws_sdk_v3/until_past_table_status'
|
9
10
|
|
10
11
|
module Dynamoid
|
12
|
+
# @private
|
11
13
|
module AdapterPlugin
|
12
14
|
# The AwsSdkV3 adapter provides support for the aws-sdk version 2 for ruby.
|
13
15
|
class AwsSdkV3
|
@@ -22,8 +24,6 @@ module Dynamoid
|
|
22
24
|
range_eq: 'EQ'
|
23
25
|
}.freeze
|
24
26
|
|
25
|
-
# Don't implement NULL and NOT_NULL because it doesn't make seanse -
|
26
|
-
# we declare schema in models
|
27
27
|
FIELD_MAP = {
|
28
28
|
eq: 'EQ',
|
29
29
|
ne: 'NE',
|
@@ -35,7 +35,9 @@ module Dynamoid
|
|
35
35
|
between: 'BETWEEN',
|
36
36
|
in: 'IN',
|
37
37
|
contains: 'CONTAINS',
|
38
|
-
not_contains: 'NOT_CONTAINS'
|
38
|
+
not_contains: 'NOT_CONTAINS',
|
39
|
+
null: 'NULL',
|
40
|
+
not_null: 'NOT_NULL',
|
39
41
|
}.freeze
|
40
42
|
HASH_KEY = 'HASH'
|
41
43
|
RANGE_KEY = 'RANGE'
|
@@ -60,6 +62,25 @@ module Dynamoid
|
|
60
62
|
|
61
63
|
attr_reader :table_cache
|
62
64
|
|
65
|
+
# Build an array of values for Condition
|
66
|
+
# Is used in ScanFilter and QueryFilter
|
67
|
+
# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html
|
68
|
+
# @param [String] operator value of RANGE_MAP or FIELD_MAP hash, e.g. "EQ", "LT" etc
|
69
|
+
# @param [Object] value scalar value or array/set
|
70
|
+
def self.attribute_value_list(operator, value)
|
71
|
+
# For BETWEEN and IN operators we should keep value as is (it should be already an array)
|
72
|
+
# NULL and NOT_NULL require absence of attribute list
|
73
|
+
# For all the other operators we wrap the value with array
|
74
|
+
# https://docs.aws.amazon.com/en_us/amazondynamodb/latest/developerguide/LegacyConditionalParameters.Conditions.html
|
75
|
+
if %w[BETWEEN IN].include?(operator)
|
76
|
+
[value].flatten
|
77
|
+
elsif %w[NULL NOT_NULL].include?(operator)
|
78
|
+
nil
|
79
|
+
else
|
80
|
+
[value]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
63
84
|
# Establish the connection to DynamoDB.
|
64
85
|
#
|
65
86
|
# @return [Aws::DynamoDB::Client] the DynamoDB connection
|
@@ -74,19 +95,28 @@ module Dynamoid
|
|
74
95
|
(Dynamoid::Config.settings.compact.keys & CONNECTION_CONFIG_OPTIONS).each do |option|
|
75
96
|
@connection_hash[option] = Dynamoid::Config.send(option)
|
76
97
|
end
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
98
|
+
|
99
|
+
# if credentials are passed, they already contain access key & secret key
|
100
|
+
if Dynamoid::Config.credentials?
|
101
|
+
@connection_hash[:credentials] = Dynamoid::Config.credentials
|
102
|
+
else
|
103
|
+
# otherwise, pass access key & secret key for credentials creation
|
104
|
+
if Dynamoid::Config.access_key?
|
105
|
+
@connection_hash[:access_key_id] = Dynamoid::Config.access_key
|
106
|
+
end
|
107
|
+
if Dynamoid::Config.secret_key?
|
108
|
+
@connection_hash[:secret_access_key] = Dynamoid::Config.secret_key
|
109
|
+
end
|
82
110
|
end
|
83
111
|
|
84
|
-
# https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-core/lib/aws-sdk-core/plugins/logging.rb
|
85
|
-
# https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-core/lib/aws-sdk-core/log/formatter.rb
|
86
|
-
formatter = Aws::Log::Formatter.new(':operation | Request :http_request_body | Response :http_response_body')
|
87
112
|
@connection_hash[:logger] = Dynamoid::Config.logger
|
88
113
|
@connection_hash[:log_level] = :debug
|
89
|
-
|
114
|
+
|
115
|
+
# https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-core/lib/aws-sdk-core/plugins/logging.rb
|
116
|
+
# https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-core/lib/aws-sdk-core/log/formatter.rb
|
117
|
+
if Dynamoid::Config.log_formatter
|
118
|
+
@connection_hash[:log_formatter] = Dynamoid::Config.log_formatter
|
119
|
+
end
|
90
120
|
|
91
121
|
@connection_hash
|
92
122
|
end
|
@@ -114,9 +144,9 @@ module Dynamoid
|
|
114
144
|
# end
|
115
145
|
#
|
116
146
|
# @param [String] table_name the name of the table
|
117
|
-
# @param [Array]
|
118
|
-
# @param [Hash] additional options
|
119
|
-
# @
|
147
|
+
# @param [Array] objects to be processed
|
148
|
+
# @param [Hash] options additional options
|
149
|
+
# @yield [true|false] invokes an optional block with argument - whether there are unprocessed items
|
120
150
|
#
|
121
151
|
# See:
|
122
152
|
# * http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html
|
@@ -171,9 +201,9 @@ module Dynamoid
|
|
171
201
|
# end
|
172
202
|
# end
|
173
203
|
#
|
174
|
-
# @param [Hash]
|
204
|
+
# @param [Hash] table_names_with_ids the hash of tables and IDs to retrieve
|
175
205
|
# @param [Hash] options to be passed to underlying BatchGet call
|
176
|
-
# @param [Proc] optional block can be passed to handle each batch of items
|
206
|
+
# @param [Proc] block optional block can be passed to handle each batch of items
|
177
207
|
#
|
178
208
|
# @return [Hash] a hash where keys are the table names and the values are the retrieved items
|
179
209
|
#
|
@@ -183,65 +213,11 @@ module Dynamoid
|
|
183
213
|
# @since 1.0.0
|
184
214
|
#
|
185
215
|
# @todo: Provide support for passing options to underlying batch_get_item
|
186
|
-
def batch_get_item(
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
ret = Hash.new([].freeze) # Default for tables where no rows are returned
|
191
|
-
|
192
|
-
table_ids.each do |t, ids|
|
193
|
-
next if ids.blank?
|
194
|
-
|
195
|
-
ids = Array(ids).dup
|
196
|
-
tbl = describe_table(t)
|
197
|
-
hk = tbl.hash_key.to_s
|
198
|
-
rng = tbl.range_key.to_s
|
199
|
-
|
200
|
-
while ids.present?
|
201
|
-
batch = ids.shift(Dynamoid::Config.batch_size)
|
202
|
-
|
203
|
-
request_items = Hash.new { |h, k| h[k] = [] }
|
204
|
-
|
205
|
-
keys = if rng.present?
|
206
|
-
Array(batch).map do |h, r|
|
207
|
-
{ hk => h, rng => r }
|
208
|
-
end
|
209
|
-
else
|
210
|
-
Array(batch).map do |id|
|
211
|
-
{ hk => id }
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
request_items[t] = {
|
216
|
-
keys: keys,
|
217
|
-
consistent_read: options[:consistent_read]
|
218
|
-
}
|
219
|
-
|
220
|
-
results = client.batch_get_item(
|
221
|
-
request_items: request_items
|
222
|
-
)
|
223
|
-
|
224
|
-
if block_given?
|
225
|
-
batch_results = Hash.new([].freeze)
|
226
|
-
|
227
|
-
results.data[:responses].each do |table, rows|
|
228
|
-
batch_results[table] += rows.collect { |r| result_item_to_hash(r) }
|
229
|
-
end
|
230
|
-
|
231
|
-
yield(batch_results, results.unprocessed_keys.present?)
|
232
|
-
else
|
233
|
-
results.data[:responses].each do |table, rows|
|
234
|
-
ret[table] += rows.collect { |r| result_item_to_hash(r) }
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
if results.unprocessed_keys.present?
|
239
|
-
ids += results.unprocessed_keys[t].keys.map { |h| h[hk] }
|
240
|
-
end
|
241
|
-
end
|
216
|
+
def batch_get_item(table_names_with_ids, options = {}, &block)
|
217
|
+
tables_with_ids = table_names_with_ids.transform_keys do |name|
|
218
|
+
describe_table(name)
|
242
219
|
end
|
243
|
-
|
244
|
-
ret unless block_given?
|
220
|
+
BatchGetItem.new(client, tables_with_ids, options).call(&block)
|
245
221
|
end
|
246
222
|
|
247
223
|
# Delete many items at once from DynamoDB. More efficient than delete each item individually.
|
@@ -299,8 +275,22 @@ module Dynamoid
|
|
299
275
|
def create_table(table_name, key = :id, options = {})
|
300
276
|
Dynamoid.logger.info "Creating #{table_name} table. This could take a while."
|
301
277
|
CreateTable.new(client, table_name, key, options).call
|
278
|
+
true
|
302
279
|
rescue Aws::DynamoDB::Errors::ResourceInUseException => e
|
303
280
|
Dynamoid.logger.error "Table #{table_name} cannot be created as it already exists"
|
281
|
+
false
|
282
|
+
end
|
283
|
+
|
284
|
+
def update_time_to_live(table_name:, attribute:)
|
285
|
+
request = {
|
286
|
+
table_name: table_name,
|
287
|
+
time_to_live_specification: {
|
288
|
+
attribute_name: attribute,
|
289
|
+
enabled: true,
|
290
|
+
}
|
291
|
+
}
|
292
|
+
|
293
|
+
client.update_time_to_live(request)
|
304
294
|
end
|
305
295
|
|
306
296
|
# Create a table on DynamoDB *synchronously*.
|
@@ -356,9 +346,14 @@ module Dynamoid
|
|
356
346
|
# @since 1.0.0
|
357
347
|
def delete_table(table_name, options = {})
|
358
348
|
resp = client.delete_table(table_name: table_name)
|
359
|
-
|
360
|
-
|
361
|
-
|
349
|
+
|
350
|
+
if options[:sync]
|
351
|
+
status = PARSE_TABLE_STATUS.call(resp, :table_description)
|
352
|
+
if status == TABLE_STATUSES[:deleting]
|
353
|
+
UntilPastTableStatus.new(client, table_name, :deleting).call
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
362
357
|
table_cache.delete(table_name)
|
363
358
|
rescue Aws::DynamoDB::Errors::ResourceInUseException => e
|
364
359
|
Dynamoid.logger.error "Table #{table_name} cannot be deleted as it is in use"
|
@@ -552,11 +547,11 @@ module Dynamoid
|
|
552
547
|
hk = table.hash_key
|
553
548
|
rk = table.range_key
|
554
549
|
|
555
|
-
scan(table_name, {}, {}).flat_map{ |i| i }.
|
556
|
-
|
557
|
-
opts[:range_key] = attributes[rk.to_sym] if rk
|
558
|
-
delete_item(table_name, attributes[hk], opts)
|
550
|
+
ids = scan(table_name, {}, {}).flat_map { |i| i }.map do |attributes|
|
551
|
+
rk ? [attributes[hk], attributes[rk.to_sym]] : attributes[hk]
|
559
552
|
end
|
553
|
+
|
554
|
+
batch_delete_item(table_name => ids)
|
560
555
|
end
|
561
556
|
|
562
557
|
def count(table_name)
|
@@ -614,28 +609,13 @@ module Dynamoid
|
|
614
609
|
end
|
615
610
|
end
|
616
611
|
|
617
|
-
# Build an array of values for Condition
|
618
|
-
# Is used in ScanFilter and QueryFilter
|
619
|
-
# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html
|
620
|
-
# @params [String] operator: value of RANGE_MAP or FIELD_MAP hash, e.g. "EQ", "LT" etc
|
621
|
-
# @params [Object] value: scalar value or array/set
|
622
|
-
def attribute_value_list(operator, value)
|
623
|
-
self.class.attribute_value_list(operator, value)
|
624
|
-
end
|
625
|
-
|
626
|
-
def self.attribute_value_list(operator, value)
|
627
|
-
# For BETWEEN and IN operators we should keep value as is (it should be already an array)
|
628
|
-
# For all the other operators we wrap the value with array
|
629
|
-
if %w[BETWEEN IN].include?(operator)
|
630
|
-
[value].flatten
|
631
|
-
else
|
632
|
-
[value]
|
633
|
-
end
|
634
|
-
end
|
635
|
-
|
636
612
|
def sanitize_item(attributes)
|
613
|
+
config_value = Dynamoid.config.store_attribute_with_nil_value
|
614
|
+
store_attribute_with_nil_value = config_value.nil? ? false : !!config_value
|
615
|
+
|
637
616
|
attributes.reject do |_, v|
|
638
|
-
|
617
|
+
((v.is_a?(Set) || v.is_a?(String)) && v.empty?) ||
|
618
|
+
(!store_attribute_with_nil_value && v.nil?)
|
639
619
|
end.transform_values do |v|
|
640
620
|
v.is_a?(Hash) ? v.stringify_keys : v
|
641
621
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
# @private
|
5
|
+
module AdapterPlugin
|
6
|
+
class AwsSdkV3
|
7
|
+
# Documentation
|
8
|
+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#batch_get_item-instance_method
|
9
|
+
class BatchGetItem
|
10
|
+
attr_reader :client, :tables_with_ids, :options
|
11
|
+
|
12
|
+
def initialize(client, tables_with_ids, options = {})
|
13
|
+
@client = client
|
14
|
+
@tables_with_ids = tables_with_ids
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
results = {}
|
20
|
+
|
21
|
+
tables_with_ids.each do |table, ids|
|
22
|
+
if ids.blank?
|
23
|
+
results[table.name] = []
|
24
|
+
next
|
25
|
+
end
|
26
|
+
|
27
|
+
ids = Array(ids).dup
|
28
|
+
|
29
|
+
while ids.present?
|
30
|
+
batch = ids.shift(Dynamoid::Config.batch_size)
|
31
|
+
request = build_request(table, batch)
|
32
|
+
api_response = client.batch_get_item(request)
|
33
|
+
response = Response.new(api_response)
|
34
|
+
|
35
|
+
if block_given?
|
36
|
+
# return batch items as a result
|
37
|
+
batch_results = Hash.new([].freeze)
|
38
|
+
batch_results.update(response.items_grouped_by_table)
|
39
|
+
|
40
|
+
yield(batch_results, response.successful_partially?)
|
41
|
+
else
|
42
|
+
# collect all the batches to return at the end
|
43
|
+
results.update(response.items_grouped_by_table) { |_, its1, its2| its1 + its2 }
|
44
|
+
end
|
45
|
+
|
46
|
+
if response.successful_partially?
|
47
|
+
ids += response.unprocessed_ids(table)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
results unless block_given?
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def build_request(table, ids)
|
58
|
+
ids = Array(ids)
|
59
|
+
|
60
|
+
keys = if table.range_key.nil?
|
61
|
+
ids.map { |hk| { table.hash_key => hk } }
|
62
|
+
else
|
63
|
+
ids.map { |hk, rk| { table.hash_key => hk, table.range_key => rk } }
|
64
|
+
end
|
65
|
+
|
66
|
+
{
|
67
|
+
request_items: {
|
68
|
+
table.name => {
|
69
|
+
keys: keys,
|
70
|
+
consistent_read: options[:consistent_read]
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Helper class to work with response
|
77
|
+
class Response
|
78
|
+
def initialize(api_response)
|
79
|
+
@api_response = api_response
|
80
|
+
end
|
81
|
+
|
82
|
+
def successful_partially?
|
83
|
+
@api_response.unprocessed_keys.present?
|
84
|
+
end
|
85
|
+
|
86
|
+
def unprocessed_ids(table)
|
87
|
+
# unprocessed_keys Hash contains as values instances of
|
88
|
+
# Aws::DynamoDB::Types::KeysAndAttributes
|
89
|
+
@api_response.unprocessed_keys[table.name].keys.map { |h| h[table.hash_key.to_s] }
|
90
|
+
end
|
91
|
+
|
92
|
+
def items_grouped_by_table
|
93
|
+
# data[:responses] is a Hash[table_name -> items]
|
94
|
+
@api_response.data[:responses].transform_values do |items|
|
95
|
+
items.map(&method(:item_to_hash))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def item_to_hash(item)
|
102
|
+
item.symbolize_keys
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|