caoutsearch 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +816 -7
  3. data/lib/caoutsearch/config/mappings.rb +1 -1
  4. data/lib/caoutsearch/filter/base.rb +11 -7
  5. data/lib/caoutsearch/filter/boolean.rb +1 -1
  6. data/lib/caoutsearch/filter/date.rb +93 -22
  7. data/lib/caoutsearch/filter/default.rb +10 -10
  8. data/lib/caoutsearch/filter/geo_point.rb +1 -1
  9. data/lib/caoutsearch/filter/match.rb +5 -5
  10. data/lib/caoutsearch/filter/none.rb +1 -1
  11. data/lib/caoutsearch/filter/range.rb +6 -6
  12. data/lib/caoutsearch/index/document.rb +11 -11
  13. data/lib/caoutsearch/index/indice_versions.rb +3 -3
  14. data/lib/caoutsearch/index/internal_dsl.rb +3 -3
  15. data/lib/caoutsearch/index/reindex.rb +11 -11
  16. data/lib/caoutsearch/index/scoping.rb +3 -3
  17. data/lib/caoutsearch/index/serialization.rb +13 -13
  18. data/lib/caoutsearch/instrumentation/base.rb +12 -12
  19. data/lib/caoutsearch/instrumentation/search.rb +11 -2
  20. data/lib/caoutsearch/mappings.rb +1 -1
  21. data/lib/caoutsearch/model/indexable.rb +57 -0
  22. data/lib/caoutsearch/model/searchable.rb +31 -0
  23. data/lib/caoutsearch/model.rb +12 -0
  24. data/lib/caoutsearch/response/aggregations.rb +50 -0
  25. data/lib/caoutsearch/response/response.rb +9 -0
  26. data/lib/caoutsearch/response/suggestions.rb +9 -0
  27. data/lib/caoutsearch/response.rb +6 -0
  28. data/lib/caoutsearch/search/adapter/active_record.rb +39 -0
  29. data/lib/caoutsearch/search/base.rb +16 -15
  30. data/lib/caoutsearch/search/batch/scroll.rb +93 -0
  31. data/lib/caoutsearch/search/batch/search_after.rb +70 -0
  32. data/lib/caoutsearch/search/batch_methods.rb +63 -0
  33. data/lib/caoutsearch/search/callbacks.rb +28 -0
  34. data/lib/caoutsearch/search/delete_methods.rb +19 -0
  35. data/lib/caoutsearch/search/dsl/item.rb +2 -2
  36. data/lib/caoutsearch/search/inspect.rb +34 -0
  37. data/lib/caoutsearch/search/instrumentation.rb +19 -0
  38. data/lib/caoutsearch/search/internal_dsl.rb +107 -0
  39. data/lib/caoutsearch/search/naming.rb +45 -0
  40. data/lib/caoutsearch/search/point_in_time.rb +28 -0
  41. data/lib/caoutsearch/search/query/boolean.rb +4 -4
  42. data/lib/caoutsearch/search/query/nested.rb +1 -1
  43. data/lib/caoutsearch/search/query/setters.rb +4 -4
  44. data/lib/caoutsearch/search/query_builder/aggregations.rb +49 -0
  45. data/lib/caoutsearch/search/query_builder.rb +89 -0
  46. data/lib/caoutsearch/search/query_methods.rb +157 -0
  47. data/lib/caoutsearch/search/records.rb +23 -0
  48. data/lib/caoutsearch/search/resettable.rb +38 -0
  49. data/lib/caoutsearch/search/response.rb +97 -0
  50. data/lib/caoutsearch/search/sanitizer.rb +2 -2
  51. data/lib/caoutsearch/search/search_methods.rb +239 -0
  52. data/lib/caoutsearch/search/type_cast.rb +14 -6
  53. data/lib/caoutsearch/search/value.rb +10 -10
  54. data/lib/caoutsearch/search/value_overflow.rb +1 -1
  55. data/lib/caoutsearch/settings.rb +1 -1
  56. data/lib/caoutsearch/testing/mock_requests.rb +105 -0
  57. data/lib/caoutsearch/testing.rb +3 -0
  58. data/lib/caoutsearch/version.rb +1 -1
  59. data/lib/caoutsearch.rb +10 -5
  60. metadata +44 -126
  61. data/lib/caoutsearch/search/search/delete_methods.rb +0 -21
  62. data/lib/caoutsearch/search/search/inspect.rb +0 -36
  63. data/lib/caoutsearch/search/search/instrumentation.rb +0 -21
  64. data/lib/caoutsearch/search/search/internal_dsl.rb +0 -77
  65. data/lib/caoutsearch/search/search/naming.rb +0 -47
  66. data/lib/caoutsearch/search/search/query_builder.rb +0 -94
  67. data/lib/caoutsearch/search/search/query_methods.rb +0 -180
  68. data/lib/caoutsearch/search/search/resettable.rb +0 -35
  69. data/lib/caoutsearch/search/search/response.rb +0 -88
  70. data/lib/caoutsearch/search/search/scroll_methods.rb +0 -113
  71. data/lib/caoutsearch/search/search/search_methods.rb +0 -230
