dynamoid 3.9.0 → 3.10.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 +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
|