dynamoid 3.10.0 → 3.11.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 +14 -0
- data/README.md +182 -2
- data/dynamoid.gemspec +4 -4
- data/lib/dynamoid/adapter.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +53 -18
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +5 -4
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +9 -7
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +9 -5
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config.rb +2 -0
- data/lib/dynamoid/criteria/chain.rb +63 -18
- data/lib/dynamoid/criteria/where_conditions.rb +13 -6
- data/lib/dynamoid/dirty.rb +86 -11
- data/lib/dynamoid/dumping.rb +36 -14
- data/lib/dynamoid/errors.rb +14 -2
- data/lib/dynamoid/finders.rb +6 -6
- data/lib/dynamoid/loadable.rb +1 -0
- data/lib/dynamoid/persistence/inc.rb +6 -7
- data/lib/dynamoid/persistence/item_updater_with_casting_and_dumping.rb +36 -0
- data/lib/dynamoid/persistence/item_updater_with_dumping.rb +33 -0
- data/lib/dynamoid/persistence/save.rb +5 -2
- data/lib/dynamoid/persistence/update_fields.rb +5 -3
- data/lib/dynamoid/persistence/upsert.rb +5 -4
- data/lib/dynamoid/persistence.rb +38 -17
- data/lib/dynamoid/transaction_write/base.rb +47 -0
- data/lib/dynamoid/transaction_write/create.rb +49 -0
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +60 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +59 -0
- data/lib/dynamoid/transaction_write/destroy.rb +79 -0
- data/lib/dynamoid/transaction_write/save.rb +164 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +102 -0
- data/lib/dynamoid/transaction_write/upsert.rb +96 -0
- data/lib/dynamoid/transaction_write.rb +464 -0
- data/lib/dynamoid/type_casting.rb +3 -1
- data/lib/dynamoid/undumping.rb +13 -2
- data/lib/dynamoid/validations.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +7 -0
- metadata +18 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e13af88750cfc2b8421710068215b18cf596f86b0c5caa207574535af04369cc
|
4
|
+
data.tar.gz: b0cc179fb6c547fd766c788300bfe8fc38a9e10117895c0b39b665fcc609e448
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b363e83838f36600b1af31c59406ac4fccad981e24af8762d5ae73fd1b648a2d03754483be89ed648f7ca982dc1640901af242c0b626ef9fb34b3002438edb4
|
7
|
+
data.tar.gz: a1bc08e5ab754f0a2ee383d2ac335396e7e058ad3b91df2d7c532d346da79daa0d239053ab856a8dfd0a2bdfb213f70a654cecdd3b42901c0261e61bcaca9ad5
|
data/CHANGELOG.md
CHANGED
@@ -11,6 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
11
11
|
### Changed
|
12
12
|
### Removed
|
13
13
|
|
14
|
+
## 3.11.0
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
* [#829](https://github.com/Dynamoid/dynamoid/pull/829) Fixed saving of in-place field changes
|
18
|
+
* [#812](https://github.com/Dynamoid/dynamoid/pull/812) Restored sanitizing of attribute names in `#where` conditions
|
19
|
+
* [#721](https://github.com/Dynamoid/dynamoid/pull/721) Fixed code examples in README.md (@ndjndj)
|
20
|
+
### Added
|
21
|
+
* [#688](https://github.com/Dynamoid/dynamoid/pull/688) Transactional modifying was added utilizing `TransactWriteItems` operation (@ckhsponge)
|
22
|
+
* [#794](https://github.com/Dynamoid/dynamoid/pull/794) Added new config option `store_empty_string_as_nil`
|
23
|
+
* [#828](https://github.com/Dynamoid/dynamoid/pull/828) Added Ruby 3.4, Rails 8.0 and Rails 7.2 in CI
|
24
|
+
### Changed
|
25
|
+
* [#832](https://github.com/Dynamoid/dynamoid/pull/832) Support String condition expressions with `#where`
|
26
|
+
* [#822](https://github.com/Dynamoid/dynamoid/pull/822) Support binary type natively. Also added new config option `store_binary_as_native` (@dalibor)
|
27
|
+
|
14
28
|
## 3.10.0
|
15
29
|
### Fixed
|
16
30
|
* [#681](https://github.com/Dynamoid/dynamoid/pull/681) Fixed saving persisted model and deleting attributes with `nil` value if `config.store_attribute_with_nil_value` is `false`
|
data/README.md
CHANGED
@@ -342,6 +342,24 @@ class Document
|
|
342
342
|
end
|
343
343
|
```
|
344
344
|
|
345
|
+
#### Note on binary type
|
346
|
+
|
347
|
+
By default binary fields are persisted as DynamoDB String value encoded
|
348
|
+
in the Base64 encoding. DynamoDB supports binary data natively. To use
|
349
|
+
it instead of String a `store_binary_as_native` field option should be
|
350
|
+
set:
|
351
|
+
|
352
|
+
```ruby
|
353
|
+
class Document
|
354
|
+
include Dynamoid::Document
|
355
|
+
|
356
|
+
field :image, :binary, store_binary_as_native: true
|
357
|
+
end
|
358
|
+
```
|
359
|
+
|
360
|
+
There is also a global config option `store_binary_as_native` that is
|
361
|
+
`false` by default as well.
|
362
|
+
|
345
363
|
#### Magic Columns
|
346
364
|
|
347
365
|
You get magic columns of `id` (`string`), `created_at` (`datetime`), and
|
@@ -701,7 +719,7 @@ users = User.import([{ name: 'Josh' }, { name: 'Nick' }])
|
|
701
719
|
|
702
720
|
### Querying
|
703
721
|
|
704
|
-
Querying can be done in one of
|
722
|
+
Querying can be done in one of the following ways:
|
705
723
|
|
706
724
|
```ruby
|
707
725
|
Address.find(address.id) # Find directly by ID.
|
@@ -710,6 +728,27 @@ Address.where(city: 'Chicago').all # Find by any number of matching criteria.
|
|
710
728
|
Address.find_by_city('Chicago') # The same as above, but using ActiveRecord's older syntax.
|
711
729
|
```
|
712
730
|
|
731
|
+
There is also a way to `#where` with a condition expression:
|
732
|
+
|
733
|
+
```ruby
|
734
|
+
Address.where('city = :c', c: 'Chicago')
|
735
|
+
```
|
736
|
+
|
737
|
+
A condition expression may contain operators (e.g. `<`, `>=`, `<>`),
|
738
|
+
keywords (e.g. `AND`, `OR`, `BETWEEN`) and built-in functions (e.g.
|
739
|
+
`begins_with`, `contains`) (see (documentation
|
740
|
+
)[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html]
|
741
|
+
for full syntax description).
|
742
|
+
|
743
|
+
**Warning:** Values (specified for a String condition expression) are
|
744
|
+
sent as is so Dynamoid field types that aren't supported natively by
|
745
|
+
DynamoDB (e.g. `datetime` and `date`) require explicit casting.
|
746
|
+
|
747
|
+
**Warning:** String condition expressions will be used by DynamoDB only
|
748
|
+
at filtering, so conditions on key attributes should be specified as a
|
749
|
+
Hash to perform Query operation instead of Scan. Don't use key
|
750
|
+
attributes in `#where`'s String condition expressions.
|
751
|
+
|
713
752
|
And you can also query on associations:
|
714
753
|
|
715
754
|
```ruby
|
@@ -934,7 +973,6 @@ validation and callbacks.
|
|
934
973
|
Address.find(id).update_attributes(city: 'Chicago')
|
935
974
|
Address.find(id).update_attribute(:city, 'Chicago')
|
936
975
|
Address.update(id, city: 'Chicago')
|
937
|
-
Address.update(id, { city: 'Chicago' }, if: { deliverable: true })
|
938
976
|
```
|
939
977
|
|
940
978
|
There are also some low level methods `#update`, `.update_fields` and
|
@@ -1047,6 +1085,143 @@ resolving the fields with a second query against the table since a query
|
|
1047
1085
|
against GSI then a query on base table is still likely faster than scan
|
1048
1086
|
on the base table*
|
1049
1087
|
|
1088
|
+
### Transactions in Dynamoid
|
1089
|
+
|
1090
|
+
> [!WARNING]
|
1091
|
+
> Please note that this API is experimental and can be changed in
|
1092
|
+
> future releases.
|
1093
|
+
|
1094
|
+
Multiple modifying actions can be grouped together and submitted as an
|
1095
|
+
all-or-nothing operation. Atomic modifying operations are supported in
|
1096
|
+
Dynamoid using transactions. If any action in the transaction fails they
|
1097
|
+
all fail.
|
1098
|
+
|
1099
|
+
The following actions are supported:
|
1100
|
+
|
1101
|
+
* `#create`/`#create!` - add a new model if it does not already exist
|
1102
|
+
* `#save`/`#save!` - create or update model
|
1103
|
+
* `#update_attributes`/`#update_attributes!` - modifies one or more attributes from an existig
|
1104
|
+
model
|
1105
|
+
* `#delete` - remove an model without callbacks nor validations
|
1106
|
+
* `#destroy`/`#destroy!` - remove an model
|
1107
|
+
* `#upsert` - add a new model or update an existing one, no callbacks
|
1108
|
+
* `#update_fields` - update a model without its instantiation
|
1109
|
+
|
1110
|
+
These methods are supposed to behave exactly like their
|
1111
|
+
non-transactional counterparts.
|
1112
|
+
|
1113
|
+
|
1114
|
+
#### Create models
|
1115
|
+
|
1116
|
+
Models can be created inside of a transaction. The partition and sort
|
1117
|
+
keys, if applicable, are used to determine uniqueness. Creating will
|
1118
|
+
fail with `Aws::DynamoDB::Errors::TransactionCanceledException` if a
|
1119
|
+
model already exists.
|
1120
|
+
|
1121
|
+
This example creates a user with a unique id and unique email address by
|
1122
|
+
creating 2 models. An additional model is upserted in the same
|
1123
|
+
transaction. Upsert will update `updated_at` but will not create
|
1124
|
+
`created_at`.
|
1125
|
+
|
1126
|
+
```ruby
|
1127
|
+
user_id = SecureRandom.uuid
|
1128
|
+
email = 'bob@bob.bob'
|
1129
|
+
|
1130
|
+
Dynamoid::TransactionWrite.execute do |txn|
|
1131
|
+
txn.create(User, id: user_id)
|
1132
|
+
txn.create(UserEmail, id: "UserEmail##{email}", user_id: user_id)
|
1133
|
+
txn.create(Address, id: 'A#2', street: '456')
|
1134
|
+
txn.upsert(Address, 'A#1', street: '123')
|
1135
|
+
end
|
1136
|
+
```
|
1137
|
+
|
1138
|
+
#### Save models
|
1139
|
+
|
1140
|
+
Models can be saved in a transaction. New records are created otherwise
|
1141
|
+
the model is updated. Save, create, update, validate and destroy
|
1142
|
+
callbacks are called around the transaction as appropriate. Validation
|
1143
|
+
failures will throw `Dynamoid::Errors::DocumentNotValid`.
|
1144
|
+
|
1145
|
+
```ruby
|
1146
|
+
user = User.find(1)
|
1147
|
+
article = Article.new(body: 'New article text', user_id: user.id)
|
1148
|
+
|
1149
|
+
Dynamoid::TransactionWrite.execute do |txn|
|
1150
|
+
txn.save(article)
|
1151
|
+
|
1152
|
+
user.last_article_id = article.id
|
1153
|
+
txn.save(user)
|
1154
|
+
end
|
1155
|
+
```
|
1156
|
+
|
1157
|
+
#### Update models
|
1158
|
+
|
1159
|
+
A model can be updated by providing a model or primary key, and the fields to update.
|
1160
|
+
|
1161
|
+
```ruby
|
1162
|
+
Dynamoid::TransactionWrite.execute do |txn|
|
1163
|
+
# change name and title for a user
|
1164
|
+
txn.update_attributes(user, name: 'bob', title: 'mister')
|
1165
|
+
|
1166
|
+
# sets the name and title for a user
|
1167
|
+
# The user is found by id (that equals 1)
|
1168
|
+
txn.update_fields(User, '1', name: 'bob', title: 'mister')
|
1169
|
+
end
|
1170
|
+
```
|
1171
|
+
|
1172
|
+
#### Destroy or delete models
|
1173
|
+
|
1174
|
+
Models can be used or the model class and key can be specified.
|
1175
|
+
`#destroy` uses callbacks and validations. Use `#delete` to skip
|
1176
|
+
callbacks and validations.
|
1177
|
+
|
1178
|
+
```ruby
|
1179
|
+
article = Article.find('1')
|
1180
|
+
tag = article.tag
|
1181
|
+
|
1182
|
+
Dynamoid::TransactionWrite.execute do |txn|
|
1183
|
+
txn.destroy(article)
|
1184
|
+
txn.delete(tag)
|
1185
|
+
|
1186
|
+
txn.delete(Tag, '2') # delete record with hash key '2' if it exists
|
1187
|
+
txn.delete(Tag, 'key#abcd', 'range#1') # when sort key is required
|
1188
|
+
end
|
1189
|
+
```
|
1190
|
+
|
1191
|
+
#### Validation failures that don't raise
|
1192
|
+
|
1193
|
+
All of the transaction methods can be called without the `!` which
|
1194
|
+
results in `false` instead of a raised exception when validation fails.
|
1195
|
+
Ignoring validation failures can lead to confusion or bugs so always
|
1196
|
+
check return status when not using a method with `!`.
|
1197
|
+
|
1198
|
+
```ruby
|
1199
|
+
user = User.find('1')
|
1200
|
+
user.red = true
|
1201
|
+
|
1202
|
+
Dynamoid::TransactionWrite.execute do |txn|
|
1203
|
+
if txn.save(user) # won't raise validation exception
|
1204
|
+
txn.update_fields(UserCount, user.id, count: 5)
|
1205
|
+
else
|
1206
|
+
puts 'ALERT: user not valid, skipping'
|
1207
|
+
end
|
1208
|
+
end
|
1209
|
+
```
|
1210
|
+
|
1211
|
+
#### Incrementally building a transaction
|
1212
|
+
|
1213
|
+
Transactions can also be built without a block.
|
1214
|
+
|
1215
|
+
```ruby
|
1216
|
+
transaction = Dynamoid::TransactionWrite.new
|
1217
|
+
|
1218
|
+
transaction.create(User, id: user_id)
|
1219
|
+
transaction.create(UserEmail, id: "UserEmail##{email}", user_id: user_id)
|
1220
|
+
transaction.upsert(Address, 'A#1', street: '123')
|
1221
|
+
|
1222
|
+
transaction.commit # changes are persisted in this moment
|
1223
|
+
```
|
1224
|
+
|
1050
1225
|
### PartiQL
|
1051
1226
|
|
1052
1227
|
To run PartiQL statements `Dynamoid.adapter.execute` method should be
|
@@ -1130,9 +1305,13 @@ Listed below are all configuration options.
|
|
1130
1305
|
fields in ISO 8601 string format. Default is `false`
|
1131
1306
|
* `store_date_as_string` - if `true` then Dynamoid stores :date fields
|
1132
1307
|
in ISO 8601 string format. Default is `false`
|
1308
|
+
* `store_empty_string_as_nil` - store attribute's empty String value as NULL. Default is `true`
|
1133
1309
|
* `store_boolean_as_native` - if `true` Dynamoid stores boolean fields
|
1134
1310
|
as native DynamoDB boolean values. Otherwise boolean fields are stored
|
1135
1311
|
as string values `'t'` and `'f'`. Default is `true`
|
1312
|
+
* `store_binary_as_native` - if `true` Dynamoid stores binary fields
|
1313
|
+
as native DynamoDB binary values. Otherwise binary fields are stored
|
1314
|
+
as Base64 encoded string values. Default is `false`
|
1136
1315
|
* `backoff` - is a hash: key is a backoff strategy (symbol), value is
|
1137
1316
|
parameters for the strategy. Is used in batch operations. Default id
|
1138
1317
|
`nil`
|
@@ -1350,6 +1529,7 @@ just as accessible to the Ruby world as MongoDB.
|
|
1350
1529
|
Also, without contributors the project wouldn't be nearly as awesome. So
|
1351
1530
|
many thanks to:
|
1352
1531
|
|
1532
|
+
* [Chris Hobbs](https://github.com/ckhsponge)
|
1353
1533
|
* [Logan Bowers](https://github.com/loganb)
|
1354
1534
|
* [Lane LaRue](https://github.com/luxx)
|
1355
1535
|
* [Craig Heneveld](https://github.com/cheneveld)
|
data/dynamoid.gemspec
CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
|
30
30
|
spec.description = "Dynamoid is an ORM for Amazon's DynamoDB that supports offline development, associations, querying, and everything else you'd expect from an ActiveRecord-style replacement."
|
31
31
|
spec.summary = "Dynamoid is an ORM for Amazon's DynamoDB"
|
32
|
-
# Ignore not
|
32
|
+
# Ignore not committed files
|
33
33
|
spec.files = Dir[
|
34
34
|
'CHANGELOG.md',
|
35
35
|
'dynamoid.gemspec',
|
@@ -51,9 +51,9 @@ Gem::Specification.new do |spec|
|
|
51
51
|
spec.metadata['wiki_uri'] = 'https://github.com/Dynamoid/dynamoid/wiki'
|
52
52
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
53
53
|
|
54
|
-
spec.
|
55
|
-
spec.
|
56
|
-
spec.
|
54
|
+
spec.add_dependency 'activemodel', '>=4'
|
55
|
+
spec.add_dependency 'aws-sdk-dynamodb', '~> 1.0'
|
56
|
+
spec.add_dependency 'concurrent-ruby', '>= 1.0'
|
57
57
|
|
58
58
|
spec.add_development_dependency 'appraisal'
|
59
59
|
spec.add_development_dependency 'bundler'
|
data/lib/dynamoid/adapter.rb
CHANGED
@@ -118,7 +118,7 @@ module Dynamoid
|
|
118
118
|
# @param [Hash] query a hash of attributes: matching records will be returned by the scan
|
119
119
|
#
|
120
120
|
# @since 0.2.0
|
121
|
-
def scan(table, query =
|
121
|
+
def scan(table, query = [], opts = {})
|
122
122
|
benchmark('Scan', table, query) { adapter.scan(table, query, opts) }
|
123
123
|
end
|
124
124
|
|
@@ -20,48 +20,83 @@ module Dynamoid
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def build
|
23
|
-
clauses =
|
23
|
+
clauses = []
|
24
|
+
|
25
|
+
@conditions.each do |conditions|
|
26
|
+
if conditions.is_a? Hash
|
27
|
+
clauses << build_for_hash(conditions) unless conditions.empty?
|
28
|
+
elsif conditions.is_a? Array
|
29
|
+
query, placeholders = conditions
|
30
|
+
clauses << build_for_string(query, placeholders)
|
31
|
+
else
|
32
|
+
raise ArgumentError, "expected Hash or Array but actual value is #{conditions}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@expression = clauses.join(' AND ')
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_for_hash(hash)
|
40
|
+
clauses = hash.map do |name, attribute_conditions|
|
24
41
|
attribute_conditions.map do |operator, value|
|
25
|
-
|
42
|
+
# replace attribute names with placeholders unconditionally to support
|
43
|
+
# - special characters (e.g. '.', ':', and '#') and
|
44
|
+
# - leading '_'
|
45
|
+
# See
|
46
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.NamingRules
|
47
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html#Expressions.ExpressionAttributeNames.AttributeNamesContainingSpecialCharacters
|
48
|
+
name_placeholder = name_placeholder_for(name)
|
26
49
|
|
27
50
|
case operator
|
28
51
|
when :eq
|
29
|
-
"#{
|
52
|
+
"#{name_placeholder} = #{value_placeholder_for(value)}"
|
30
53
|
when :ne
|
31
|
-
"#{
|
54
|
+
"#{name_placeholder} <> #{value_placeholder_for(value)}"
|
32
55
|
when :gt
|
33
|
-
"#{
|
56
|
+
"#{name_placeholder} > #{value_placeholder_for(value)}"
|
34
57
|
when :lt
|
35
|
-
"#{
|
58
|
+
"#{name_placeholder} < #{value_placeholder_for(value)}"
|
36
59
|
when :gte
|
37
|
-
"#{
|
60
|
+
"#{name_placeholder} >= #{value_placeholder_for(value)}"
|
38
61
|
when :lte
|
39
|
-
"#{
|
62
|
+
"#{name_placeholder} <= #{value_placeholder_for(value)}"
|
40
63
|
when :between
|
41
|
-
"#{
|
64
|
+
"#{name_placeholder} BETWEEN #{value_placeholder_for(value[0])} AND #{value_placeholder_for(value[1])}"
|
42
65
|
when :begins_with
|
43
|
-
"begins_with (#{
|
66
|
+
"begins_with (#{name_placeholder}, #{value_placeholder_for(value)})"
|
44
67
|
when :in
|
45
68
|
list = value.map(&method(:value_placeholder_for)).join(' , ')
|
46
|
-
"#{
|
69
|
+
"#{name_placeholder} IN (#{list})"
|
47
70
|
when :contains
|
48
|
-
"contains (#{
|
71
|
+
"contains (#{name_placeholder}, #{value_placeholder_for(value)})"
|
49
72
|
when :not_contains
|
50
|
-
"NOT contains (#{
|
73
|
+
"NOT contains (#{name_placeholder}, #{value_placeholder_for(value)})"
|
51
74
|
when :null
|
52
|
-
"attribute_not_exists (#{
|
75
|
+
"attribute_not_exists (#{name_placeholder})"
|
53
76
|
when :not_null
|
54
|
-
"attribute_exists (#{
|
77
|
+
"attribute_exists (#{name_placeholder})"
|
55
78
|
end
|
56
79
|
end
|
57
80
|
end.flatten
|
58
81
|
|
59
|
-
|
82
|
+
if clauses.empty?
|
83
|
+
nil
|
84
|
+
else
|
85
|
+
clauses.join(' AND ')
|
86
|
+
end
|
60
87
|
end
|
61
88
|
|
62
|
-
def
|
63
|
-
|
89
|
+
def build_for_string(query, placeholders)
|
90
|
+
placeholders.each do |(k, v)|
|
91
|
+
k = k.to_s
|
92
|
+
k = ":#{k}" unless k.start_with?(':')
|
93
|
+
@value_placeholders[k] = v
|
94
|
+
end
|
95
|
+
|
96
|
+
"(#{query})"
|
97
|
+
end
|
64
98
|
|
99
|
+
def name_placeholder_for(name)
|
65
100
|
placeholder = @name_placeholder_sequence.call
|
66
101
|
@name_placeholders[placeholder] = name
|
67
102
|
placeholder
|
@@ -97,10 +97,11 @@ module Dynamoid
|
|
97
97
|
|
98
98
|
private
|
99
99
|
|
100
|
-
#
|
100
|
+
# It's a single low level component available in a public API (with
|
101
|
+
# Document#update/#update! methods). So duplicate sanitizing to some
|
102
|
+
# degree.
|
101
103
|
#
|
102
|
-
#
|
103
|
-
# attribute value is nil or not.
|
104
|
+
# Keep in sync with AwsSdkV3.sanitize_item.
|
104
105
|
def sanitize_attributes(attributes)
|
105
106
|
# rubocop:disable Lint/DuplicateBranch
|
106
107
|
attributes.transform_values do |v|
|
@@ -108,7 +109,7 @@ module Dynamoid
|
|
108
109
|
v.stringify_keys
|
109
110
|
elsif v.is_a?(Set) && v.empty?
|
110
111
|
nil
|
111
|
-
elsif v.is_a?(String) && v.empty?
|
112
|
+
elsif v.is_a?(String) && v.empty? && Config.store_empty_string_as_nil
|
112
113
|
nil
|
113
114
|
else
|
114
115
|
v
|
@@ -21,13 +21,15 @@ module Dynamoid
|
|
21
21
|
return if @names.nil? || @names.empty?
|
22
22
|
|
23
23
|
clauses = @names.map do |name|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
# replace attribute names with placeholders unconditionally to support
|
25
|
+
# - special characters (e.g. '.', ':', and '#') and
|
26
|
+
# - leading '_'
|
27
|
+
# See
|
28
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.NamingRules
|
29
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html#Expressions.ExpressionAttributeNames.AttributeNamesContainingSpecialCharacters
|
30
|
+
placeholder = @name_placeholder_sequence.call
|
31
|
+
@name_placeholders[placeholder] = name
|
32
|
+
placeholder
|
31
33
|
end
|
32
34
|
|
33
35
|
@expression = clauses.join(' , ')
|
@@ -69,7 +69,7 @@ module Dynamoid
|
|
69
69
|
limit = [record_limit, scan_limit, batch_size].compact.min
|
70
70
|
|
71
71
|
# key condition expression
|
72
|
-
convertor = FilterExpressionConvertor.new(@key_conditions, name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
72
|
+
convertor = FilterExpressionConvertor.new([@key_conditions], name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
73
73
|
key_condition_expression = convertor.expression
|
74
74
|
value_placeholders = convertor.value_placeholders
|
75
75
|
name_placeholders = convertor.name_placeholders
|
@@ -13,7 +13,7 @@ module Dynamoid
|
|
13
13
|
class Scan
|
14
14
|
attr_reader :client, :table, :conditions, :options
|
15
15
|
|
16
|
-
def initialize(client, table, conditions =
|
16
|
+
def initialize(client, table, conditions = [], options = {})
|
17
17
|
@client = client
|
18
18
|
@table = table
|
19
19
|
@conditions = conditions
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Prepare all the actions of the transaction for sending to the AWS SDK.
|
4
|
+
module Dynamoid
|
5
|
+
module AdapterPlugin
|
6
|
+
class AwsSdkV3
|
7
|
+
class Transact
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
@client = client
|
12
|
+
end
|
13
|
+
|
14
|
+
# Perform all of the item actions in a single transaction.
|
15
|
+
#
|
16
|
+
# @param [Array] items of type Dynamoid::Transaction::Action or
|
17
|
+
# any other object whose to_h is a transact_item hash
|
18
|
+
#
|
19
|
+
def transact_write_items(items)
|
20
|
+
transact_items = items.map(&:to_h)
|
21
|
+
params = {
|
22
|
+
transact_items: transact_items,
|
23
|
+
return_consumed_capacity: 'TOTAL',
|
24
|
+
return_item_collection_metrics: 'SIZE'
|
25
|
+
}
|
26
|
+
client.transact_write_items(params) # returns this
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -8,6 +8,7 @@ require_relative 'aws_sdk_v3/batch_get_item'
|
|
8
8
|
require_relative 'aws_sdk_v3/item_updater'
|
9
9
|
require_relative 'aws_sdk_v3/table'
|
10
10
|
require_relative 'aws_sdk_v3/until_past_table_status'
|
11
|
+
require_relative 'aws_sdk_v3/transact'
|
11
12
|
|
12
13
|
module Dynamoid
|
13
14
|
# @private
|
@@ -289,6 +290,10 @@ module Dynamoid
|
|
289
290
|
raise Dynamoid::Errors::ConditionalCheckFailedException, e
|
290
291
|
end
|
291
292
|
|
293
|
+
def transact_write_items(items)
|
294
|
+
Transact.new(client).transact_write_items(items)
|
295
|
+
end
|
296
|
+
|
292
297
|
# Create a table on DynamoDB. This usually takes a long time to complete.
|
293
298
|
#
|
294
299
|
# @param [String] table_name the name of the table to create
|
@@ -512,7 +517,7 @@ module Dynamoid
|
|
512
517
|
# @since 1.0.0
|
513
518
|
#
|
514
519
|
# @todo Provide support for various other options http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#query-instance_method
|
515
|
-
def query(table_name, key_conditions, non_key_conditions =
|
520
|
+
def query(table_name, key_conditions, non_key_conditions = [], options = {})
|
516
521
|
Enumerator.new do |yielder|
|
517
522
|
table = describe_table(table_name)
|
518
523
|
|
@@ -545,7 +550,7 @@ module Dynamoid
|
|
545
550
|
# @since 1.0.0
|
546
551
|
#
|
547
552
|
# @todo: Provide support for various options http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#scan-instance_method
|
548
|
-
def scan(table_name, conditions =
|
553
|
+
def scan(table_name, conditions = [], options = {})
|
549
554
|
Enumerator.new do |yielder|
|
550
555
|
table = describe_table(table_name)
|
551
556
|
|
@@ -558,7 +563,7 @@ module Dynamoid
|
|
558
563
|
end
|
559
564
|
end
|
560
565
|
|
561
|
-
def scan_count(table_name, conditions =
|
566
|
+
def scan_count(table_name, conditions = [], options = {})
|
562
567
|
table = describe_table(table_name)
|
563
568
|
options[:select] = 'COUNT'
|
564
569
|
|
@@ -662,8 +667,7 @@ module Dynamoid
|
|
662
667
|
store_attribute_with_nil_value = config_value.nil? ? false : !!config_value
|
663
668
|
|
664
669
|
attributes.reject do |_, v|
|
665
|
-
|
666
|
-
(!store_attribute_with_nil_value && v.nil?)
|
670
|
+
!store_attribute_with_nil_value && v.nil?
|
667
671
|
end.transform_values do |v|
|
668
672
|
v.is_a?(Hash) ? v.stringify_keys : v
|
669
673
|
end
|
data/lib/dynamoid/components.rb
CHANGED
@@ -13,6 +13,7 @@ module Dynamoid
|
|
13
13
|
|
14
14
|
define_model_callbacks :create, :save, :destroy, :update
|
15
15
|
define_model_callbacks :initialize, :find, :touch, only: :after
|
16
|
+
define_model_callbacks :commit, :rollback, only: :after
|
16
17
|
|
17
18
|
before_save :set_expires_field
|
18
19
|
after_initialize :set_inheritance_field
|
data/lib/dynamoid/config.rb
CHANGED
@@ -48,7 +48,9 @@ module Dynamoid
|
|
48
48
|
option :dynamodb_timezone, default: :utc # available values - :utc, :local, time zone name like "Hawaii"
|
49
49
|
option :store_datetime_as_string, default: false # store Time fields in ISO 8601 string format
|
50
50
|
option :store_date_as_string, default: false # store Date fields in ISO 8601 string format
|
51
|
+
option :store_empty_string_as_nil, default: true # store attribute's empty String value as null
|
51
52
|
option :store_boolean_as_native, default: true
|
53
|
+
option :store_binary_as_native, default: false
|
52
54
|
option :backoff, default: nil # callable object to handle exceeding of table throughput limit
|
53
55
|
option :backoff_strategies, default: {
|
54
56
|
constant: BackoffStrategies::ConstantBackoff,
|