dynamoid_advanced_where 1.5.0 → 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: 1b7427e4321a6c8c50704320ff3912868bd50d3471dae090a26059c107f25210
4
- data.tar.gz: e87fbb9ac716caefdc16bf3190b2cbd0075263dae6aff9e88154271717055691
3
+ metadata.gz: a830c51235f4fec181d2c9b6a5308f55015c23aea40be329dfecaa023f561b37
4
+ data.tar.gz: 95af2de8d86ad0de5ff9925591d5d161bdc3226c5b955544cfce32595c7297c6
5
5
  SHA512:
6
- metadata.gz: 33c0c156eef5278bc77f7679bf56762f23937e2dc949da35a4dd16b8c4d0e3fa4e4f20d864faaac9b0e4ec99a9befe56994d87287c96452570582b93921e649f
7
- data.tar.gz: 1b0eab5b78254e4352d72d2930daf2aa1645b27e2759e87024387dd6efeaa9ed9bc70536e3fc0fd1c0fe0cdafcf4c2b22f8548672d0d3496ebb1c64687efe0ec
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.0)
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,13 +22,13 @@ GEM
22
22
  thor (>= 0.14.0)
23
23
  ast (2.4.2)
24
24
  aws-eventstream (1.2.0)
25
- aws-partitions (1.654.0)
26
- aws-sdk-core (3.166.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)
30
30
  jmespath (~> 1, >= 1.6.1)
31
- aws-sdk-dynamodb (1.78.0)
31
+ aws-sdk-dynamodb (1.83.0)
32
32
  aws-sdk-core (~> 3, >= 3.165.0)
33
33
  aws-sigv4 (~> 1.1)
34
34
  aws-sigv4 (1.5.2)
@@ -39,11 +39,11 @@ GEM
39
39
  childprocess (4.1.0)
40
40
  coderay (1.1.3)
41
41
  colorize (0.8.1)
42
- concurrent-ruby (1.1.10)
42
+ concurrent-ruby (1.2.2)
43
43
  crack (0.4.5)
44
44
  rexml
45
45
  diff-lcs (1.5.0)
46
- dynamoid (3.7.1)
46
+ dynamoid (3.8.0)
47
47
  activemodel (>= 4)
48
48
  aws-sdk-dynamodb (~> 1.0)
49
49
  concurrent-ruby (>= 1.0)
@@ -54,10 +54,10 @@ GEM
54
54
  i18n (1.12.0)
55
55
  concurrent-ruby (~> 1.0)
56
56
  iniparse (1.5.0)
57
- jmespath (1.6.1)
57
+ jmespath (1.6.2)
58
58
  json (2.6.2)
59
59
  method_source (1.0.0)
60
- minitest (5.16.3)
60
+ minitest (5.18.0)
61
61
  overcommit (0.59.1)
62
62
  childprocess (>= 0.6.3, < 5)
63
63
  iniparse (~> 1.4)
@@ -103,7 +103,7 @@ GEM
103
103
  sexp_processor (~> 4.16)
104
104
  sexp_processor (4.16.1)
105
105
  thor (1.2.1)
106
- tzinfo (2.0.5)
106
+ tzinfo (2.0.6)
107
107
  concurrent-ruby (~> 1.0)
108
108
  unicode-display_width (2.3.0)
109
109
  webmock (3.18.1)
@@ -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,71 +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.lh_operation.is_a?(Nodes::FieldNode) &&
97
- node.lh_operation.field_path.length == 1 &&
98
- node.lh_operation.field_path[0].to_s == hash_key
99
- end
100
-
101
- def extract_range_key_node
102
- return unless extract_query_filter_node
103
-
104
- @extract_range_key_node ||=
105
- case expression_node
106
- when Nodes::AndNode
107
- id_filters = expression_node.child_nodes.select do |i|
108
- field_node_valid_for_range_filter(i)
109
- end
110
-
111
- if id_filters.length == 1
112
- self.expression_node = Nodes::AndNode.new(
113
- *(expression_node.child_nodes - id_filters)
114
- )
115
-
116
- id_filters.first
117
- end
118
- end
119
- end
120
-
121
- def field_node_valid_for_range_filter(node)
122
- node.lh_operation.is_a?(Nodes::FieldNode) &&
123
- node.lh_operation.field_path.length == 1 &&
124
- node.lh_operation.field_path[0].to_s == range_key &&
125
- VALID_COMPARETORS_FOR_RANGE_FILTER.any? { |type| node.is_a?(type) }
126
- end
127
-
128
- def hash_key
129
- @hash_key ||= klass.hash_key.to_s
130
- end
131
-
132
- def range_key
133
- @range_key ||= klass.range_key.to_s
134
- end
135
97
  end
136
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.0'
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.0
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: 2022-11-01 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