fluent-plugin-droonga 0.8.0 → 0.9.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.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile +7 -0
  4. data/benchmark/watch/benchmark-notify.rb +6 -6
  5. data/benchmark/watch/benchmark-notify.sh +3 -2
  6. data/bin/grn2jsons +0 -3
  7. data/doc/text/news.md +13 -0
  8. data/fluent-plugin-droonga.gemspec +1 -1
  9. data/lib/droonga/catalog/base.rb +8 -3
  10. data/lib/droonga/catalog.rb +1 -16
  11. data/lib/droonga/catalog_observer.rb +57 -0
  12. data/lib/droonga/collector.rb +1 -1
  13. data/lib/droonga/dispatcher.rb +3 -1
  14. data/lib/droonga/distributor_plugin.rb +47 -19
  15. data/lib/droonga/handler_messenger.rb +26 -2
  16. data/lib/droonga/message_processing_error.rb +6 -8
  17. data/lib/droonga/output_message.rb +17 -1
  18. data/lib/droonga/plugin/collector/basic.rb +44 -81
  19. data/lib/droonga/plugin/collector/groonga.rb +83 -0
  20. data/lib/droonga/plugin/collector/search.rb +104 -0
  21. data/lib/droonga/plugin/distributor/crud.rb +41 -0
  22. data/lib/droonga/plugin/distributor/distributed_search_planner.rb +4 -4
  23. data/lib/droonga/plugin/distributor/groonga.rb +38 -0
  24. data/lib/droonga/plugin/handler/add.rb +4 -1
  25. data/lib/droonga/plugin/handler/groonga/column_create.rb +7 -0
  26. data/lib/droonga/plugin/handler/groonga/table_remove.rb +42 -0
  27. data/lib/droonga/plugin/handler/groonga.rb +17 -2
  28. data/lib/droonga/plugin/handler/watch.rb +4 -4
  29. data/lib/droonga/plugin/input_adapter/crud.rb +27 -0
  30. data/lib/droonga/plugin/input_adapter/groonga.rb +16 -1
  31. data/lib/droonga/plugin/output_adapter/crud.rb +51 -0
  32. data/lib/droonga/plugin/output_adapter/groonga.rb +8 -1
  33. data/lib/droonga/plugin.rb +1 -1
  34. data/lib/droonga/replier.rb +7 -1
  35. data/lib/droonga/searcher.rb +168 -74
  36. data/lib/droonga/session.rb +1 -1
  37. data/lib/groonga_command_converter.rb +7 -1
  38. data/test/command/config/default/catalog.json +1 -1
  39. data/test/command/suite/add/error/invalid-integer.expected +31 -1
  40. data/test/command/suite/add/error/invalid-time.expected +31 -1
  41. data/test/command/suite/add/error/missing-key.expected +17 -1
  42. data/test/command/suite/add/error/missing-table.expected +17 -1
  43. data/test/command/suite/add/error/unknown-column.expected +31 -1
  44. data/test/command/suite/add/error/unknown-table.expected +17 -1
  45. data/test/command/suite/add/minimum.expected +1 -1
  46. data/test/command/suite/add/with-values.expected +1 -1
  47. data/test/command/suite/add/without-key.expected +1 -1
  48. data/test/command/suite/groonga/column_create/scalar.expected +2 -2
  49. data/test/command/suite/groonga/column_create/unknown-table.expected +18 -0
  50. data/test/command/suite/groonga/column_create/unknown-table.test +7 -0
  51. data/test/command/suite/groonga/column_create/vector.expected +2 -2
  52. data/test/command/suite/groonga/select/minimum.expected +1 -1
  53. data/test/command/suite/groonga/table_create/array.expected +1 -1
  54. data/test/command/suite/groonga/table_create/hash.expected +1 -1
  55. data/test/command/suite/groonga/table_remove/success.expected +17 -0
  56. data/test/command/suite/groonga/table_remove/success.test +8 -0
  57. data/test/command/suite/groonga/table_remove/unknown-table.expected +18 -0
  58. data/test/command/suite/groonga/table_remove/unknown-table.test +7 -0
  59. data/test/command/suite/message/error/missing-dataset.expected +1 -1
  60. data/test/command/suite/message/error/unknown-command.expected +1 -1
  61. data/test/command/suite/message/error/unknown-dataset.expected +1 -1
  62. data/test/command/suite/search/attributes/array.expected +1 -1
  63. data/test/command/suite/search/attributes/hash.expected +1 -1
  64. data/test/command/suite/search/complex.expected +1 -1
  65. data/test/command/suite/search/condition/nested.expected +1 -1
  66. data/test/command/suite/search/condition/query.expected +1 -1
  67. data/test/command/suite/search/condition/script.expected +1 -1
  68. data/test/command/suite/search/error/cyclic-source.expected +1 -1
  69. data/test/command/suite/search/error/deeply-cyclic-source.expected +1 -1
  70. data/test/command/suite/search/error/missing-source-parameter.expected +1 -1
  71. data/test/command/suite/search/error/unknown-source.expected +1 -1
  72. data/test/command/suite/search/group/count.expected +1 -1
  73. data/test/command/suite/search/group/limit.expected +1 -1
  74. data/test/command/suite/search/group/string.expected +1 -1
  75. data/test/command/suite/search/multiple/chained.expected +1 -1
  76. data/test/command/suite/search/multiple/parallel.expected +1 -1
  77. data/test/command/suite/search/range/only-output.expected +1 -1
  78. data/test/command/suite/search/range/only-sort.expected +1 -1
  79. data/test/command/suite/search/range/sort-and-output.expected +1 -1
  80. data/test/command/suite/search/range/too-large-output-offset.expected +1 -1
  81. data/test/command/suite/search/range/too-large-sort-offset.expected +1 -1
  82. data/test/command/suite/search/response/records/value/time.expected +1 -1
  83. data/test/command/suite/search/simple.expected +1 -1
  84. data/test/command/suite/search/sort/default-offset-limit.expected +1 -1
  85. data/test/command/suite/search/sort/invisible-column.expected +1 -1
  86. data/test/command/suite/watch/subscribe.expected +1 -1
  87. data/test/command/suite/watch/unsubscribe.expected +1 -1
  88. data/test/performance/run-test.rb +56 -0
  89. data/{benchmark → test/performance}/watch/catalog.json +0 -0
  90. data/test/performance/watch/feed.json +9 -0
  91. data/{benchmark → test/performance}/watch/fluentd.conf +0 -0
  92. data/test/performance/watch/subscribe.json +3 -0
  93. data/test/unit/catalog/test_version1.rb +34 -0
  94. data/test/unit/plugin/collector/test_basic.rb +300 -479
  95. data/test/unit/plugin/collector/test_search.rb +814 -0
  96. data/test/unit/plugin/distributor/test_search.rb +4 -4
  97. data/test/unit/plugin/distributor/test_search_planner.rb +260 -260
  98. data/test/unit/plugin/handler/groonga/test_column_create.rb +15 -1
  99. data/test/unit/plugin/handler/groonga/test_table_create.rb +6 -2
  100. data/test/unit/plugin/handler/groonga/test_table_remove.rb +58 -0
  101. data/test/unit/plugin/handler/test_add.rb +3 -1
  102. data/test/unit/plugin/handler/test_groonga.rb +24 -0
  103. data/test/unit/plugin/handler/test_search.rb +294 -0
  104. data/test/unit/plugin/handler/test_watch.rb +1 -1
  105. data/test/unit/plugin/{adapter → input_adapter}/groonga/test_select.rb +5 -37
  106. data/test/unit/plugin/output_adapter/groonga/test_select.rb +55 -0
  107. metadata +38 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f03ac63db8e9b4b082da89c26c8bbf7caea45e76
