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 +14 -3
- data/lib/redis/search/base.rb +11 -7
- data/lib/redis/search/finder.rb +74 -32
- data/lib/redis/search/index.rb +12 -2
- metadata +10 -10
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-
|
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
|
data/lib/redis/search/base.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
data/lib/redis/search/finder.rb
CHANGED
@@ -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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
-
|
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}"
|
data/lib/redis/search/index.rb
CHANGED
@@ -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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *2156513140
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rmmseg-cpp-huacnlee
|
27
|
-
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: *
|
35
|
+
version_requirements: *2156512660
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: redis-namespace
|
38
|
-
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: *
|
46
|
+
version_requirements: *2156512200
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: redis
|
49
|
-
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: *
|
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:
|