redis-search 0.9.5 → 0.9.6
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.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/redis-search.rb +1 -1
- data/lib/redis/search/base.rb +21 -22
- data/lib/redis/search/config.rb +13 -13
- data/lib/redis/search/finder.rb +227 -226
- data/lib/redis/search/index.rb +108 -74
- data/lib/redis/search/tasks.rb +8 -6
- metadata +12 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f06b8b05df240f527992050fa3174ae7726cb0e
|
4
|
+
data.tar.gz: 1f37d2e08628b4a5fe6a9a5416a88f5c6b1b989d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec74e499cf300aeab20a0851c2228f0bacbbae75683d4517f44894bb4c331f83c753ae98c112b342762e59457b9adf36b645728dcf495b890d516a52351871fe
|
7
|
+
data.tar.gz: c6ea104e95e3d83ba984a18522925dc881eb37fb021750c5b1476b14c8ae90eab70d07b14fe88c56a3bd3e298f3923a8fb83236b52c0a5033370e382be9bf63a
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ High performance real-time search (Support Chinese), index in Redis for Rails ap
|
|
8
8
|
|
9
9
|

|
10
10
|
|
11
|
-
You can try the search feature in [`
|
11
|
+
You can try the search feature in [`IMAX.im`](http://imax.im) | [`shu.im`](http://shu.im)
|
12
12
|
|
13
13
|
And there is an [Example App](https://github.com/huacnlee/redis-search-example) to show you how to use redis-search.
|
14
14
|
|
@@ -38,7 +38,7 @@ And there is an [Example App](https://github.com/huacnlee/redis-search-example)
|
|
38
38
|
|
39
39
|
```ruby
|
40
40
|
gem 'ruby-pinyin', '0.3.0'
|
41
|
-
gem 'redis-search', '0.9.
|
41
|
+
gem 'redis-search', '0.9.6'
|
42
42
|
```
|
43
43
|
|
44
44
|
```bash
|
@@ -151,4 +151,4 @@ There is my performance test result.
|
|
151
151
|
|
152
152
|
## License
|
153
153
|
|
154
|
-
*
|
154
|
+
* MIT
|
data/lib/redis-search.rb
CHANGED
data/lib/redis/search/base.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
class Redis
|
4
4
|
module Search
|
5
5
|
autoload :PinYin, 'ruby-pinyin'
|
6
|
-
|
6
|
+
|
7
7
|
extend ::ActiveSupport::Concern
|
8
8
|
|
9
9
|
module ClassMethods
|
@@ -15,21 +15,22 @@ class Redis
|
|
15
15
|
# ext_fields What kind fields do you need inlucde to search indexes
|
16
16
|
# score_field Give a score for search sort, need Integer value, default is `created_at`
|
17
17
|
def redis_search_index(options = {})
|
18
|
-
title_field
|
19
|
-
alias_field
|
18
|
+
title_field = options[:title_field] || :title
|
19
|
+
alias_field = options[:alias_field] || nil
|
20
20
|
prefix_index_enable = options[:prefix_index_enable] || false
|
21
|
-
ext_fields
|
22
|
-
score_field
|
23
|
-
condition_fields
|
21
|
+
ext_fields = options[:ext_fields] || []
|
22
|
+
score_field = options[:score_field] || :created_at
|
23
|
+
condition_fields = options[:condition_fields] || []
|
24
|
+
|
24
25
|
# Add score field to ext_fields
|
25
26
|
ext_fields |= [score_field]
|
26
27
|
# Add condition fields to ext_fields
|
27
28
|
ext_fields |= condition_fields
|
28
|
-
|
29
|
+
|
29
30
|
if RUBY_VERSION.start_with?('1.8')
|
30
31
|
condition_fields = "[#{condition_fields.collect { |c| "'#{c}'" }.join(',')}]"
|
31
32
|
end
|
32
|
-
|
33
|
+
|
33
34
|
# store Model name to indexed_models for Rake tasks
|
34
35
|
Search.indexed_models = [] if Search.indexed_models == nil
|
35
36
|
Search.indexed_models << self
|
@@ -42,9 +43,9 @@ class Redis
|
|
42
43
|
end
|
43
44
|
exts
|
44
45
|
end
|
45
|
-
|
46
|
+
|
46
47
|
def redis_search_alias_value(field)
|
47
|
-
return [] if field.blank?
|
48
|
+
return [] if field.blank? || field == "_was"
|
48
49
|
val = (instance_eval("self.\#{field}") || "").clone
|
49
50
|
return [] if !val.class.in?([String,Array])
|
50
51
|
if val.is_a?(String)
|
@@ -54,10 +55,10 @@ class Redis
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def redis_search_index_create
|
57
|
-
s = Search::Index.new(:title => self.#{title_field},
|
58
|
-
:aliases => self.redis_search_alias_value(#{alias_field.inspect}),
|
59
|
-
:id => self.id,
|
60
|
-
:exts => self.redis_search_fields_to_hash(#{ext_fields.inspect}),
|
58
|
+
s = Search::Index.new(:title => self.#{title_field},
|
59
|
+
:aliases => self.redis_search_alias_value(#{alias_field.inspect}),
|
60
|
+
:id => self.id,
|
61
|
+
:exts => self.redis_search_fields_to_hash(#{ext_fields.inspect}),
|
61
62
|
:type => self.class.to_s,
|
62
63
|
:condition_fields => #{condition_fields},
|
63
64
|
:score => self.#{score_field}.to_i,
|
@@ -67,7 +68,7 @@ class Redis
|
|
67
68
|
s = nil
|
68
69
|
true
|
69
70
|
end
|
70
|
-
|
71
|
+
|
71
72
|
def redis_search_index_delete(titles)
|
72
73
|
titles.uniq.each do |title|
|
73
74
|
next if title.blank?
|
@@ -75,24 +76,22 @@ class Redis
|
|
75
76
|
end
|
76
77
|
true
|
77
78
|
end
|
78
|
-
|
79
79
|
|
80
80
|
before_destroy do
|
81
81
|
titles = []
|
82
82
|
titles = redis_search_alias_value("#{alias_field}")
|
83
83
|
titles << self.#{title_field}
|
84
|
-
|
84
|
+
|
85
85
|
redis_search_index_delete(titles)
|
86
86
|
true
|
87
87
|
end
|
88
|
-
|
89
|
-
|
88
|
+
|
90
89
|
def redis_search_index_need_reindex
|
91
90
|
index_fields_changed = false
|
92
91
|
#{ext_fields.inspect}.each do |f|
|
93
92
|
next if f.to_s == "id"
|
94
93
|
field_method = f.to_s + "_changed?"
|
95
|
-
if
|
94
|
+
if self.methods.index(field_method.to_sym) == nil
|
96
95
|
Search.warn("#{self.class.name} model reindex on update need "+field_method+" method.")
|
97
96
|
next
|
98
97
|
end
|
@@ -111,7 +110,7 @@ class Redis
|
|
111
110
|
end
|
112
111
|
return index_fields_changed
|
113
112
|
end
|
114
|
-
|
113
|
+
|
115
114
|
after_update do
|
116
115
|
if self.redis_search_index_need_reindex
|
117
116
|
titles = []
|
@@ -124,7 +123,7 @@ class Redis
|
|
124
123
|
|
125
124
|
after_save :redis_search_index_update
|
126
125
|
def redis_search_index_update
|
127
|
-
if self.redis_search_index_need_reindex
|
126
|
+
if self.redis_search_index_need_reindex || self.new_record?
|
128
127
|
self.redis_search_index_create
|
129
128
|
end
|
130
129
|
true
|
data/lib/redis/search/config.rb
CHANGED
@@ -3,20 +3,20 @@ class Redis
|
|
3
3
|
module Search
|
4
4
|
class << self
|
5
5
|
attr_accessor :config, :indexed_models
|
6
|
-
|
6
|
+
|
7
7
|
def configure
|
8
|
-
yield
|
9
|
-
|
10
|
-
if not
|
8
|
+
yield @config ||= Config.new
|
9
|
+
|
10
|
+
if not @config.disable_rmmseg
|
11
11
|
require "rmmseg"
|
12
12
|
# loading RMMSeg chinese word dicts.
|
13
13
|
RMMSeg::Dictionary.load_dictionaries
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
class Config
|
19
|
-
# Redis
|
19
|
+
# Redis
|
20
20
|
attr_accessor :redis
|
21
21
|
# Debug toggle, default false
|
22
22
|
attr_accessor :debug
|
@@ -32,14 +32,14 @@ class Redis
|
|
32
32
|
attr_accessor :pinyin_match
|
33
33
|
# Disable RMMSeg, if you don't need, disable this will save memory. (true|false) default = true
|
34
34
|
attr_accessor :disable_rmmseg
|
35
|
-
|
35
|
+
|
36
36
|
def initialize
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
@debug = false
|
38
|
+
@redis = nil
|
39
|
+
@complete_max_length = 100
|
40
|
+
@pinyin_match = false
|
41
|
+
@disable_rmmseg = false
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
|
-
end
|
45
|
+
end
|
data/lib/redis/search/finder.rb
CHANGED
@@ -1,248 +1,249 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
class Redis
|
3
|
-
module Search
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
3
|
+
module Search
|
4
|
+
class << self
|
5
|
+
# use rmmseg to split words
|
6
|
+
def split(text)
|
7
|
+
_split(text)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Use for short title search, this method is search by chars, for example Tag, User, Category ...
|
11
|
+
#
|
12
|
+
# h3. params:
|
13
|
+
# type model name
|
14
|
+
# w search char
|
15
|
+
# :limit result limit
|
16
|
+
# h3. usage:
|
17
|
+
# * Redis::Search.complete("Tag","r") => ["Ruby","Rails", "REST", "Redis", "Redmine"]
|
18
|
+
# * Redis::Search.complete("Tag","re") => ["Redis", "Redmine"]
|
19
|
+
# * Redis::Search.complete("Tag","red") => ["Redis", "Redmine"]
|
20
|
+
# * Redis::Search.complete("Tag","redi") => ["Redis"]
|
21
|
+
def complete(type, w, options = {})
|
22
|
+
limit = options[:limit] || 10
|
23
|
+
conditions = options[:conditions] || []
|
24
|
+
return [] if (w.blank? && conditions.blank?) || type.blank?
|
25
|
+
|
26
|
+
prefix_matchs = []
|
27
|
+
# This is not random, try to get replies < MTU size
|
28
|
+
rangelen = self.config.complete_max_length
|
29
|
+
prefix = w.downcase
|
30
|
+
key = self.mk_complete_key(type)
|
31
|
+
|
32
|
+
if start = self.config.redis.zrank(key,prefix)
|
33
|
+
count = limit
|
34
|
+
max_range = start + (rangelen * limit) - 1
|
35
|
+
range = self.config.redis.zrange(key,start,max_range)
|
36
|
+
while prefix_matchs.length <= count
|
37
|
+
start += rangelen
|
38
|
+
break if !range || range.length == 0
|
39
|
+
range.each do |entry|
|
40
|
+
minlen = [entry.length,prefix.length].min
|
41
|
+
if entry[0...minlen] != prefix[0...minlen]
|
42
|
+
count = prefix_matchs.count
|
43
|
+
break
|
44
|
+
end
|
45
|
+
if entry[-1..-1] == "*" && prefix_matchs.length != count
|
46
|
+
prefix_matchs << entry[0...-1]
|
47
|
+
end
|
47
48
|
end
|
48
|
-
|
49
|
-
|
49
|
+
|
50
|
+
range = range[start..max_range]
|
51
|
+
end
|
50
52
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
53
|
+
prefix_matchs.uniq!
|
54
|
+
|
55
|
+
# 组合 words 的特别 key 名
|
56
|
+
words = prefix_matchs.collect { |w| self.mk_sets_key(type,w) }
|
57
|
+
|
58
|
+
# 组合特别 key ,但这里不会像 query 那样放入 words, 因为在 complete 里面 words 是用 union 取的,condition_keys 和 words 应该取交集
|
59
|
+
condition_keys = []
|
60
|
+
if !conditions.blank?
|
61
|
+
conditions = conditions[0] if conditions.is_a?(Array)
|
62
|
+
conditions.keys.each do |c|
|
63
|
+
condition_keys << self.mk_condition_key(type,c,conditions[c])
|
64
|
+
end
|
63
65
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
66
|
+
|
67
|
+
# 按词语搜索
|
68
|
+
temp_store_key = "tmpsunionstore:#{words.join("+")}"
|
69
|
+
if words.length > 1
|
70
|
+
if !self.config.redis.exists(temp_store_key)
|
71
|
+
# 将多个词语组合对比,得到并集,并存入临时区域
|
72
|
+
self.config.redis.sunionstore(temp_store_key,*words)
|
73
|
+
# 将临时搜索设为1天后自动清除
|
74
|
+
self.config.redis.expire(temp_store_key,86400)
|
75
|
+
end
|
76
|
+
# 根据需要的数量取出 ids
|
77
|
+
else
|
78
|
+
temp_store_key = words.first
|
74
79
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
if !Redis::Search.config.redis.exists(temp_store_key)
|
85
|
-
Redis::Search.config.redis.sinterstore(temp_store_key,*condition_keys)
|
86
|
-
Redis::Search.config.redis.expire(temp_store_key,86400)
|
80
|
+
|
81
|
+
# 如果有条件,这里再次组合一下
|
82
|
+
if !condition_keys.blank?
|
83
|
+
condition_keys << temp_store_key if !words.blank?
|
84
|
+
temp_store_key = "tmpsinterstore:#{condition_keys.join('+')}"
|
85
|
+
if !self.config.redis.exists(temp_store_key)
|
86
|
+
self.config.redis.sinterstore(temp_store_key,*condition_keys)
|
87
|
+
self.config.redis.expire(temp_store_key,86400)
|
88
|
+
end
|
87
89
|
end
|
90
|
+
|
91
|
+
ids = self.config.redis.sort(temp_store_key,
|
92
|
+
:limit => [0,limit],
|
93
|
+
:by => self.mk_score_key(type,"*"),
|
94
|
+
:order => "desc")
|
95
|
+
return [] if ids.blank?
|
96
|
+
self.hmget(type,ids)
|
88
97
|
end
|
89
|
-
|
90
|
-
ids = Redis::Search.config.redis.sort(temp_store_key,
|
91
|
-
:limit => [0,limit],
|
92
|
-
:by => Search.mk_score_key(type,"*"),
|
93
|
-
:order => "desc")
|
94
|
-
return [] if ids.blank?
|
95
|
-
hmget(type,ids)
|
96
|
-
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
99
|
+
# Search items, this will split words by Libmmseg
|
100
|
+
#
|
101
|
+
# h3. params:
|
102
|
+
# type model name
|
103
|
+
# text search text
|
104
|
+
# :limit result limit
|
105
|
+
# h3. usage:
|
106
|
+
# * Redis::Search.query("Tag","Ruby vs Python")
|
107
|
+
def query(type, text, options = {})
|
108
|
+
tm = Time.now
|
109
|
+
result = []
|
110
|
+
|
111
|
+
limit = options[:limit] || 10
|
112
|
+
sort_field = options[:sort_field] || "id"
|
113
|
+
conditions = options[:conditions] || []
|
114
|
+
|
115
|
+
# 如果搜索文本和查询条件均没有,那就直接返回 []
|
116
|
+
return result if text.strip.blank? && conditions.blank?
|
117
|
+
|
118
|
+
words = self.split(text)
|
119
|
+
words = words.collect { |w| self.mk_sets_key(type,w) }
|
120
|
+
|
121
|
+
condition_keys = []
|
122
|
+
if !conditions.blank?
|
123
|
+
conditions = conditions[0] if conditions.is_a?(Array)
|
124
|
+
conditions.keys.each do |c|
|
125
|
+
condition_keys << self.mk_condition_key(type,c,conditions[c])
|
126
|
+
end
|
127
|
+
# 将条件的 key 放入关键词搜索集合内,用于 sinterstore 搜索
|
128
|
+
words += condition_keys
|
126
129
|
end
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
130
|
+
|
131
|
+
return result if words.blank?
|
132
|
+
|
133
|
+
temp_store_key = "tmpinterstore:#{words.join("+")}"
|
134
|
+
|
135
|
+
if words.length > 1
|
136
|
+
if !self.config.redis.exists(temp_store_key)
|
137
|
+
self.config.redis.pipelined do
|
138
|
+
# 将多个词语组合对比,得到交集,并存入临时区域
|
139
|
+
self.config.redis.sinterstore(temp_store_key,*words)
|
140
|
+
# 将临时搜索设为1天后自动清除
|
141
|
+
self.config.redis.expire(temp_store_key,86400)
|
142
|
+
|
143
|
+
# 拼音搜索
|
144
|
+
if self.config.pinyin_match
|
145
|
+
pinyin_words = self.split_pinyin(text)
|
146
|
+
pinyin_words = pinyin_words.collect { |w| self.mk_sets_key(type,w) }
|
147
|
+
pinyin_words += condition_keys
|
148
|
+
temp_sunion_key = "tmpsunionstore:#{words.join("+")}"
|
149
|
+
temp_pinyin_store_key = "tmpinterstore:#{pinyin_words.join("+")}"
|
150
|
+
# 找出拼音的
|
151
|
+
self.config.redis.sinterstore(temp_pinyin_store_key,*pinyin_words)
|
152
|
+
# 合并中文和拼音的搜索结果
|
153
|
+
self.config.redis.sunionstore(temp_sunion_key,*[temp_store_key,temp_pinyin_store_key])
|
154
|
+
# 将临时搜索设为1天后自动清除
|
155
|
+
self.config.redis.expire(temp_pinyin_store_key,86400)
|
156
|
+
self.config.redis.expire(temp_sunion_key,86400)
|
157
|
+
end
|
158
|
+
temp_store_key = temp_sunion_key
|
150
159
|
end
|
151
|
-
# 找出拼音的
|
152
|
-
Redis::Search.config.redis.sinterstore(temp_pinyin_store_key,*pinyin_words)
|
153
|
-
# 合并中文和拼音的搜索结果
|
154
|
-
Redis::Search.config.redis.sunionstore(temp_sunion_key,*[temp_store_key,temp_pinyin_store_key])
|
155
|
-
# 将临时搜索设为1天后自动清除
|
156
|
-
Redis::Search.config.redis.expire(temp_pinyin_store_key,86400)
|
157
|
-
Redis::Search.config.redis.expire(temp_sunion_key,86400)
|
158
|
-
temp_store_key = temp_sunion_key
|
159
160
|
end
|
161
|
+
else
|
162
|
+
temp_store_key = words.first
|
160
163
|
end
|
161
|
-
|
162
|
-
|
164
|
+
|
165
|
+
# 根据需要的数量取出 ids
|
166
|
+
ids = self.config.redis.sort(temp_store_key,
|
167
|
+
:limit => [0,limit],
|
168
|
+
:by => self.mk_score_key(type,"*"),
|
169
|
+
:order => "desc")
|
170
|
+
result = self.hmget(type,ids, :sort_field => sort_field)
|
171
|
+
self.info("{#{type} : \"#{text}\"} | Time spend: #{Time.now - tm}s")
|
172
|
+
result
|
163
173
|
end
|
164
|
-
|
165
|
-
|
166
|
-
ids = Search.config.redis.sort(temp_store_key,
|
167
|
-
:limit => [0,limit],
|
168
|
-
:by => Search.mk_score_key(type,"*"),
|
169
|
-
:order => "desc")
|
170
|
-
result = hmget(type,ids, :sort_field => sort_field)
|
171
|
-
Search.info("{#{type} : \"#{text}\"} | Time spend: #{Time.now - tm}s")
|
172
|
-
result
|
173
|
-
end
|
174
|
-
|
174
|
+
end # end class << self
|
175
|
+
|
175
176
|
protected
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
177
|
+
def self.split_pinyin(text)
|
178
|
+
# Pinyin search split as pinyin again
|
179
|
+
_split(PinYin.sentence(text))
|
180
|
+
end
|
181
|
+
|
181
182
|
private
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
end
|
194
|
-
words
|
195
|
-
end
|
196
|
-
|
197
|
-
def self.warn(msg)
|
198
|
-
return if not Redis::Search.config.debug
|
199
|
-
msg = "\e[33m[Redis::Search] #{msg}\e[0m"
|
200
|
-
if defined?(Rails) == 'constant' && Rails.class == Class
|
201
|
-
::Rails.logger.warn(msg)
|
202
|
-
else
|
203
|
-
puts msg
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def self.info(msg)
|
208
|
-
return if not Redis::Search.config.debug
|
209
|
-
msg = "\e[32m[Redis::Search] #{msg}\e[0m"
|
210
|
-
if defined?(Rails) == 'constant' && Rails.class == Class
|
211
|
-
::Rails.logger.debug(msg)
|
212
|
-
else
|
213
|
-
puts msg
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# 生成 uuid,用于作为 hashes 的 field, sets 关键词的值
|
218
|
-
def self.mk_sets_key(type, key)
|
219
|
-
"#{type}:#{key.downcase}"
|
220
|
-
end
|
221
|
-
|
222
|
-
def self.mk_score_key(type, id)
|
223
|
-
"#{type}:_score_:#{id}"
|
183
|
+
def self._split(text)
|
184
|
+
return [] if text.blank?
|
185
|
+
# return chars if disabled rmmseg
|
186
|
+
return text.split("") if Search.config.disable_rmmseg
|
187
|
+
|
188
|
+
algor = RMMSeg::Algorithm.new(text)
|
189
|
+
words = []
|
190
|
+
loop do
|
191
|
+
tok = algor.next_token
|
192
|
+
break if tok.nil?
|
193
|
+
words << tok.text
|
224
194
|
end
|
225
|
-
|
226
|
-
|
227
|
-
|
195
|
+
words
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.warn(msg)
|
199
|
+
return if not Redis::Search.config.debug
|
200
|
+
msg = "\e[33m[Redis::Search] #{msg}\e[0m"
|
201
|
+
if defined?(Rails) == 'constant' && Rails.class == Class
|
202
|
+
::Rails.logger.warn(msg)
|
203
|
+
else
|
204
|
+
puts msg
|
228
205
|
end
|
229
|
-
|
230
|
-
|
231
|
-
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.info(msg)
|
209
|
+
return if not Redis::Search.config.debug
|
210
|
+
msg = "\e[32m[Redis::Search] #{msg}\e[0m"
|
211
|
+
if defined?(Rails) == 'constant' && Rails.class == Class
|
212
|
+
::Rails.logger.debug(msg)
|
213
|
+
else
|
214
|
+
puts msg
|
232
215
|
end
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
216
|
+
end
|
217
|
+
|
218
|
+
# 生成 uuid,用于作为 hashes 的 field, sets 关键词的值
|
219
|
+
def self.mk_sets_key(type, key)
|
220
|
+
"#{type}:#{key.downcase}"
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.mk_score_key(type, id)
|
224
|
+
"#{type}:_score_:#{id}"
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.mk_condition_key(type, field, id)
|
228
|
+
"#{type}:_by:_#{field}:#{id}"
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.mk_complete_key(type)
|
232
|
+
"Compl#{type}"
|
233
|
+
end
|
234
|
+
|
235
|
+
def self.hmget(type, ids, options = {})
|
236
|
+
result = []
|
237
|
+
sort_field = options[:sort_field] || "id"
|
238
|
+
return result if ids.blank?
|
239
|
+
self.config.redis.hmget(type,*ids).each do |r|
|
240
|
+
begin
|
241
|
+
result << JSON.parse(r) if !r.blank?
|
242
|
+
rescue => e
|
243
|
+
self.warn("Search.query failed: #{e}")
|
244
244
|
end
|
245
|
-
result
|
246
245
|
end
|
247
|
-
|
246
|
+
result
|
247
|
+
end
|
248
|
+
end # end Search
|
248
249
|
end
|
data/lib/redis/search/index.rb
CHANGED
@@ -1,109 +1,143 @@
|
|
1
1
|
class Redis
|
2
2
|
module Search
|
3
3
|
class Index
|
4
|
-
attr_accessor :type, :title, :id
|
4
|
+
attr_accessor :type, :title, :id, :score, :aliases, :exts, :condition_fields, :prefix_index_enable
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def redis
|
8
|
+
Redis::Search.config.redis
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove(options = {})
|
12
|
+
type = options[:type]
|
13
|
+
|
14
|
+
self.redis.pipelined do
|
15
|
+
self.redis.hdel(type,options[:id])
|
16
|
+
self.redis.del(Search.mk_score_key(type,options[:id]))
|
17
|
+
|
18
|
+
words = self.split_words_for_index(options[:title])
|
19
|
+
words.each do |word|
|
20
|
+
self.redis.srem(Search.mk_sets_key(type,word), options[:id])
|
21
|
+
end
|
22
|
+
|
23
|
+
# remove set for prefix index key
|
24
|
+
self.redis.srem(Search.mk_sets_key(type,options[:title]),options[:id])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def split_words_for_index(title)
|
29
|
+
words = Search.split(title)
|
30
|
+
if Search.config.pinyin_match
|
31
|
+
# covert Chinese to pinyin to as an index
|
32
|
+
pinyin_full = Search.split_pinyin(title)
|
33
|
+
pinyin_first = pinyin_full.collect { |p| p[0] }.join("")
|
34
|
+
words += pinyin_full
|
35
|
+
words << pinyin_first
|
36
|
+
pinyin_full = nil
|
37
|
+
pinyin_first = nil
|
38
|
+
end
|
39
|
+
words.uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
def bm
|
43
|
+
t1 = Time.now
|
44
|
+
yield
|
45
|
+
t2 = Time.now
|
46
|
+
puts "spend (#{t2 - t1}s)"
|
47
|
+
end
|
48
|
+
end # end class << self
|
49
|
+
|
50
|
+
def redis
|
51
|
+
self.class.redis
|
52
|
+
end
|
53
|
+
|
5
54
|
def initialize(options = {})
|
6
55
|
# default data
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
56
|
+
@condition_fields = []
|
57
|
+
@exts = []
|
58
|
+
@aliases = []
|
59
|
+
@prefix_index_enable = false
|
11
60
|
|
12
61
|
# set attributes value from params
|
13
62
|
options.keys.each do |k|
|
14
|
-
|
63
|
+
self.send("#{k}=", options[k])
|
15
64
|
end
|
16
|
-
|
17
|
-
|
65
|
+
@aliases << self.title
|
66
|
+
@aliases.uniq!
|
18
67
|
end
|
19
68
|
|
20
69
|
def save
|
21
|
-
return if
|
22
|
-
data = {:title => self.title, :id => self.id, :type => self.type}
|
23
|
-
self.exts.each do |f|
|
24
|
-
data[f[0]] = f[1]
|
25
|
-
end
|
26
|
-
|
27
|
-
# 将原始数据存入 hashes
|
28
|
-
res = Redis::Search.config.redis.hset(self.type, self.id, data.to_json)
|
70
|
+
return if @title.blank?
|
29
71
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
72
|
+
self.redis.pipelined do
|
73
|
+
data = {:title => @title, :id => @id, :type => @type}
|
74
|
+
self.exts.each do |f|
|
75
|
+
data[f[0]] = f[1]
|
76
|
+
end
|
34
77
|
|
35
|
-
|
36
|
-
|
78
|
+
# 将原始数据存入 hashes
|
79
|
+
res = self.redis.hset(@type, @id, data.to_json)
|
37
80
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
return if words.blank?
|
42
|
-
words.each do |word|
|
43
|
-
Redis::Search.config.redis.sadd(Search.mk_sets_key(self.type,word), self.id)
|
81
|
+
# 将目前的编号保存到条件(conditions)字段所创立的索引上面
|
82
|
+
self.condition_fields.each do |field|
|
83
|
+
self.redis.sadd(Search.mk_condition_key(@type,field,data[field.to_sym]), @id)
|
44
84
|
end
|
45
|
-
end
|
46
85
|
|
47
|
-
|
48
|
-
|
49
|
-
end
|
86
|
+
# score for search sort
|
87
|
+
self.redis.set(Search.mk_score_key(@type,@id),@score)
|
50
88
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
89
|
+
# 保存 sets 索引,以分词的单词为key,用于后面搜索,里面存储 ids
|
90
|
+
self.aliases.each do |val|
|
91
|
+
words = Search::Index.split_words_for_index(val)
|
92
|
+
next if words.blank?
|
93
|
+
words.each do |word|
|
94
|
+
self.redis.sadd(Search.mk_sets_key(@type,word), @id)
|
95
|
+
end
|
96
|
+
end
|
55
97
|
|
56
|
-
|
57
|
-
|
58
|
-
Redis::Search.config.redis.srem(Search.mk_sets_key(type,word), options[:id])
|
98
|
+
# 建立前缀索引
|
99
|
+
save_prefix_index if prefix_index_enable
|
59
100
|
end
|
60
|
-
|
61
|
-
# remove set for prefix index key
|
62
|
-
Redis::Search.config.redis.srem(Search.mk_sets_key(type,options[:title]),options[:id])
|
63
101
|
end
|
64
102
|
|
65
103
|
private
|
66
|
-
|
67
|
-
|
104
|
+
def save_prefix_index
|
105
|
+
sorted_set_key = Search.mk_complete_key(@type)
|
106
|
+
sorted_vals = []
|
107
|
+
|
108
|
+
self.aliases.each do |val|
|
109
|
+
words = []
|
110
|
+
words << val.downcase
|
111
|
+
|
112
|
+
self.redis.sadd(Search.mk_sets_key(@type,val), @id)
|
113
|
+
|
68
114
|
if Search.config.pinyin_match
|
69
|
-
|
70
|
-
pinyin_full = Search.split_pinyin(title)
|
115
|
+
pinyin_full = Search.split_pinyin(val.downcase)
|
71
116
|
pinyin_first = pinyin_full.collect { |p| p[0] }.join("")
|
72
|
-
|
117
|
+
pinyin = pinyin_full.join("")
|
118
|
+
|
119
|
+
words << pinyin
|
73
120
|
words << pinyin_first
|
121
|
+
|
122
|
+
self.redis.sadd(Search.mk_sets_key(@type,pinyin), @id)
|
123
|
+
|
74
124
|
pinyin_full = nil
|
75
125
|
pinyin_first = nil
|
126
|
+
pinyin = nil
|
76
127
|
end
|
77
|
-
words.uniq
|
78
|
-
end
|
79
128
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
Redis::Search.config.redis.sadd(Search.mk_sets_key(self.type,val), self.id)
|
85
|
-
if Search.config.pinyin_match
|
86
|
-
pinyin_full = Search.split_pinyin(val.downcase)
|
87
|
-
pinyin_first = pinyin_full.collect { |p| p[0] }.join("")
|
88
|
-
pinyin = pinyin_full.join("")
|
89
|
-
words << pinyin
|
90
|
-
words << pinyin_first
|
91
|
-
Redis::Search.config.redis.sadd(Search.mk_sets_key(self.type,pinyin), self.id)
|
92
|
-
pinyin_full = nil
|
93
|
-
pinyin_first = nil
|
94
|
-
pinyin = nil
|
95
|
-
end
|
96
|
-
|
97
|
-
words.each do |word|
|
98
|
-
key = Search.mk_complete_key(self.type)
|
99
|
-
(1..(word.length)).each do |l|
|
100
|
-
prefix = word[0...l]
|
101
|
-
Redis::Search.config.redis.zadd(key, 0, prefix)
|
102
|
-
end
|
103
|
-
Redis::Search.config.redis.zadd(key, 0, word + "*")
|
129
|
+
words.each do |word|
|
130
|
+
(1..(word.length)).each do |l|
|
131
|
+
prefix = word[0...l]
|
132
|
+
sorted_vals << [0, prefix]
|
104
133
|
end
|
134
|
+
sorted_vals << [0, "#{word}*"]
|
105
135
|
end
|
106
136
|
end
|
107
|
-
|
137
|
+
|
138
|
+
self.redis.zadd(sorted_set_key, sorted_vals)
|
139
|
+
end
|
140
|
+
|
141
|
+
end # end Index
|
108
142
|
end
|
109
143
|
end
|
data/lib/redis/search/tasks.rb
CHANGED
@@ -21,11 +21,13 @@ namespace :redis_search do
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
elsif klass.included_modules.collect { |m| m.to_s }.include?("Mongoid::Document")
|
24
|
-
klass.all.
|
25
|
-
item
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
klass.all.each_slice(1000) do |items|
|
25
|
+
items.each do |item|
|
26
|
+
item.redis_search_index_create
|
27
|
+
item = nil
|
28
|
+
count += 1
|
29
|
+
print "."
|
30
|
+
end
|
29
31
|
end
|
30
32
|
else
|
31
33
|
puts "skiped, not support this ORM in current."
|
@@ -37,4 +39,4 @@ namespace :redis_search do
|
|
37
39
|
puts "Indexed #{count} rows | Time spend: #{(Time.now - tm)}s".rjust(120)
|
38
40
|
puts "Rebuild Index done.".rjust(120)
|
39
41
|
end
|
40
|
-
end
|
42
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Lee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-pinyin
|
@@ -36,30 +36,30 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 1.0
|
39
|
+
version: 1.3.0
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 1.0
|
46
|
+
version: 1.3.0
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: redis
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
53
|
+
version: 3.0.0
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
61
|
-
description: 'High performance real-time search (Support Chinese),
|
62
|
-
for Rails
|
60
|
+
version: 3.0.0
|
61
|
+
description: 'High performance real-time search (Support Chinese), indexes store in
|
62
|
+
Redis for Rails applications. '
|
63
63
|
email:
|
64
64
|
- huacnlee@gmail.com
|
65
65
|
executables: []
|
@@ -95,9 +95,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
95
|
version: 1.3.6
|
96
96
|
requirements: []
|
97
97
|
rubyforge_project:
|
98
|
-
rubygems_version: 2.2.
|
98
|
+
rubygems_version: 2.2.2
|
99
99
|
signing_key:
|
100
100
|
specification_version: 4
|
101
|
-
summary: High performance real-time search (Support Chinese),
|
102
|
-
|
101
|
+
summary: High performance real-time search (Support Chinese), indexes store in Redis
|
102
|
+
for Rails applications.
|
103
103
|
test_files: []
|
104
|
+
has_rdoc:
|