4
- data.tar.gz: 20b6f9a80dc7f198003fb4932b483f85c6a1d976
3
+ metadata.gz: 80594c4e021918c28b58451b90293961f7b65174
4
+ data.tar.gz: ff89eb342312841a7d9eaa8831a7d190bec34c35
5
5
  SHA512:
6
- metadata.gz: 06af50c670901878e0d42f72182cd9848194eb5b14f04428fbe788098b7a7b715cb57e94241742620a0bf89fa7cbea03f7e8ea825a1e4816c3aedf59e2a19e33
7
- data.tar.gz: 69cc3370c3401f24bf1749ef71f7fe2b45cf1de4b7c06c8d5171c3650e296d75ac8576851375ddefd3856c137fa5836a414e2dafef21c03e908b0296d1e5c464
6
+ metadata.gz: 39351f8baffeae2e33ac2b3d1ad6f06c39fe08a626ee8f260c62501e03b5acb05a67f2e0667833250f200679664d518da774b57c2356c30f260cd0afb83f8940
7
+ data.tar.gz: c26dd8db11b63e7f6130826f6eb4ce97b05dae064e39b9502b3cc7cbf0f54a912c5fc3885bab2855ace0a21cba9af851e64e9a4d40c136310790c848d9e0aadd
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  /test/command/tmp
3
3
  /doc/
