dynamoid_advanced_where 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca5af3b66602bc1a123044bd91797fefc1417d08e2ebf8c8103f0540aa68f0af
4
- data.tar.gz: 167687a1a39ec4b088445dda5bbb15ad3463b03ee1cf9bf6d4ddff7359812185
3
+ metadata.gz: a830c51235f4fec181d2c9b6a5308f55015c23aea40be329dfecaa023f561b37
4
+ data.tar.gz: 95af2de8d86ad0de5ff9925591d5d161bdc3226c5b955544cfce32595c7297c6
5
5
  SHA512:
6
- metadata.gz: 164d8e1236c19769ecdeb29370aa5d0c0305a0070eb00477e7871d573a364e1288d1be77a1cb1fd2fa2ed3eea2885e887cb20fab1a7846613dbd8eb64b426576
7
- data.tar.gz: 735bc10d7925ae510f8154e0a3bffa0e6e77c76c5de700fa15b39b96fa6b2aebceb75607da860676ad5478cc34148a141ca8cf686457aed23b95de45247d9c42
6
+ metadata.gz: 9f90439b8db774d0aae74bd08ee096b9a9fbd091b61e07e01d4b470b77f6f8de78fc52b296cf2625fd42c40b81be0b230402c378c46f1358459ffd6ddfe58036
7
+ data.tar.gz: c6f26e04f8be1dab29bc4f909fe04ae0afeaf27abc2f60515102822c13ac7690708545d1d43f14f9487acc860b77162e4618da9542d86be8f4e0d7bee289e947
@@ -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.5.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.2)
11
- activesupport (= 7.0.4.2)
12
- activesupport (7.0.4.2)
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.725.0)
26
- aws-sdk-core (3.170.0)
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
- self.expression_node = root_node.child_node
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
- extract_query_filter_node,
21
- extract_range_key_node,
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 must_scan?
36
- !extract_query_filter_node.is_a?(Nodes::BaseNode)
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
- extract_query_filter_node,
44
- extract_range_key_node,
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
@@ -12,7 +12,7 @@ module DynamoidAdvancedWhere
12
12
  def initialize(klass:, &blk)
13
13
  self.klass = klass
14
14
  evaluate_block(blk) if blk
15
- self.child_node ||= NullNode.new
15
+ self.child_node ||= AndNode.new
16
16
  freeze
17
17
  end
18
18
 
@@ -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 || []).each do |item|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DynamoidAdvancedWhere
4
- VERSION = '1.5.1'
4
+ VERSION = '1.6.0'
5
5
  end
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.5.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-03-13 00:00:00.000000000 Z
11
+ date: 2023-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dynamoid