redi_search 2.0.2 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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