4
4
  /.yardoc/
5
+ /pkg/
data/Gemfile CHANGED
@@ -50,3 +50,10 @@ if File.exist?(drntest_dir)
50
50
  else
51
51
  gem "drntest", :github => "droonga/drntest"
52
52
  end
53
+
54
+ drnbench_dir = File.join(parent_dir, "drnbench")
55
+ if File.exist?(drnbench_dir)
56
+ gem "drnbench", :path => drnbench_dir
57
+ else
58
+ gem "drnbench", :github => "droonga/drnbench"
59
+ end
@@ -54,11 +54,11 @@ class NotifyBenchmark
54
54
  do_feed("#{WATCHING_KEYWORD} #{index}")
55
55
  end
56
56
 
57
- notifications = []
58
- while notifications.size != @n_times
59
- notifications << @receiver.new_message
57
+ published_messages = []
58
+ while published_messages.size != @n_times
59
+ published_messages << @receiver.new_message
60
60
  end
61
- notifications
61
+ published_messages
62
62
  end
63
63
 
64
64
  def add_subscribers(n_subscribers)
@@ -118,8 +118,8 @@ options[:n_steps].times do |try_count|
118
118
  percentage = nil
119
119
  result = Benchmark.bm do |benchmark|
120
120
  benchmark.report(label) do
121
- sent_notifications = notify_benchmark.run
122
- percentage = sent_notifications.size.to_f / options[:n_times] * 100
121
+ published_messages = notify_benchmark.run
122
+ percentage = published_messages.size.to_f / options[:n_times] * 100
123
123
  end
124
124
  end
125
125
  puts "=> #{percentage} % feeds are notified"
@@ -1,13 +1,14 @@
1
1
  #!/bin/sh
2
2
 
3
3
  base_dir=$(cd $(dirname $0); pwd)
4
+ work_dir=$base_dir/../../performance/watch
4
5
 
5
6
  rm -rf $base_dir/watch
6
7
  mkdir -p $base_dir/watch
7
8
 
8
- DROONGA_CATALOG=$base_dir/catalog.json \
9
+ DROONGA_CATALOG=$work_dir/catalog.json \
9
10
  bundle exec fluentd \
10
- --config $base_dir/fluentd.conf &
11
+ --config $work_dir/fluentd.conf &
11
12
  FLUENTD_PID=$!
12
13
 
13
14
  sleep 1
data/bin/grn2jsons CHANGED
@@ -45,9 +45,6 @@ option_parser = OptionParser.new do |parser|
45
45
  end
46
46
  args = option_parser.parse!(ARGV)
47
47
 
48
- if options.reply_to.nil?
49
- raise "You must specify the value of the \"replyTo\" field by --reply-to option."
50
- end
51
48
  if options.dataset.nil?
52
49
  raise "You must specify the name of the dataset by --dataset option."
53
50
  end
data/doc/text/news.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # News
2
2
 
3
+ ## 0.9.0: 2014-01-29
4
+
5
+ ### Improvements
6
+
7
+ * `search`: Supported `"attributes"` for `elements` of `output`.
8
+ * `table_remove`: Implemented Groonga compatible `table_remove`
9
+ command.
10
+ * `column_create`: Implemented error handling.
11
+ * `catalog`: Supported auto reloading.
12
+ * Supported reducing responses from two or more nodes for Groonga
13
+ compatible commands.
14
+ * Supported three or more partitions.
15
+
3
16
  ## 0.8.0: 2013-12-29
4
17
 
5
18
  ### Improvements
@@ -17,7 +17,7 @@
17
17
 
