redis-search 0.6.3 → 0.7.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.
data/README.markdown CHANGED
@@ -14,6 +14,7 @@ High performance real-time search (Support Chinese), index in Redis for Rails ap
14
14
  * Support ActiveRecord and Mongoid
15
15
  * Sort results by one field
16
16
  * Homophone search, pinyin search
17
+ * Conditions support
17
18
 
18
19
  ## Requirements
19
20
 
@@ -26,7 +27,8 @@ in Rails application Gemfile
26
27
  gem 'redis','>= 2.1.1'
27
28
  gem 'chinese_pinyin', '0.4.1'
28
29
  gem 'rmmseg-cpp-huacnlee', '0.2.9'
29
- gem 'redis-search', '0.6.2'
30
+ gem 'redis-namespace','~> 1.1.0'
31
+ gem 'redis-search', '0.6.3'
30
32
 
31
33
  install bundlers
32
34
 
@@ -36,9 +38,14 @@ install bundlers
36
38
 
37
39
  create file in: config/initializers/redis_search.rb
38
40
 
41
+ require "redis"
42
+ require "redis-namespace"
39
43
  require "redis-search"
40
44
  # don't forget change namespace
41
45
  redis = Redis.new(:host => "127.0.0.1",:port => "6379")
46
+ # We suggest you use a special db in Redis, when you need to clear all data, you can use flushdb command to clear them.
47
+ redis.select(3)
48
+ # Give a special namespace as prefix for Redis key, when your have more than one project used redis-search, this config will make them work fine.
42
49
  redis = Redis::Namespace.new("your_app_name:redis_search", :redis => redis)
43
50
  Redis::Search.configure do |config|
44
51
  config.redis = redis
@@ -63,6 +70,7 @@ bind Redis::Search callback event, it will to rebuild search indexes when data c
63
70
 
64
71
  redis_search_index(:title_field => :title,
65
72
  :score_field => :hits,
73
+ :condition_fields => [:user_id, :category_id],
66
74
  :ext_fields => [:category_name])
67
75
 
68
76
  def category_name
@@ -88,12 +96,12 @@ bind Redis::Search callback event, it will to rebuild search indexes when data c
88
96
  class SearchController < ApplicationController
89
97
  # GET /searchs?q=title
90
98
  def index
91
- Redis::Search.query("Post", params[:q])
99
+ Redis::Search.query("Post", params[:q], :conditions => {:user_id => 12})
92
100
  end
93
101
 
94
102
  # GET /search_users?q=j
95
103
  def search_users
96
- Redis::Search.complete("Post", params[:q])
104
+ Redis::Search.complete("Post", params[:q], :conditions => {:user_id => 12, :category_id => 4})
97
105
  end
98
106
  end
99
107
 