@@ -1,180 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Caoutsearch
4
- module Search
5
- module Search
6
- module QueryMethods
7
- delegate :merge, :merge!, :to_h, :to_json, :as_json, :filters,
8
- :add_none, :add_filter, :add_sort, :add_aggregation, :add_suggestion,
9
- :build_terms, :should_filter_on, :must_filter_on, :must_not_filter_on,
10
- :nested_queries, :nested_query,
11
- to: :elasticsearch_query
12
-
13
- # Accessors
14
- # ------------------------------------------------------------------------
15
- def elasticsearch_query
16
- @elasticsearch_query ||= Caoutsearch::Search::Query::Base.new
17
- end
18
-
19
- # Applying criteria
20
- # ------------------------------------------------------------------------
21
- def search_by(criteria)
22
- case criteria
23
- when Array then criteria.each { |criterion| search_by(criterion) }
24
- when Hash then criteria.each { |key, value| filter_by(key, value) }
25
- when String then apply_dsl_match_all(criteria)
26
- end
27
- end
28
-
29
- def filter_by(key, value)
30
- case key
31
- when Array
32
- items = key.filter_map { |k| config[:filters][k] }
33
- apply_dsl_filters(items, value)
34
- when Caoutsearch::Search::DSL::Item
35
- apply_dsl_filter(key, value)
36
- else
37
- if config[:filters].key?(key)
38
- apply_dsl_filter(config[:filters][key], value)
39
- elsif config[:filters].key?(:__undef__)
40
- block = config[:filters][:__undef__]
41
- instance_exec(key, value, &block)
42
- end
43
- end
44
- end
45
-
46
- # Applying DSL filters items
47
- # ------------------------------------------------------------------------
48
- def apply_dsl_match_all(value)
49
- return unless (block = config[:match_all])
50
-
51
- instance_exec(value, &block)
52
- end
53
-
54
- def apply_dsl_filter(item, value)
55
- return instance_exec(value, &item.block) if item.has_block?
56
-
57
- terms = []
58
- indexes = item.indexes
59
- options = item.options.dup
60
- query = elasticsearch_query
61
-
62
- if options[:nested_query]
63
- query = nested_query(options[:nested_query])
64
- options[:nested] = nil
65
- end
66
-
67
- indexes.each do |index|
68
- options_index = options.dup
69
- options_index[:type] = mappings.find_type(index) unless options_index.key?(:type)
70
- options_index[:nested] = mappings.nested_path(index) unless options_index.key?(:nested)
71
- options_index[:include_in_parent] = mappings.include_in_parent?(index) unless options_index.key?(:include_in_parent) || !options_index[:nested]
72
-
73
- terms += query.build_terms(index, value, **options_index)
74
- end
75
-
76
- query.should_filter_on(terms)
77
- end
78
-
79
- def apply_dsl_filters(items, value)
80
- terms = []
81
-
82
- items.each do |item|
83
- terms += isolate_dsl_filter(item, value)
84
- end
85
-
86
- should_filter_on(terms)
87
- end
88
-
89
- def isolate_dsl_filter(item, value)
90
- isolated_instance = clone
91
- isolated_instance.apply_dsl_filter(item, value)
92
- isolated_instance.elasticsearch_query.dig(:query, :bool, :filter) || []
93
- end
94
-
95
- # Applying orders
96
- # ------------------------------------------------------------------------
97
- def order_by(keys)
98
- case keys
99
- when Array then keys.each { |key| order_by(key) }
100
- when Hash then keys.each { |key, direction| sort_by(key, direction) }
101
- when String, Symbol then sort_by(keys)
102
- end
103
- end
104
-
105
- def sort_by(key, direction = nil)
106
- if config[:sorts].key?(key)
107
- apply_dsl_sort(config[:sorts][key], direction)
108
- elsif config[:sorts].key?(:__undef__)
109
- block = config[:sorts][:__undef__]
110
- instance_exec(key, direction, &block)
111
- end
112
- end
113
-
114
- def apply_dsl_sort(item, direction)
115
- return instance_exec(direction, &item.block) if item.has_block?
116
-
117
- indexes = item.indexes
118
- indexes.each do |index|
119
- case index
120
- when :default, "default"
121
- sort_by(:default, direction)
122
-
123
- when Hash
124
- index.map do |key, value|
125
- key_direction = (value.to_s == direction.to_s ? :asc : :desc)
126
- add_sort(key, key_direction)
127
- end
128
-
129
- else
130
- add_sort(index, direction)
131
- end
132
- end
133
- end
134
-
135
- # Applying aggregations
136
- # ------------------------------------------------------------------------
137
- def aggregate_with(*args)
138
- args.each do |arg|
139
- if arg.is_a?(Hash)
140
- arg.each do |key, value|
141
- next unless (item = config[:aggregations][key])
142
-
143
- apply_dsl_aggregate(item, value)
144
- end
145
- elsif (item = config[:aggregations][arg])
146
- apply_dsl_aggregate(item)
147
- end
148
- end
149
- end
150
-
151
- def apply_dsl_aggregate(item, *args)
152
- return instance_exec(*args, &item.block) if item.has_block?
153
-
154
- add_aggregation(item.name, item.options)
155
- end
156
-
157
- # Applying Suggests
158
- # ------------------------------------------------------------------------
159
- def suggest_with(*args)
160
- args.each do |(hash, options)|
161
- raise ArgumentError unless hash.is_a?(Hash)
162
-
163
- hash.each do |key, value|
164
- next unless (item = config[:suggestions][key.to_s])
165
-
166
- options ||= {}
167
- apply_dsl_suggest(item, value, **options)
168
- end
169
- end
170
- end
171
-
172
- def apply_dsl_suggest(item, query, **options)
173
- return instance_exec(query, **options, &item.block) if item.has_block?
174
-
175
- add_suggestion(item.name, query, **options)
176
- end
177
- end
178
- end
179
- end
180
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Caoutsearch
4
- module Search
5
- module Search
6
- module Resettable
7
- def spawn
8
- clone
9
- end
10
-
11
- def clone
12
- super.reset
13
- end
14
-
15
- def dup
16
- super.reset
17
- end
18
-
19
- def reset
20
- reset_variable(:@elasticsearch_query)
21
- reset_variable(:@nested_queries)
22
- reset_variable(:@response)
23
- reset_variable(:@total_count)
24
- self
25
- end
26
-
27
- private
28
-
29
- def reset_variable(key)
30
- remove_instance_variable(key) if instance_variable_defined?(key)
31
- end
32
- end
33
- end
34
- end
35
- end
@@ -1,88 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Caoutsearch
4
- module Search
5
- module Search
6
- module Response
7
- delegate :empty?, :size, :slice, :[], :to_a, :to_ary, to: :hits
8
- delegate_missing_to :each
9
-
10
- def response
11
- load unless @response
12
- @response
13
- end
14
-
15
- def took
16
- response["took"]
17
- end
18
-
19
- def timed_out
20
- response["timed_out"]
21
- end
22
-
23
- def shards
24
- response["_shards"]
25
- end
26
-
27
- def hits
28
- response.dig("hits", "hits")
29
- end
30
-
31
- def max_score
32
- response.dig("hits", "max_score")
33
- end
34
-
35
- def total_count
36
- if response.dig("hits", "total", "relation") == "gte" && !@track_total_hits
37
- @total_count ||= spawn.track_total_hits(true).total_count
38
- else
39
- response.dig("hits", "total", "value")
40
- end
41
- end
42
-
43
- def total_pages
44
- (total_count.to_f / current_limit).ceil
45
- end
46
-
47
- def ids
48
- hits.pluck("_id")
49
- end
50
-
51
- def aggregations
52
- # TODO
53
- end
54
-
55
- def suggestions
56
- # TODO
57
- end
58
-
59
- def records
60
- model.where(model.primary_key => ids)
61
- end
62
-
63
- def each(&block)
64
- return to_enum(:each) { hits.size } unless block
65
-
66
- hits.each(&block)
67
- end
68
-
69
- def load
70
- @response = perform_search_query(build.to_h)
71
- self
72
- end
73
-
74
- def perform_search_query(query)
75
- request_payload = {
76
- index: index_name,
77
- body: query
78
- }
79
-
80
- instrument(:search) do |event_payload|
81
- event_payload[:request] = request_payload
82
- event_payload[:response] = client.search(request_payload)
83
- end
84
- end
85
- end
86
- end
87
- end
88
- end
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Caoutsearch
4
- module Search
5
- module Search
6
- module ScrollMethods
7
- def scroll(scroll: "1h", &block)
8
- return to_enum(scroll, scroll: scroll) unless block
9
-
10
- request_payload = {
11
- index: index_name,
12
- scroll: scroll,
13
- body: build.to_h
14
- }
15
-
16
- total = 0
17
- progress = 0
18
- requested_at = nil
19
- last_response_time = nil
20
-
21
- results = instrument(:scroll_search) do |event_payload|
22
- response = client.search(request_payload)
23
- last_response_time = Time.current
24
-
25
- total = response["hits"]["total"]["value"]
26
- progress += response["hits"]["hits"].size
27
-
28
- event_payload[:request] = request_payload
29
- event_payload[:response] = response
30
- event_payload[:total] = total
31
- event_payload[:progress] = progress
32
-
33
- response
34
- end
35
-
36
- scroll_id = results["_scroll_id"]
37
- hits = results["hits"]["hits"]
38
-
39
- yield hits, { progress: progress, total: total, scroll_id: scroll_id }
40
-
41
- while progress < total
42
- request_payload = {
43
- scroll_id: scroll_id,
44
- scroll: scroll
45
- }
46
-
47
- requested_at = Time.current
48
-
49
- results = instrument(:scroll, scroll: scroll_id) do |event_payload|
50
- response = client.scroll(request_payload)
51
- last_response_time = Time.current
52
-
53
- total = response["hits"]["total"]["value"]
54
- progress += response["hits"]["hits"].size
55
-
56
- event_payload[:request] = request_payload
57
- event_payload[:response] = response
58
- event_payload[:total] = total
59
- event_payload[:progress] = progress
60
-
61
- response
62
- rescue Elastic::Transport::Transport::Errors::NotFound => e
63
- raise_enhance_message_when_scroll_failed(e, scroll, requested_at, last_response_time)
64
- end
65
-
66
- hits = results["hits"]["hits"]
67
- progress += hits.size
68
-
69
- break if hits.empty?
70
-
71
- yield hits, { progress: progress, total: total, scroll_id: scroll_id }
72
- end
73
-
74
- total
75
- ensure
76
- clear_scroll(scroll_id) if scroll_id
77
- end
78
-
79
- def clear_scroll(scroll_id)
80
- client.clear_scroll(scroll_id: scroll_id)
81
- rescue ::Elastic::Transport::Transport::Errors::NotFound
82
- # We dont care if the scroll ID is already expired
83
- end
84
-
85
- def scroll_records_in_batches(**options)
86
- return to_enum(:scroll_records_in_batches, **options) unless block_given?
87
-
88
- scroll(**options) do |hits|
89
- ids = hits.map { |doc| doc["_id"] }
90
- yield model.where(id: ids)
91
- model.connection.clear_query_cache
92
- end
93
- end
94
-
95
- def scroll_records(**options, &block)
96
- return to_enum(:scroll_records, **options) unless block
97
-
98
- scroll_records_in_batches(**options) do |relation|
99
- relation.each(&block)
100
- end
101
- end
102
-
103
- private
104
-
105
- def raise_enhance_message_when_scroll_failed(error, scroll, requested_at, last_response_time)
106
- elapsed = (requested_at - last_response_time).round(1).seconds
107
-
108
- raise error.exception("Scroll registered for #{scroll}, #{elapsed.inspect} elapsed between. #{error.message}")
109
- end
110
- end
111
- end
112
- end
113
- end
@@ -1,230 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Caoutsearch
4
- module Search
5
- module Search
6
- module SearchMethods
7
- attr_reader :current_context, :current_order, :current_aggregations,
8
- :current_suggestions, :current_fields, :current_source
9
-
10
- # Public API
11
- # ------------------------------------------------------------------------
12
- def search(...)
13
- spawn.search!(...)
14
- end
15
-
16
- def context(...)
17
- spawn.context!(...)
18
- end
19
-
20
- def order(...)
21
- spawn.order!(...)
22
- end
23
-
24
- def page(...)
25
- spawn.page!(...)
26
- end
27
-
28
- def limit(...)
29
- spawn.limit!(...)
30
- end
31
- alias_method :per, :limit
32
-
33
- def offset(...)
34
- spawn.offset!(...)
35
- end
36
-
37
- def aggregate(...)
38
- spawn.aggregate!(...)
39
- end
40
-
41
- def suggest(...)
42
- spawn.suggest!(...)
43
- end
44
-
45
- def fields(...)
46
- spawn.fields!(...)
47
- end
48
- alias_method :returns, :fields
49
-
50
- def source(...)
51
- spawn.source!(...)
52
- end
53
- alias_method :with_sources, :source
54
-
55
- def without_sources
56
- spawn.source!(false)
57
- end
58
-
59
- def without_hits
60
- spawn.source!(false).limit!(0)
61
- end
62
-
63
- def track_total_hits(...)
64
- spawn.track_total_hits!(...)
65
- end
66
-
67
- def prepend(...)
68
- spawn.prepend!(...)
69
- end
70
-
71
- def append(...)
72
- spawn.append!(...)
73
- end
74
-
75
- def unscope(...)
76
- spawn.unscope!(...)
77
- end
78
-
79
- # Setters
80
- # ------------------------------------------------------------------------
81
- def search!(*values)
82
- values = values.flatten.map do |value|
83
- value = value.stringify_keys if value.is_a?(Hash)
84
- value
85
- end
86
-
87
- @search_criteria ||= []
88
- @search_criteria += values
89
- self
90
- end
91
-
92
- def context!(value)
93
- @current_context = value
94
- self
95
- end
96
-
97
- def order!(value)
98
- @current_order = value
99
- self
100
- end
101
-
102
- def page!(value)
103
- @current_page = value
104
- self
105
- end
106
-
107
- def limit!(value)
108
- @current_limit = value
109
- self
110
- end
111
-
112
- def offset!(value)
113
- @current_offset = value
114
- self
115
- end
116
-
117
- def aggregate!(*values)
118
- @current_aggregations ||= []
119
- @current_aggregations += values.flatten
120
- self
121
- end
122
-
123
- def suggest!(values, **options)
124
- raise ArgumentError unless values.is_a?(Hash)
125
-
126
- @current_suggestions ||= []
127
- @current_suggestions << [values, options]
128
- self
129
- end
130
-
131
- def fields!(*values)
132
- @current_fields ||= []
133
- @current_fields += values.flatten
134
- self
135
- end
136
-
137
- def source!(*values)
138
- @current_source ||= []
139
- @current_source += values.flatten
140
- self
141
- end
142
-
143
- def track_total_hits!(value = true)
144
- @track_total_hits = value
145
- self
146
- end
147
-
148
- def prepend!(hash)
149
- @prepend_hash = hash
150
- self
151
- end
152
-
153
- def append!(hash)
154
- @append_hash = hash
155
- self
156
- end
157
-
158
- UNSCOPE_KEYS = {
159
- "search" => :@search_criteria,
160
- "search_criteria" => :@search_criteria,
161
- "limit" => :@current_limit,
162
- "per" => :@current_limit,
163
- "aggregate" => :@current_aggregations,
164
- "aggregations" => :@current_aggregations,
165
- "suggest" => :@current_suggestions,
166
- "suggestions" => :@current_suggestions,
167
- "context" => :@current_context,
168
- "order" => :@current_order,
169
- "page" => :@current_page,
170
- "offset" => :@current_offset,
171
- "fields" => :@current_fields,
172
- "source" => :@current_source
173
- }.freeze
174
-
175
- def unscope!(key)
176
- raise ArgumentError unless (variable = UNSCOPE_KEYS[key.to_s])
177
-
178
- reset_variable(variable)
179
- self
180
- end
181
-
182
- # Getters
183
- # ------------------------------------------------------------------------
184
- def search_criteria
185
- @search_criteria ||= []
186
- end
187
-
188
- def current_page
189
- @current_page&.to_i || 1
190
- end
191
-
192
- def current_limit
193
- @current_limit&.to_i || 10
194
- end
195
-
196
- def current_offset
197
- if @current_offset
198
- @current_offset.to_i
199
- elsif @current_page
200
- (current_limit * (current_page - 1))
201
- else
202
- 0
203
- end
204
- end
205
-
206
- # Criteria handlers
207
- # ------------------------------------------------------------------------
208
- def find_criterion(key)
209
- key = key.to_s
210
- search_criteria.find do |value|
211
- return value[key] if value.is_a?(Hash) && value.key?(key)
212
- end
213
- end
214
-
215
- def has_criterion?(key)
216
- key = key.to_s
217
- search_criteria.any? do |value|
218
- return true if value.is_a?(Hash) && value.key?(key)
219
- end
220
- end
221
-
222
- def search_criteria_keys
223
- search_criteria.each_with_object([]) do |criterion, keys|
224
- keys.concat(criterion.keys) if criterion.is_a?(Hash)
225
- end
226
- end
227
- end
228
- end
229
- end
230
- end