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.
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