dynamoid 3.9.0 → 3.10.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 +13 -7
- data/README.md +20 -23
- data/dynamoid.gemspec +1 -2
- data/lib/dynamoid/adapter.rb +18 -12
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +78 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +19 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +38 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +33 -27
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +87 -62
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +1 -0
- data/lib/dynamoid/criteria/chain.rb +95 -133
- data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
- data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
- data/lib/dynamoid/criteria/where_conditions.rb +29 -0
- data/lib/dynamoid/dirty.rb +1 -1
- data/lib/dynamoid/document.rb +1 -1
- data/lib/dynamoid/dumping.rb +2 -2
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +6 -8
- data/lib/dynamoid/finders.rb +17 -26
- data/lib/dynamoid/indexes.rb +6 -7
- data/lib/dynamoid/loadable.rb +2 -2
- data/lib/dynamoid/persistence/save.rb +12 -16
- data/lib/dynamoid/persistence/update_fields.rb +2 -2
- data/lib/dynamoid/persistence/update_validations.rb +1 -1
- data/lib/dynamoid/persistence.rb +39 -4
- data/lib/dynamoid/type_casting.rb +15 -14
- data/lib/dynamoid/undumping.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- metadata +17 -16
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2cc1feec6853b756fff467fa687cf2eaa3a2dc5f0a88dac948e5a92a4aa1073
|
4
|
+
data.tar.gz: 7165e081fa9979c45e7402265138e62cb6ee3c1f6f594ab6949c1f984621bc89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e18700ff74b9466e1ec166706446a1dca5248c6b8c33365e469c74e9a67b92f4afc9cacb0a06c2698d5165f4b68a93cdd214b83f5e3b749b92bcaff06fbc7628
|
7
|
+
data.tar.gz: 9b18fb2522f90eb3cf9b0b6014ca24beb08bcdebf403c57487222c46ec71d306f4e85e686aa89607c77ad7d994359a934537e8d22202b145bd478c0862bf497b
|
data/CHANGELOG.md
CHANGED
@@ -7,14 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
9
|
### Fixed
|
10
|
-
|
11
10
|
### Added
|
12
|
-
|
13
11
|
### Changed
|
14
|
-
|
15
12
|
### Removed
|
16
13
|
|
17
|
-
## 3.
|
14
|
+
## 3.10.0
|
15
|
+
### Fixed
|
16
|
+
* [#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`
|
17
|
+
* [#716](https://github.com/Dynamoid/dynamoid/pull/716), [#691](https://github.com/Dynamoid/dynamoid/pull/691), [#687](https://github.com/Dynamoid/dynamoid/pull/687), [#660](https://github.com/Dynamoid/dynamoid/pull/660) Numerous fixes in README.md and RDoc documentation (@ndjndj, @kiharito, @dunkOnIT)
|
18
|
+
### Added
|
19
|
+
* [#656](https://github.com/Dynamoid/dynamoid/pull/656) Added a `create_table_on_save` configuration flag to create table on save (@imaximix)
|
20
|
+
* [#697](https://github.com/Dynamoid/dynamoid/pull/697) Ensure Ruby 3.3 and Rails 7.1 versions are supported and added them on CI
|
21
|
+
### Changed
|
22
|
+
* [#655](https://github.com/Dynamoid/dynamoid/pull/655) Support multiple `where` in the same chain with multiple conditions for the same field
|
23
|
+
|
24
|
+
## 3.9.0 / 2023-04-13
|
18
25
|
### Fixed
|
19
26
|
* [#610](https://github.com/Dynamoid/dynamoid/pull/610) Specs in JRuby; Support for JRuby 9.4.0.0 (@pboling)
|
20
27
|
* [#624](https://github.com/Dynamoid/dynamoid/pull/624) Fixed `#increment!`/`#decrement!` methods and made them compatible with Rails counterparts
|
@@ -50,15 +57,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
50
57
|
* `#touch`
|
51
58
|
* `#increment!`
|
52
59
|
* `#decrement!`
|
53
|
-
* [#642](https://github.com/Dynamoid/dynamoid/pull/642) Run specs on CI
|
60
|
+
* [#642](https://github.com/Dynamoid/dynamoid/pull/642) Run specs on CI against Ruby 3.2
|
54
61
|
* [#645](https://github.com/Dynamoid/dynamoid/pull/645) Added `after_find` callback
|
55
62
|
### Changed
|
56
63
|
* [#610](https://github.com/Dynamoid/dynamoid/pull/610) Switch to [`rubocop-lts`](https://rubocop-lts.gitlab.io/) (@pboling)
|
57
|
-
### Removed
|
58
64
|
* [#633](https://github.com/Dynamoid/dynamoid/pull/633) Change `#inspect` method to return only attributes
|
59
65
|
* [#623](https://github.com/Dynamoid/dynamoid/pull/623) Optimized performance of persisting to send only changed attributes in a request to DynamoDB
|
60
66
|
|
61
|
-
## 3.8.0
|
67
|
+
## 3.8.0 / 2022-11-09
|
62
68
|
### Fixed
|
63
69
|
* [#525](https://github.com/Dynamoid/dynamoid/pull/525) Don't mark an attribute as changed if new assigned value equals the old one (@a5-stable)
|
64
70
|
* Minor changes in the documentation:
|
data/README.md
CHANGED
@@ -67,10 +67,10 @@ For example, to configure AWS access:
|
|
67
67
|
Create `config/initializers/aws.rb` as follows:
|
68
68
|
|
69
69
|
```ruby
|
70
|
-
Aws.config.update(
|
71
|
-
|
70
|
+
Aws.config.update(
|
71
|
+
region: 'us-west-2',
|
72
72
|
credentials: Aws::Credentials.new('REPLACE_WITH_ACCESS_KEY_ID', 'REPLACE_WITH_SECRET_ACCESS_KEY'),
|
73
|
-
|
73
|
+
)
|
74
74
|
```
|
75
75
|
|
76
76
|
Alternatively, if you don't want Aws connection settings to be
|
@@ -132,8 +132,8 @@ end
|
|
132
132
|
Dynamoid supports Ruby >= 2.3 and Rails >= 4.2.
|
133
133
|
|
134
134
|
Its compatibility is tested against following Ruby versions: 2.3, 2.4,
|
135
|
-
2.5, 2.6, 2.7, 3.0, 3.1 and 3.
|
136
|
-
5.2, 6.0, 6.1 and 7.
|
135
|
+
2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and 3.3, JRuby 9.4.x and against Rails versions: 4.2, 5.0, 5.1,
|
136
|
+
5.2, 6.0, 6.1, 7.0 and 7.1.
|
137
137
|
|
138
138
|
## Setup
|
139
139
|
|
@@ -723,18 +723,6 @@ join, but instead finds all the user's addresses and naively filters
|
|
723
723
|
them in Ruby. For large associations this is a performance hit compared
|
724
724
|
to relational database engines.
|
725
725
|
|
726
|
-
**WARNING:** There is a limitation of conditions passed to `where`
|
727
|
-
method. Only one condition for some particular field could be specified.
|
728
|
-
The last one only will be applied and others will be ignored. E.g. in
|
729
|
-
examples:
|
730
|
-
|
731
|
-
```ruby
|
732
|
-
User.where('age.gt': 10, 'age.lt': 20)
|
733
|
-
User.where(name: 'Mike').where('name.begins_with': 'Ed')
|
734
|
-
```
|
735
|
-
|
736
|
-
the first one will be ignored and the last one will be used.
|
737
|
-
|
738
726
|
**Warning:** There is a caveat with filtering documents by `nil` value
|
739
727
|
attribute. By default Dynamoid ignores attributes with `nil` value and
|
740
728
|
doesn't store them in a DynamoDB document. This behavior could be
|
@@ -752,7 +740,7 @@ If Dynamoid keeps `nil` value attributes `eq`/`ne` operators should be
|
|
752
740
|
used instead:
|
753
741
|
|
754
742
|
```ruby
|
755
|
-
Address.where(
|
743
|
+
Address.where(postcode: nil)
|
756
744
|
Address.where('postcode.ne': nil)
|
757
745
|
```
|
758
746
|
|
@@ -926,8 +914,8 @@ If you have a range index, Dynamoid provides a number of additional
|
|
926
914
|
other convenience methods to make your life a little easier:
|
927
915
|
|
928
916
|
```ruby
|
929
|
-
User.where(
|
930
|
-
User.where(
|
917
|
+
User.where('created_at.gt': DateTime.now - 1.day).all
|
918
|
+
User.where('created_at.lt': DateTime.now - 1.day).all
|
931
919
|
```
|
932
920
|
|
933
921
|
It also supports `gte` and `lte`. Turning those into symbols and
|
@@ -975,7 +963,7 @@ To idempotently create-but-not-update a record, apply the `unless_exists` condit
|
|
975
963
|
to its keys when you upsert.
|
976
964
|
|
977
965
|
```ruby
|
978
|
-
Address.upsert(id, { city: 'Chicago' },
|
966
|
+
Address.upsert(id, { city: 'Chicago' }, { unless_exists: [:id] })
|
979
967
|
```
|
980
968
|
|
981
969
|
### Deleting
|
@@ -1144,12 +1132,12 @@ Listed below are all configuration options.
|
|
1144
1132
|
in ISO 8601 string format. Default is `false`
|
1145
1133
|
* `store_boolean_as_native` - if `true` Dynamoid stores boolean fields
|
1146
1134
|
as native DynamoDB boolean values. Otherwise boolean fields are stored
|
1147
|
-
as string values `'t'` and `'f'`. Default is true
|
1135
|
+
as string values `'t'` and `'f'`. Default is `true`
|
1148
1136
|
* `backoff` - is a hash: key is a backoff strategy (symbol), value is
|
1149
1137
|
parameters for the strategy. Is used in batch operations. Default id
|
1150
1138
|
`nil`
|
1151
1139
|
* `backoff_strategies`: is a hash and contains all available strategies.
|
1152
|
-
Default is { constant: ..., exponential: ...}
|
1140
|
+
Default is `{ constant: ..., exponential: ...}`
|
1153
1141
|
* `log_formatter`: overrides default AWS SDK formatter. There are
|
1154
1142
|
several canned formatters: `Aws::Log::Formatter.default`,
|
1155
1143
|
`Aws::Log::Formatter.colored` and `Aws::Log::Formatter.short`. Please
|
@@ -1167,6 +1155,9 @@ Listed below are all configuration options.
|
|
1167
1155
|
* `http_read_timeout`:The number of seconds to wait for HTTP response
|
1168
1156
|
data. Default option value is `nil`. If not specified effected value
|
1169
1157
|
is `60`
|
1158
|
+
* `create_table_on_save`: if `true` then Dynamoid creates a
|
1159
|
+
corresponding table in DynamoDB at model persisting if the table
|
1160
|
+
doesn't exist yet. Default is `true`
|
1170
1161
|
|
1171
1162
|
|
1172
1163
|
## Concurrency
|
@@ -1305,6 +1296,12 @@ RSpec.configure do |config|
|
|
1305
1296
|
end
|
1306
1297
|
```
|
1307
1298
|
|
1299
|
+
In addition, the first test for each model may fail if the relevant models are not included in `included_models`. This can be fixed by adding this line before the `DynamoidReset` module:
|
1300
|
+
```ruby
|
1301
|
+
Dir[File.join(Dynamoid::Config.models_dir, '**/*.rb')].sort.each { |file| require file }
|
1302
|
+
```
|
1303
|
+
Note that this will require _all_ models in your models folder - you can also explicitly require only certain models if you would prefer to.
|
1304
|
+
|
1308
1305
|
In Rails, you may also want to ensure you do not delete non-test data
|
1309
1306
|
accidentally by adding the following to your test environment setup:
|
1310
1307
|
|
data/dynamoid.gemspec
CHANGED
@@ -59,8 +59,7 @@ Gem::Specification.new do |spec|
|
|
59
59
|
spec.add_development_dependency 'bundler'
|
60
60
|
spec.add_development_dependency 'pry', '~> 0.14'
|
61
61
|
spec.add_development_dependency 'rake', '~> 13.0'
|
62
|
+
spec.add_development_dependency 'rexml'
|
62
63
|
spec.add_development_dependency 'rspec', '~> 3.12'
|
63
|
-
# 'rubocop-lts' is for Ruby 2.3+, see https://rubocop-lts.gitlab.io/
|
64
|
-
spec.add_development_dependency 'rubocop-lts', '~> 10.0'
|
65
64
|
spec.add_development_dependency 'yard'
|
66
65
|
end
|
data/lib/dynamoid/adapter.rb
CHANGED
@@ -171,19 +171,25 @@ module Dynamoid
|
|
171
171
|
# only really useful for range queries, since it can only find by one hash key at once. Only provide
|
172
172
|
# one range key to the hash.
|
173
173
|
#
|
174
|
+
# Dynamoid.adapter.query('users', { id: [[:eq, '1']], age: [[:between, [10, 30]]] }, { batch_size: 1000 })
|
175
|
+
#
|
174
176
|
# @param [String] table_name the name of the table
|
175
|
-
# @param [
|
176
|
-
# @
|
177
|
-
# @
|
178
|
-
# @option
|
179
|
-
# @option
|
180
|
-
# @option
|
181
|
-
# @option
|
182
|
-
#
|
183
|
-
# @
|
184
|
-
#
|
185
|
-
|
186
|
-
|
177
|
+
# @param [Array[Array]] key_conditions conditions for the primary key attributes
|
178
|
+
# @param [Array[Array]] non_key_conditions (optional) conditions for non-primary key attributes
|
179
|
+
# @param [Hash] options (optional) the options to query the table with
|
180
|
+
# @option options [Boolean] :consistent_read You can set the ConsistentRead parameter to true and obtain a strongly consistent result
|
181
|
+
# @option options [Boolean] :scan_index_forward Specifies the order for index traversal: If true (default), the traversal is performed in ascending order; if false, the traversal is performed in descending order.
|
182
|
+
# @option options [Symbop] :select The attributes to be returned in the result (one of ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, ...)
|
183
|
+
# @option options [Symbol] :index_name The name of an index to query. This index can be any local secondary index or global secondary index on the table.
|
184
|
+
# @option options [Hash] :exclusive_start_key The primary key of the first item that this operation will evaluate.
|
185
|
+
# @option options [Integer] :batch_size The number of items to lazily load one by one
|
186
|
+
# @option options [Integer] :record_limit The maximum number of items to return (not necessarily the number of evaluated items)
|
187
|
+
# @option options [Integer] :scan_limit The maximum number of items to evaluate (not necessarily the number of matching items)
|
188
|
+
# @option options [Array[Symbol]] :project The attributes to retrieve from the table
|
189
|
+
#
|
190
|
+
# @return [Enumerable] matching items
|
191
|
+
def query(table_name, key_conditions, non_key_conditions = {}, options = {})
|
192
|
+
adapter.query(table_name, key_conditions, non_key_conditions, options)
|
187
193
|
end
|
188
194
|
|
189
195
|
def self.adapter_plugin_class
|
@@ -29,7 +29,7 @@ module Dynamoid
|
|
29
29
|
gs_indexes = options[:global_secondary_indexes]
|
30
30
|
|
31
31
|
key_schema = {
|
32
|
-
hash_key_schema: { key =>
|
32
|
+
hash_key_schema: { key => options[:hash_key_type] || :string },
|
33
33
|
range_key_schema: options[:range_key]
|
34
34
|
}
|
35
35
|
attribute_definitions = build_all_attribute_definitions(
|
@@ -69,7 +69,7 @@ module Dynamoid
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
resp = client.create_table(client_opts)
|
72
|
-
options[:sync] = true if !options.key?(:sync) && ls_indexes.present? || gs_indexes.present?
|
72
|
+
options[:sync] = true if (!options.key?(:sync) && ls_indexes.present?) || gs_indexes.present?
|
73
73
|
|
74
74
|
if options[:sync]
|
75
75
|
status = PARSE_TABLE_STATUS.call(resp, :table_description)
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
# @private
|
5
|
+
module AdapterPlugin
|
6
|
+
class AwsSdkV3
|
7
|
+
class FilterExpressionConvertor
|
8
|
+
attr_reader :expression, :name_placeholders, :value_placeholders
|
9
|
+
|
10
|
+
def initialize(conditions, name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
11
|
+
@conditions = conditions
|
12
|
+
@name_placeholders = name_placeholders.dup
|
13
|
+
@value_placeholders = value_placeholders.dup
|
14
|
+
@name_placeholder_sequence = name_placeholder_sequence
|
15
|
+
@value_placeholder_sequence = value_placeholder_sequence
|
16
|
+
|
17
|
+
build
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def build
|
23
|
+
clauses = @conditions.map do |name, attribute_conditions|
|
24
|
+
attribute_conditions.map do |operator, value|
|
25
|
+
name_or_placeholder = name_or_placeholder_for(name)
|
26
|
+
|
27
|
+
case operator
|
28
|
+
when :eq
|
29
|
+
"#{name_or_placeholder} = #{value_placeholder_for(value)}"
|
30
|
+
when :ne
|
31
|
+
"#{name_or_placeholder} <> #{value_placeholder_for(value)}"
|
32
|
+
when :gt
|
33
|
+
"#{name_or_placeholder} > #{value_placeholder_for(value)}"
|
34
|
+
when :lt
|
35
|
+
"#{name_or_placeholder} < #{value_placeholder_for(value)}"
|
36
|
+
when :gte
|
37
|
+
"#{name_or_placeholder} >= #{value_placeholder_for(value)}"
|
38
|
+
when :lte
|
39
|
+
"#{name_or_placeholder} <= #{value_placeholder_for(value)}"
|
40
|
+
when :between
|
41
|
+
"#{name_or_placeholder} BETWEEN #{value_placeholder_for(value[0])} AND #{value_placeholder_for(value[1])}"
|
42
|
+
when :begins_with
|
43
|
+
"begins_with (#{name_or_placeholder}, #{value_placeholder_for(value)})"
|
44
|
+
when :in
|
45
|
+
list = value.map(&method(:value_placeholder_for)).join(' , ')
|
46
|
+
"#{name_or_placeholder} IN (#{list})"
|
47
|
+
when :contains
|
48
|
+
"contains (#{name_or_placeholder}, #{value_placeholder_for(value)})"
|
49
|
+
when :not_contains
|
50
|
+
"NOT contains (#{name_or_placeholder}, #{value_placeholder_for(value)})"
|
51
|
+
when :null
|
52
|
+
"attribute_not_exists (#{name_or_placeholder})"
|
53
|
+
when :not_null
|
54
|
+
"attribute_exists (#{name_or_placeholder})"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end.flatten
|
58
|
+
|
59
|
+
@expression = clauses.join(' AND ')
|
60
|
+
end
|
61
|
+
|
62
|
+
def name_or_placeholder_for(name)
|
63
|
+
return name unless name.upcase.in?(Dynamoid::AdapterPlugin::AwsSdkV3::RESERVED_WORDS)
|
64
|
+
|
65
|
+
placeholder = @name_placeholder_sequence.call
|
66
|
+
@name_placeholders[placeholder] = name
|
67
|
+
placeholder
|
68
|
+
end
|
69
|
+
|
70
|
+
def value_placeholder_for(value)
|
71
|
+
placeholder = @value_placeholder_sequence.call
|
72
|
+
@value_placeholders[placeholder] = value
|
73
|
+
placeholder
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -50,7 +50,19 @@ module Dynamoid
|
|
50
50
|
# Replaces the values of one or more attributes
|
51
51
|
#
|
52
52
|
def set(values)
|
53
|
-
|
53
|
+
values_sanitized = sanitize_attributes(values)
|
54
|
+
|
55
|
+
if Dynamoid.config.store_attribute_with_nil_value
|
56
|
+
@updates.merge!(values_sanitized)
|
57
|
+
else
|
58
|
+
# delete explicitly attributes if assigned nil value and configured
|
59
|
+
# to not store nil values
|
60
|
+
values_to_update = values_sanitized.reject { |_, v| v.nil? }
|
61
|
+
values_to_delete = values_sanitized.select { |_, v| v.nil? }
|
62
|
+
|
63
|
+
@updates.merge!(values_to_update)
|
64
|
+
@deletions.merge!(values_to_delete)
|
65
|
+
end
|
54
66
|
end
|
55
67
|
|
56
68
|
#
|
@@ -85,7 +97,12 @@ module Dynamoid
|
|
85
97
|
|
86
98
|
private
|
87
99
|
|
100
|
+
# Keep in sync with AwsSdkV3.sanitize_item.
|
101
|
+
#
|
102
|
+
# The only difference is that to update item we need to track whether
|
103
|
+
# attribute value is nil or not.
|
88
104
|
def sanitize_attributes(attributes)
|
105
|
+
# rubocop:disable Lint/DuplicateBranch
|
89
106
|
attributes.transform_values do |v|
|
90
107
|
if v.is_a?(Hash)
|
91
108
|
v.stringify_keys
|
@@ -97,6 +114,7 @@ module Dynamoid
|
|
97
114
|
v
|
98
115
|
end
|
99
116
|
end
|
117
|
+
# rubocop:enable Lint/DuplicateBranch
|
100
118
|
end
|
101
119
|
end
|
102
120
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
# @private
|
5
|
+
module AdapterPlugin
|
6
|
+
class AwsSdkV3
|
7
|
+
class ProjectionExpressionConvertor
|
8
|
+
attr_reader :expression, :name_placeholders
|
9
|
+
|
10
|
+
def initialize(names, name_placeholders, name_placeholder_sequence)
|
11
|
+
@names = names
|
12
|
+
@name_placeholders = name_placeholders.dup
|
13
|
+
@name_placeholder_sequence = name_placeholder_sequence
|
14
|
+
|
15
|
+
build
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def build
|
21
|
+
return if @names.nil? || @names.empty?
|
22
|
+
|
23
|
+
clauses = @names.map do |name|
|
24
|
+
if name.upcase.in?(Dynamoid::AdapterPlugin::AwsSdkV3::RESERVED_WORDS)
|
25
|
+
placeholder = @name_placeholder_sequence.call
|
26
|
+
@name_placeholders[placeholder] = name
|
27
|
+
placeholder
|
28
|
+
else
|
29
|
+
name.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@expression = clauses.join(' , ')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative 'middleware/backoff'
|
4
4
|
require_relative 'middleware/limit'
|
5
5
|
require_relative 'middleware/start_key'
|
6
|
+
require_relative 'filter_expression_convertor'
|
7
|
+
require_relative 'projection_expression_convertor'
|
6
8
|
|
7
9
|
module Dynamoid
|
8
10
|
# @private
|
@@ -10,20 +12,19 @@ module Dynamoid
|
|
10
12
|
class AwsSdkV3
|
11
13
|
class Query
|
12
14
|
OPTIONS_KEYS = %i[
|
13
|
-
|
14
|
-
|
15
|
-
project
|
15
|
+
consistent_read scan_index_forward select index_name batch_size
|
16
|
+
exclusive_start_key record_limit scan_limit project
|
16
17
|
].freeze
|
17
18
|
|
18
19
|
attr_reader :client, :table, :options, :conditions
|
19
20
|
|
20
|
-
def initialize(client, table,
|
21
|
+
def initialize(client, table, key_conditions, non_key_conditions, options)
|
21
22
|
@client = client
|
22
23
|
@table = table
|
23
24
|
|
24
|
-
|
25
|
-
@
|
26
|
-
@
|
25
|
+
@key_conditions = key_conditions
|
26
|
+
@non_key_conditions = non_key_conditions
|
27
|
+
@options = options.slice(*OPTIONS_KEYS)
|
27
28
|
end
|
28
29
|
|
29
30
|
def call
|
@@ -53,6 +54,37 @@ module Dynamoid
|
|
53
54
|
private
|
54
55
|
|
55
56
|
def build_request
|
57
|
+
# expressions
|
58
|
+
name_placeholder = +'#_a0'
|
59
|
+
value_placeholder = +':_a0'
|
60
|
+
|
61
|
+
name_placeholder_sequence = -> { name_placeholder.next!.dup }
|
62
|
+
value_placeholder_sequence = -> { value_placeholder.next!.dup }
|
63
|
+
|
64
|
+
name_placeholders = {}
|
65
|
+
value_placeholders = {}
|
66
|
+
|
67
|
+
# Deal with various limits and batching
|
68
|
+
batch_size = options[:batch_size]
|
69
|
+
limit = [record_limit, scan_limit, batch_size].compact.min
|
70
|
+
|
71
|
+
# key condition expression
|
72
|
+
convertor = FilterExpressionConvertor.new(@key_conditions, name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
73
|
+
key_condition_expression = convertor.expression
|
74
|
+
value_placeholders = convertor.value_placeholders
|
75
|
+
name_placeholders = convertor.name_placeholders
|
76
|
+
|
77
|
+
# filter expression
|
78
|
+
convertor = FilterExpressionConvertor.new(@non_key_conditions, name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
79
|
+
filter_expression = convertor.expression
|
80
|
+
value_placeholders = convertor.value_placeholders
|
81
|
+
name_placeholders = convertor.name_placeholders
|
82
|
+
|
83
|
+
# projection expression
|
84
|
+
convertor = ProjectionExpressionConvertor.new(options[:project], name_placeholders, name_placeholder_sequence)
|
85
|
+
projection_expression = convertor.expression
|
86
|
+
name_placeholders = convertor.name_placeholders
|
87
|
+
|
56
88
|
request = options.slice(
|
57
89
|
:consistent_read,
|
58
90
|
:scan_index_forward,
|
@@ -61,15 +93,13 @@ module Dynamoid
|
|
61
93
|
:exclusive_start_key
|
62
94
|
).compact
|
63
95
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
request[:
|
69
|
-
request[:
|
70
|
-
request[:
|
71
|
-
request[:query_filter] = query_filter
|
72
|
-
request[:attributes_to_get] = attributes_to_get
|
96
|
+
request[:table_name] = table.name
|
97
|
+
request[:limit] = limit if limit
|
98
|
+
request[:key_condition_expression] = key_condition_expression if key_condition_expression.present?
|
99
|
+
request[:filter_expression] = filter_expression if filter_expression.present?
|
100
|
+
request[:expression_attribute_values] = value_placeholders if value_placeholders.present?
|
101
|
+
request[:expression_attribute_names] = name_placeholders if name_placeholders.present?
|
102
|
+
request[:projection_expression] = projection_expression if projection_expression.present?
|
73
103
|
|
74
104
|
request
|
75
105
|
end
|
@@ -81,51 +111,6 @@ module Dynamoid
|
|
81
111
|
def scan_limit
|
82
112
|
options[:scan_limit]
|
83
113
|
end
|
84
|
-
|
85
|
-
def hash_key_name
|
86
|
-
(options[:hash_key] || table.hash_key)
|
87
|
-
end
|
88
|
-
|
89
|
-
def range_key_name
|
90
|
-
(options[:range_key] || table.range_key)
|
91
|
-
end
|
92
|
-
|
93
|
-
def key_conditions
|
94
|
-
result = {
|
95
|
-
hash_key_name => {
|
96
|
-
comparison_operator: AwsSdkV3::EQ,
|
97
|
-
attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::EQ, options[:hash_value].freeze)
|
98
|
-
}
|
99
|
-
}
|
100
|
-
|
101
|
-
conditions.slice(*AwsSdkV3::RANGE_MAP.keys).each do |k, _v|
|
102
|
-
op = AwsSdkV3::RANGE_MAP[k]
|
103
|
-
|
104
|
-
result[range_key_name] = {
|
105
|
-
comparison_operator: op,
|
106
|
-
attribute_value_list: AwsSdkV3.attribute_value_list(op, conditions[k].freeze)
|
107
|
-
}
|
108
|
-
end
|
109
|
-
|
110
|
-
result
|
111
|
-
end
|
112
|
-
|
113
|
-
def query_filter
|
114
|
-
conditions.except(*AwsSdkV3::RANGE_MAP.keys).reduce({}) do |result, (attr, cond)|
|
115
|
-
condition = {
|
116
|
-
comparison_operator: AwsSdkV3::FIELD_MAP[cond.keys[0]],
|
117
|
-
attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::FIELD_MAP[cond.keys[0]], cond.values[0].freeze)
|
118
|
-
}
|
119
|
-
result[attr] = condition
|
120
|
-
result
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def attributes_to_get
|
125
|
-
return if options[:project].nil?
|
126
|
-
|
127
|
-
options[:project].map(&:to_s)
|
128
|
-
end
|
129
114
|
end
|
130
115
|
end
|
131
116
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative 'middleware/backoff'
|
4
4
|
require_relative 'middleware/limit'
|
5
5
|
require_relative 'middleware/start_key'
|
6
|
+
require_relative 'filter_expression_convertor'
|
7
|
+
require_relative 'projection_expression_convertor'
|
6
8
|
|
7
9
|
module Dynamoid
|
8
10
|
# @private
|
@@ -45,6 +47,31 @@ module Dynamoid
|
|
45
47
|
private
|
46
48
|
|
47
49
|
def build_request
|
50
|
+
# expressions
|
51
|
+
name_placeholder = +'#_a0'
|
52
|
+
value_placeholder = +':_a0'
|
53
|
+
|
54
|
+
name_placeholder_sequence = -> { name_placeholder.next!.dup }
|
55
|
+
value_placeholder_sequence = -> { value_placeholder.next!.dup }
|
56
|
+
|
57
|
+
name_placeholders = {}
|
58
|
+
value_placeholders = {}
|
59
|
+
|
60
|
+
# Deal with various limits and batching
|
61
|
+
batch_size = options[:batch_size]
|
62
|
+
limit = [record_limit, scan_limit, batch_size].compact.min
|
63
|
+
|
64
|
+
# filter expression
|
65
|
+
convertor = FilterExpressionConvertor.new(conditions, name_placeholders, value_placeholders, name_placeholder_sequence, value_placeholder_sequence)
|
66
|
+
filter_expression = convertor.expression
|
67
|
+
value_placeholders = convertor.value_placeholders
|
68
|
+
name_placeholders = convertor.name_placeholders
|
69
|
+
|
70
|
+
# projection expression
|
71
|
+
convertor = ProjectionExpressionConvertor.new(options[:project], name_placeholders, name_placeholder_sequence)
|
72
|
+
projection_expression = convertor.expression
|
73
|
+
name_placeholders = convertor.name_placeholders
|
74
|
+
|
48
75
|
request = options.slice(
|
49
76
|
:consistent_read,
|
50
77
|
:exclusive_start_key,
|
@@ -52,14 +79,12 @@ module Dynamoid
|
|
52
79
|
:index_name
|
53
80
|
).compact
|
54
81
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
request[:
|
60
|
-
request[:
|
61
|
-
request[:scan_filter] = scan_filter
|
62
|
-
request[:attributes_to_get] = attributes_to_get
|
82
|
+
request[:table_name] = table.name
|
83
|
+
request[:limit] = limit if limit
|
84
|
+
request[:filter_expression] = filter_expression if filter_expression.present?
|
85
|
+
request[:expression_attribute_values] = value_placeholders if value_placeholders.present?
|
86
|
+
request[:expression_attribute_names] = name_placeholders if name_placeholders.present?
|
87
|
+
request[:projection_expression] = projection_expression if projection_expression.present?
|
63
88
|
|
64
89
|
request
|
65
90
|
end
|
@@ -71,25 +96,6 @@ module Dynamoid
|
|
71
96
|
def scan_limit
|
72
97
|
options[:scan_limit]
|
73
98
|
end
|
74
|
-
|
75
|
-
def scan_filter
|
76
|
-
conditions.reduce({}) do |result, (attr, cond)|
|
77
|
-
condition = {
|
78
|
-
comparison_operator: AwsSdkV3::FIELD_MAP[cond.keys[0]],
|
79
|
-
attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::FIELD_MAP[cond.keys[0]], cond.values[0].freeze)
|
80
|
-
}
|
81
|
-
# nil means operator doesn't require attribute value list
|
82
|
-
conditions.delete(:attribute_value_list) if conditions[:attribute_value_list].nil?
|
83
|
-
result[attr] = condition
|
84
|
-
result
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def attributes_to_get
|
89
|
-
return if options[:project].nil?
|
90
|
-
|
91
|
-
options[:project].map(&:to_s)
|
92
|
-
end
|
93
99
|
end
|
94
100
|
end
|
95
101
|
end
|