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 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