dynamoid_advanced_where 1.5.1 → 1.6.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_materializer.rb +52 -3
- 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: a830c51235f4fec181d2c9b6a5308f55015c23aea40be329dfecaa023f561b37
|
4
|
+
data.tar.gz: 95af2de8d86ad0de5ff9925591d5d161bdc3226c5b955544cfce32595c7297c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f90439b8db774d0aae74bd08ee096b9a9fbd091b61e07e01d4b470b77f6f8de78fc52b296cf2625fd42c40b81be0b230402c378c46f1358459ffd6ddfe58036
|
7
|
+
data.tar.gz: c6f26e04f8be1dab29bc4f909fe04ae0afeaf27abc2f60515102822c13ac7690708545d1d43f14f9487acc860b77162e4618da9542d86be8f4e0d7bee289e947
|
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
|
@@ -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
|
@@ -49,6 +47,7 @@ module DynamoidAdvancedWhere
|
|
49
47
|
def each_page_via_query
|
50
48
|
query = {
|
51
49
|
table_name: table_name,
|
50
|
+
index_name: selected_index_for_query,
|
52
51
|
}.merge(filter_builder.to_query_filter)
|
53
52
|
|
54
53
|
query[:limit] = query_builder.record_limit if query_builder.record_limit
|
@@ -59,7 +58,7 @@ module DynamoidAdvancedWhere
|
|
59
58
|
loop do
|
60
59
|
results = client.query(query.merge(exclusive_start_key: page_start))
|
61
60
|
|
62
|
-
items = (results.items || []).
|
61
|
+
items = (results.items || []).map do |item|
|
63
62
|
klass.from_database(item.symbolize_keys)
|
64
63
|
end
|
65
64
|
|
@@ -109,6 +108,56 @@ module DynamoidAdvancedWhere
|
|
109
108
|
)
|
110
109
|
end
|
111
110
|
|
111
|
+
# Pick the index to query.
|
112
|
+
# 1) The first index chosen should be one that has the range and hash key satisfied.
|
113
|
+
# 2) The second should be one that has the hash key
|
114
|
+
def selected_index_for_query
|
115
|
+
possible_fields = filter_builder.extractable_fields_for_hash_and_range
|
116
|
+
|
117
|
+
satisfiable_indexes.each do |name, definition|
|
118
|
+
next unless possible_fields.key?(definition[:hash_key]) &&
|
119
|
+
possible_fields.key?(definition[:range_key])
|
120
|
+
|
121
|
+
filter_builder.select_node_for_range_key(possible_fields[definition[:range_key]])
|
122
|
+
filter_builder.select_node_for_query_filter(possible_fields[definition[:hash_key]])
|
123
|
+
|
124
|
+
return name
|
125
|
+
end
|
126
|
+
|
127
|
+
# Just take the first matching query then
|
128
|
+
name, definition = satisfiable_indexes.first
|
129
|
+
filter_builder.select_node_for_query_filter(possible_fields[definition[:hash_key]])
|
130
|
+
filter_builder.select_node_for_range_key(possible_fields[definition[:range_key]]) unless possible_fields[definition[:range_key]].blank?
|
131
|
+
|
132
|
+
name
|
133
|
+
end
|
134
|
+
|
135
|
+
def must_scan?
|
136
|
+
satisfiable_indexes.empty?
|
137
|
+
end
|
138
|
+
|
139
|
+
# find all indexes where we have a predicate on the hash key
|
140
|
+
def satisfiable_indexes
|
141
|
+
possible_fields = filter_builder.extractable_fields_for_hash_and_range
|
142
|
+
|
143
|
+
all_possible_indexes.select do |_, definition|
|
144
|
+
possible_fields.key?(definition[:hash_key])
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def all_possible_indexes
|
149
|
+
# The nil index name is the table itself
|
150
|
+
idx = { nil => { hash_key: klass.hash_key.to_s, range_key: klass.range_key.to_s } }
|
151
|
+
|
152
|
+
klass.indexes.each do |_, definition|
|
153
|
+
next unless definition.projected_attributes == :all
|
154
|
+
|
155
|
+
idx[definition.name] = { hash_key: definition.hash_key.to_s, range_key: definition.range_key.to_s }
|
156
|
+
end
|
157
|
+
|
158
|
+
idx
|
159
|
+
end
|
160
|
+
|
112
161
|
private
|
113
162
|
|
114
163
|
def client
|
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.6.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-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dynamoid
|