dynamoid_advanced_where 1.5.1 → 1.7.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/.rubocop-https---raw-githubusercontent-com-GetTerminus-ruby-shared-configs-master--rubocop-yml +2 -4
- data/Gemfile.lock +6 -6
- data/lib/dynamoid_advanced_where/filter_builder.rb +36 -76
- data/lib/dynamoid_advanced_where/nodes/root_node.rb +1 -1
- data/lib/dynamoid_advanced_where/query_builder.rb +8 -2
- data/lib/dynamoid_advanced_where/query_materializer.rb +72 -27
- data/lib/dynamoid_advanced_where/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 239094fa5509e994cefa186a296b6f00bbc7a13913ea67803f82f173e6aea82a
|
4
|
+
data.tar.gz: cb46ded36d28fc09b54ea3b1598c34e2172232824056c9b6462f846f491e9673
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bce42bfde6205d3a63ea557b0624391170f586220247fc3c7ab17a1203eb6876d4356b6758a41df0756ea9de07ee06b29ce84ba4c40b516143a803d2d361a9cc
|
7
|
+
data.tar.gz: 633267b36adc9112e67aa8c541946d4ce9a56821d68aa339af211c5ffa400c6b6491b188e33f1dcda01ebd4ea32842fffcde432a783687d14080fc589beecb52
|
data/.rubocop-https---raw-githubusercontent-com-GetTerminus-ruby-shared-configs-master--rubocop-yml
CHANGED
@@ -3,7 +3,8 @@ AllCops:
|
|
3
3
|
- Makefile
|
4
4
|
- vendor/**/*
|
5
5
|
- bin/**/*
|
6
|
-
|
6
|
+
- '**/*_pb.rb'
|
7
|
+
|
7
8
|
Layout/EndOfLine:
|
8
9
|
Enabled: false
|
9
10
|
|
@@ -40,8 +41,6 @@ Layout/LineLength:
|
|
40
41
|
Max: 280
|
41
42
|
IgnoreCopDirectives: true
|
42
43
|
AllowedPatterns: ['\A#', '\A\s*sig { .* }\Z']
|
43
|
-
Exclude:
|
44
|
-
- '**/*_pb.rb'
|
45
44
|
|
46
45
|
Metrics/AbcSize:
|
47
46
|
Enabled: true
|
@@ -76,7 +75,6 @@ Metrics/BlockLength:
|
|
76
75
|
Max: 30
|
77
76
|
Exclude:
|
78
77
|
- spec/**/*.rb
|
79
|
-
- '**/*_pb.rb'
|
80
78
|
|
81
79
|
Metrics/ParameterLists:
|
82
80
|
Max: 6
|
data/Gemfile.lock
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dynamoid_advanced_where (1.
|
4
|
+
dynamoid_advanced_where (1.6.0)
|
5
5
|
dynamoid (>= 3.2, < 4)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (7.0.4.
|
11
|
-
activesupport (= 7.0.4.
|
12
|
-
activesupport (7.0.4.
|
10
|
+
activemodel (7.0.4.3)
|
11
|
+
activesupport (= 7.0.4.3)
|
12
|
+
activesupport (7.0.4.3)
|
13
13
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
14
|
i18n (>= 1.6, < 2)
|
15
15
|
minitest (>= 5.1)
|
@@ -22,8 +22,8 @@ GEM
|
|
22
22
|
thor (>= 0.14.0)
|
23
23
|
ast (2.4.2)
|
24
24
|
aws-eventstream (1.2.0)
|
25
|
-
aws-partitions (1.
|
26
|
-
aws-sdk-core (3.
|
25
|
+
aws-partitions (1.743.0)
|
26
|
+
aws-sdk-core (3.171.0)
|
27
27
|
aws-eventstream (~> 1, >= 1.0.2)
|
28
28
|
aws-partitions (~> 1, >= 1.651.0)
|
29
29
|
aws-sigv4 (~> 1.5)
|
@@ -8,17 +8,18 @@ module DynamoidAdvancedWhere
|
|
8
8
|
Nodes::GreaterThanNode,
|
9
9
|
].freeze
|
10
10
|
|
11
|
-
attr_accessor :expression_node, :klass
|
11
|
+
attr_accessor :expression_node, :query_filter_node, :range_key_node, :klass
|
12
12
|
|
13
13
|
def initialize(root_node:, klass:)
|
14
|
-
|
14
|
+
node = root_node.child_node
|
15
|
+
self.expression_node = node.is_a?(Nodes::AndNode) ? node : Nodes::AndNode.new(node)
|
15
16
|
self.klass = klass
|
16
17
|
end
|
17
18
|
|
18
19
|
def index_nodes
|
19
20
|
[
|
20
|
-
|
21
|
-
|
21
|
+
query_filter_node,
|
22
|
+
range_key_node,
|
22
23
|
].compact
|
23
24
|
end
|
24
25
|
|
@@ -32,16 +33,43 @@ module DynamoidAdvancedWhere
|
|
32
33
|
expression_filters
|
33
34
|
end
|
34
35
|
|
35
|
-
def
|
36
|
-
|
36
|
+
def select_node_for_range_key(node)
|
37
|
+
raise 'node not found in expression' unless expression_node.child_nodes.include?(node)
|
38
|
+
|
39
|
+
self.range_key_node = node
|
40
|
+
|
41
|
+
self.expression_node = Nodes::AndNode.new(
|
42
|
+
*(expression_node.child_nodes - [node])
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def select_node_for_query_filter(node)
|
47
|
+
raise 'node not found in expression' unless expression_node.child_nodes.include?(node)
|
48
|
+
|
49
|
+
self.query_filter_node = node
|
50
|
+
|
51
|
+
self.expression_node = Nodes::AndNode.new(
|
52
|
+
*(expression_node.child_nodes - [node])
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a hash of the field name and the node that filters on it
|
57
|
+
def extractable_fields_for_hash_and_range
|
58
|
+
expression_node.child_nodes.each_with_object({}) do |node, hash|
|
59
|
+
next unless node.respond_to?(:lh_operation) &&
|
60
|
+
node.lh_operation.is_a?(Nodes::FieldNode) &&
|
61
|
+
node.lh_operation.field_path.length == 1
|
62
|
+
|
63
|
+
hash[node.lh_operation.field_path[0].to_s] = node
|
64
|
+
end
|
37
65
|
end
|
38
66
|
|
39
67
|
private
|
40
68
|
|
41
69
|
def key_condition_expression
|
42
70
|
@key_condition_expression ||= [
|
43
|
-
|
44
|
-
|
71
|
+
query_filter_node,
|
72
|
+
range_key_node,
|
45
73
|
].compact.map(&:to_expression).join(' AND ')
|
46
74
|
end
|
47
75
|
|
@@ -66,73 +94,5 @@ module DynamoidAdvancedWhere
|
|
66
94
|
expression_attribute_values: expression_attribute_values,
|
67
95
|
}.delete_if { |_, v| v.nil? || v.empty? }
|
68
96
|
end
|
69
|
-
|
70
|
-
def extract_query_filter_node
|
71
|
-
@extract_query_filter_node ||=
|
72
|
-
case expression_node
|
73
|
-
when Nodes::EqualityNode
|
74
|
-
node = expression_node
|
75
|
-
if field_node_valid_for_key_filter(expression_node)
|
76
|
-
self.expression_node = Nodes::NullNode.new
|
77
|
-
node
|
78
|
-
end
|
79
|
-
when Nodes::AndNode
|
80
|
-
id_filters = expression_node.child_nodes.select do |i|
|
81
|
-
field_node_valid_for_key_filter(i)
|
82
|
-
end
|
83
|
-
|
84
|
-
if id_filters.length == 1
|
85
|
-
self.expression_node = Nodes::AndNode.new(
|
86
|
-
*(expression_node.child_nodes - id_filters)
|
87
|
-
)
|
88
|
-
|
89
|
-
id_filters.first
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def field_node_valid_for_key_filter(node)
|
95
|
-
node.is_a?(Nodes::EqualityNode) &&
|
96
|
-
node.respond_to?(:lh_operation) &&
|
97
|
-
node.lh_operation.is_a?(Nodes::FieldNode) &&
|
98
|
-
node.lh_operation.field_path.length == 1 &&
|
99
|
-
node.lh_operation.field_path[0].to_s == hash_key
|
100
|
-
end
|
101
|
-
|
102
|
-
def extract_range_key_node
|
103
|
-
return unless extract_query_filter_node
|
104
|
-
|
105
|
-
@extract_range_key_node ||=
|
106
|
-
case expression_node
|
107
|
-
when Nodes::AndNode
|
108
|
-
id_filters = expression_node.child_nodes.select do |i|
|
109
|
-
field_node_valid_for_range_filter(i)
|
110
|
-
end
|
111
|
-
|
112
|
-
if id_filters.length == 1
|
113
|
-
self.expression_node = Nodes::AndNode.new(
|
114
|
-
*(expression_node.child_nodes - id_filters)
|
115
|
-
)
|
116
|
-
|
117
|
-
id_filters.first
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def field_node_valid_for_range_filter(node)
|
123
|
-
node.respond_to?(:lh_operation) &&
|
124
|
-
node.lh_operation.is_a?(Nodes::FieldNode) &&
|
125
|
-
node.lh_operation.field_path.length == 1 &&
|
126
|
-
node.lh_operation.field_path[0].to_s == range_key &&
|
127
|
-
VALID_COMPARETORS_FOR_RANGE_FILTER.any? { |type| node.is_a?(type) }
|
128
|
-
end
|
129
|
-
|
130
|
-
def hash_key
|
131
|
-
@hash_key ||= klass.hash_key.to_s
|
132
|
-
end
|
133
|
-
|
134
|
-
def range_key
|
135
|
-
@range_key ||= klass.range_key.to_s
|
136
|
-
end
|
137
97
|
end
|
138
98
|
end
|
@@ -6,15 +6,16 @@ require_relative './batched_updater'
|
|
6
6
|
|
7
7
|
module DynamoidAdvancedWhere
|
8
8
|
class QueryBuilder
|
9
|
-
attr_accessor :klass, :root_node, :start_hash, :record_limit
|
9
|
+
attr_accessor :klass, :root_node, :start_hash, :record_limit, :projected_fields
|
10
10
|
|
11
11
|
delegate :all, :each_page, :each, to: :query_materializer
|
12
12
|
|
13
|
-
def initialize(klass:, record_limit: nil, start_hash: nil, root_node: nil, &blk)
|
13
|
+
def initialize(klass:, projected_fields: [], record_limit: nil, start_hash: nil, root_node: nil, &blk)
|
14
14
|
self.klass = klass
|
15
15
|
self.root_node = root_node || Nodes::RootNode.new(klass: klass, &blk)
|
16
16
|
self.start_hash = start_hash
|
17
17
|
self.record_limit = record_limit
|
18
|
+
self.projected_fields = projected_fields
|
18
19
|
|
19
20
|
freeze
|
20
21
|
end
|
@@ -49,6 +50,10 @@ module DynamoidAdvancedWhere
|
|
49
50
|
end
|
50
51
|
alias and where
|
51
52
|
|
53
|
+
def project(*fields)
|
54
|
+
dup_with_changes(projected_fields: projected_fields + fields)
|
55
|
+
end
|
56
|
+
|
52
57
|
def limit(value)
|
53
58
|
dup_with_changes(record_limit: value)
|
54
59
|
end
|
@@ -68,6 +73,7 @@ module DynamoidAdvancedWhere
|
|
68
73
|
klass: klass,
|
69
74
|
start_hash: start_hash,
|
70
75
|
root_node: root_node,
|
76
|
+
projected_fields: projected_fields,
|
71
77
|
}.merge(changes))
|
72
78
|
end
|
73
79
|
end
|
@@ -11,8 +11,6 @@ module DynamoidAdvancedWhere
|
|
11
11
|
delegate :table_name, to: :klass
|
12
12
|
delegate :to_a, :first, to: :each
|
13
13
|
|
14
|
-
delegate :must_scan?, to: :filter_builder
|
15
|
-
|
16
14
|
def initialize(query_builder:)
|
17
15
|
self.query_builder = query_builder
|
18
16
|
end
|
@@ -46,10 +44,13 @@ module DynamoidAdvancedWhere
|
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
49
|
-
def
|
50
|
-
query =
|
51
|
-
|
52
|
-
|
47
|
+
def enumerate_results(starting_query)
|
48
|
+
query = starting_query.dup
|
49
|
+
|
50
|
+
unless query_builder.projected_fields.empty?
|
51
|
+
query[:select] = 'SPECIFIC_ATTRIBUTES'
|
52
|
+
query[:projection_expression] = query_builder.projected_fields.map(&:to_s).join(',')
|
53
|
+
end
|
53
54
|
|
54
55
|
query[:limit] = query_builder.record_limit if query_builder.record_limit
|
55
56
|
|
@@ -57,9 +58,10 @@ module DynamoidAdvancedWhere
|
|
57
58
|
|
58
59
|
Enumerator.new do |yielder|
|
59
60
|
loop do
|
60
|
-
|
61
|
+
query[:exclusive_start_key] = page_start
|
62
|
+
results = yield(query)
|
61
63
|
|
62
|
-
items = (results.items || []).
|
64
|
+
items = (results.items || []).map do |item|
|
63
65
|
klass.from_database(item.symbolize_keys)
|
64
66
|
end
|
65
67
|
|
@@ -74,39 +76,82 @@ module DynamoidAdvancedWhere
|
|
74
76
|
end.lazy
|
75
77
|
end
|
76
78
|
|
79
|
+
def each_page_via_query
|
80
|
+
query = {
|
81
|
+
table_name: table_name,
|
82
|
+
index_name: selected_index_for_query,
|
83
|
+
}.merge(filter_builder.to_query_filter)
|
84
|
+
|
85
|
+
enumerate_results(query) do |q|
|
86
|
+
client.query(q)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
77
90
|
def each_page_via_scan
|
78
91
|
query = {
|
79
92
|
table_name: table_name,
|
80
93
|
}.merge(filter_builder.to_scan_filter)
|
81
94
|
|
82
|
-
query
|
95
|
+
enumerate_results(query) do |q|
|
96
|
+
client.scan(q)
|
97
|
+
end
|
98
|
+
end
|
83
99
|
|
84
|
-
|
100
|
+
def filter_builder
|
101
|
+
@filter_builder ||= FilterBuilder.new(
|
102
|
+
root_node: query_builder.root_node,
|
103
|
+
klass: klass,
|
104
|
+
)
|
105
|
+
end
|
85
106
|
|
86
|
-
|
87
|
-
|
88
|
-
|
107
|
+
# Pick the index to query.
|
108
|
+
# 1) The first index chosen should be one that has the range and hash key satisfied.
|
109
|
+
# 2) The second should be one that has the hash key
|
110
|
+
def selected_index_for_query
|
111
|
+
possible_fields = filter_builder.extractable_fields_for_hash_and_range
|
89
112
|
|
90
|
-
|
91
|
-
|
92
|
-
|
113
|
+
satisfiable_indexes.each do |name, definition|
|
114
|
+
next unless possible_fields.key?(definition[:hash_key]) &&
|
115
|
+
possible_fields.key?(definition[:range_key])
|
93
116
|
|
94
|
-
|
117
|
+
filter_builder.select_node_for_range_key(possible_fields[definition[:range_key]])
|
118
|
+
filter_builder.select_node_for_query_filter(possible_fields[definition[:hash_key]])
|
95
119
|
|
96
|
-
|
120
|
+
return name
|
121
|
+
end
|
97
122
|
|
98
|
-
|
123
|
+
# Just take the first matching query then
|
124
|
+
name, definition = satisfiable_indexes.first
|
125
|
+
filter_builder.select_node_for_query_filter(possible_fields[definition[:hash_key]])
|
126
|
+
filter_builder.select_node_for_range_key(possible_fields[definition[:range_key]]) unless possible_fields[definition[:range_key]].blank?
|
99
127
|
|
100
|
-
|
101
|
-
end
|
102
|
-
end.lazy
|
128
|
+
name
|
103
129
|
end
|
104
130
|
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
131
|
+
def must_scan?
|
132
|
+
satisfiable_indexes.empty?
|
133
|
+
end
|
134
|
+
|
135
|
+
# find all indexes where we have a predicate on the hash key
|
136
|
+
def satisfiable_indexes
|
137
|
+
possible_fields = filter_builder.extractable_fields_for_hash_and_range
|
138
|
+
|
139
|
+
all_possible_indexes.select do |_, definition|
|
140
|
+
possible_fields.key?(definition[:hash_key])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def all_possible_indexes
|
145
|
+
# The nil index name is the table itself
|
146
|
+
idx = { nil => { hash_key: klass.hash_key.to_s, range_key: klass.range_key.to_s } }
|
147
|
+
|
148
|
+
klass.indexes.each do |_, definition|
|
149
|
+
next unless definition.projected_attributes == :all
|
150
|
+
|
151
|
+
idx[definition.name] = { hash_key: definition.hash_key.to_s, range_key: definition.range_key.to_s }
|
152
|
+
end
|
153
|
+
|
154
|
+
idx
|
110
155
|
end
|
111
156
|
|
112
157
|
private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynamoid_advanced_where
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Malinconico
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dynamoid
|