caoutsearch 0.0.1 → 0.0.4
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.
- checksums.yaml +4 -4
- data/README.md +816 -7
- data/lib/caoutsearch/config/mappings.rb +1 -1
- data/lib/caoutsearch/filter/base.rb +11 -7
- data/lib/caoutsearch/filter/boolean.rb +1 -1
- data/lib/caoutsearch/filter/date.rb +93 -22
- data/lib/caoutsearch/filter/default.rb +10 -10
- data/lib/caoutsearch/filter/geo_point.rb +1 -1
- data/lib/caoutsearch/filter/match.rb +5 -5
- data/lib/caoutsearch/filter/none.rb +1 -1
- data/lib/caoutsearch/filter/range.rb +6 -6
- data/lib/caoutsearch/index/document.rb +11 -11
- data/lib/caoutsearch/index/indice_versions.rb +3 -3
- data/lib/caoutsearch/index/internal_dsl.rb +3 -3
- data/lib/caoutsearch/index/reindex.rb +11 -11
- data/lib/caoutsearch/index/scoping.rb +3 -3
- data/lib/caoutsearch/index/serialization.rb +13 -13
- data/lib/caoutsearch/instrumentation/base.rb +12 -12
- data/lib/caoutsearch/instrumentation/search.rb +11 -2
- data/lib/caoutsearch/mappings.rb +1 -1
- data/lib/caoutsearch/model/indexable.rb +57 -0
- data/lib/caoutsearch/model/searchable.rb +31 -0
- data/lib/caoutsearch/model.rb +12 -0
- data/lib/caoutsearch/response/aggregations.rb +50 -0
- data/lib/caoutsearch/response/response.rb +9 -0
- data/lib/caoutsearch/response/suggestions.rb +9 -0
- data/lib/caoutsearch/response.rb +6 -0
- data/lib/caoutsearch/search/adapter/active_record.rb +39 -0
- data/lib/caoutsearch/search/base.rb +16 -15
- data/lib/caoutsearch/search/batch/scroll.rb +93 -0
- data/lib/caoutsearch/search/batch/search_after.rb +71 -0
- data/lib/caoutsearch/search/batch_methods.rb +63 -0
- data/lib/caoutsearch/search/callbacks.rb +28 -0
- data/lib/caoutsearch/search/delete_methods.rb +19 -0
- data/lib/caoutsearch/search/dsl/item.rb +2 -2
- data/lib/caoutsearch/search/inspect.rb +34 -0
- data/lib/caoutsearch/search/instrumentation.rb +19 -0
- data/lib/caoutsearch/search/internal_dsl.rb +107 -0
- data/lib/caoutsearch/search/naming.rb +45 -0
- data/lib/caoutsearch/search/point_in_time.rb +28 -0
- data/lib/caoutsearch/search/query/boolean.rb +4 -4
- data/lib/caoutsearch/search/query/nested.rb +1 -1
- data/lib/caoutsearch/search/query/setters.rb +4 -4
- data/lib/caoutsearch/search/query_builder/aggregations.rb +49 -0
- data/lib/caoutsearch/search/query_builder.rb +89 -0
- data/lib/caoutsearch/search/query_methods.rb +157 -0
- data/lib/caoutsearch/search/records.rb +23 -0
- data/lib/caoutsearch/search/resettable.rb +38 -0
- data/lib/caoutsearch/search/response.rb +97 -0
- data/lib/caoutsearch/search/sanitizer.rb +2 -2
- data/lib/caoutsearch/search/search_methods.rb +239 -0
- data/lib/caoutsearch/search/type_cast.rb +14 -6
- data/lib/caoutsearch/search/value.rb +10 -10
- data/lib/caoutsearch/search/value_overflow.rb +1 -1
- data/lib/caoutsearch/settings.rb +1 -1
- data/lib/caoutsearch/testing/mock_requests.rb +105 -0
- data/lib/caoutsearch/testing.rb +3 -0
- data/lib/caoutsearch/version.rb +1 -1
- data/lib/caoutsearch.rb +10 -5
- metadata +45 -127
- data/lib/caoutsearch/search/search/delete_methods.rb +0 -21
- data/lib/caoutsearch/search/search/inspect.rb +0 -36
- data/lib/caoutsearch/search/search/instrumentation.rb +0 -21
- data/lib/caoutsearch/search/search/internal_dsl.rb +0 -77
- data/lib/caoutsearch/search/search/naming.rb +0 -47
- data/lib/caoutsearch/search/search/query_builder.rb +0 -94
- data/lib/caoutsearch/search/search/query_methods.rb +0 -180
- data/lib/caoutsearch/search/search/resettable.rb +0 -35
- data/lib/caoutsearch/search/search/response.rb +0 -88
- data/lib/caoutsearch/search/search/scroll_methods.rb +0 -113
- data/lib/caoutsearch/search/search/search_methods.rb +0 -230
@@ -3,7 +3,7 @@
|
|
3
3
|
module Caoutsearch
|
4
4
|
module Search
|
5
5
|
module Sanitizer
|
6
|
-
ESCAPED_CHARACTERS
|
6
|
+
ESCAPED_CHARACTERS = "+-&|!(){}[]^~*?:"
|
7
7
|
ESCAPED_CHARACTERS_REGEXP = /([+\-&|!(){}\[\]\^~*?:])/
|
8
8
|
|
9
9
|
class << self
|
@@ -14,7 +14,7 @@ module Caoutsearch
|
|
14
14
|
when Hash
|
15
15
|
value.each { |k, v| value[k] = sanitize(v) }
|
16
16
|
when String
|
17
|
-
regexp
|
17
|
+
regexp = ESCAPED_CHARACTERS_REGEXP if characters == ESCAPED_CHARACTERS
|
18
18
|
regexp ||= Regexp.new("([#{Regexp.escape(characters)}])")
|
19
19
|
|
20
20
|
value.gsub(regexp, '\\\\\1')
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caoutsearch
|
4
|
+
module Search
|
5
|
+
module SearchMethods
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
attr_reader :current_context, :current_order, :current_aggregations,
|
9
|
+
:current_suggestions, :current_fields, :current_source
|
10
|
+
|
11
|
+
# Public API
|
12
|
+
# ------------------------------------------------------------------------
|
13
|
+
class_methods do
|
14
|
+
delegate :search, :order, :page, :limit, :offset, :aggregate,
|
15
|
+
:suggest, :fields, :source, :without_sources, :without_hits,
|
16
|
+
:track_total_hits, :prepend, :append,
|
17
|
+
to: :new
|
18
|
+
end
|
19
|
+
|
20
|
+
def search(...)
|
21
|
+
spawn.search!(...)
|
22
|
+
end
|
23
|
+
|
24
|
+
def context(...)
|
25
|
+
spawn.context!(...)
|
26
|
+
end
|
27
|
+
|
28
|
+
def order(...)
|
29
|
+
spawn.order!(...)
|
30
|
+
end
|
31
|
+
|
32
|
+
def page(...)
|
33
|
+
spawn.page!(...)
|
34
|
+
end
|
35
|
+
|
36
|
+
def limit(...)
|
37
|
+
spawn.limit!(...)
|
38
|
+
end
|
39
|
+
alias_method :per, :limit
|
40
|
+
|
41
|
+
def offset(...)
|
42
|
+
spawn.offset!(...)
|
43
|
+
end
|
44
|
+
|
45
|
+
def aggregate(...)
|
46
|
+
spawn.aggregate!(...)
|
47
|
+
end
|
48
|
+
|
49
|
+
def suggest(...)
|
50
|
+
spawn.suggest!(...)
|
51
|
+
end
|
52
|
+
|
53
|
+
def fields(...)
|
54
|
+
spawn.fields!(...)
|
55
|
+
end
|
56
|
+
alias_method :returns, :fields
|
57
|
+
|
58
|
+
def source(...)
|
59
|
+
spawn.source!(...)
|
60
|
+
end
|
61
|
+
alias_method :with_sources, :source
|
62
|
+
|
63
|
+
def without_sources
|
64
|
+
spawn.source!(false)
|
65
|
+
end
|
66
|
+
|
67
|
+
def without_hits
|
68
|
+
spawn.source!(false).limit!(0)
|
69
|
+
end
|
70
|
+
|
71
|
+
def track_total_hits(...)
|
72
|
+
spawn.track_total_hits!(...)
|
73
|
+
end
|
74
|
+
|
75
|
+
def prepend(...)
|
76
|
+
spawn.prepend!(...)
|
77
|
+
end
|
78
|
+
|
79
|
+
def append(...)
|
80
|
+
spawn.append!(...)
|
81
|
+
end
|
82
|
+
|
83
|
+
def unscope(...)
|
84
|
+
spawn.unscope!(...)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Setters
|
88
|
+
# ------------------------------------------------------------------------
|
89
|
+
def search!(*values)
|
90
|
+
values = values.flatten.filter_map do |value|
|
91
|
+
value = value.stringify_keys if value.is_a?(Hash)
|
92
|
+
value
|
93
|
+
end
|
94
|
+
|
95
|
+
@search_criteria ||= []
|
96
|
+
@search_criteria += values
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def context!(value)
|
101
|
+
@current_context = value
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def order!(value)
|
106
|
+
@current_order = value
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def page!(value)
|
111
|
+
@current_page = value
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
def limit!(value)
|
116
|
+
@current_limit = value
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def offset!(value)
|
121
|
+
@current_offset = value
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
def aggregate!(*values)
|
126
|
+
@current_aggregations ||= []
|
127
|
+
@current_aggregations += values.flatten
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
def suggest!(values, **options)
|
132
|
+
raise ArgumentError unless values.is_a?(Hash)
|
133
|
+
|
134
|
+
@current_suggestions ||= []
|
135
|
+
@current_suggestions << [values, options]
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
def fields!(*values)
|
140
|
+
@current_fields ||= []
|
141
|
+
@current_fields += values.flatten
|
142
|
+
self
|
143
|
+
end
|
144
|
+
|
145
|
+
def source!(*values)
|
146
|
+
@current_source ||= []
|
147
|
+
@current_source += values.flatten
|
148
|
+
self
|
149
|
+
end
|
150
|
+
|
151
|
+
def track_total_hits!(value = true)
|
152
|
+
@track_total_hits = value
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
156
|
+
def prepend!(hash)
|
157
|
+
@prepend_hash = hash
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
def append!(hash)
|
162
|
+
@append_hash = hash
|
163
|
+
self
|
164
|
+
end
|
165
|
+
|
166
|
+
# rubocop:disable Layout/HashAlignment
|
167
|
+
UNSCOPE_KEYS = {
|
168
|
+
"search" => :@search_criteria,
|
169
|
+
"search_criteria" => :@search_criteria,
|
170
|
+
"limit" => :@current_limit,
|
171
|
+
"per" => :@current_limit,
|
172
|
+
"aggregate" => :@current_aggregations,
|
173
|
+
"aggregations" => :@current_aggregations,
|
174
|
+
"suggest" => :@current_suggestions,
|
175
|
+
"suggestions" => :@current_suggestions,
|
176
|
+
"context" => :@current_context,
|
177
|
+
"order" => :@current_order,
|
178
|
+
"page" => :@current_page,
|
179
|
+
"offset" => :@current_offset,
|
180
|
+
"fields" => :@current_fields,
|
181
|
+
"source" => :@current_source
|
182
|
+
}.freeze
|
183
|
+
# rubocop:enable Layout/HashAlignment
|
184
|
+
|
185
|
+
def unscope!(key)
|
186
|
+
raise ArgumentError unless (variable = UNSCOPE_KEYS[key.to_s])
|
187
|
+
|
188
|
+
reset_variable(variable)
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
# Getters
|
193
|
+
# ------------------------------------------------------------------------
|
194
|
+
def search_criteria
|
195
|
+
@search_criteria ||= []
|
196
|
+
end
|
197
|
+
|
198
|
+
def current_page
|
199
|
+
@current_page&.to_i || 1
|
200
|
+
end
|
201
|
+
|
202
|
+
def current_limit
|
203
|
+
@current_limit&.to_i || 10
|
204
|
+
end
|
205
|
+
|
206
|
+
def current_offset
|
207
|
+
if @current_offset
|
208
|
+
@current_offset.to_i
|
209
|
+
elsif @current_page
|
210
|
+
(current_limit * (current_page - 1))
|
211
|
+
else
|
212
|
+
0
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Criteria handlers
|
217
|
+
# ------------------------------------------------------------------------
|
218
|
+
def find_criterion(key)
|
219
|
+
key = key.to_s
|
220
|
+
search_criteria.find do |value|
|
221
|
+
return value[key] if value.is_a?(Hash) && value.key?(key)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def has_criterion?(key)
|
226
|
+
key = key.to_s
|
227
|
+
search_criteria.any? do |value|
|
228
|
+
return true if value.is_a?(Hash) && value.key?(key)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def search_criteria_keys
|
233
|
+
search_criteria.each_with_object([]) do |criterion, keys|
|
234
|
+
keys.concat(criterion.keys) if criterion.is_a?(Hash)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -15,7 +15,7 @@ module Caoutsearch
|
|
15
15
|
when "geo_point"
|
16
16
|
cast_as_geo_point(value)
|
17
17
|
when "date"
|
18
|
-
value
|
18
|
+
cast_as_date(value)
|
19
19
|
else
|
20
20
|
value
|
21
21
|
end
|
@@ -23,17 +23,17 @@ module Caoutsearch
|
|
23
23
|
|
24
24
|
def cast_as_integer(value)
|
25
25
|
case value
|
26
|
-
when nil
|
27
|
-
when Array
|
28
|
-
when String
|
26
|
+
when nil then nil
|
27
|
+
when Array then value.map { |v| cast_as_integer(v) }
|
28
|
+
when String then value.delete(" ").to_i
|
29
29
|
else value.to_i
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
def cast_as_float(value)
|
34
34
|
case value
|
35
|
-
when nil
|
36
|
-
when Array
|
35
|
+
when nil then nil
|
36
|
+
when Array then value.map { |v| cast_as_float(v) }
|
37
37
|
when String then value.to_s.delete(" ").tr(",", ".").to_f
|
38
38
|
else value.to_f
|
39
39
|
end
|
@@ -70,6 +70,14 @@ module Caoutsearch
|
|
70
70
|
|
71
71
|
value.map(&:to_f).reverse
|
72
72
|
end
|
73
|
+
|
74
|
+
def cast_as_date(value)
|
75
|
+
return value if value.is_a?(String) && value.match?(/\Anow[+-]{0,1}\d*[yMwdhHms]{0,1}(\/d){0,1}\Z/)
|
76
|
+
|
77
|
+
json_value = value.as_json if value.respond_to?(:as_json)
|
78
|
+
json_value ||= MultiJson.dump(value)
|
79
|
+
json_value if Time.parse(json_value)
|
80
|
+
end
|
73
81
|
end
|
74
82
|
end
|
75
83
|
end
|
@@ -6,12 +6,12 @@ module Caoutsearch
|
|
6
6
|
attr_reader :original_value, :type, :null_values, :transform_proc, :overflow_strategy
|
7
7
|
|
8
8
|
def initialize(original_value, type, null_values: nil, transform: nil, sanitize: false)
|
9
|
-
@original_value
|
10
|
-
@type
|
11
|
-
@null_values
|
12
|
-
@sanitize_value
|
13
|
-
@transform_proc
|
14
|
-
@transform_proc
|
9
|
+
@original_value = original_value
|
10
|
+
@type = type
|
11
|
+
@null_values = Array.wrap(null_values)
|
12
|
+
@sanitize_value = sanitize
|
13
|
+
@transform_proc = transform
|
14
|
+
@transform_proc = transform.to_proc if transform.is_a?(Symbol)
|
15
15
|
end
|
16
16
|
|
17
17
|
def value
|
@@ -50,7 +50,7 @@ module Caoutsearch
|
|
50
50
|
|
51
51
|
if range
|
52
52
|
raise Caoutsearch::Search::ValueOverflow.new(:lower, value, range.first) if value < range.first
|
53
|
-
raise Caoutsearch::Search::ValueOverflow.new(:upper, value, range.last)
|
53
|
+
raise Caoutsearch::Search::ValueOverflow.new(:upper, value, range.last) if range && value > range.last
|
54
54
|
end
|
55
55
|
|
56
56
|
value
|
@@ -63,10 +63,10 @@ module Caoutsearch
|
|
63
63
|
end
|
64
64
|
|
65
65
|
INTEGER_TYPE_LIMITS = {
|
66
|
-
"long"
|
66
|
+
"long" => bytes_size_to_integer_range(64),
|
67
67
|
"integer" => bytes_size_to_integer_range(32),
|
68
|
-
"short"
|
69
|
-
"byte"
|
68
|
+
"short" => bytes_size_to_integer_range(16),
|
69
|
+
"byte" => bytes_size_to_integer_range(8)
|
70
70
|
}.freeze
|
71
71
|
|
72
72
|
def transform_value(value)
|
data/lib/caoutsearch/settings.rb
CHANGED
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webmock"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
module Caoutsearch
|
7
|
+
module Testing
|
8
|
+
module MockRequests
|
9
|
+
def stub_elasticsearch_request(verb, pattern)
|
10
|
+
transport = Caoutsearch.client.transport
|
11
|
+
host = transport.__full_url(transport.hosts[0])
|
12
|
+
|
13
|
+
# Elasticsearch::Client is verify the connection
|
14
|
+
#
|
15
|
+
unless @subbed_verify
|
16
|
+
root_url = URI.join(host, "/").to_s
|
17
|
+
body = +MultiJson.dump({version: {number: "8.4.1"}})
|
18
|
+
@subbed_verify = stub_request(:get, root_url).to_return(
|
19
|
+
headers: {"Content-Type" => "application/json", "X-Elastic-Product" => "Elasticsearch"},
|
20
|
+
status: 200,
|
21
|
+
body: body
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
case pattern
|
26
|
+
when String
|
27
|
+
pattern = URI.join(host, pattern).to_s
|
28
|
+
when Regexp
|
29
|
+
pattern = URI.join(host, pattern.source).to_s
|
30
|
+
pattern = Regexp.new(pattern)
|
31
|
+
else
|
32
|
+
raise TypeError, "wrong type received for URL pattern"
|
33
|
+
end
|
34
|
+
|
35
|
+
stub_request(verb, pattern)
|
36
|
+
end
|
37
|
+
|
38
|
+
def stub_elasticsearch_search_request(index_name, hits, sources: true, total: nil)
|
39
|
+
hits = hits.map.each_with_index do |item, index|
|
40
|
+
hit = {}
|
41
|
+
hit = item if item.is_a?(Hash)
|
42
|
+
hit = yield(item) if block_given?
|
43
|
+
hit["_index"] ||= index_name
|
44
|
+
hit["_id"] ||= item.respond_to?(:id) ? item.id.to_s : (index + 1).to_s
|
45
|
+
hit["_score"] ||= 1
|
46
|
+
hit["_source"] ||= (sources && item.respond_to?(:as_indexed_json)) ? item.as_indexed_json : {}
|
47
|
+
hit
|
48
|
+
end
|
49
|
+
|
50
|
+
total ||= hits.size
|
51
|
+
total = {"value" => total} if total.is_a?(Numeric)
|
52
|
+
|
53
|
+
stub_elasticsearch_request(:post, "#{index_name}/_search").to_return_json(body: {
|
54
|
+
"took" => 10,
|
55
|
+
"hits" => {
|
56
|
+
"total" => total,
|
57
|
+
"max_score" => hits.max { |hit| hit["_score"] },
|
58
|
+
"hits" => hits
|
59
|
+
}
|
60
|
+
})
|
61
|
+
end
|
62
|
+
|
63
|
+
def stub_elasticsearch_reindex_request(index_name)
|
64
|
+
stub_elasticsearch_request(:post, "#{index_name}/_bulk").to_return_json(body: {
|
65
|
+
"took" => 100,
|
66
|
+
"items" => [],
|
67
|
+
"errors" => false
|
68
|
+
})
|
69
|
+
|
70
|
+
stub_elasticsearch_request(:post, "#{index_name}/_refresh").to_return_json(body: {
|
71
|
+
"_shards" => {
|
72
|
+
"total" => 1,
|
73
|
+
"failed" => 0,
|
74
|
+
"successful" => 1
|
75
|
+
}
|
76
|
+
})
|
77
|
+
end
|
78
|
+
|
79
|
+
def stub_elasticsearch_batching_requests(index_name, hits = [], keep_alive: "1m", batch_size: 1000)
|
80
|
+
pid_id = SecureRandom.base64
|
81
|
+
|
82
|
+
stub_elasticsearch_request(:post, "#{index_name}/_pit?keep_alive=#{keep_alive}")
|
83
|
+
.to_return_json(body: {id: pid_id})
|
84
|
+
|
85
|
+
stub_elasticsearch_request(:delete, "_pit")
|
86
|
+
.with(body: {id: pid_id})
|
87
|
+
.to_return_json(body: {succeed: true})
|
88
|
+
|
89
|
+
search_request = stub_elasticsearch_request(:post, "_search")
|
90
|
+
.with { |request| request.body.include?(pid_id) }
|
91
|
+
|
92
|
+
hits.each_slice(batch_size).each_with_index do |slice, index|
|
93
|
+
total = index.zero? ? {value: hits.size} : {value: slice.size, relation: "gte"}
|
94
|
+
|
95
|
+
search_request.to_return_json(body: {
|
96
|
+
hits: {total: total, hits: slice},
|
97
|
+
pit_id: pid_id
|
98
|
+
})
|
99
|
+
end
|
100
|
+
|
101
|
+
search_request
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/caoutsearch/version.rb
CHANGED
data/lib/caoutsearch.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext"
|
5
|
+
require "elasticsearch"
|
6
|
+
require "hashie"
|
3
7
|
require "zeitwerk"
|
8
|
+
|
4
9
|
loader = Zeitwerk::Loader.for_gem
|
5
|
-
loader.
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
)
|
10
|
+
loader.ignore("#{__dir__}/caoutsearch/testing.rb")
|
11
|
+
loader.ignore("#{__dir__}/caoutsearch/testing")
|
12
|
+
loader.inflector.inflect("dsl" => "DSL")
|
13
|
+
loader.inflector.inflect("internal_dsl" => "InternalDSL")
|
14
|
+
loader.inflector.inflect("none" => "NONE")
|
10
15
|
loader.setup
|
11
16
|
|
12
17
|
module Caoutsearch
|