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
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# Copyright (C) 2013-2014 Droonga Project
|
|
2
|
-
#
|
|
3
|
-
# This library is free software; you can redistribute it and/or
|
|
4
|
-
# modify it under the terms of the GNU Lesser General Public
|
|
5
|
-
# License version 2.1 as published by the Free Software Foundation.
|
|
6
|
-
#
|
|
7
|
-
# This library is distributed in the hope that it will be useful,
|
|
8
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
10
|
-
# Lesser General Public License for more details.
|
|
11
|
-
#
|
|
12
|
-
# You should have received a copy of the GNU Lesser General Public
|
|
13
|
-
# License along with this library; if not, write to the Free Software
|
|
14
|
-
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
15
|
-
|
|
16
|
-
module Droonga
|
|
17
|
-
class LegacyPluginRepository
|
|
18
|
-
include Enumerable
|
|
19
|
-
|
|
20
|
-
def initialize
|
|
21
|
-
@plugins = {}
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def each(&block)
|
|
25
|
-
@plugins.each(&block)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def register(name, klass)
|
|
29
|
-
@plugins[name] = klass
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def [](name)
|
|
33
|
-
@plugins[name]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def clear
|
|
37
|
-
@plugins.clear
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def instantiate(name, *args, &block)
|
|
41
|
-
plugin_class = self[name]
|
|
42
|
-
if plugin_class.nil?
|
|
43
|
-
# TODO: use the original error
|
|
44
|
-
raise ArgumentError, "unknown plugin: <#{name}>"
|
|
45
|
-
end
|
|
46
|
-
begin
|
|
47
|
-
plugin_class.new(*args, &block)
|
|
48
|
-
rescue
|
|
49
|
-
p [plugin_class, plugin_class.method(:new), plugin_class.method(:new).arity, args.size]
|
|
50
|
-
raise
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
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/legacy_plugin"
|
|
19
|
-
require "droonga/distributed_command_planner"
|
|
20
|
-
|
|
21
|
-
module Droonga
|
|
22
|
-
class PlannerPlugin < LegacyPlugin
|
|
23
|
-
extend PluginRegisterable
|
|
24
|
-
|
|
25
|
-
def initialize(planner)
|
|
26
|
-
super()
|
|
27
|
-
@planner = planner
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def scatter(message, options={})
|
|
31
|
-
planner = DistributedCommandPlanner.new(message)
|
|
32
|
-
planner.scatter
|
|
33
|
-
planner.key = options[:key]
|
|
34
|
-
planner.reduce(options[:reduce])
|
|
35
|
-
planner.plan
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def broadcast(message, options={})
|
|
39
|
-
planner = DistributedCommandPlanner.new(message)
|
|
40
|
-
planner.broadcast(:write => options[:write])
|
|
41
|
-
planner.reduce(options[:reduce])
|
|
42
|
-
planner.plan
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
def process_error(command, error, arguments)
|
|
47
|
-
if error.is_a?(MessageProcessingError)
|
|
48
|
-
raise error
|
|
49
|
-
else
|
|
50
|
-
super
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,98 +0,0 @@
|
|
|
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/plugin/collector/basic"
|
|
19
|
-
|
|
20
|
-
module Droonga
|
|
21
|
-
class SearchCollector < BasicCollector
|
|
22
|
-
repository.register("search", self)
|
|
23
|
-
|
|
24
|
-
command :collector_search_gather
|
|
25
|
-
def collector_search_gather(result)
|
|
26
|
-
output = body ? body[input_name] : input_name
|
|
27
|
-
if output.is_a?(Hash)
|
|
28
|
-
elements = output["elements"]
|
|
29
|
-
if elements && elements.is_a?(Hash)
|
|
30
|
-
# because "count" mapper requires all records,
|
|
31
|
-
# I have to apply it at first, before "limit" and "offset" are applied.
|
|
32
|
-
count_mapper = elements["count"]
|
|
33
|
-
if count_mapper
|
|
34
|
-
if count_mapper["no_output"]
|
|
35
|
-
result.delete("count")
|
|
36
|
-
else
|
|
37
|
-
result["count"] = result[count_mapper["target"]].size
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
records_mapper = elements["records"]
|
|
42
|
-
if records_mapper && result["records"]
|
|
43
|
-
if records_mapper["no_output"]
|
|
44
|
-
result.delete("records")
|
|
45
|
-
else
|
|
46
|
-
result["records"] = apply_output_range(result["records"], records_mapper)
|
|
47
|
-
result["records"] = apply_output_attributes_and_format(result["records"], records_mapper)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
output = output["output"]
|
|
52
|
-
end
|
|
53
|
-
emit(output, result)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def apply_output_attributes_and_format(items, output)
|
|
57
|
-
attributes = output["attributes"] || []
|
|
58
|
-
if output["format"] == "complex"
|
|
59
|
-
items.collect! do |item|
|
|
60
|
-
complex_item = {}
|
|
61
|
-
attributes.each_with_index do |label, index|
|
|
62
|
-
complex_item[label] = item[index]
|
|
63
|
-
end
|
|
64
|
-
complex_item
|
|
65
|
-
end
|
|
66
|
-
else
|
|
67
|
-
items.collect! do |item|
|
|
68
|
-
item[0...attributes.size]
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
items
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
command :collector_search_reduce
|
|
75
|
-
def collector_search_reduce(request)
|
|
76
|
-
#XXX This is just a workaround. Errors should be handled by the framework itself.
|
|
77
|
-
if input_name == "errors"
|
|
78
|
-
return collector_reduce(request)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
return unless request
|
|
82
|
-
body[input_name].each do |output, elements|
|
|
83
|
-
value = request
|
|
84
|
-
old_value = output_values[output]
|
|
85
|
-
value = reduce_elements(elements, old_value, request) if old_value
|
|
86
|
-
emit(output, value)
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def reduce_elements(elements, left_values, right_values)
|
|
91
|
-
result = {}
|
|
92
|
-
elements.each do |key, deal|
|
|
93
|
-
result[key] = reduce(deal, left_values[key], right_values[key])
|
|
94
|
-
end
|
|
95
|
-
result
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
@@ -1,49 +0,0 @@
|
|
|
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/planner_plugin"
|
|
19
|
-
|
|
20
|
-
module Droonga
|
|
21
|
-
class CRUDPlanner < Droonga::PlannerPlugin
|
|
22
|
-
repository.register("crud", self)
|
|
23
|
-
|
|
24
|
-
command :add
|
|
25
|
-
def add(message)
|
|
26
|
-
scatter(message)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
command :update
|
|
30
|
-
def update(message)
|
|
31
|
-
scatter(message)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# TODO: What is this?
|
|
35
|
-
command :reset
|
|
36
|
-
def reset(message)
|
|
37
|
-
scatter(message)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
def scatter(message)
|
|
42
|
-
super(message,
|
|
43
|
-
:key => message["body"]["key"] || rand.to_s,
|
|
44
|
-
:reduce => {
|
|
45
|
-
"success" => "and"
|
|
46
|
-
})
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
@@ -1,393 +0,0 @@
|
|
|
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
|
-
class DistributedSearchPlanner < DistributedCommandPlanner
|
|
23
|
-
def initialize(search_request_message)
|
|
24
|
-
super
|
|
25
|
-
|
|
26
|
-
@request = @source_message["body"]
|
|
27
|
-
raise NoQuery.new unless @request
|
|
28
|
-
|
|
29
|
-
@request = Marshal.load(Marshal.dump(@request))
|
|
30
|
-
@queries = @request["queries"]
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def plan
|
|
34
|
-
raise Searcher::NoQuery.new if @queries.nil? || @queries.empty?
|
|
35
|
-
|
|
36
|
-
Searcher::QuerySorter.validate_dependencies(@queries)
|
|
37
|
-
|
|
38
|
-
ensure_unifiable!
|
|
39
|
-
|
|
40
|
-
@queries.each do |input_name, query|
|
|
41
|
-
transform_query(input_name, query)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
@dataset = @source_message["dataset"] || @request["dataset"]
|
|
45
|
-
broadcast(:body => @request)
|
|
46
|
-
|
|
47
|
-
super
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
UNLIMITED = -1
|
|
52
|
-
|
|
53
|
-
def reduce_command
|
|
54
|
-
"search_reduce"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def gather_command
|
|
58
|
-
"search_gather"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def ensure_unifiable!
|
|
62
|
-
@queries.each do |name, query|
|
|
63
|
-
if unifiable?(name) && query["output"]
|
|
64
|
-
query["output"]["unifiable"] = true
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def unifiable?(name)
|
|
70
|
-
query = @queries[name]
|
|
71
|
-
return true if query["groupBy"]
|
|
72
|
-
name = query["source"]
|
|
73
|
-
return false unless @queries.keys.include?(name)
|
|
74
|
-
unifiable?(name)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def transform_query(input_name, query)
|
|
78
|
-
output = query["output"]
|
|
79
|
-
|
|
80
|
-
# Skip reducing phase for a result with no output.
|
|
81
|
-
if output.nil? or
|
|
82
|
-
output["elements"].nil? or
|
|
83
|
-
(!output["elements"].include?("count") &&
|
|
84
|
-
!output["elements"].include?("records"))
|
|
85
|
-
return
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
transformer = QueryTransformer.new(query)
|
|
89
|
-
|
|
90
|
-
elements = transformer.mappers
|
|
91
|
-
mapper = {}
|
|
92
|
-
mapper["elements"] = elements unless elements.empty?
|
|
93
|
-
reduce(input_name => { :reduce => transformer.reducers,
|
|
94
|
-
:gather => mapper })
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
class QueryTransformer
|
|
98
|
-
attr_reader :reducers, :mappers
|
|
99
|
-
|
|
100
|
-
def initialize(query)
|
|
101
|
-
@query = query
|
|
102
|
-
@output = @query["output"]
|
|
103
|
-
@reducers = {}
|
|
104
|
-
@mappers = {}
|
|
105
|
-
@output_records = true
|
|
106
|
-
transform!
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def transform!
|
|
110
|
-
# The collector module supports only "simple" format search results.
|
|
111
|
-
# So we have to override the format and restore it on the gathering
|
|
112
|
-
# phase.
|
|
113
|
-
@records_format = @output["format"] || "simple"
|
|
114
|
-
if @output["format"] && @output["format"] != "simple"
|
|
115
|
-
@output["format"] = "simple"
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
@sort_keys = @query["sortBy"] || []
|
|
119
|
-
@sort_keys = @sort_keys["keys"] || [] if @sort_keys.is_a?(Hash)
|
|
120
|
-
|
|
121
|
-
calculate_offset_and_limit!
|
|
122
|
-
build_count_mapper_and_reducer!
|
|
123
|
-
build_records_mapper_and_reducer!
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def calculate_offset_and_limit!
|
|
127
|
-
@original_sort_offset = sort_offset
|
|
128
|
-
@original_output_offset = output_offset
|
|
129
|
-
@original_sort_limit = sort_limit
|
|
130
|
-
@original_output_limit = output_limit
|
|
131
|
-
|
|
132
|
-
calculate_sort_offset!
|
|
133
|
-
calculate_output_offset!
|
|
134
|
-
|
|
135
|
-
# We have to calculate limit based on offset.
|
|
136
|
-
# <A, B = limited integer (0...MAXINT)>
|
|
137
|
-
# | sort limit | output limit | => | worker's sort limit | worker's output limit | final limit |
|
|
138
|
-
# ============================= ====================================================================
|
|
139
|
-
# | UNLIMITED | UNLIMITED | => | UNLIMITED | UNLIMITED | UNLIMITED |
|
|
140
|
-
# | UNLIMITED | B | => | final_offset + B | final_offset + B | B |
|
|
141
|
-
# | A | UNLIMITED | => | final_offset + A | final_offset + A | A |
|
|
142
|
-
# | A | B | => | final_offset + max(A, B) | final_offset + min(A, B)| min(A, B) |
|
|
143
|
-
|
|
144
|
-
# XXX final_limit and final_offset calculated in many times
|
|
145
|
-
|
|
146
|
-
@records_offset = final_offset
|
|
147
|
-
@records_limit = final_limit
|
|
148
|
-
|
|
149
|
-
updated_sort_limit = nil
|
|
150
|
-
updated_output_limit = nil
|
|
151
|
-
if final_limit == UNLIMITED
|
|
152
|
-
updated_output_limit = UNLIMITED
|
|
153
|
-
else
|
|
154
|
-
if rich_sort?
|
|
155
|
-
updated_sort_limit = final_offset + [sort_limit, output_limit].max
|
|
156
|
-
end
|
|
157
|
-
updated_output_limit = final_offset + final_limit
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
if updated_sort_limit && updated_sort_limit != @query["sortBy"]["limit"]
|
|
161
|
-
@query["sortBy"]["limit"] = updated_sort_limit
|
|
162
|
-
end
|
|
163
|
-
if updated_output_limit && @output["limit"] && updated_output_limit != @output["limit"]
|
|
164
|
-
@output["limit"] = updated_output_limit
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def calculate_sort_offset!
|
|
169
|
-
# Offset for workers must be zero, because we have to apply "limit" and
|
|
170
|
-
# "offset" on the last gathering phase instead of each reducing phase.
|
|
171
|
-
if rich_sort?
|
|
172
|
-
@query["sortBy"]["offset"] = 0
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def sort_offset
|
|
177
|
-
if rich_sort?
|
|
178
|
-
@query["sortBy"]["offset"] || 0
|
|
179
|
-
else
|
|
180
|
-
0
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def output_offset
|
|
185
|
-
@output["offset"] || 0
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def sort_limit
|
|
189
|
-
if rich_sort?
|
|
190
|
-
@query["sortBy"]["limit"] || UNLIMITED
|
|
191
|
-
else
|
|
192
|
-
UNLIMITED
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def output_limit
|
|
197
|
-
@output["limit"] || 0
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def calculate_output_offset!
|
|
201
|
-
@output["offset"] = 0 if have_records? && @output["offset"]
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def final_offset
|
|
205
|
-
@original_sort_offset + @original_output_offset
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def final_limit
|
|
209
|
-
if @original_sort_limit == UNLIMITED && @original_output_limit == UNLIMITED
|
|
210
|
-
UNLIMITED
|
|
211
|
-
else
|
|
212
|
-
if @original_sort_limit == UNLIMITED
|
|
213
|
-
@original_output_limit
|
|
214
|
-
elsif @original_output_limit == UNLIMITED
|
|
215
|
-
@original_sort_limit
|
|
216
|
-
else
|
|
217
|
-
[@original_sort_limit, @original_output_limit].min
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
def have_records?
|
|
223
|
-
@output["elements"].include?("records")
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def rich_sort?
|
|
227
|
-
@query["sortBy"].is_a?(Hash)
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def unifiable?
|
|
231
|
-
@output["unifiable"]
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def build_count_mapper_and_reducer!
|
|
235
|
-
return unless @output["elements"].include?("count")
|
|
236
|
-
|
|
237
|
-
@reducers["count"] = {
|
|
238
|
-
"type" => "sum",
|
|
239
|
-
}
|
|
240
|
-
if unifiable?
|
|
241
|
-
@query["sortBy"]["limit"] = -1 if @query["sortBy"].is_a?(Hash)
|
|
242
|
-
@output["limit"] = -1
|
|
243
|
-
mapper = {
|
|
244
|
-
"target" => "records",
|
|
245
|
-
}
|
|
246
|
-
unless @output["elements"].include?("records")
|
|
247
|
-
@records_limit = -1
|
|
248
|
-
@output["elements"] << "records"
|
|
249
|
-
@output["attributes"] ||= ["_key"]
|
|
250
|
-
@output_records = false
|
|
251
|
-
end
|
|
252
|
-
@mappers["count"] = mapper
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
def build_records_mapper_and_reducer!
|
|
257
|
-
# Skip reducing phase for a result with no record output.
|
|
258
|
-
return if !@output["elements"].include?("records") || @records_limit.zero?
|
|
259
|
-
|
|
260
|
-
# Append sort key attributes to the list of output attributes
|
|
261
|
-
# temporarily, for the reducing phase. After all extra columns
|
|
262
|
-
# are removed on the gathering phase.
|
|
263
|
-
final_attributes = output_attribute_names
|
|
264
|
-
update_output_attributes!
|
|
265
|
-
|
|
266
|
-
@reducers["records"] = build_records_reducer
|
|
267
|
-
|
|
268
|
-
mapper = {}
|
|
269
|
-
if @output_records
|
|
270
|
-
mapper["format"] = @records_format unless @records_format == "simple"
|
|
271
|
-
mapper["attributes"] = final_attributes unless final_attributes.empty?
|
|
272
|
-
mapper["offset"] = @records_offset unless @records_offset.zero?
|
|
273
|
-
mapper["limit"] = @records_limit unless @records_limit.zero?
|
|
274
|
-
else
|
|
275
|
-
mapper["no_output"] = true
|
|
276
|
-
end
|
|
277
|
-
@mappers["records"] = mapper
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def output_attribute_names
|
|
281
|
-
attributes = @output["attributes"] || []
|
|
282
|
-
if attributes.is_a?(Hash)
|
|
283
|
-
attributes.keys
|
|
284
|
-
else
|
|
285
|
-
attributes.collect do |attribute|
|
|
286
|
-
if attribute.is_a?(Hash)
|
|
287
|
-
attribute["label"] || attribute["source"]
|
|
288
|
-
else
|
|
289
|
-
attribute
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
def update_output_attributes!
|
|
296
|
-
@output["attributes"] = array_style_attributes
|
|
297
|
-
@output["attributes"] += sort_attribute_names
|
|
298
|
-
if unifiable? && !source_column_names.include?("_key")
|
|
299
|
-
@output["attributes"] << "_key"
|
|
300
|
-
end
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
def array_style_attributes
|
|
304
|
-
attributes = @output["attributes"] || []
|
|
305
|
-
if attributes.is_a?(Hash)
|
|
306
|
-
attributes.keys.collect do |key|
|
|
307
|
-
attribute = attributes[key]
|
|
308
|
-
case attribute
|
|
309
|
-
when String
|
|
310
|
-
{
|
|
311
|
-
"label" => key,
|
|
312
|
-
"source" => attribute,
|
|
313
|
-
}
|
|
314
|
-
when Hash
|
|
315
|
-
attribute["label"] = key
|
|
316
|
-
attribute
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
else
|
|
320
|
-
attributes
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
def source_column_names
|
|
325
|
-
attributes = @output["attributes"] || []
|
|
326
|
-
if attributes.is_a?(Hash)
|
|
327
|
-
attributes_hash = attributes
|
|
328
|
-
attributes = []
|
|
329
|
-
attributes_hash.each do |key, attribute|
|
|
330
|
-
attributes << attribute["source"] || key
|
|
331
|
-
end
|
|
332
|
-
attributes
|
|
333
|
-
else
|
|
334
|
-
attributes.collect do |attribute|
|
|
335
|
-
if attribute.is_a?(Hash)
|
|
336
|
-
attribute["source"] || attribute["label"]
|
|
337
|
-
else
|
|
338
|
-
attribute
|
|
339
|
-
end
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
def sort_attribute_names
|
|
345
|
-
sort_attributes = @sort_keys.collect do |key|
|
|
346
|
-
key = key[1..-1] if key[0] == "-"
|
|
347
|
-
key
|
|
348
|
-
end
|
|
349
|
-
attributes = source_column_names
|
|
350
|
-
sort_attributes.reject! do |attribute|
|
|
351
|
-
attributes.include?(attribute)
|
|
352
|
-
end
|
|
353
|
-
sort_attributes
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
ASCENDING_OPERATOR = "<"
|
|
357
|
-
DESCENDING_OPERATOR = ">"
|
|
358
|
-
|
|
359
|
-
def build_records_reducer
|
|
360
|
-
attributes = source_column_names
|
|
361
|
-
key_column_index = attributes.index("_key")
|
|
362
|
-
|
|
363
|
-
operators = @sort_keys.collect do |sort_key|
|
|
364
|
-
operator = ASCENDING_OPERATOR
|
|
365
|
-
if sort_key[0] == "-"
|
|
366
|
-
operator = DESCENDING_OPERATOR
|
|
367
|
-
sort_key = sort_key[1..-1]
|
|
368
|
-
end
|
|
369
|
-
{
|
|
370
|
-
"operator" => operator,
|
|
371
|
-
"column" => attributes.index(sort_key),
|
|
372
|
-
}
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
reducer = {
|
|
376
|
-
"type" => "sort",
|
|
377
|
-
"operators" => operators,
|
|
378
|
-
}
|
|
379
|
-
if unifiable? && !key_column_index.nil?
|
|
380
|
-
reducer["key_column"] = key_column_index
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
# On the reducing phase, we apply only "limit". We cannot apply
|
|
384
|
-
# "offset" on this phase because the collector merges a pair of
|
|
385
|
-
# results step by step even if there are three or more results.
|
|
386
|
-
# Instead, we apply "offset" on the gathering phase.
|
|
387
|
-
reducer["limit"] = @output["limit"]
|
|
388
|
-
|
|
389
|
-
reducer
|
|
390
|
-
end
|
|
391
|
-
end
|
|
392
|
-
end
|
|
393
|
-
end
|