caoutsearch 0.0.1 → 0.0.4

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 +71 -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 +45 -127
  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
@@ -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 = ESCAPED_CHARACTERS_REGEXP if characters == ESCAPED_CHARACTERS
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.to_date.as_json
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 then nil
27
- when Array then value.map { |v| cast_as_integer(v) }
28
- when String then value.delete(" ").to_i
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 then nil
36
- when Array then value.map { |v| cast_as_float(v) }
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 = 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)
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) if range && 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" => bytes_size_to_integer_range(64),
66
+ "long" => bytes_size_to_integer_range(64),
67
67
  "integer" => bytes_size_to_integer_range(32),
68
- "short" => bytes_size_to_integer_range(16),
69
- "byte" => bytes_size_to_integer_range(8)
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)
@@ -6,7 +6,7 @@ module Caoutsearch
6
6
  attr_reader :value, :limit, :type
7
7
 
8
8
  def initialize(type, value, limit)
9
- @type = type
9
+ @type = type
10
10
  @value = value
11
11
  @limit = limit
12
12
 
@@ -16,7 +16,7 @@ module Caoutsearch
16
16
  alias_method :as_json, :to_hash
17
17
 
18
18
  def to_json(*)
19
- as_json.to_json
19
+ MultiJson.dump(as_json)
20
20
  end
21
21
  end
22
22
  end
@@ -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
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "testing/mock_requests"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caoutsearch
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.4"
5
5
  end
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.inflector.inflect(
6
- "dsl" => "DSL",
7
- "internal_dsl" => "InternalDSL",
8
- "none" => "NONE"
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