Class: DynamoidAdvancedWhere::QueryMaterializer

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/dynamoid_advanced_where/query_materializer.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(query_builder:) ⇒ QueryMaterializer

Returns a new instance of QueryMaterializer.



14
15
16
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 14

def initialize(query_builder:)
  self.query_builder = query_builder
end

Instance Attribute Details

#query_builderObject

Returns the value of attribute query_builder.



8
9
10
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 8

def query_builder
  @query_builder
end

Instance Method Details

#allObject



18
19
20
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 18

def all
  each.to_a
end

#all_possible_indexesObject



148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 148

def all_possible_indexes
  # The nil index name is the table itself
  idx = { nil => { hash_key: klass.hash_key.to_s, range_key: klass.range_key.to_s } }

  klass.indexes.each do |_, definition|
    next unless definition.projected_attributes == :all

    idx[definition.name] = { hash_key: definition.hash_key.to_s, range_key: definition.range_key.to_s }
  end

  idx
end

#construct_items(items) ⇒ Object



74
75
76
77
78
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 74

def construct_items(items)
  (items || []).map do |item|
    klass.from_database(item)
  end
end

#each(&blk) ⇒ Object Also known as: find_each



22
23
24
25
26
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 22

def each(&blk)
  return enum_for(:each) unless blk

  records.each(&blk)
end

#each_page(&blk) ⇒ Object



29
30
31
32
33
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 29

def each_page(&blk)
  return enum_for(:each_page) unless blk

  pages.each(&blk)
end

#each_page_via_queryObject



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 80

def each_page_via_query
  query = {
    table_name: table_name,
    index_name: selected_index_for_query,
    scan_index_forward: query_builder.scanning_index_forward,
  }.merge(filter_builder.to_query_filter)

  enumerate_results(query) do |q|
    client.query(q)
  end
end

#each_page_via_scanObject



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 92

def each_page_via_scan
  raise 'Unable to scan a table backwards' unless query_builder.scanning_index_forward

  query = {
    table_name: table_name,
  }.merge(filter_builder.to_scan_filter)

  enumerate_results(query) do |q|
    client.scan(q)
  end
end

#enumerate_results(starting_query) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 47

def enumerate_results(starting_query)
  query = starting_query.dup

  unless query_builder.projected_fields.empty?
    query[:select] = 'SPECIFIC_ATTRIBUTES'
    query[:projection_expression] = query_builder.projected_fields.map(&:to_s).join(',')
  end

  query[:limit] = query_builder.record_limit if query_builder.record_limit

  query[:exclusive_start_key] = start_hash

  Enumerator.new do |yielder|
    loop do
      results = yield(query)

      yielder.yield(construct_items(results.items), results)

      query[:limit] = query[:limit] - results.items.length if query[:limit]

      break if results.last_evaluated_key.nil? || query[:limit]&.zero?

      query[:exclusive_start_key] = results.last_evaluated_key
    end
  end.lazy
end

#filter_builderObject



104
105
106
107
108
109
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 104

def filter_builder
  @filter_builder ||= FilterBuilder.new(
    root_node: query_builder.root_node,
    klass: klass,
  )
end

#must_scan?Boolean

Returns:

  • (Boolean)


135
136
137
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 135

def must_scan?
  satisfiable_indexes.empty?
end

#pagesObject



39
40
41
42
43
44
45
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 39

def pages
  if must_scan?
    each_page_via_scan
  else
    each_page_via_query
  end
end

#recordsObject



35
36
37
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 35

def records
  pages.flat_map { |i, _| i }
end

#satisfiable_indexesObject

find all indexes where we have a predicate on the hash key



140
141
142
143
144
145
146
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 140

def satisfiable_indexes
  possible_fields = filter_builder.extractable_fields_for_hash_and_range

  all_possible_indexes.select do |_, definition|
    possible_fields.key?(definition[:hash_key])
  end
end

#selected_index_for_queryObject

Pick the index to query.

1) The first index chosen should be one that has the range and hash key satisfied.
2) The second should be one that has the hash key


114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/dynamoid_advanced_where/query_materializer.rb', line 114

def selected_index_for_query
  possible_fields = filter_builder.extractable_fields_for_hash_and_range

  satisfiable_indexes.each do |name, definition|
    next unless possible_fields.key?(definition[:hash_key]) &&
                possible_fields.key?(definition[:range_key])

    filter_builder.select_node_for_range_key(possible_fields[definition[:range_key]])
    filter_builder.select_node_for_query_filter(possible_fields[definition[:hash_key]])

    return name
  end

  # Just take the first matching query then
  name, definition = satisfiable_indexes.first
  filter_builder.select_node_for_query_filter(possible_fields[definition[:hash_key]])
  filter_builder.select_node_for_range_key(possible_fields[definition[:range_key]]) unless possible_fields[definition[:range_key]].blank?

  name
end