caoutsearch 0.0.0

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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +43 -0
  4. data/lib/caoutsearch/config/client.rb +13 -0
  5. data/lib/caoutsearch/config/mappings.rb +40 -0
  6. data/lib/caoutsearch/config/settings.rb +29 -0
  7. data/lib/caoutsearch/filter/base.rb +101 -0
  8. data/lib/caoutsearch/filter/boolean.rb +19 -0
  9. data/lib/caoutsearch/filter/date.rb +49 -0
  10. data/lib/caoutsearch/filter/default.rb +51 -0
  11. data/lib/caoutsearch/filter/geo_point.rb +11 -0
  12. data/lib/caoutsearch/filter/match.rb +57 -0
  13. data/lib/caoutsearch/filter/none.rb +7 -0
  14. data/lib/caoutsearch/filter/range.rb +28 -0
  15. data/lib/caoutsearch/filter.rb +29 -0
  16. data/lib/caoutsearch/index/base.rb +35 -0
  17. data/lib/caoutsearch/index/document.rb +107 -0
  18. data/lib/caoutsearch/index/indice.rb +55 -0
  19. data/lib/caoutsearch/index/indice_versions.rb +123 -0
  20. data/lib/caoutsearch/index/instrumentation.rb +19 -0
  21. data/lib/caoutsearch/index/internal_dsl.rb +77 -0
  22. data/lib/caoutsearch/index/naming.rb +29 -0
  23. data/lib/caoutsearch/index/reindex.rb +77 -0
  24. data/lib/caoutsearch/index/scoping.rb +54 -0
  25. data/lib/caoutsearch/index/serialization.rb +136 -0
  26. data/lib/caoutsearch/index.rb +7 -0
  27. data/lib/caoutsearch/instrumentation/base.rb +69 -0
  28. data/lib/caoutsearch/instrumentation/index.rb +57 -0
  29. data/lib/caoutsearch/instrumentation/search.rb +41 -0
  30. data/lib/caoutsearch/mappings.rb +79 -0
  31. data/lib/caoutsearch/search/base.rb +27 -0
  32. data/lib/caoutsearch/search/dsl/item.rb +42 -0
  33. data/lib/caoutsearch/search/query/base.rb +16 -0
  34. data/lib/caoutsearch/search/query/boolean.rb +63 -0
  35. data/lib/caoutsearch/search/query/cleaning.rb +29 -0
  36. data/lib/caoutsearch/search/query/getters.rb +35 -0
  37. data/lib/caoutsearch/search/query/merge.rb +27 -0
  38. data/lib/caoutsearch/search/query/nested.rb +23 -0
  39. data/lib/caoutsearch/search/query/setters.rb +68 -0
  40. data/lib/caoutsearch/search/sanitizer.rb +28 -0
  41. data/lib/caoutsearch/search/search/delete_methods.rb +21 -0
  42. data/lib/caoutsearch/search/search/inspect.rb +36 -0
  43. data/lib/caoutsearch/search/search/instrumentation.rb +21 -0
  44. data/lib/caoutsearch/search/search/internal_dsl.rb +77 -0
  45. data/lib/caoutsearch/search/search/naming.rb +47 -0
  46. data/lib/caoutsearch/search/search/query_builder.rb +94 -0
  47. data/lib/caoutsearch/search/search/query_methods.rb +180 -0
  48. data/lib/caoutsearch/search/search/resettable.rb +35 -0
  49. data/lib/caoutsearch/search/search/response.rb +88 -0
  50. data/lib/caoutsearch/search/search/scroll_methods.rb +113 -0
  51. data/lib/caoutsearch/search/search/search_methods.rb +230 -0
  52. data/lib/caoutsearch/search/type_cast.rb +76 -0
  53. data/lib/caoutsearch/search/value.rb +111 -0
  54. data/lib/caoutsearch/search/value_overflow.rb +17 -0
  55. data/lib/caoutsearch/search.rb +6 -0
  56. data/lib/caoutsearch/settings.rb +22 -0
  57. data/lib/caoutsearch/version.rb +5 -0
  58. data/lib/caoutsearch.rb +38 -0
  59. metadata +268 -0
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Instrumentation
5
+ class Index < Base
6
+ def get(event)
7
+ log_request("Get", event, format: log_request_format)
8
+ log_response("Get", event)
9
+ end
10
+
11
+ def index(event)
12
+ log_request("Index", event, format: log_request_format)
13
+ log_response("Index", event)
14
+ end
15
+
16
+ def update(event)
17
+ log_request("Update", event, format: log_request_format)
18
+ log_response("Update", event)
19
+ end
20
+
21
+ def delete(event)
22
+ log_request("Delete", event, format: log_request_format)
23
+ log_response("Delete", event)
24
+ end
25
+
26
+ def bulk(event)
27
+ method = event[:method].to_s.titleize
28
+
29
+ log_request("Bulk #{method}", event, format: log_request_format)
30
+ log_response("Bulk #{method}", event, warn_errors: true)
31
+ end
32
+
33
+ def reindex(event)
34
+ log_request("Reindex", event, format: log_request_format)
35
+ log_response("Reindex", event, warn_errors: true) do |message|
36
+ payload = event.payload
37
+ request = payload[:request]
38
+ request_size = inspect_json_size(request)
39
+
40
+ response = payload[:response]
41
+ response_size = inspect_json_size(response)
42
+
43
+ message += ", request size: #{request_size}"
44
+ message += ", response size: #{response_size}"
45
+ message += ", progress: #{payload[:progress]} / #{payload[:total]}"
46
+ message
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def log_request_format
53
+ Caoutsearch.instrumentation_options[:index]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Instrumentation
5
+ class Search < Base
6
+ def search(event)
7
+ log_request("Search", event, format: log_request_format)
8
+ log_response("Search", event)
9
+ end
10
+
11
+ def scroll_search(event)
12
+ log_request("Search", event, format: log_request_format)
13
+ log_response("Search", event) do |message|
14
+ payload = event.payload
15
+ message += ", progress: #{payload[:progress]} / #{payload[:total]}"
16
+ message
17
+ end
18
+ end
19
+
20
+ def scroll(event)
21
+ log_request("Scroll", event, format: "truncated")
22
+ log_response("Scroll", event) do |message|
23
+ payload = event.payload
24
+ message += ", progress: #{payload[:progress]} / #{payload[:total]}"
25
+ message
26
+ end
27
+ end
28
+
29
+ def delete(event)
30
+ log_request("Delete", event, format: log_request_format)
31
+ log_response("Delete", event)
32
+ end
33
+
34
+ private
35
+
36
+ def log_request_format
37
+ Caoutsearch.instrumentation_options[:search]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ class Mappings
5
+ attr_reader :index_name
6
+
7
+ # Build an index mapping
8
+ #
9
+ # Caoutsearch::Mapping.new({ properties: [...], )
10
+ #
11
+ def initialize(mappings)
12
+ @mappings = mappings.deep_symbolize_keys
13
+ end
14
+
15
+ def to_hash
16
+ @mappings
17
+ end
18
+ alias_method :as_json, :to_hash
19
+
20
+ def to_json(*)
21
+ as_json.to_json
22
+ end
23
+
24
+ def include?(*args)
25
+ find(*args).present?
26
+ end
27
+
28
+ def find(*args)
29
+ each_path(*args).map { |hash, _| hash }.last
30
+ end
31
+
32
+ def find_type(*args)
33
+ definition = find(*args)
34
+ definition[:type]&.to_s if definition.present?
35
+ end
36
+
37
+ def nested?(*args)
38
+ nested_path(*args).present?
39
+ end
40
+
41
+ def include_in_parent?(*args)
42
+ path = nested_path(*args)
43
+ return nil unless path
44
+
45
+ !!find(path)[:include_in_parent]
46
+ end
47
+
48
+ def nested_path(*args)
49
+ return nil unless include?(*args)
50
+
51
+ each_path(*args) do |hash, current_path|
52
+ break nil unless hash
53
+ return current_path if hash[:type] == "nested"
54
+ end
55
+
56
+ nil
57
+ end
58
+
59
+ private
60
+
61
+ def key_path(*args)
62
+ args.flat_map { |path| path.to_s.split(".").map(&:to_sym) }
63
+ end
64
+
65
+ def each_path(*args)
66
+ return to_enum(:each_path, *args) unless block_given?
67
+
68
+ hash = to_hash
69
+ path = []
70
+
71
+ key_path(*args).each do |key|
72
+ path << key
73
+ hash = hash.dig(:properties, key) || hash.dig(:fields, key) if hash
74
+
75
+ yield hash, path.join(".")
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ class Base
6
+ include Caoutsearch::Config::Client
7
+ include Caoutsearch::Config::Mappings
8
+ include Caoutsearch::Config::Settings
9
+
10
+ include Caoutsearch::Search::Search::DeleteMethods
11
+ include Caoutsearch::Search::Search::Inspect
12
+ include Caoutsearch::Search::Search::Instrumentation
13
+ include Caoutsearch::Search::Search::InternalDSL
14
+ include Caoutsearch::Search::Search::Naming
15
+ include Caoutsearch::Search::Search::QueryBuilder
16
+ include Caoutsearch::Search::Search::QueryMethods
17
+ include Caoutsearch::Search::Search::Resettable
18
+ include Caoutsearch::Search::Search::Response
19
+ include Caoutsearch::Search::Search::ScrollMethods
20
+ include Caoutsearch::Search::Search::SearchMethods
21
+
22
+ def self.search(*args)
23
+ new.search(*args)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module DSL
6
+ class Item
7
+ attr_reader :name, :options, :block
8
+
9
+ PROPERTIES_TO_INSPECT = %i[name options block].freeze
10
+
11
+ def initialize(name, options = {}, &block)
12
+ @name = name
13
+ @options = options
14
+ @block = block if block
15
+ end
16
+
17
+ def has_block?
18
+ block.present?
19
+ end
20
+
21
+ def indexes
22
+ @indexes ||= Array.wrap(options[:indexes].presence || name)
23
+ end
24
+
25
+ def inspect
26
+ properties = properties_to_inspect.map { |k, v| " #{k}: #{v}" }
27
+
28
+ "#<#{self.class}#{properties.join(",")}>"
29
+ end
30
+
31
+ private
32
+
33
+ def properties_to_inspect
34
+ PROPERTIES_TO_INSPECT.each_with_object({}) do |name, properties|
35
+ value = instance_variable_get("@#{name}")
36
+ properties[name] = value.inspect if value.present?
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Query
6
+ class Base < ::Hash
7
+ include Caoutsearch::Search::Query::Boolean
8
+ include Caoutsearch::Search::Query::Cleaning
9
+ include Caoutsearch::Search::Query::Getters
10
+ include Caoutsearch::Search::Query::Merge
11
+ include Caoutsearch::Search::Query::Nested
12
+ include Caoutsearch::Search::Query::Setters
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Query
6
+ module Boolean
7
+ def should_filter_on(terms)
8
+ terms = flatten_bool_terms(:should, terms)
9
+ terms_without_none = terms.without(Caoutsearch::Filter::NONE)
10
+ return if terms.empty?
11
+
12
+ if terms.size == 1
13
+ filters << terms[0]
14
+ elsif terms_without_none.size == 1
15
+ filters << terms_without_none[0]
16
+ elsif terms_without_none.size > 1
17
+ filters << { bool: { should: terms_without_none } }
18
+ end
19
+ end
20
+
21
+ def must_filter_on(terms)
22
+ terms = flatten_bool_terms(:must, terms)
23
+ return if terms.empty?
24
+
25
+ if terms.include?(Caoutsearch::Filter::NONE)
26
+ filters << Caoutsearch::Filter::NONE
27
+ elsif terms.size == 1
28
+ filters << terms[0]
29
+ elsif terms.size > 1
30
+ filters.push(*terms)
31
+ end
32
+ end
33
+
34
+ def must_not_filter_on(terms)
35
+ terms = flatten_bool_terms(:must_not, terms)
36
+ terms = terms.without(Caoutsearch::Filter::NONE)
37
+ return if terms.empty?
38
+
39
+ filters <<
40
+ if terms.size == 1
41
+ { bool: { must_not: terms[0] } }
42
+ else
43
+ filters << { bool: { must_not: terms } }
44
+ end
45
+ end
46
+
47
+ def flatten_bool_terms(operator, raw_terms)
48
+ terms = []
49
+
50
+ raw_terms.flatten.each do |value|
51
+ if value.is_a?(Hash) && value.keys == [:bool] && value[:bool].keys == [operator]
52
+ terms += Array.wrap(value.dig(:bool, operator))
53
+ else
54
+ terms << value
55
+ end
56
+ end
57
+
58
+ terms.uniq
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Query
6
+ module Cleaning
7
+ def clean
8
+ nested_queries.each_value(&:clean)
9
+ remove_duplicate_filters
10
+ remove_empty_clauses
11
+ self
12
+ end
13
+
14
+ def remove_duplicate_filters
15
+ filters.replace(filters.uniq) if dig(:query, :bool, :filter)
16
+ self
17
+ end
18
+
19
+ def remove_empty_clauses
20
+ each do |key, value|
21
+ delete(key) if value.blank? && value != false
22
+ end
23
+
24
+ self
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Query
6
+ module Getters
7
+ def filters
8
+ fetch(:query, :bool, :filter, [])
9
+ end
10
+
11
+ def aggregations
12
+ fetch(:aggregations, {})
13
+ end
14
+
15
+ def suggestions
16
+ fetch(:suggest, {})
17
+ end
18
+
19
+ def sort
20
+ fetch(:sort, [])
21
+ end
22
+
23
+ def fetch(*keys, default_value)
24
+ value = self
25
+
26
+ keys[0..-2].each do |key|
27
+ value = value[key] ||= {}
28
+ end
29
+
30
+ value[keys[-1]] ||= default_value
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Query
6
+ module Merge
7
+ def merge(other_hash)
8
+ dup.merge!(other_hash)
9
+ end
10
+
11
+ def merge!(other_hash)
12
+ merged_hash = to_h.deep_merge(other_hash) do |key, this_val, other_val|
13
+ if this_val.is_a?(Array) && other_val.is_a?(Array)
14
+ (this_val + other_val).uniq
15
+ elsif block_given?
16
+ yield key, this_val, other_val
17
+ else
18
+ other_val
19
+ end
20
+ end
21
+
22
+ super(merged_hash)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Query
6
+ module Nested
7
+ def nested_queries
8
+ @nested_queries ||= {}
9
+ end
10
+
11
+ def nested_query(path)
12
+ nested_queries[path.to_s] ||= begin
13
+ query = Caoutsearch::Search::Query::Base.new
14
+ query[:path] = path.to_s
15
+
16
+ filters << { nested: query }
17
+ query
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Query
6
+ module Setters
7
+ def add_none
8
+ filters << Caoutsearch::Filter::NONE
9
+ end
10
+
11
+ def add_filter(...)
12
+ should_filter_on(build_terms(...))
13
+ self
14
+ end
15
+
16
+ # Compose filters
17
+ #
18
+ # Examples :
19
+ #
20
+ # query.build_terms('key', 'value')
21
+ # => [ { term: { "key" => "value" } } ] } } }
22
+ #
23
+ # query.build_terms('key', 'value', type: :integer)
24
+ # => [ { term: { "key" => 0 } } ] } } }
25
+ #
26
+ # query.build_terms('key', 'value', as: :boolean)
27
+ # => [ { term: { "key" => true } } ] } } }
28
+ #
29
+ def build_terms(keys, value, type: :keyword, as: :default, **options)
30
+ filter_class = Caoutsearch::Filter[as]
31
+ raise ArgumentError, "unexpected type of filter: #{as.inspect}" unless filter_class
32
+
33
+ terms = Array.wrap(keys).flat_map { |key| filter_class.new(key, value, type, options).as_json }
34
+ terms.select(&:present?)
35
+ rescue Caoutsearch::Search::ValueOverflow
36
+ [Caoutsearch::Filter::NONE]
37
+ end
38
+
39
+ def add_sort(prop, direction)
40
+ if direction.to_s == "desc"
41
+ sort.push(prop => direction)
42
+ else
43
+ sort.push(prop)
44
+ end
45
+ self
46
+ end
47
+
48
+ def add_aggregation(key, value)
49
+ aggregations[key] = value
50
+ end
51
+
52
+ # TODO,
53
+ # Handle types, ex: prefix, input, text
54
+ # Suggestions should probably use _source: false
55
+ def add_suggestion(key, value, **options)
56
+ suggestions[key] = {
57
+ prefix: value,
58
+ completion: options.reverse_merge(
59
+ field: key,
60
+ skip_duplicates: true,
61
+ fuzzy: true
62
+ )
63
+ }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Sanitizer
6
+ ESCAPED_CHARACTERS = "\+-&|!(){}[]^~*?:"
7
+ ESCAPED_CHARACTERS_REGEXP = /([+\-&|!(){}\[\]\^~*?:])/
8
+
9
+ class << self
10
+ def sanitize(value, characters = ESCAPED_CHARACTERS)
11
+ case value
12
+ when Array
13
+ value.map { |v| sanitize(v) }
14
+ when Hash
15
+ value.each { |k, v| value[k] = sanitize(v) }
16
+ when String
17
+ regexp = ESCAPED_CHARACTERS_REGEXP if characters == ESCAPED_CHARACTERS
18
+ regexp ||= Regexp.new("([#{Regexp.escape(characters)}])")
19
+
20
+ value.gsub(regexp, '\\\\\1')
21
+ else
22
+ value
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Search
6
+ module DeleteMethods
7
+ def delete_documents
8
+ request_payload = {
9
+ index: index_name,
10
+ body: build.to_h
11
+ }
12
+
13
+ instrument(:delete) do |event_payload|
14
+ event_payload[:request] = request_payload
15
+ event_payload[:response] = client.delete_by_query(request_payload)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Search
6
+ module Inspect
7
+ PROPERTIES_TO_INSPECT = %i[
8
+ search_criteria
9
+ current_context
10
+ current_order
11
+ current_page
12
+ current_limit
13
+ current_offset
14
+ current_aggregations
15
+ current_suggestions
16
+ current_returns
17
+ ].freeze
18
+
19
+ def inspect
20
+ properties = properties_to_inspect.map { |k, v| " #{k}: #{v}" }
21
+
22
+ "#<#{self.class}#{properties.join(",")}>"
23
+ end
24
+
25
+ private
26
+
27
+ def properties_to_inspect
28
+ PROPERTIES_TO_INSPECT.each_with_object({}) do |name, properties|
29
+ value = instance_variable_get("@#{name}")
30
+ properties[name] = value.inspect if value
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Search
6
+ module Instrumentation
7
+ extend ActiveSupport::Concern
8
+
9
+ def instrument(action, **options, &block)
10
+ ActiveSupport::Notifications.instrument("#{action}.caoutsearch_search", **options, klass: self.class.to_s, &block)
11
+ end
12
+
13
+ class_methods do
14
+ def instrument(action, **options, &block)
15
+ ActiveSupport::Notifications.instrument("#{action}.caoutsearch_search", **options, klass: to_s, &block)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end