fluent-plugin-droonga 0.0.2
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 +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +40 -0
- data/LICENSE.txt +14 -0
- data/README.md +18 -0
- data/Rakefile +25 -0
- data/benchmark/benchmark.rb +123 -0
- data/benchmark/utils.rb +243 -0
- data/benchmark/watch/benchmark-notify.rb +143 -0
- data/benchmark/watch/benchmark-notify.sh +19 -0
- data/benchmark/watch/benchmark-publish.rb +120 -0
- data/benchmark/watch/benchmark-scan.rb +210 -0
- data/benchmark/watch/catalog.json +32 -0
- data/benchmark/watch/fluentd.conf +12 -0
- data/bin/grn2jsons +85 -0
- data/fluent-plugin-droonga.gemspec +41 -0
- data/lib/droonga/adapter.rb +156 -0
- data/lib/droonga/catalog.rb +153 -0
- data/lib/droonga/command_mapper.rb +45 -0
- data/lib/droonga/engine.rb +83 -0
- data/lib/droonga/executor.rb +289 -0
- data/lib/droonga/handler.rb +140 -0
- data/lib/droonga/handler_plugin.rb +35 -0
- data/lib/droonga/job_queue.rb +83 -0
- data/lib/droonga/job_queue_schema.rb +65 -0
- data/lib/droonga/logger.rb +34 -0
- data/lib/droonga/plugin.rb +41 -0
- data/lib/droonga/plugin/adapter/groonga/select.rb +88 -0
- data/lib/droonga/plugin/adapter_groonga.rb +40 -0
- data/lib/droonga/plugin/handler/groonga/column_create.rb +103 -0
- data/lib/droonga/plugin/handler/groonga/table_create.rb +100 -0
- data/lib/droonga/plugin/handler_add.rb +44 -0
- data/lib/droonga/plugin/handler_forward.rb +70 -0
- data/lib/droonga/plugin/handler_groonga.rb +52 -0
- data/lib/droonga/plugin/handler_proxy.rb +82 -0
- data/lib/droonga/plugin/handler_search.rb +33 -0
- data/lib/droonga/plugin/handler_watch.rb +102 -0
- data/lib/droonga/proxy.rb +371 -0
- data/lib/droonga/searcher.rb +415 -0
- data/lib/droonga/server.rb +112 -0
- data/lib/droonga/sweeper.rb +42 -0
- data/lib/droonga/watch_schema.rb +88 -0
- data/lib/droonga/watcher.rb +256 -0
- data/lib/droonga/worker.rb +51 -0
- data/lib/fluent/plugin/out_droonga.rb +56 -0
- data/lib/groonga_command_converter.rb +137 -0
- data/sample/cluster/catalog.json +43 -0
- data/sample/cluster/fluentd.conf +12 -0
- data/sample/fluentd.conf +8 -0
- data/test/fixtures/catalog.json +43 -0
- data/test/fixtures/document.grn +23 -0
- data/test/helper.rb +24 -0
- data/test/helper/fixture.rb +28 -0
- data/test/helper/sandbox.rb +73 -0
- data/test/helper/stub_worker.rb +27 -0
- data/test/helper/watch_helper.rb +35 -0
- data/test/plugin/adapter/groonga/test_select.rb +176 -0
- data/test/plugin/handler/groonga/test_column_create.rb +127 -0
- data/test/plugin/handler/groonga/test_table_create.rb +140 -0
- data/test/plugin/handler/test_handler_add.rb +135 -0
- data/test/plugin/handler/test_handler_groonga.rb +64 -0
- data/test/plugin/handler/test_handler_search.rb +512 -0
- data/test/plugin/handler/test_handler_watch.rb +168 -0
- data/test/run-test.rb +55 -0
- data/test/test_adapter.rb +48 -0
- data/test/test_catalog.rb +59 -0
- data/test/test_command_mapper.rb +44 -0
- data/test/test_groonga_command_converter.rb +242 -0
- data/test/test_handler.rb +53 -0
- data/test/test_job_queue_schema.rb +45 -0
- data/test/test_output.rb +99 -0
- data/test/test_sweeper.rb +95 -0
- data/test/test_watch_schema.rb +57 -0
- data/test/test_watcher.rb +336 -0
- data/test/test_worker.rb +144 -0
- metadata +299 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 droonga project
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License version 2.1 as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
17
|
+
|
18
|
+
require "groonga"
|
19
|
+
|
20
|
+
module Droonga
|
21
|
+
module Server
|
22
|
+
def before_run
|
23
|
+
$log.trace("#{log_tag}: before_run: start")
|
24
|
+
$log.trace("#{log_tag}: before_run: done")
|
25
|
+
end
|
26
|
+
|
27
|
+
def after_run
|
28
|
+
$log.trace("#{log_tag}: after_run: start")
|
29
|
+
$log.trace("#{log_tag}: after_run: done")
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop(stop_graceful)
|
33
|
+
$log.trace("#{log_tag}: stop: start")
|
34
|
+
super(stop_graceful)
|
35
|
+
$log.trace("#{log_tag}: stop: done")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def log_tag
|
40
|
+
"[#{Process.ppid}][#{Process.pid}] server"
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_worker(wid)
|
44
|
+
worker = super(wid)
|
45
|
+
worker.extend(WorkerStopper)
|
46
|
+
worker
|
47
|
+
end
|
48
|
+
|
49
|
+
module WorkerStopper
|
50
|
+
def send_stop(stop_graceful)
|
51
|
+
in_signal_sending do
|
52
|
+
open_queue do |queue|
|
53
|
+
$log.trace("#{log_tag}: stop: start")
|
54
|
+
|
55
|
+
$log.trace("#{log_tag}: stop: queue: unblock: start")
|
56
|
+
max_n_retries = 10
|
57
|
+
max_n_retries.times do |i|
|
58
|
+
$log.trace("#{log_tag}: stop: queue: unblock: #{i}: start")
|
59
|
+
super(stop_graceful)
|
60
|
+
queue.unblock
|
61
|
+
alive_p = alive?
|
62
|
+
$log.trace("#{log_tag}: stop: queue: unblock: #{i}: done: " +
|
63
|
+
"#{alive_p}")
|
64
|
+
break unless alive_p
|
65
|
+
sleep(i * 0.1)
|
66
|
+
end
|
67
|
+
$log.trace("#{log_tag}: stop: queue: unblock: done")
|
68
|
+
|
69
|
+
$log.trace("#{log_tag}: stop: done")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_reload
|
75
|
+
in_signal_sending do
|
76
|
+
open_queue do |queue|
|
77
|
+
$log.trace("#{log_tag}: reload: start")
|
78
|
+
super
|
79
|
+
queue.unblock
|
80
|
+
$log.trace("#{log_tag}: reload: done")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def log_tag
|
87
|
+
"[#{Process.ppid}][#{Process.pid}][#{@wid}] server: worker-stopper"
|
88
|
+
end
|
89
|
+
|
90
|
+
def in_signal_sending
|
91
|
+
Thread.new do
|
92
|
+
yield
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def open_queue
|
97
|
+
config = @worker.config
|
98
|
+
# TODO: Use JobQueue object
|
99
|
+
context = Groonga::Context.new
|
100
|
+
database = context.open_database(config[:database])
|
101
|
+
queue = context[config[:queue_name]]
|
102
|
+
begin
|
103
|
+
yield(queue)
|
104
|
+
ensure
|
105
|
+
queue.close
|
106
|
+
database.close
|
107
|
+
context.close
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 droonga project
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License version 2.1 as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
17
|
+
|
18
|
+
module Droonga
|
19
|
+
class Sweeper
|
20
|
+
SUBSCRIBER_LIFETIME_SECONDS = 10 * 60 # 10 min
|
21
|
+
|
22
|
+
def initialize(context)
|
23
|
+
@context = context
|
24
|
+
end
|
25
|
+
|
26
|
+
def sweep_expired_subscribers(options={})
|
27
|
+
now = options[:now] || Time.now
|
28
|
+
boundary = now - SUBSCRIBER_LIFETIME_SECONDS
|
29
|
+
expired_subscribers = @context["Subscriber"].select do |subscriber|
|
30
|
+
subscriber.last_modified < boundary
|
31
|
+
end
|
32
|
+
expired_subscribers.each do |subscriber|
|
33
|
+
watcher.unsubscribe(:subscriber => subscriber._key)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def watcher
|
39
|
+
@watcher ||= Watcher.new(@context)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 droonga project
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License version 2.1 as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
17
|
+
|
18
|
+
require "groonga"
|
19
|
+
|
20
|
+
module Droonga
|
21
|
+
class WatchSchema
|
22
|
+
def initialize(context)
|
23
|
+
@context = context
|
24
|
+
end
|
25
|
+
|
26
|
+
def ensure_created
|
27
|
+
if @context["Keyword"]
|
28
|
+
$log.trace "#{log_tag} skip table creation"
|
29
|
+
return
|
30
|
+
end
|
31
|
+
$log.trace "#{log_tag} ensure_tables: start"
|
32
|
+
ensure_tables
|
33
|
+
$log.trace "#{log_tag} ensure_tables: done"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def ensure_tables
|
38
|
+
Groonga::Schema.define(:context => @context) do |schema|
|
39
|
+
schema.create_table("Keyword",
|
40
|
+
:type => :patricia_trie,
|
41
|
+
:key_type => "ShortText",
|
42
|
+
:key_normalize => true,
|
43
|
+
:force => true) do |table|
|
44
|
+
end
|
45
|
+
|
46
|
+
schema.create_table("Query",
|
47
|
+
:type => :hash,
|
48
|
+
:key_type => "ShortText",
|
49
|
+
:force => true) do |table|
|
50
|
+
end
|
51
|
+
|
52
|
+
schema.create_table("Route",
|
53
|
+
:type => :hash,
|
54
|
+
:key_type => "ShortText",
|
55
|
+
:force => true) do |table|
|
56
|
+
end
|
57
|
+
|
58
|
+
schema.create_table("Subscriber",
|
59
|
+
:type => :hash,
|
60
|
+
:key_type => "ShortText",
|
61
|
+
:force => true) do |table|
|
62
|
+
table.time("last_modified")
|
63
|
+
end
|
64
|
+
|
65
|
+
schema.change_table("Query") do |table|
|
66
|
+
table.reference("keywords", "Keyword", :type => :vector)
|
67
|
+
end
|
68
|
+
|
69
|
+
schema.change_table("Subscriber") do |table|
|
70
|
+
table.reference("route", "Route")
|
71
|
+
table.reference("subscriptions", "Query", :type => :vector)
|
72
|
+
end
|
73
|
+
|
74
|
+
schema.change_table("Keyword") do |table|
|
75
|
+
table.index("Query", "keywords", :name => "queries")
|
76
|
+
end
|
77
|
+
|
78
|
+
schema.change_table("Query") do |table|
|
79
|
+
table.index("Subscriber", "subscriptions", :name => "subscribers")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def log_tag
|
85
|
+
"[#{Process.ppid}][#{Process.pid}] watch_schema"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 droonga project
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License version 2.1 as published by the Free Software Foundation.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
17
|
+
|
18
|
+
module Droonga
|
19
|
+
class Watcher
|
20
|
+
EXACT_MATCH = false
|
21
|
+
|
22
|
+
def initialize(context)
|
23
|
+
@context = context
|
24
|
+
|
25
|
+
@subscriber_table = @context["Subscriber"]
|
26
|
+
@query_table = @context["Query"]
|
27
|
+
@keyword_table = @context["Keyword"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def subscribe(request)
|
31
|
+
subscriber = request[:subscriber]
|
32
|
+
condition = request[:condition]
|
33
|
+
query = request[:query]
|
34
|
+
route = request[:route]
|
35
|
+
|
36
|
+
# XXX better validation and error class must be written!!
|
37
|
+
if subscriber.nil? || subscriber.empty? || condition.nil? ||
|
38
|
+
query.nil? || route.nil?
|
39
|
+
raise "invalid request"
|
40
|
+
end
|
41
|
+
raise "too long query" if query.size > 4095
|
42
|
+
|
43
|
+
query_record = @query_table[query]
|
44
|
+
unless query_record
|
45
|
+
keywords = pick_keywords([], condition)
|
46
|
+
query_record = @query_table.add(query, :keywords => keywords)
|
47
|
+
end
|
48
|
+
subscriber_record = @subscriber_table[subscriber]
|
49
|
+
if subscriber_record
|
50
|
+
subscriptions = subscriber_record.subscriptions
|
51
|
+
unless subscriptions.include?(query_record)
|
52
|
+
subscriptions << query_record
|
53
|
+
subscriber_record.subscriptions = subscriptions
|
54
|
+
end
|
55
|
+
subscriber_record.last_modified = Time.now
|
56
|
+
else
|
57
|
+
@subscriber_table.add(subscriber,
|
58
|
+
:subscriptions => [query_record],
|
59
|
+
:route => route,
|
60
|
+
:last_modified => Time.now)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def unsubscribe(request)
|
65
|
+
subscriber = request[:subscriber]
|
66
|
+
query = request[:query]
|
67
|
+
|
68
|
+
if subscriber.nil? || subscriber.empty?
|
69
|
+
raise "invalid request"
|
70
|
+
end
|
71
|
+
|
72
|
+
subscriber_record = @subscriber_table[subscriber]
|
73
|
+
return unless subscriber_record
|
74
|
+
|
75
|
+
if query.nil? || query.empty?
|
76
|
+
delete_subscriber(subscriber_record)
|
77
|
+
else
|
78
|
+
query_record = @query_table[query]
|
79
|
+
return unless query_record
|
80
|
+
|
81
|
+
subscriptions = subscriber_record.subscriptions
|
82
|
+
new_subscriptions = subscriptions.select do |query|
|
83
|
+
query != query_record
|
84
|
+
end
|
85
|
+
|
86
|
+
if new_subscriptions.empty?
|
87
|
+
delete_subscriber(subscriber_record)
|
88
|
+
else
|
89
|
+
subscriber_record.subscriptions = new_subscriptions
|
90
|
+
sweep_orphan_queries(subscriptions)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def feed(request, &block)
|
96
|
+
targets = request[:targets]
|
97
|
+
|
98
|
+
hits = []
|
99
|
+
targets.each do |key, target|
|
100
|
+
scan_body(hits, target)
|
101
|
+
end
|
102
|
+
|
103
|
+
publish(hits, request, &block)
|
104
|
+
end
|
105
|
+
|
106
|
+
def pick_keywords(memo, condition)
|
107
|
+
case condition
|
108
|
+
when Hash
|
109
|
+
memo << condition["query"]
|
110
|
+
when String
|
111
|
+
memo << condition
|
112
|
+
when Array
|
113
|
+
condition[1..-1].each do |element|
|
114
|
+
pick_keywords(memo, element)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
memo
|
118
|
+
end
|
119
|
+
|
120
|
+
def scan_body(hits, body)
|
121
|
+
trimmed = body.strip
|
122
|
+
candidates = {}
|
123
|
+
# FIXME scan reports the longest keyword matched only
|
124
|
+
@keyword_table.scan(trimmed).each do |keyword, word, start, length|
|
125
|
+
@query_table.select do |query|
|
126
|
+
query.keywords =~ keyword
|
127
|
+
end.each do |record|
|
128
|
+
candidates[record.key] ||= []
|
129
|
+
candidates[record.key] << keyword
|
130
|
+
end
|
131
|
+
end
|
132
|
+
candidates.each do |query, keywords|
|
133
|
+
hits << query if query_match(query, keywords)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def query_match(query, keywords)
|
138
|
+
return true unless EXACT_MATCH
|
139
|
+
@conditions = {} unless @conditions
|
140
|
+
condition = @conditions[query.id]
|
141
|
+
unless condition
|
142
|
+
condition = JSON.parse(query.key)
|
143
|
+
@conditions[query.id] = condition
|
144
|
+
# CAUTION: @conditions can be huge.
|
145
|
+
end
|
146
|
+
words = {}
|
147
|
+
keywords.each do |keyword|
|
148
|
+
words[keyword.key] = true
|
149
|
+
end
|
150
|
+
eval_condition(condition, words)
|
151
|
+
end
|
152
|
+
|
153
|
+
def eval_condition(condition, words)
|
154
|
+
case condition
|
155
|
+
when Hash
|
156
|
+
# todo
|
157
|
+
when String
|
158
|
+
words[condition]
|
159
|
+
when Array
|
160
|
+
case condition.first
|
161
|
+
when "||"
|
162
|
+
condition[1..-1].each do |element|
|
163
|
+
return true if eval_condition(element, words)
|
164
|
+
end
|
165
|
+
false
|
166
|
+
when "&&"
|
167
|
+
condition[1..-1].each do |element|
|
168
|
+
return false unless eval_condition(element, words)
|
169
|
+
end
|
170
|
+
true
|
171
|
+
when "-"
|
172
|
+
return false unless eval_condition(condition[1], words)
|
173
|
+
condition[2..-1].each do |element|
|
174
|
+
return false if eval_condition(element, words)
|
175
|
+
end
|
176
|
+
true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def publish(hits, request)
|
182
|
+
routes = {}
|
183
|
+
hits.each do |query|
|
184
|
+
subscribers = @subscriber_table.select do |subscriber|
|
185
|
+
subscriber.subscriptions =~ query
|
186
|
+
end
|
187
|
+
subscribers.each do |subscriber|
|
188
|
+
route = subscriber.route.key
|
189
|
+
routes[route] ||= []
|
190
|
+
routes[route] << subscriber.key.key
|
191
|
+
end
|
192
|
+
=begin
|
193
|
+
# "group" version. This is slower than above...
|
194
|
+
route_records = subscribers.group("route",
|
195
|
+
:max_n_sub_records => subscribers.size)
|
196
|
+
route_records.each do |route_record|
|
197
|
+
route = route_record._key
|
198
|
+
routes[route] ||= []
|
199
|
+
route_record.sub_records.each do |subscriber|
|
200
|
+
routes[route] << subscriber.key.key
|
201
|
+
end
|
202
|
+
end
|
203
|
+
=end
|
204
|
+
end
|
205
|
+
routes.each do |route, subscribers|
|
206
|
+
yield(route, subscribers)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
def delete_subscriber(subscriber)
|
212
|
+
queries = subscriber.subscriptions
|
213
|
+
route = subscriber.route
|
214
|
+
subscriber.delete
|
215
|
+
sweep_orphan_queries(queries)
|
216
|
+
sweep_orphan_route(route)
|
217
|
+
end
|
218
|
+
|
219
|
+
def delete_query(query)
|
220
|
+
keywords = query.keywords
|
221
|
+
query.delete
|
222
|
+
sweep_orphan_keywords(keywords)
|
223
|
+
end
|
224
|
+
|
225
|
+
def sweep_orphan_queries(queries)
|
226
|
+
queries.each do |query|
|
227
|
+
related_subscribers = @subscriber_table.select do |subscriber|
|
228
|
+
subscriber.subscriptions =~ query
|
229
|
+
end
|
230
|
+
if related_subscribers.empty?
|
231
|
+
delete_query(query)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def sweep_orphan_keywords(keywords)
|
237
|
+
keywords.each do |keyword|
|
238
|
+
related_queries = @query_table.select do |query|
|
239
|
+
query.keywords =~ keyword
|
240
|
+
end
|
241
|
+
if related_queries.empty?
|
242
|
+
keyword.delete
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def sweep_orphan_route(route)
|
248
|
+
related_subscribers = @subscriber_table.select do |subscriber|
|
249
|
+
subscriber.route == route
|
250
|
+
end
|
251
|
+
if related_subscribers.empty?
|
252
|
+
route.delete
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|