18
18
  Gem::Specification.new do |gem|
19
19
  gem.name = "fluent-plugin-droonga"
20
- gem.version = "0.8.0"
20
+ gem.version = "0.9.0"
21
21
  gem.authors = ["Droonga Project"]
22
22
  gem.email = ["droonga@groonga.org"]
23
23
  gem.description = "Droonga(distributed Groonga) plugin for Fluent event collector"
@@ -34,9 +34,7 @@ module Droonga
34
34
  @data["datasets"].each do |name, dataset|
35
35
  number_of_partitions = dataset["number_of_partitions"]
36
36
  next if number_of_partitions < 2
37
- total_weight = dataset["ring"].reduce do |a, b|
38
- a[1]["weight"] + b[1]["weight"]
39
- end
37
+ total_weight = compute_total_weight(dataset)
40
38
  continuum = []
41
39
  dataset["ring"].each do |key, value|
42
40
  points = number_of_partitions * 160 * value["weight"] / total_weight
@@ -135,6 +133,13 @@ module Droonga
135
133
  end
136
134
  end
137
135
  end
136
+
137
+ private
138
+ def compute_total_weight(dataset)
139
+ dataset["ring"].reduce(0) do |result, zone|
140
+ result + zone[1]["weight"]
141
+ end
142
+ end
138
143
  end
139
144
  end
140
145
  end
@@ -19,21 +19,6 @@ require "droonga/catalog_loader"
19
19
 
20
20
  module Droonga
21
21
  class << self
22
- def catalog
23
- @catalog ||= Catalog.load
24
- end
25
- end
26
-
27
- module Catalog
28
- PATH = "catalog.json"
29
-
30
- class << self
31
- def load(path=nil)
32
- path = ENV["DROONGA_CATALOG"] || PATH
33
- path = File.expand_path(path)
34
- loader = CatalogLoader.new(path)
35
- loader.load
36
- end
37
- end
22
+ attr_accessor :catalog
38
23
  end
39
24
  end
@@ -0,0 +1,57 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 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
+ module Droonga
19
+ class CatalogObserver
20
+ DEFAULT_CATALOG_PATH = "catalog.json"
21
+ CHECK_INTERVAL = 1
22
+
23
+ def initialize(loop)
24
+ @catalog_path = catalog_path
25
+ load_catalog
26
+
27
+ watcher = Cool.io::TimerWatcher.new(CHECK_INTERVAL, true)
28
+ observer = self
29
+ watcher.on_timer do
30
+ observer.ensure_latest_catalog_loaded
31
+ end
32
+ loop.attach(watcher)
33
+ end
34
+
35
+ def ensure_latest_catalog_loaded
36
+ if catalog_updated?
37
+ load_catalog
38
+ end
39
+ end
40
+
41
+ def catalog_path
42
+ path = ENV["DROONGA_CATALOG"] || DEFAULT_CATALOG_PATH
43
+ File.expand_path(path)
44
+ end
45
+
46
+ def catalog_updated?
47
+ File.mtime(catalog_path) > @catalog_mtime
48
+ end
49
+
50
+ def load_catalog
51
+ loader = CatalogLoader.new(@catalog_path)
52
+ Droonga.catalog = loader.load
53
+ @catalog_mtime = File.mtime(@catalog_path)
54
+ $log.info "catalog loaded", path: @catalog_path, mtime: @catalog_mtime
55
+ end
56
+ end
57
+ end
@@ -23,7 +23,7 @@ module Droonga
23
23
  include Pluggable
24
24
 
25
25
  def initialize
26
- load_plugins(["basic"]) # TODO: make customizable
26
+ load_plugins(["basic", "search", "groonga"]) # TODO: make customizable
27
27
  end
28
28
 
29
29
  private
@@ -27,6 +27,7 @@ require "droonga/farm"
27
27
  require "droonga/session"
28
28
  require "droonga/replier"
29
29
  require "droonga/message_processing_error"
30
+ require "droonga/catalog_observer"
30
31
 
31
32
  module Droonga
32
33
  class Dispatcher
@@ -48,6 +49,8 @@ module Droonga
48
49
  def initialize(options)
49
50
  @options = options
50
51
  @name = @options[:name]
52
+ @loop = EventLoop.new
53
+ @catalog_observer = CatalogObserver.new(@loop)
51
54
  @sessions = {}
52
55
  @current_id = 0
53
56
  @local = Regexp.new("^#{@name}")
@@ -55,7 +58,6 @@ module Droonga
55
58
  InputAdapter.new(self, :plugins => Droonga.catalog.option("plugins"))
56
59
  @output_adapter =
57
60
  OutputAdapter.new(self, :plugins => Droonga.catalog.option("plugins"))
58
- @loop = EventLoop.new
59
61
  @farm = Farm.new(name, @loop, :dispatcher => self)
60
62
  @forwarder = Forwarder.new(@loop)
61
63
  @replier = Replier.new(@forwarder)
@@ -31,28 +31,13 @@ module Droonga
31
31
  end
32
32
 
33
33
  def scatter_all(message, key)
34
- distribute_message = [{
35
- "command"=> message["type"],
36
- "dataset"=> message["dataset"],
37
- "body"=> message["body"],
38
- "key"=> key,
39
- "type"=> "scatter",
40
- "replica"=> "all",
41
- "post"=> true
42
- }]
43
- distribute(distribute_message)
34
+ messages = [reducer(message), gatherer(message), scatterer(message, key)]
35
+ distribute(messages)
44
36
  end
45
37
 
46
38
  def broadcast_all(message)
47
- distribute_message = [{
48
- "command"=> message["type"],
49
- "dataset"=> message["dataset"],
50
- "body"=> message["body"],
51
- "type"=> "broadcast",
52
- "replica"=> "all",
53
- "post"=> true
54
- }]
55
- distribute(distribute_message)
39
+ messages = [reducer(message), gatherer(message), broadcaster(message)]
40
+ distribute(messages)
56
41
  end
57
42
 
58
43
  private
@@ -63,5 +48,48 @@ module Droonga
63
48
  super
64
49
  end
65
50
  end
51
+
52
+ def scatterer(message, key)
53
+ {
54
+ "command" => message["type"],
55
+ "dataset" => message["dataset"],
56
+ "body" => message["body"],
57
+ "key" => key,
58
+ "type" => "scatter",
59
+ "outputs" => [],
60
+ "replica" => "all",
61
+ "post" => true
62
+ }
63
+ end
64
+
65
+ def broadcaster(message)
66
+ {
67
+ "command" => message["type"],
68
+ "dataset" => message["dataset"],
69
+ "body" => message["body"],
70
+ "type" => "broadcast",
71
+ "outputs" => [],
72
+ "replica" => "all",
73
+ "post" => true
74
+ }
75
+ end
76
+
77
+ def reducer(message)
78
+ {
79
+ "type" => "reduce",
80
+ "body" => {},
81
+ "inputs" => [],
82
+ "outputs" => [],
83
+ }
84
+ end
85
+
86
+ def gatherer(message)
87
+ {
88
+ "type" => "gather",
89
+ "body" => {},
90
+ "inputs" => [],
91
+ "post" => true,
92
+ }
93
+ end
66
94
  end
67
95
  end
@@ -18,12 +18,15 @@ require "droonga/forwarder"
18
18
 
19
19
  module Droonga
20
20
  class HandlerMessenger
21
+ attr_reader :database_name
22
+
21
23
  def initialize(forwarder, message, options={})
22
24
  @forwarder = forwarder
23
25
  @message = message
24
26
  @options = options
25
27
  @replier = Replier.new(@forwarder)
26
28
  @dispatcher = @options[:dispatcher]
29
+ @database_name = options[:database]
27
30
  end
28
31
 
29
32
  def emit(value)
@@ -60,8 +63,29 @@ module Droonga
60
63
  "body" => body)
61
64
  @replier.reply(response)
62
65
  else
63
- #XXX IMPLEMENT ME!!
64
- raise error
66
+ body = {
67
+ "id" => @message.id,
68
+ "input" => "errors",
69
+ "value" => {
70
+ database_name => {
71
+ "statusCode" => status_code,
72
+ "body" => body,
73
+ },
74
+ },
75
+ }
76
+ all_dests = []
77
+ descendants.each do |name, dests|
78
+ all_dests += dests
79
+ end
80
+ all_dests.each do |dest|
81
+ if @dispatcher
82
+ @dispatcher.dispatch(body, dest)
83
+ else
84
+ message = raw_message.merge("statusCode" => status_code,
85
+ "body" => body,)
86
+ forward(message, "to" => dest, "type" => "dispatcher")
87
+ end
88
+ end
65
89
  end