@@ -109,6 +117,9 @@ see [Rdoc.info redis-search](http://rubydoc.info/gems/redis-search)
109
117
 
110
118
  ## Benchmark test
111
119
 
120
+ You can run the rake command (see Rakefile) to make test.
121
+ There is my performance test result.
122
+
112
123
  * [https://gist.github.com/1150933](https://gist.github.com/1150933)
113
124
 
114
125
  ## Demo
@@ -15,15 +15,18 @@ class Redis
15
15
  prefix_index_enable = options[:prefix_index_enable] || false
16
16
  ext_fields = options[:ext_fields] || []
17
17
  score_field = options[:score_field] || :created_at
18
+ condition_fields = options[:condition_fields] || []
18
19
  # Add score field to ext_fields
19
- ext_fields << score_field if !ext_fields.include?(score_field)
20
+ ext_fields |= [score_field]
21
+ # Add condition fields to ext_fields
22
+ ext_fields |= condition_fields
20
23
 
21
24
  # store Model name to indexed_models for Rake tasks
22
25
  Search.indexed_models = [] if Search.indexed_models == nil
23
26
  Search.indexed_models << self
24
27
  # bind instance methods and callback events
25
28
  class_eval %(
26
- def redis_search_ext_fields(ext_fields)
29
+ def redis_search_fields_to_hash(ext_fields)
27
30
  exts = {}
28
31
  ext_fields.each do |f|
29
32
  exts[f] = instance_eval(f.to_s)
@@ -34,11 +37,12 @@ class Redis
34
37
  # after_create :redis_search_index_create
35
38
  def redis_search_index_create
36
39
  s = Search::Index.new(:title => self.#{title_field},
37
- :id => self.id,
38
- :exts => self.redis_search_ext_fields(#{ext_fields.inspect}),
39
- :type => self.class.to_s,
40
- :score => self.#{score_field}.to_i,
41
- :prefix_index_enable => #{prefix_index_enable})
40
+ :id => self.id,
41
+ :exts => self.redis_search_fields_to_hash(#{ext_fields.inspect}),
42
+ :type => self.class.to_s,
43
+ :condition_fields => #{condition_fields},
44
+ :score => self.#{score_field}.to_i,
45
+ :prefix_index_enable => #{prefix_index_enable})
42
46
  s.save
43
47
  # release s
44
48
  s = nil
@@ -20,39 +20,54 @@ class Redis
20
20
  # * Redis::Search.complete("Tag","red") => ["Redis", "Redmine"]
21
21
  # * Redis::Search.complete("Tag","redi") => ["Redis"]
22
22
  def self.complete(type, w, options = {})
23
- return [] if w.blank? or type.blank?
24
23
  limit = options[:limit] || 10
25
-
24
+ conditions = options[:conditions] || []
25
+ return [] if (w.blank? and conditions.blank?) or type.blank?
26
+
26
27
  prefix_matchs = []
27
28
  # This is not random, try to get replies < MTU size
28
29
  rangelen = Redis::Search.config.complete_max_length
29
30
  prefix = w.downcase
30
31
  key = Search.mk_complete_key(type)
31
32
 
32
- start = Redis::Search.config.redis.zrank(key,prefix)
33
- return [] if !start
34
- count = limit
35
- max_range = start+(rangelen*limit)-1
36
- range = Redis::Search.config.redis.zrange(key,start,max_range)
37
- while prefix_matchs.length <= count
38
- start += rangelen
39
- break if !range or range.length == 0
40
- range.each {|entry|
41
- minlen = [entry.length,prefix.length].min
42
- if entry[0...minlen] != prefix[0...minlen]
43
- count = prefix_matchs.count
44
- break
45
- end
46
- if entry[-1..-1] == "*" and prefix_matchs.length != count
47
- prefix_matchs << entry[0...-1]
48
- end
49
- }
50
- range = range[start..max_range]
33
+
34
+ if start = Redis::Search.config.redis.zrank(key,prefix)
35
+ count = limit
36
+ max_range = start+(rangelen*limit)-1
37
+ range = Redis::Search.config.redis.zrange(key,start,max_range)
38
+ while prefix_matchs.length <= count
39
+ start += rangelen
40
+ break if !range or range.length == 0
41
+ range.each {|entry|
42
+ minlen = [entry.length,prefix.length].min
43
+ if entry[0...minlen] != prefix[0...minlen]
44
+ count = prefix_matchs.count
45
+ break
46
+ end
47
+ if entry[-1..-1] == "*" and prefix_matchs.length != count
48
+ prefix_matchs << entry[0...-1]
49
+ end
50
+ }
51
+ range = range[start..max_range]
52
+ end
51
53
  end
54
+
55
+ # 组合 words 的特别 key 名
52
56
  words = []
53
57
  words = prefix_matchs.uniq.collect { |w| Search.mk_sets_key(type,w) }
58
+
59
+ # 组合特别 key ,但这里不会像 query 那样放入 words, 因为在 complete 里面 words 是用 union 取的,condition_keys 和 words 应该取交集
60
+ condition_keys = []
61
+ if !conditions.blank?
62
+ conditions = conditions[0] if conditions.is_a?(Array)
63
+ conditions.keys.each do |c|
64
+ condition_keys << Search.mk_condition_key(type,c,conditions[c])
65
+ end
66
+ end
67
+
68
+ # 按词语搜索
69
+ temp_store_key = "tmpsunionstore:#{words.join("+")}"
54
70
  if words.length > 1
55
- temp_store_key = "tmpsunionstore:#{words.join("+")}"
56
71
  if !Redis::Search.config.redis.exists(temp_store_key)
57
72
  # 将多个词语组合对比,得到并集,并存入临时区域
58
73
  Redis::Search.config.redis.sunionstore(temp_store_key,*words)
@@ -60,16 +75,24 @@ class Redis
60
75
  Redis::Search.config.redis.expire(temp_store_key,86400)
61
76
  end
62
77
  # 根据需要的数量取出 ids
63
- ids = Redis::Search.config.redis.sort(temp_store_key,
64
- :limit => [0,limit],
65
- :by => Search.mk_score_key(type,"*"),
66
- :order => "desc")
67
78
  else
68
- ids = Redis::Search.config.redis.sort(words.first,
69
- :limit => [0,limit],
70
- :by => Search.mk_score_key(type,"*"),
71
- :order => "desc")
79
+ temp_store_key = words.first
72
80
  end
81
+
82
+ # 如果有条件,这里再次组合一下
83
+ if !condition_keys.blank?
84
+ condition_keys << temp_store_key if !words. blank?
85
+ temp_store_key = "tmpsinterstore:#{condition_keys.join('+')}"
86
+ if !Redis::Search.config.redis.exists(temp_store_key)
87
+ Redis::Search.config.redis.sinterstore(temp_store_key,*condition_keys)
88
+ Redis::Search.config.redis.expire(temp_store_key,86400)
89
+ end
90
+ end
91
+
92
+ ids = Redis::Search.config.redis.sort(temp_store_key,
93
+ :limit => [0,limit],
94
+ :by => Search.mk_score_key(type,"*"),
95
+ :order => "desc")
73
96
  return [] if ids.blank?
74
97
  hmget(type,ids)
75
98
  end
@@ -85,14 +108,28 @@ class Redis
85
108
  def self.query(type, text,options = {})
86
109
  tm = Time.now
87
110
  result = []
88
- return result if text.strip.blank?
89
-
111
+
90
112
  limit = options[:limit] || 10
91
113
  sort_field = options[:sort_field] || "id"
114
+ conditions = options[:conditions] || []
115
+
116
+ # 如果搜索文本和查询条件均没有,那就直接返回 []
117
+ return result if text.strip.blank? and conditions.blank?
92
118
 
93
119
  words = Search.split(text)
94
120
  words = words.collect { |w| Search.mk_sets_key(type,w) }
95
121
 
122
+
123
+ condition_keys = []
124
+ if !conditions.blank?
125
+ conditions = conditions[0] if conditions.is_a?(Array)
126
+ conditions.keys.each do |c|
127
+ condition_keys << Search.mk_condition_key(type,c,conditions[c])
128
+ end
129
+ # 将条件的 key 放入关键词搜索集合内,用于 sinterstore 搜索
130
+ words += condition_keys
131
+ end
132
+
96
133
  return result if words.blank?
97
134
 
98
135
  temp_store_key = "tmpinterstore:#{words.join("+")}"
@@ -108,6 +145,7 @@ class Redis
108
145
  if Search.config.pinyin_match
109
146
  pinyin_words = Search.split_pinyin(text)
110
147
  pinyin_words = pinyin_words.collect { |w| Search.mk_sets_key(type,w) }
148
+ pinyin_words += condition_keys
111
149
  temp_sunion_key = "tmpsunionstore:#{words.join("+")}"
112
150
  if Search.config.pinyin_match
113
151
  temp_pinyin_store_key = "tmpinterstore:#{pinyin_words.join("+")}"
@@ -182,6 +220,10 @@ class Redis
182
220
  def self.mk_score_key(type, id)
183
221
  "#{type}:_score_:#{id}"
184
222
  end
223
+
224
+ def self.mk_condition_key(type, field, id)
225
+ "#{type}:_by:_#{field}:#{id}"
226
+ end
185
227
 
186
228
  def self.mk_complete_key(type)
187
229
  "Compl#{type}"
@@ -1,9 +1,14 @@
1
1
  class Redis
2
2
  module Search
3
3
  class Index
4
- attr_accessor :type, :title, :id,:score, :exts, :prefix_index_enable
4
+ attr_accessor :type, :title, :id,:score, :exts, :condition_fields, :prefix_index_enable
5
5
  def initialize(options = {})
6
+ # default data
7
+ self.condition_fields = []
6
8
  self.exts = []
9
+ self.prefix_index_enable = false
10
+
11
+ # set attributes value from params
7
12
  options.keys.each do |k|
8
13
  eval("self.#{k} = options[k]")
9
14
  end
@@ -28,11 +33,16 @@ class Redis
28
33
  # score for search sort
29
34
  Redis::Search.config.redis.set(Search.mk_score_key(self.type,self.id),self.score)
30
35
  end
36
+
37
+ # 将目前的编号保存到条件(conditions)字段所创立的索引上面
38
+ self.condition_fields.each do |field|
39
+ Redis::Search.config.redis.sadd(Search.mk_condition_key(self.type,field,data[field.to_sym]), self.id)
40
+ end
31
41
 
32
42
  # 建立前最索引
33
43
  if prefix_index_enable
34
44
  save_prefix_index
35
- end
45
+ end
36
46
  end
37
47
 
38
48
  def self.remove(options = {})
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-14 00:00:00.000000000Z
12
+ date: 2011-10-17 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chinese_pinyin
16
- requirement: &2156524340 !ruby/object:Gem::Requirement
16
+ requirement: &2156513140 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.3.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2156524340
24
+ version_requirements: *2156513140
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rmmseg-cpp-huacnlee
27
- requirement: &2156523860 !ruby/object:Gem::Requirement
27
+ requirement: &2156512660 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.2.8
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2156523860
35
+ version_requirements: *2156512660
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: redis-namespace
38
- requirement: &2156523400 !ruby/object:Gem::Requirement
38
+ requirement: &2156512200 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 1.0.2
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *2156523400
46
+ version_requirements: *2156512200
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: redis
49
- requirement: &2156522920 !ruby/object:Gem::Requirement
49
+ requirement: &2156511720 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: 2.1.1
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *2156522920
57
+ version_requirements: *2156511720
58
58
  description: High performance real-time search (Support Chinese), index in Redis for
59
59
  Rails application.
60
60
  email: