redi_search 2.0.2 → 5.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +13 -11
  3. data/.github/workflows/tests.yml +19 -14
  4. data/.rubocop.yml +71 -5
  5. data/Appraisals +8 -4
  6. data/Gemfile +3 -2
  7. data/README.md +15 -33
  8. data/Rakefile +1 -0
  9. data/bin/console +6 -2
  10. data/gemfiles/{activerecord_51.gemfile → activerecord_60.gemfile} +4 -2
  11. data/gemfiles/{activerecord_52.gemfile → activerecord_61.gemfile} +4 -2
  12. data/gemfiles/activerecord_70.gemfile +15 -0
  13. data/lib/redi_search/add_field.rb +1 -1
  14. data/lib/redi_search/client/response.rb +1 -1
  15. data/lib/redi_search/client.rb +15 -5
  16. data/lib/redi_search/create.rb +2 -1
  17. data/lib/redi_search/document/display.rb +4 -4
  18. data/lib/redi_search/document/finder.rb +12 -50
  19. data/lib/redi_search/document.rb +11 -16
  20. data/lib/redi_search/hset.rb +28 -0
  21. data/lib/redi_search/index.rb +19 -17
  22. data/lib/redi_search/lazily_load.rb +2 -2
  23. data/lib/redi_search/log_subscriber.rb +7 -6
  24. data/lib/redi_search/model.rb +4 -10
  25. data/lib/redi_search/schema/field.rb +10 -2
  26. data/lib/redi_search/schema/geo_field.rb +1 -1
  27. data/lib/redi_search/schema/numeric_field.rb +1 -1
  28. data/lib/redi_search/schema/tag_field.rb +5 -1
  29. data/lib/redi_search/schema/text_field.rb +1 -1
  30. data/lib/redi_search/schema.rb +10 -5
  31. data/lib/redi_search/search/clauses/boolean.rb +4 -4
  32. data/lib/redi_search/search/clauses/or.rb +1 -1
  33. data/lib/redi_search/search/result.rb +2 -2
  34. data/lib/redi_search/search.rb +1 -1
  35. data/lib/redi_search/spellcheck/result.rb +4 -4
  36. data/lib/redi_search/spellcheck.rb +1 -1
  37. data/lib/redi_search/version.rb +1 -1
  38. data/redi_search.gemspec +3 -3
  39. metadata +15 -14
  40. data/lib/redi_search/add.rb +0 -67
@@ -9,10 +9,12 @@ module RediSearch
9
9
  class Client
10
10
  def initialize(redis = Redis.new)
11
11
  @redis = redis
12
+ @pipeline = false
12
13
  end
13
14
 
14
- def call!(command, *params)
15
+ def call!(command, *params, skip_ft: false)
15
16
  instrument(command.downcase, query: [command, params]) do
17
+ command = "FT.#{command}" unless skip_ft
16
18
  send_command(command, *params)
17
19
  end
18
20
  end
@@ -20,23 +22,31 @@ module RediSearch
20
22
  def multi
21
23
  Response.new(redis.multi do
22
24
  instrument("pipeline", query: ["begin pipeline"])
23
- yield
25
+ capture_pipeline { yield }
24
26
  instrument("pipeline", query: ["finish pipeline"])
25
27
  end)
26
28
  end
27
29
 
28
30
  private
29
31
 
30
- attr_reader :redis
32
+ attr_reader :redis
33
+ attr_accessor :pipeline
34
+
35
+ def capture_pipeline
36
+ self.pipeline = true
37
+ yield
38
+ self.pipeline = false
39
+ end
31
40
 
32
41
  def send_command(command, *params)
33
- Response.new(redis.call("FT.#{command}", *params))
42
+ Response.new(redis.call(command, *params))
34
43
  end
35
44
 
36
45
  def instrument(action, payload, &block)
37
46
  ActiveSupport::Notifications.instrument(
38
47
  "action.redi_search",
39
- { name: "RediSearch", action: action }.merge(payload),
48
+ { name: "RediSearch", action: action, inside_pipeline: pipeline }.
49
+ merge(payload),
40
50
  &Proc.new(&(block || proc {}))
41
51
  )
42
52
  end
@@ -31,7 +31,8 @@ module RediSearch
31
31
  attr_reader :index, :schema, :options
32
32
 
33
33
  def command
