dynamoid 3.2.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|