redis-search 0.6.3 → 0.7.0

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