66
90
  end
67
91
 
@@ -15,8 +15,10 @@
15
15
 
16
16
  module Droonga
17
17
  class MessageProcessingError < StandardError
18
+ STATUS_CODE = 500
19
+
18
20
  attr_reader :message, :detail
19
-
21
+
20
22
  def initialize(message, detail=nil)
21
23
  @message = message
22
24
  @detail = detail
@@ -27,7 +29,7 @@ module Droonga
27
29
  end
28
30
 
29
31
  def status_code
30
- 500
32
+ self.class::STATUS_CODE
31
33
  end
32
34
 
33
35
  def response_body
@@ -41,14 +43,10 @@ module Droonga
41
43
  end
42
44
 
43
45
  class BadRequest < MessageProcessingError
44
- def status_code
45
- 400
46
- end
46
+ STATUS_CODE = 400
47
47
  end
48
48
 
49
49
  class NotFound < MessageProcessingError
50
- def status_code
51
- 404
52
- end
50
+ STATUS_CODE = 404
53
51
  end
54
52
  end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013 Droonga Project
1
+ # Copyright (C) 2013-2014 Droonga Project
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -26,6 +26,22 @@ module Droonga
26
26
  @raw_message
27
27
  end
28
28
 
29
+ def status_code
30
+ @raw_message["statusCode"]
31
+ end
32
+
33
+ def status_code=(status_code)
34
+ @raw_message["statusCode"] = status_code
35
+ end
36
+
37
+ def errors
38
+ @raw_message["errors"]
39
+ end
40
+
41
+ def errors=(errors)
42
+ @raw_message["errors"] = errors
43
+ end
44
+
29
45
  def body
30
46
  @raw_message["body"]
31
47
  end
@@ -27,42 +27,49 @@ module Droonga
27
27
  def collector_gather(result)
28
28
  output = body ? body[input_name] : input_name
29
29
  if output.is_a?(Hash)
30
- elements = output["elements"]
31
- if elements && elements.is_a?(Hash)
32
- # phase 1: pre-process
33
- elements.each do |element, mapper|
34
- case mapper["type"]
35
- when "count"
36
- result[element] = result[mapper["target"]].size
37
- when "sort"
38
- # do nothing on this phase!
39
- end
40
- end
41
- # phase 2: post-process
42
- elements.each do |element, mapper|
43
- if mapper["no_output"]
44
- result.delete(element)
45
- next
46
- end
47
-
48
- case mapper["type"]
49
- when "count"
50
- # do nothing on this phase!
51
- when "sort"
52
- # because "count" type mapper requires all items of the array,
53
- # I have to apply "sort" type mapper later.
54
- if result[element]
55
- result[element] = apply_output_range(result[element], mapper)
56
- result[element] = apply_output_attributes_and_format(result[element], mapper)
57
- end
58
- end
59
- end
60
- end
61
30
  output = output["output"]
62
31
  end
63
32
  emit(output, result)
64
33
  end
65
34
 
35
+ command :collector_reduce
36
+ def collector_reduce(request)
37
+ body[input_name].each do |output, deal|
38
+ left_value = output_values[output]
39
+ right_value = request
40
+ value = reduce(deal, left_value, right_value)
41
+ emit(output, value)
42
+ end
43
+ end
44
+
45
+ def reduce(deal, left_value, right_value)
46
+ if left_value.nil? || right_value.nil?
47
+ return right_value || left_value
48
+ end
49
+
50
+ reduced_value = nil
51
+
52
+ case deal["type"]
53
+ when "and"
54
+ reduced_value = left_value && right_value
55
+ when "sum"
56
+ reduced_value = sum(left_value, right_value)
57
+ reduced_value = apply_output_range(reduced_value,
58
+ "limit" => deal["limit"])
59
+ when "average"
60
+ reduced_value = (left_value.to_f + right_value.to_f) / 2
61
+ when "sort"
62
+ reduced_value = merge(left_value,
63
+ right_value,
64
+ :operators => deal["operators"],
65
+ :key_column => deal["key_column"])
66
+ reduced_value = apply_output_range(reduced_value,
67
+ "limit" => deal["limit"])
68
+ end
69
+
70
+ reduced_value
71
+ end
72
+
66
73
  def apply_output_range(items, output)
