Skip to content

Commit 8f5b14c

Browse files
authored
Merge pull request #363 from Dynamoid/refactor-key-fields-detector
Refactor KeyFieldsDetector
2 parents 0afb3aa + 87d0957 commit 8f5b14c

File tree

3 files changed

+104
-32
lines changed

3 files changed

+104
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
## Fixes
1010
* Fix: [#357](https://github.com/Dynamoid/dynamoid/pull/357) Fix synchronous table creation issue
11+
* Fix: [#362](https://github.com/Dynamoid/dynamoid/pull/362) Fix issue with selecting Global Secondary Index (@atyndall)
1112

1213
---
1314

lib/dynamoid/criteria/key_fields_detector.rb

Lines changed: 102 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,126 @@
33
module Dynamoid #:nodoc:
44
module Criteria
55
class KeyFieldsDetector
6-
attr_reader :hash_key, :range_key, :index_name
6+
7+
class Query
8+
def initialize(query_hash)
9+
@query_hash = query_hash
10+
@fields_with_operator = query_hash.keys.map(&:to_s)
11+
@fields = query_hash.keys.map(&:to_s).map { |s| s.split('.').first }
12+
end
13+
14+
def contain?(field_name)
15+
@fields.include?(field_name.to_s)
16+
end
17+
18+
def contain_with_eq_operator?(field_name)
19+
@fields_with_operator.include?(field_name.to_s)
20+
end
21+
end
722

823
def initialize(query, source)
924
@query = query
1025
@source = source
11-
12-
detect_keys
26+
@query = Query.new(query)
27+
@result = find_keys_in_query
1328
end
1429

1530
def key_present?
16-
@hash_key.present?
31+
@result.present?
32+
end
33+
34+
def hash_key
35+
@result && @result[:hash_key]
36+
end
37+
38+
def range_key
39+
@result && @result[:range_key]
40+
end
41+
42+
def index_name
43+
@result && @result[:index_name]
1744
end
1845

1946
private
2047

21-
def detect_keys
22-
query_keys = @query.keys.collect { |k| k.to_s.split('.').first }
48+
def find_keys_in_query
49+
match_table_and_sort_key ||
50+
match_local_secondary_index ||
51+
match_global_secondary_index_and_sort_key ||
52+
match_table ||
53+
match_global_secondary_index
54+
end
2355

24-
# See if querying based on table hash key
25-
if @query.keys.map(&:to_s).include?(@source.hash_key.to_s)
26-
@hash_key = @source.hash_key
56+
# Use table's default range key
57+
def match_table_and_sort_key
58+
return unless @query.contain_with_eq_operator?(@source.hash_key)
59+
return unless @source.range_key
2760

28-
# Use table's default range key
29-
if query_keys.include?(@source.range_key.to_s)
30-
@range_key = @source.range_key
31-
return
32-
end
61+
if @query.contain?(@source.range_key)
62+
{
63+
hash_key: @source.hash_key,
64+
range_key: @source.range_key
65+
}
66+
end
67+
end
68+
69+
# See if can use any local secondary index range key
70+
# Chooses the first LSI found that can be utilized for the query
71+
def match_local_secondary_index
72+
return unless @query.contain_with_eq_operator?(@source.hash_key)
73+
74+
lsi = @source.local_secondary_indexes.values.find do |lsi|
75+
@query.contain?(lsi.range_key)
76+
end
77+
78+
if lsi.present?
79+
{
80+
hash_key: @source.hash_key,
81+
range_key: lsi.range_key,
82+
index_name: lsi.name,
83+
}
84+
end
85+
end
3386

34-
# See if can use any local secondary index range key
35-
# Chooses the first LSI found that can be utilized for the query
36-
@source.local_secondary_indexes.each do |_, lsi|
37-
next unless query_keys.include?(lsi.range_key.to_s)
87+
# See if can use any global secondary index
88+
# Chooses the first GSI found that can be utilized for the query
89+
# GSI with range key involved into query conditions has higher priority
90+
# But only do so if projects ALL attributes otherwise we won't
91+
# get back full data
92+
def match_global_secondary_index_and_sort_key
93+
gsi = @source.global_secondary_indexes.values.find do |gsi|
94+
@query.contain_with_eq_operator?(gsi.hash_key) && gsi.projected_attributes == :all &&
95+
@query.contain?(gsi.range_key)
96+
end
3897

39-
@range_key = lsi.range_key
40-
@index_name = lsi.name
41-
end
98+
if gsi.present?
99+
{
100+
hash_key: gsi.hash_key,
101+
range_key: gsi.range_key,
102+
index_name: gsi.name,
103+
}
42104
end
105+
end
106+
107+
def match_table
108+
return unless @query.contain_with_eq_operator?(@source.hash_key)
43109

44-
# See if can use any global secondary index
45-
# Chooses the first GSI found that can be utilized for the query
46-
# But only do so if projects ALL attributes otherwise we won't
47-
# get back full data
48-
@source.global_secondary_indexes.each do |_, gsi|
49-
next unless @query.keys.map(&:to_s).include?(gsi.hash_key.to_s) && gsi.projected_attributes == :all
50-
next if @hash_key.present? && !query_keys.include?(gsi.range_key.to_s)
110+
{
111+
hash_key: @source.hash_key,
112+
}
113+
end
114+
115+
def match_global_secondary_index
116+
gsi = @source.global_secondary_indexes.values.find do |gsi|
117+
@query.contain_with_eq_operator?(gsi.hash_key) && gsi.projected_attributes == :all
118+
end
51119

52-
@hash_key = gsi.hash_key
53-
@range_key = gsi.range_key
54-
@index_name = gsi.name
120+
if gsi.present?
121+
{
122+
hash_key: gsi.hash_key,
123+
range_key: gsi.range_key,
124+
index_name: gsi.name,
125+
}
55126
end
56127
end
57128
end

spec/dynamoid/criteria/chain_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ def request_params
11091109
expect(model.where('name': nil).to_a).to eq [@johndoe]
11101110
end
11111111

1112-
it 'supports "in [nil]" check', log_level: :debug do
1112+
it 'supports "in [nil]" check' do
11131113
pending 'because of temporary bug with nil type casting'
11141114
expect(model.where('name.in': [nil]).to_a).to eq [@johndoe]
11151115
end

0 commit comments

Comments
 (0)