fluent-plugin-droonga 0.9.9 → 1.0.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.
- checksums.yaml +4 -4
- data/.dir-locals.el +3 -0
- data/.travis.yml +6 -2
- data/README.md +6 -7
- data/Rakefile +23 -6
- data/fluent-plugin-droonga.gemspec +2 -2
- data/lib/droonga/adapter.rb +12 -3
- data/lib/droonga/adapter_runner.rb +28 -23
- data/lib/droonga/catalog/base.rb +7 -111
- data/lib/droonga/catalog/dataset.rb +13 -25
- data/lib/droonga/catalog/errors.rb +94 -0
- data/lib/droonga/catalog/schema.rb +277 -0
- data/lib/droonga/catalog/version1.rb +404 -0
- data/lib/droonga/catalog/version2.rb +160 -0
- data/lib/droonga/catalog_loader.rb +27 -4
- data/lib/droonga/catalog_observer.rb +44 -6
- data/lib/droonga/collector.rb +12 -10
- data/lib/droonga/{handler_plugin.rb → collector_message.rb} +47 -20
- data/lib/droonga/collector_runner.rb +64 -0
- data/lib/droonga/collectors.rb +18 -0
- data/lib/droonga/{catalog.rb → collectors/add.rb} +9 -7
- data/lib/droonga/{command_repository.rb → collectors/and.rb} +7 -14
- data/lib/droonga/collectors/sum.rb +26 -0
- data/lib/droonga/dispatcher.rb +74 -41
- data/lib/droonga/distributed_command_planner.rb +2 -2
- data/lib/droonga/engine.rb +13 -5
- data/lib/droonga/{message_processing_error.rb → error.rb} +33 -12
- data/lib/droonga/{plugin/planner/search.rb → error_messages.rb} +12 -10
- data/lib/droonga/farm.rb +15 -14
- data/lib/droonga/fluent_message_sender.rb +15 -11
- data/lib/droonga/forwarder.rb +22 -18
- data/lib/droonga/handler.rb +8 -2
- data/lib/droonga/handler_runner.rb +47 -26
- data/lib/droonga/input_message.rb +6 -6
- data/lib/droonga/{command.rb → loggable.rb} +7 -14
- data/lib/droonga/logger.rb +56 -15
- data/lib/droonga/message_matcher.rb +12 -7
- data/lib/droonga/message_pusher.rb +8 -4
- data/lib/droonga/message_receiver.rb +11 -9
- data/lib/droonga/output_message.rb +2 -0
- data/lib/droonga/planner.rb +21 -10
- data/lib/droonga/plugin.rb +15 -0
- data/lib/droonga/plugin/metadata/{adapter_message.rb → adapter_input_message.rb} +6 -14
- data/lib/droonga/plugin/metadata/adapter_output_message.rb +39 -0
- data/lib/droonga/plugin/metadata/collector_message.rb +39 -0
- data/lib/droonga/plugin/metadata/input_message.rb +15 -0
- data/lib/droonga/plugin_loader.rb +33 -25
- data/lib/droonga/plugin_registry.rb +9 -1
- data/lib/droonga/plugins/basic.rb +54 -0
- data/lib/droonga/plugins/crud.rb +36 -15
- data/lib/droonga/plugins/error.rb +5 -4
- data/lib/droonga/plugins/groonga.rb +9 -6
- data/lib/droonga/plugins/groonga/column_create.rb +10 -5
- data/lib/droonga/plugins/groonga/generic_command.rb +2 -8
- data/lib/droonga/plugins/groonga/generic_response.rb +2 -2
- data/lib/droonga/plugins/groonga/select.rb +2 -2
- data/lib/droonga/plugins/groonga/table_create.rb +9 -4
- data/lib/droonga/plugins/groonga/table_remove.rb +10 -5
- data/lib/droonga/plugins/search.rb +106 -5
- data/lib/droonga/plugins/search/distributed_search_planner.rb +398 -0
- data/lib/droonga/plugins/watch.rb +41 -20
- data/lib/droonga/processor.rb +12 -9
- data/lib/droonga/{plugin/collector/basic.rb → reducer.rb} +36 -50
- data/lib/droonga/replier.rb +7 -4
- data/lib/droonga/searcher.rb +40 -37
- data/lib/droonga/server.rb +8 -6
- data/lib/droonga/session.rb +17 -7
- data/lib/droonga/single_step.rb +53 -0
- data/lib/droonga/{plugin/planner/watch.rb → single_step_definition.rb} +27 -26
- data/lib/droonga/{partition.rb → slice.rb} +23 -12
- data/lib/droonga/status_code.rb +25 -0
- data/lib/droonga/step_runner.rb +63 -0
- data/lib/droonga/watch_schema.rb +7 -3
- data/lib/droonga/watcher.rb +4 -4
- data/lib/droonga/worker.rb +6 -6
- data/lib/fluent/plugin/out_droonga.rb +27 -2
- data/sample/cluster/catalog.json +33 -32
- data/test/command/config/default/catalog.json +72 -45
- data/test/command/config/version1/catalog.json +68 -0
- data/test/command/config/version1/fluentd.conf +11 -0
- data/test/command/suite/message/error/missing-dataset.expected +1 -1
- data/test/command/suite/message/error/unknown-dataset.expected +1 -1
- data/test/command/suite/message/error/unknown-type.expected +13 -0
- data/test/command/suite/message/error/{unknown-command.test → unknown-type.test} +1 -1
- data/test/command/suite/search/error/missing-source-parameter.expected +1 -1
- data/test/command/suite/search/error/unknown-source.expected +15 -3
- data/test/command/suite/watch/subscribe.expected +1 -3
- data/test/command/suite/watch/unsubscribe.expected +1 -3
- data/test/performance/watch/catalog.json +1 -0
- data/test/unit/catalog/test_dataset.rb +16 -358
- data/test/unit/catalog/test_schema.rb +285 -0
- data/test/unit/catalog/test_version1.rb +222 -28
- data/test/unit/catalog/test_version2.rb +155 -0
- data/test/unit/fixtures/catalog/version2.json +62 -0
- data/test/unit/helper/distributed_search_planner_helper.rb +2 -2
- data/test/unit/plugins/crud/test_add.rb +13 -13
- data/test/unit/plugins/groonga/test_column_create.rb +14 -11
- data/test/unit/plugins/groonga/test_table_create.rb +4 -9
- data/test/unit/plugins/groonga/test_table_remove.rb +4 -9
- data/test/unit/{plugin/planner/search_planner → plugins/search/planner}/test_basic.rb +0 -0
- data/test/unit/{plugin/planner/search_planner → plugins/search/planner}/test_group_by.rb +0 -0
- data/test/unit/{plugin/planner/search_planner → plugins/search/planner}/test_output.rb +0 -0
- data/test/unit/{plugin/planner/search_planner → plugins/search/planner}/test_sort_by.rb +0 -0
- data/test/unit/{plugin/collector/test_search.rb → plugins/search/test_collector.rb} +40 -39
- data/test/unit/plugins/{test_search.rb → search/test_handler.rb} +6 -5
- data/test/unit/{plugin/planner/test_search.rb → plugins/search/test_planner.rb} +3 -3
- data/test/unit/{plugin/collector → plugins}/test_basic.rb +68 -50
- data/test/unit/plugins/test_groonga.rb +2 -15
- data/test/unit/plugins/test_watch.rb +25 -22
- data/test/unit/test_message_matcher.rb +29 -6
- data/test/unit/test_output.rb +4 -0
- metadata +58 -50
- data/lib/droonga/collector_plugin.rb +0 -50
- data/lib/droonga/legacy_pluggable.rb +0 -66
- data/lib/droonga/legacy_plugin.rb +0 -57
- data/lib/droonga/legacy_plugin_repository.rb +0 -54
- data/lib/droonga/planner_plugin.rb +0 -54
- data/lib/droonga/plugin/collector/search.rb +0 -98
- data/lib/droonga/plugin/planner/crud.rb +0 -49
- data/lib/droonga/plugin/planner/distributed_search_planner.rb +0 -393
- data/lib/droonga/plugin/planner/groonga.rb +0 -54
- data/lib/droonga/plugin_registerable.rb +0 -75
- data/test/command/suite/message/error/unknown-command.expected +0 -13
- data/test/unit/test_command_repository.rb +0 -39
- data/test/unit/test_legacy_plugin.rb +0 -50
- data/test/unit/test_legacy_plugin_repository.rb +0 -89
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2013-2014 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 "droonga/searcher"
|
|
19
|
+
require "droonga/distributed_command_planner"
|
|
20
|
+
|
|
21
|
+
module Droonga
|
|
22
|
+
module Plugins
|
|
23
|
+
module Search
|
|
24
|
+
class DistributedSearchPlanner < DistributedCommandPlanner
|
|
25
|
+
def initialize(search_request_message)
|
|
26
|
+
super
|
|
27
|
+
|
|
28
|
+
@request = @source_message["body"]
|
|
29
|
+
raise NoQuery.new unless @request
|
|
30
|
+
|
|
31
|
+
@request = Marshal.load(Marshal.dump(@request))
|
|
32
|
+
@queries = @request["queries"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def plan
|
|
36
|
+
raise Searcher::NoQuery.new if @queries.nil? or @queries.empty?
|
|
37
|
+
|
|
38
|
+
Searcher::QuerySorter.validate_dependencies(@queries)
|
|
39
|
+
|
|
40
|
+
ensure_unifiable!
|
|
41
|
+
|
|
42
|
+
@queries.each do |input_name, query|
|
|
43
|
+
transform_query(input_name, query)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@dataset = @source_message["dataset"] || @request["dataset"]
|
|
47
|
+
broadcast(:body => @request)
|
|
48
|
+
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
UNLIMITED = -1
|
|
54
|
+
|
|
55
|
+
def reduce_command
|
|
56
|
+
"search_reduce"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def gather_command
|
|
60
|
+
"search_gather"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def ensure_unifiable!
|
|
64
|
+
@queries.each do |name, query|
|
|
65
|
+
if unifiable?(name) and query["output"]
|
|
66
|
+
query["output"]["unifiable"] = true
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def unifiable?(name)
|
|
72
|
+
query = @queries[name]
|
|
73
|
+
return true if query["groupBy"]
|
|
74
|
+
name = query["source"]
|
|
75
|
+
return false unless @queries.keys.include?(name)
|
|
76
|
+
unifiable?(name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def transform_query(input_name, query)
|
|
80
|
+
output = query["output"]
|
|
81
|
+
|
|
82
|
+
# Skip reducing phase for a result with no output.
|
|
83
|
+
if output.nil? or
|
|
84
|
+
output["elements"].nil? or
|
|
85
|
+
(!output["elements"].include?("count") and
|
|
86
|
+
!output["elements"].include?("records"))
|
|
87
|
+
return
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
transformer = QueryTransformer.new(query)
|
|
91
|
+
|
|
92
|
+
elements = transformer.mappers
|
|
93
|
+
mapper = {}
|
|
94
|
+
mapper["elements"] = elements unless elements.empty?
|
|
95
|
+
reduce(input_name => { :reduce => transformer.reducers,
|
|
96
|
+
:gather => mapper })
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class QueryTransformer
|
|
100
|
+
attr_reader :reducers, :mappers
|
|
101
|
+
|
|
102
|
+
def initialize(query)
|
|
103
|
+
@query = query
|
|
104
|
+
@output = @query["output"]
|
|
105
|
+
@reducers = {}
|
|
106
|
+
@mappers = {}
|
|
107
|
+
@output_records = true
|
|
108
|
+
transform!
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def transform!
|
|
112
|
+
# The collector module supports only "simple" format search results.
|
|
113
|
+
# So we have to override the format and restore it on the gathering
|
|
114
|
+
# phase.
|
|
115
|
+
@records_format = @output["format"] || "simple"
|
|
116
|
+
if @output["format"] and @output["format"] != "simple"
|
|
117
|
+
@output["format"] = "simple"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
@sort_keys = @query["sortBy"] || []
|
|
121
|
+
@sort_keys = @sort_keys["keys"] || [] if @sort_keys.is_a?(Hash)
|
|
122
|
+
|
|
123
|
+
calculate_offset_and_limit!
|
|
124
|
+
build_count_mapper_and_reducer!
|
|
125
|
+
build_records_mapper_and_reducer!
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def calculate_offset_and_limit!
|
|
129
|
+
@original_sort_offset = sort_offset
|
|
130
|
+
@original_output_offset = output_offset
|
|
131
|
+
@original_sort_limit = sort_limit
|
|
132
|
+
@original_output_limit = output_limit
|
|
133
|
+
|
|
134
|
+
calculate_sort_offset!
|
|
135
|
+
calculate_output_offset!
|
|
136
|
+
|
|
137
|
+
# We have to calculate limit based on offset.
|
|
138
|
+
# <A, B = limited integer (0...MAXINT)>
|
|
139
|
+
# | sort limit | output limit | => | worker's sort limit | worker's output limit | final limit |
|
|
140
|
+
# ============================= ====================================================================
|
|
141
|
+
# | UNLIMITED | UNLIMITED | => | UNLIMITED | UNLIMITED | UNLIMITED |
|
|
142
|
+
# | UNLIMITED | B | => | final_offset + B | final_offset + B | B |
|
|
143
|
+
# | A | UNLIMITED | => | final_offset + A | final_offset + A | A |
|
|
144
|
+
# | A | B | => | final_offset + max(A, B) | final_offset + min(A, B)| min(A, B) |
|
|
145
|
+
|
|
146
|
+
# XXX final_limit and final_offset calculated in many times
|
|
147
|
+
|
|
148
|
+
@records_offset = final_offset
|
|
149
|
+
@records_limit = final_limit
|
|
150
|
+
|
|
151
|
+
updated_sort_limit = nil
|
|
152
|
+
updated_output_limit = nil
|
|
153
|
+
if final_limit == UNLIMITED
|
|
154
|
+
updated_output_limit = UNLIMITED
|
|
155
|
+
else
|
|
156
|
+
if rich_sort?
|
|
157
|
+
updated_sort_limit = final_offset + [sort_limit, output_limit].max
|
|
158
|
+
end
|
|
159
|
+
updated_output_limit = final_offset + final_limit
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
if updated_sort_limit and updated_sort_limit != @query["sortBy"]["limit"]
|
|
163
|
+
@query["sortBy"]["limit"] = updated_sort_limit
|
|
164
|
+
end
|
|
165
|
+
if updated_output_limit and @output["limit"] and updated_output_limit != @output["limit"]
|
|
166
|
+
@output["limit"] = updated_output_limit
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def calculate_sort_offset!
|
|
171
|
+
# Offset for workers must be zero, because we have to apply "limit" and
|
|
172
|
+
# "offset" on the last gathering phase instead of each reducing phase.
|
|
173
|
+
if rich_sort?
|
|
174
|
+
@query["sortBy"]["offset"] = 0
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def sort_offset
|
|
179
|
+
if rich_sort?
|
|
180
|
+
@query["sortBy"]["offset"] || 0
|
|
181
|
+
else
|
|
182
|
+
0
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def output_offset
|
|
187
|
+
@output["offset"] || 0
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def sort_limit
|
|
191
|
+
if rich_sort?
|
|
192
|
+
@query["sortBy"]["limit"] || UNLIMITED
|
|
193
|
+
else
|
|
194
|
+
UNLIMITED
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def output_limit
|
|
199
|
+
@output["limit"] || 0
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def calculate_output_offset!
|
|
203
|
+
@output["offset"] = 0 if have_records? and @output["offset"]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def final_offset
|
|
207
|
+
@original_sort_offset + @original_output_offset
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def final_limit
|
|
211
|
+
if @original_sort_limit == UNLIMITED and
|
|
212
|
+
@original_output_limit == UNLIMITED
|
|
213
|
+
UNLIMITED
|
|
214
|
+
else
|
|
215
|
+
if @original_sort_limit == UNLIMITED
|
|
216
|
+
@original_output_limit
|
|
217
|
+
elsif @original_output_limit == UNLIMITED
|
|
218
|
+
@original_sort_limit
|
|
219
|
+
else
|
|
220
|
+
[@original_sort_limit, @original_output_limit].min
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def have_records?
|
|
226
|
+
@output["elements"].include?("records")
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def rich_sort?
|
|
230
|
+
@query["sortBy"].is_a?(Hash)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def unifiable?
|
|
234
|
+
@output["unifiable"]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def build_count_mapper_and_reducer!
|
|
238
|
+
return unless @output["elements"].include?("count")
|
|
239
|
+
|
|
240
|
+
@reducers["count"] = {
|
|
241
|
+
"type" => "sum",
|
|
242
|
+
}
|
|
243
|
+
if unifiable?
|
|
244
|
+
@query["sortBy"]["limit"] = -1 if @query["sortBy"].is_a?(Hash)
|
|
245
|
+
@output["limit"] = -1
|
|
246
|
+
mapper = {
|
|
247
|
+
"target" => "records",
|
|
248
|
+
}
|
|
249
|
+
unless @output["elements"].include?("records")
|
|
250
|
+
@records_limit = -1
|
|
251
|
+
@output["elements"] << "records"
|
|
252
|
+
@output["attributes"] ||= ["_key"]
|
|
253
|
+
@output_records = false
|
|
254
|
+
end
|
|
255
|
+
@mappers["count"] = mapper
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def build_records_mapper_and_reducer!
|
|
260
|
+
# Skip reducing phase for a result with no record output.
|
|
261
|
+
return if !@output["elements"].include?("records") || @records_limit.zero?
|
|
262
|
+
|
|
263
|
+
# Append sort key attributes to the list of output attributes
|
|
264
|
+
# temporarily, for the reducing phase. After all extra columns
|
|
265
|
+
# are removed on the gathering phase.
|
|
266
|
+
final_attributes = output_attribute_names
|
|
267
|
+
update_output_attributes!
|
|
268
|
+
|
|
269
|
+
@reducers["records"] = build_records_reducer
|
|
270
|
+
|
|
271
|
+
mapper = {}
|
|
272
|
+
if @output_records
|
|
273
|
+
mapper["format"] = @records_format unless @records_format == "simple"
|
|
274
|
+
mapper["attributes"] = final_attributes unless final_attributes.empty?
|
|
275
|
+
mapper["offset"] = @records_offset unless @records_offset.zero?
|
|
276
|
+
mapper["limit"] = @records_limit unless @records_limit.zero?
|
|
277
|
+
else
|
|
278
|
+
mapper["no_output"] = true
|
|
279
|
+
end
|
|
280
|
+
@mappers["records"] = mapper
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def output_attribute_names
|
|
284
|
+
attributes = @output["attributes"] || []
|
|
285
|
+
if attributes.is_a?(Hash)
|
|
286
|
+
attributes.keys
|
|
287
|
+
else
|
|
288
|
+
attributes.collect do |attribute|
|
|
289
|
+
if attribute.is_a?(Hash)
|
|
290
|
+
attribute["label"] || attribute["source"]
|
|
291
|
+
else
|
|
292
|
+
attribute
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def update_output_attributes!
|
|
299
|
+
@output["attributes"] = array_style_attributes
|
|
300
|
+
@output["attributes"] += sort_attribute_names
|
|
301
|
+
if unifiable? and !source_column_names.include?("_key")
|
|
302
|
+
@output["attributes"] << "_key"
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def array_style_attributes
|
|
307
|
+
attributes = @output["attributes"] || []
|
|
308
|
+
if attributes.is_a?(Hash)
|
|
309
|
+
attributes.keys.collect do |key|
|
|
310
|
+
attribute = attributes[key]
|
|
311
|
+
case attribute
|
|
312
|
+
when String
|
|
313
|
+
{
|
|
314
|
+
"label" => key,
|
|
315
|
+
"source" => attribute,
|
|
316
|
+
}
|
|
317
|
+
when Hash
|
|
318
|
+
attribute["label"] = key
|
|
319
|
+
attribute
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
attributes
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def source_column_names
|
|
328
|
+
attributes = @output["attributes"] || []
|
|
329
|
+
if attributes.is_a?(Hash)
|
|
330
|
+
attributes_hash = attributes
|
|
331
|
+
attributes = []
|
|
332
|
+
attributes_hash.each do |key, attribute|
|
|
333
|
+
attributes << attribute["source"] || key
|
|
334
|
+
end
|
|
335
|
+
attributes
|
|
336
|
+
else
|
|
337
|
+
attributes.collect do |attribute|
|
|
338
|
+
if attribute.is_a?(Hash)
|
|
339
|
+
attribute["source"] || attribute["label"]
|
|
340
|
+
else
|
|
341
|
+
attribute
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def sort_attribute_names
|
|
348
|
+
sort_attributes = @sort_keys.collect do |key|
|
|
349
|
+
key = key[1..-1] if key[0] == "-"
|
|
350
|
+
key
|
|
351
|
+
end
|
|
352
|
+
attributes = source_column_names
|
|
353
|
+
sort_attributes.reject! do |attribute|
|
|
354
|
+
attributes.include?(attribute)
|
|
355
|
+
end
|
|
356
|
+
sort_attributes
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
ASCENDING_OPERATOR = "<"
|
|
360
|
+
DESCENDING_OPERATOR = ">"
|
|
361
|
+
|
|
362
|
+
def build_records_reducer
|
|
363
|
+
attributes = source_column_names
|
|
364
|
+
key_column_index = attributes.index("_key")
|
|
365
|
+
|
|
366
|
+
operators = @sort_keys.collect do |sort_key|
|
|
367
|
+
operator = ASCENDING_OPERATOR
|
|
368
|
+
if sort_key[0] == "-"
|
|
369
|
+
operator = DESCENDING_OPERATOR
|
|
370
|
+
sort_key = sort_key[1..-1]
|
|
371
|
+
end
|
|
372
|
+
{
|
|
373
|
+
"operator" => operator,
|
|
374
|
+
"column" => attributes.index(sort_key),
|
|
375
|
+
}
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
reducer = {
|
|
379
|
+
"type" => "sort",
|
|
380
|
+
"operators" => operators,
|
|
381
|
+
}
|
|
382
|
+
if unifiable? and !key_column_index.nil?
|
|
383
|
+
reducer["key_column"] = key_column_index
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# On the reducing phase, we apply only "limit". We cannot apply
|
|
387
|
+
# "offset" on this phase because the collector merges a pair of
|
|
388
|
+
# results step by step even if there are three or more results.
|
|
389
|
+
# Instead, we apply "offset" on the gathering phase.
|
|
390
|
+
reducer["limit"] = @output["limit"]
|
|
391
|
+
|
|
392
|
+
reducer
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
@@ -21,7 +21,8 @@ require "droonga/watch_schema"
|
|
|
21
21
|
module Droonga
|
|
22
22
|
module Plugins
|
|
23
23
|
module Watch
|
|
24
|
-
Plugin
|
|
24
|
+
extend Plugin
|
|
25
|
+
register("watch")
|
|
25
26
|
|
|
26
27
|
module SchemaCreatable
|
|
27
28
|
private
|
|
@@ -51,7 +52,11 @@ module Droonga
|
|
|
51
52
|
subscriber = request["subscriber"]
|
|
52
53
|
condition = request["condition"]
|
|
53
54
|
route = request["route"] || message["from"]
|
|
54
|
-
|
|
55
|
+
if condition
|
|
56
|
+
query = condition.to_json
|
|
57
|
+
else
|
|
58
|
+
query = nilondition
|
|
59
|
+
end
|
|
55
60
|
[subscriber, condition, query, route]
|
|
56
61
|
end
|
|
57
62
|
end
|
|
@@ -60,14 +65,12 @@ module Droonga
|
|
|
60
65
|
include SchemaCreatable
|
|
61
66
|
include MessageParsable
|
|
62
67
|
|
|
63
|
-
message.type = "watch.subscribe"
|
|
64
|
-
|
|
65
68
|
def initialize(*args)
|
|
66
69
|
super
|
|
67
70
|
ensure_schema_created # TODO: REMOVE ME
|
|
68
71
|
end
|
|
69
72
|
|
|
70
|
-
def handle(message
|
|
73
|
+
def handle(message)
|
|
71
74
|
subscriber, condition, query, route = parse_message(message)
|
|
72
75
|
normalized_request = {
|
|
73
76
|
:subscriber => subscriber,
|
|
@@ -77,25 +80,27 @@ module Droonga
|
|
|
77
80
|
}
|
|
78
81
|
watcher = Watcher.new(@context)
|
|
79
82
|
watcher.subscribe(normalized_request)
|
|
80
|
-
|
|
81
|
-
"success" => true,
|
|
82
|
-
}
|
|
83
|
-
messenger.emit(outputs)
|
|
83
|
+
true
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
define_single_step do |step|
|
|
88
|
+
step.name = "watch.subscribe"
|
|
89
|
+
step.write = true
|
|
90
|
+
step.handler = SubscribeHandler
|
|
91
|
+
step.collector = Collectors::And
|
|
92
|
+
end
|
|
93
|
+
|
|
87
94
|
class UnsubscribeHandler < Droonga::Handler
|
|
88
95
|
include SchemaCreatable
|
|
89
96
|
include MessageParsable
|
|
90
97
|
|
|
91
|
-
message.type = "watch.unsubscribe"
|
|
92
|
-
|
|
93
98
|
def initialize(*args)
|
|
94
99
|
super
|
|
95
100
|
ensure_schema_created # TODO: REMOVE ME
|
|
96
101
|
end
|
|
97
102
|
|
|
98
|
-
def handle(message
|
|
103
|
+
def handle(message)
|
|
99
104
|
subscriber, condition, query, route = parse_message(message)
|
|
100
105
|
normalized_request = {
|
|
101
106
|
:subscriber => subscriber,
|
|
@@ -104,24 +109,26 @@ module Droonga
|
|
|
104
109
|
}
|
|
105
110
|
watcher = Watcher.new(@context)
|
|
106
111
|
watcher.unsubscribe(normalized_request)
|
|
107
|
-
|
|
108
|
-
"success" => true,
|
|
109
|
-
}
|
|
110
|
-
messenger.emit(outputs)
|
|
112
|
+
true
|
|
111
113
|
end
|
|
112
114
|
end
|
|
113
115
|
|
|
116
|
+
define_single_step do |step|
|
|
117
|
+
step.name = "watch.unsubscribe"
|
|
118
|
+
step.write = true
|
|
119
|
+
step.handler = UnsubscribeHandler
|
|
120
|
+
step.collector = Collectors::And
|
|
121
|
+
end
|
|
122
|
+
|
|
114
123
|
class FeedHandler < Droonga::Handler
|
|
115
124
|
include SchemaCreatable
|
|
116
125
|
|
|
117
|
-
message.type = "watch.feed"
|
|
118
|
-
|
|
119
126
|
def initialize(*args)
|
|
120
127
|
super
|
|
121
128
|
ensure_schema_created # TODO: REMOVE ME
|
|
122
129
|
end
|
|
123
130
|
|
|
124
|
-
def handle(message
|
|
131
|
+
def handle(message)
|
|
125
132
|
request = message.request
|
|
126
133
|
watcher = Watcher.new(@context)
|
|
127
134
|
watcher.feed(:targets => request["targets"]) do |route, subscribers|
|
|
@@ -133,9 +140,16 @@ module Droonga
|
|
|
133
140
|
messenger.forward(published_message,
|
|
134
141
|
"to" => route, "type" => "watch.publish")
|
|
135
142
|
end
|
|
143
|
+
nil
|
|
136
144
|
end
|
|
137
145
|
end
|
|
138
146
|
|
|
147
|
+
define_single_step do |step|
|
|
148
|
+
step.name = "watch.feed"
|
|
149
|
+
step.write = true
|
|
150
|
+
step.handler = FeedHandler
|
|
151
|
+
end
|
|
152
|
+
|
|
139
153
|
class SweepHandler < Droonga::Handler
|
|
140
154
|
include SchemaCreatable
|
|
141
155
|
|
|
@@ -146,11 +160,18 @@ module Droonga
|
|
|
146
160
|
ensure_schema_created # TODO: REMOVE ME
|
|
147
161
|
end
|
|
148
162
|
|
|
149
|
-
def
|
|
163
|
+
def handle(message)
|
|
150
164
|
sweeper = Sweeper.new(@context)
|
|
151
165
|
sweeper.sweep_expired_subscribers
|
|
166
|
+
nil
|
|
152
167
|
end
|
|
153
168
|
end
|
|
169
|
+
|
|
170
|
+
define_single_step do |step|
|
|
171
|
+
step.name = "watch.sweep"
|
|
172
|
+
step.write = true
|
|
173
|
+
step.handler = SweepHandler
|
|
174
|
+
end
|
|
154
175
|
end
|
|
155
176
|
end
|
|
156
177
|
end
|