67
74
  if items && items.is_a?(Array)
68
75
  offset = output["offset"] || 0
@@ -78,58 +85,14 @@ module Droonga
78
85
  items
79
86
  end
80
87
 
81
- def apply_output_attributes_and_format(items, output)
82
- attributes = output["attributes"]
83
- if attributes
84
- format = output["format"]
85
- if format == "complex"
86
- items.collect! do |item|
87
- complex_item = {}
88
- attributes.each_with_index do |label, index|
89
- complex_item[label] = item[index]
90
- end
91
- complex_item
92
- end
93
- else
94
- items.collect! do |item|
95
- item[0...attributes.size]
96
- end
97
- end
98
- end
99
- items
100
- end
101
-
102
- command :collector_reduce
103
- def collector_reduce(request)
104
- return unless request
105
- body[input_name].each do |output, elements|
106
- value = request
107
- old_value = output_values[output]
108
- value = reduce(elements, old_value, request) if old_value
109
- emit(output, value)
110
- end
111
- end
112
-
113
- def reduce(elements, *values)
114
- result = {}
115
- elements.each do |key, deal|
116
- reduced_values = nil
117
-
118
- case deal["type"]
119
- when "sum"
120
- reduced_values = values[0][key] + values[1][key]
121
- when "sort"
122
- reduced_values = merge(values[0][key],
123
- values[1][key],
124
- :operators => deal["operators"],
125
- :key_column => deal["key_column"])
126
- end
127
-
128
- reduced_values = apply_output_range(reduced_values, "limit" => deal["limit"])
88
+ def sum(x, y)
89
+ return x || y if x.nil? or y.nil?
129
90
 
130
- result[key] = reduced_values
91
+ if x.is_a?(Hash) && y.is_a?(Hash)
92
+ x.merge(y)
93
+ else
94
+ x + y
131
95
  end
132
- return result
133
96
  end
134
97
 
135
98
  def merge(x, y, options={})
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 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 GroongaCollector < BasicCollector
22
+ repository.register("groonga", self)
23
+
24
+ command :collector_groonga_gather
25
+ def collector_groonga_gather(result)
26
+ collector_gather(result)
27
+ end
28
+
29
+ command :collector_groonga_reduce
30
+ def collector_groonga_reduce(request)
31
+ collector_reduce(request)
32
+ end
33
+
34
+ def reduce(deal, left_value, right_value)
35
+ reduced_value = nil
36
+
37
+ case deal["type"]
38
+ when "groonga_result"
39
+ #XXX how to merge multiple erros?
40
+ #XXX how to mix regular results and erros?
41
+ # reduced_value = merge_groonga_result(left_value, right_value)
42
+ reduced_value = left_value || right_value
43
+ else
44
+ reduced_value = super
45
+ end
46
+
47
+ reduced_value
48
+ end
49
+
50
+ def merge_groonga_result(left_value, right_value)
51
+ result = []
52
+
53
+ result << merge_groonga_header(left_value.shift, right_value.shift)
54
+
55
+ left_value.each_with_index do |left, index|
56
+ right = right_value[index]
57
+ result << reduce({ "type" => "and" }, left, right)
58
+ end
59
+
60
+ result
61
+ end
62
+
63
+ def merge_groonga_header(left_header, right_header)
64
+ status = [left_header.shift, right_header.shift].min
65
+
66
+ start_time = reduce({ "type" => "average" },
67
+ left_header.shift,
68
+ right_header.shift)
69
+
70
+ elapsed_time = reduce({ "type" => "average" },
71
+ left_header.shift,
72
+ right_header.shift)
73
+
74
+ #XXX we should merge error informations more smarter...
75
+ error_information = reduce({ "type" => "sum",
76
+ "limit" => UNLIMITED },
77
+ left_header,
78
+ right_header)
79
+
80
+ [status, start_time, elapsed_time] + error_information
81
+ end
82
+ end
83
+ end