caoutsearch 0.0.0 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +819 -6
  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 +4 -4
  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