34
- ["CREATE", index.name, *extract_options.compact, "SCHEMA", schema.to_a]
34
+ ["CREATE", index.name, "ON", "HASH", "PREFIX", 1, index.name,
35
+ *extract_options.compact, "SCHEMA", schema.to_a]
35
36
  end
36
37
 
37
38
  def extract_options
@@ -4,9 +4,9 @@ module RediSearch
4
4
  class Document
5
5
  module Display
6
6
  def inspect
7
- inspection = pretty_print_attributes.map do |field_name|
7
+ inspection = pretty_print_attributes.filter_map do |field_name|
8
8
  "#{field_name}: #{public_send(field_name)}"
9
- end.compact.join(", ")
9
+ end.join(", ")
10
10
 
11
11
  "#<#{self.class} #{inspection}>"
12
12
  end
@@ -19,7 +19,7 @@ module RediSearch
19
19
  pp_attrs.compact
20
20
  end
21
21
 
22
- #:nocov:
22
+ # :nocov:
23
23
  def pretty_print(printer) # rubocop:disable Metrics/MethodLength
24
24
  printer.object_address_group(self) do
25
25
  printer.seplist(
@@ -35,7 +35,7 @@ module RediSearch
35
35
  end
36
36
  end
37
37
  end
38
- #:nocov:
38
+ # :nocov:
39
39
  end
40
40
  end
41
41
  end
@@ -3,75 +3,37 @@
3
3
  module RediSearch
4
4
  class Document
5
5
  class Finder
6
- def initialize(index, *document_ids)
6
+ def initialize(index, document_id)
7
7
  @index = index
8
- @document_ids = [*document_ids]
8
+ @document_id = document_id
9
9
  end
10
10
 
11
11
  def find
12
- if multi?
13
- parse_multi_documents
14
- else
15
- parse_document(document_ids.first, response)
16
- end
12
+ Document.new(index, document_id, Hash[*response]) if response?
17
13
  end
18
14
 
19
15
  private
20
16
 
21
- attr_reader :index, :document_ids
17
+ attr_reader :index, :document_id
22
18
 
23
19
  def response
24
- @response ||= call!(get_command, index.name, *prepended_document_ids)
20
+ @response ||= call!("HGETALL", prepended_document_id)
25
21
  end
26
22
 
27
23
  def call!(*command)
28
- RediSearch.client.call!(*command)
29
- end
30
-
31
- def get_command
32
- if multi?
33
- "MGET"
34
- else
35
- "GET"
36
- end
37
- end
38
-
39
- def multi?
40
- document_ids.size > 1
41
- end
42
-
43
- def prepended_document_ids
44
- document_ids.map do |document_id|
45
- prepend_document_id(document_id)
46
- end
24
+ RediSearch.client.call!(*command, skip_ft: true)
47
25
  end
48
26
 
49
- def prepend_document_id(id)
50
- if id.to_s.start_with? index.name
51
- id
27
+ def prepended_document_id
28
+ if document_id.to_s.start_with? index.name
29
+ document_id
52
30
  else
53
- "#{index.name}#{id}"
31
+ "#{index.name}#{document_id}"
54
32
  end
55
33
  end
56
34
 
57
- def parse_multi_documents
58
- document_ids.map.with_index do |document_id, index|
59
- parse_document(document_id, response[index])
60
- end.compact
61
- end
62
-
63
- def parse_document(document_id, document_response)
64
- return unless document_response?(document_response)
65
-
66
- Document.new(index, document_id, Hash[*document_response])
67
- end
68
-
69
- def document_response?(document_response)
70
- if document_response.respond_to?(:empty?)
71
- !document_response.empty?
72
- else
73
- !document_response.nil?
74
- end
35
+ def response?
36
+ !response.to_a.empty?
75
37
  end
76
38
  end
77
39
  end
@@ -11,11 +11,11 @@ module RediSearch
11
11
  def for_object(index, record, serializer: nil, only: [])
12
12
  object_to_serialize = serializer&.new(record) || record
13
13
 
14
- field_values = index.schema.fields.map do |field|
15
- next unless only.empty? || only.include?(field.to_sym)
14
+ field_values = index.schema.fields.map(&:name).filter_map do |field|
15
+ next unless only.empty? || only.include?(field)
16
16
 
17
17
  [field.to_s, object_to_serialize.public_send(field)]
18
- end.compact.to_h
18
+ end.to_h
19
19
 
20
20
  new(index, object_to_serialize.id, field_values)
21
21
  end
@@ -23,10 +23,6 @@ module RediSearch
23
23
  def get(index, document_id)
24
24
  Finder.new(index, document_id).find
25
25
  end
26
-
27
- def mget(index, *document_ids)
28
- Finder.new(index, *document_ids).find
29
- end
30
26
  end
31
27
 
32
28
  attr_reader :attributes, :score
@@ -40,17 +36,20 @@ module RediSearch
40
36
  load_attributes
41
37
  end
42
38
 
43
- def del(delete_document: false)
44
- command = ["DEL", index.name, document_id, ("DD" if delete_document)]
45
- call!(*command.compact).ok?
39
+ def del
40
+ RediSearch.client.call!("DEL", document_id, skip_ft: true).ok?
46
41
  end
47
42
 
48
43
  def schema_fields
49
- @schema_fields ||= index.schema.fields.map(&:to_s)
44
+ @schema_fields ||= index.schema.fields.map do |field|
45
+ field.name.to_s
46
+ end
50
47
  end
51
48
 
52
49
  def redis_attributes
53
- attributes.to_a.flatten
50
+ attributes.flat_map do |field, value|
51
+ [field, index.schema[field.to_sym].serialize(value)]
52
+ end
54
53
  end
55
54
 
56
55
  def document_id
@@ -73,10 +72,6 @@ module RediSearch
73
72
 
74
73
  attr_reader :index
75
74
 
76
- def call!(*command)
77
- RediSearch.client.call!(*command)
78
- end
79
-
80
75
  def load_attributes
81
76
  attributes.each do |field, value|
82
77
  next unless schema_fields.include? field.to_s
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RediSearch
4
+ class Hset
5
+ def initialize(index, document)
6
+ @index = index
7
+ @document = document
8
+ end
9
+
10
+ def call!
11
+ RediSearch.client.call!(*command, skip_ft: true)
12
+ end
13
+
14
+ def call
15
+ call!
16
+ rescue Redis::CommandError
17
+ false
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :index, :document
23
+
24
+ def command
25
+ ["HSET", document.document_id, document.redis_attributes].compact
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "redi_search/add"
3
+ require "redi_search/hset"
4
4
  require "redi_search/create"
5
5
  require "redi_search/schema"
6
6
  require "redi_search/search"
@@ -33,34 +33,36 @@ module RediSearch
33
33
  Create.new(self, schema, options).call!
34
34
  end
35
35
 
36
- def drop
37
- drop!
36
+ def drop(keep_docs: false)
37
+ drop!(keep_docs: keep_docs)
38
38
  rescue Redis::CommandError
39
39
  false
40
40
  end
41
41
 
42
- def drop!
43
- client.call!("DROP", name).ok?
42
+ def drop!(keep_docs: false)
43
+ command = ["DROPINDEX", name]
44
+ command << "DD" unless keep_docs
45
+ client.call!(*command.compact).ok?
44
46
  end
45
47
 
46
- def add(document, **options)
47
- Add.new(self, document, **options).call
48
+ def add(document)
49
+ Hset.new(self, document).call
48
50
  end
49
51
 
50
- def add!(document, **options)
51
- Add.new(self, document, **options).call!
52
+ def add!(document)
53
+ Hset.new(self, document).call!
52
54
  end
53
55
 
54
- def add_multiple(documents, **options)
56
+ def add_multiple(documents)
55
57
  client.multi do
56
58
  documents.each do |document|
57
- add(document, **options)
59
+ add(document)
58
60
  end
59
- end.ok?
61
+ end.all? { |response| response >= 0 }
60
62
  end
61
63
 
62
- def del(document, delete_document: false)
63
- document.del(delete_document: delete_document)
64
+ def del(document)
65
+ document.del
64
66
  end
65
67
 
66
68
  def exist?
@@ -78,14 +80,14 @@ module RediSearch
78
80
  end
79
81
 
80
82
  def fields
81
- schema.fields.map(&:to_s)
83
+ schema.fields.map { |field| field.name.to_s }
82
84
  end
83
85
 
84
- def reindex(documents, recreate: false, **options)
86
+ def reindex(documents, recreate: false)
85
87
  drop if recreate
86
88
  create unless exist?
87
89
 
88
- add_multiple documents, **options
90
+ add_multiple documents
89
91
  end
90
92
 
91
93
  def document_count
@@ -30,7 +30,7 @@ module RediSearch
30
30
  end
31
31
  end
32
32
 
33
- #:nocov:
33
+ # :nocov:
34
34
  def pretty_print(printer)
35
35
  execute_and_rescue_inspection do
36
36
  return super(inspect) unless valid?
@@ -38,7 +38,7 @@ module RediSearch
38
38
  printer.pp(documents)
39
39
  end
40
40
  end
41
- #:nocov:
41
+ # :nocov:
42
42
 
43
43
  private
44
44
 
@@ -12,12 +12,12 @@ module RediSearch
12
12
  Thread.current[:redi_search_runtime] ||= 0
13
13
  end
14
14
 
15
- #:nocov:
15
+ # :nocov:
16
16
  def self.reset_runtime
17
17
  rt, self.runtime = runtime, 0
18
18
  rt
19
19
  end
20
- #:nocov:
20
+ # :nocov:
21
21
 
22
22
  def action(event)
23
23
  self.class.runtime += event.duration
@@ -39,9 +39,9 @@ module RediSearch
39
39
  def action_color(action)
40
40
  case action.to_sym
41
41
  when :search, :spellcheck then YELLOW
42
- when :create, :add then GREEN
43
- when :drop, :del then RED
44
- when :get, :mget, :info then CYAN
42
+ when :create, :hset then GREEN
43
+ when :dropindex, :del then RED
44
+ when :hgetall, :info then CYAN
45
45
  when :pipeline then MAGENTA
46
46
  when :explaincli then BLUE
47
47
  end
@@ -52,6 +52,7 @@ module RediSearch
52
52
  event.payload[:query].flatten.map.with_index do |arg, i|
53
53
  arg = "FT.#{arg}" if prepend_ft?(arg, i)
54
54
  arg = arg.inspect if inspect_arg?(event.payload, arg)
55
+ arg = " #{arg}" if event.payload[:inside_pipeline]
55
56
  arg
56
57
  end.join(" ")
57
58
  end
@@ -61,7 +62,7 @@ module RediSearch
61
62
  end
62
63
 
63
64
  def prepend_ft?(arg, index)
64
- index.zero? && !multiword?(arg)
65
+ index.zero? && !multiword?(arg) && %w(HSET HGETALL DEL).exclude?(arg.to_s)
65
66
  end
66
67
 
67
68
  def inspect_arg?(payload, arg)
@@ -48,11 +48,11 @@ module RediSearch
48
48
  redi_search_index.spellcheck(term, distance: distance)
49
49
  end
50
50
 
51
- def reindex(recreate: false, only: [], **options)
51
+ def reindex(recreate: false, only: [])
52
52
  search_import.find_in_batches.all? do |group|
53
53
  redi_search_index.reindex(
54
54
  group.map { |record| record.redi_search_document(only: only) },
55
- recreate: recreate, **options.deep_merge(replace: { partial: true })
55
+ recreate: recreate
56
56
  )
57
57
  end
58
58
  end
@@ -67,17 +67,11 @@ module RediSearch
67
67
  end
68
68
 
69
69
  def redi_search_delete_document
70
- return unless self.class.redi_search_index.exist?
71
-
72
- self.class.redi_search_index.del(
73
- redi_search_document, delete_document: true
74
- )
70
+ self.class.redi_search_index.del(redi_search_document)
75
71
  end
76
72
 
77
73
  def redi_search_add_document
78
- return unless self.class.redi_search_index.exist?
79
-
80
- self.class.redi_search_index.add(redi_search_document, replace: true)
74
+ self.class.redi_search_index.add(redi_search_document)
81
75
  end
82
76
  end
83
77
  end
@@ -3,6 +3,14 @@
3
3
  module RediSearch
4
4
  class Schema
5
5
  class Field
6
+ def name
7
+ @name&.to_sym
8
+ end
9
+
10
+ def serialize(value)
11
+ value
12
+ end
13
+
6
14
  private
7
15
 
8
16
  FALSES = [
@@ -10,10 +18,10 @@ module RediSearch
10
18
  ].freeze
11
19
 
12
20
  def boolean_options_string
13
- boolean_options.map do |option|
21
+ boolean_options.filter_map do |option|
14
22
  option.to_s.upcase.split("_").join unless
15
23
  FALSES.include?(send(option))
16
- end.compact
24
+ end
17
25
  end
18
26
  end
19
27
  end