|
3 | 3 | module Dynamoid #:nodoc: |
4 | 4 | module Criteria |
5 | 5 | 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 |
7 | 22 |
|
8 | 23 | def initialize(query, source) |
9 | 24 | @query = query |
10 | 25 | @source = source |
11 | | - |
12 | | - detect_keys |
| 26 | + @query = Query.new(query) |
| 27 | + @result = find_keys_in_query |
13 | 28 | end |
14 | 29 |
|
15 | 30 | 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] |
17 | 44 | end |
18 | 45 |
|
19 | 46 | private |
20 | 47 |
|
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 |
23 | 55 |
|
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 |
27 | 60 |
|
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 |
33 | 86 |
|
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 |
38 | 97 |
|
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 | + } |
42 | 104 | end |
| 105 | + end |
| 106 | + |
| 107 | + def match_table |
| 108 | + return unless @query.contain_with_eq_operator?(@source.hash_key) |
43 | 109 |
|
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 |
51 | 119 |
|
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 | + } |
55 | 126 | end |
56 | 127 | end |
57 | 128 | end |
|
0 commit comments