fluent-plugin-droonga 0.0.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.yardopts +7 -0
  4. data/Gemfile +14 -2
  5. data/LICENSE.txt +1 -1
  6. data/README.md +1 -1
  7. data/Rakefile +27 -5
  8. data/benchmark/benchmark.rb +1 -1
  9. data/benchmark/utils.rb +9 -6
  10. data/benchmark/watch/benchmark-notify.rb +2 -2
  11. data/benchmark/watch/benchmark-publish.rb +1 -1
  12. data/benchmark/watch/benchmark-scan.rb +1 -1
  13. data/benchmark/watch/catalog.json +1 -1
  14. data/bin/grn2jsons +1 -1
  15. data/fluent-plugin-droonga.gemspec +5 -3
  16. data/lib/droonga/adapter.rb +13 -130
  17. data/lib/droonga/adapter_plugin.rb +51 -0
  18. data/lib/droonga/catalog.rb +2 -2
  19. data/lib/droonga/collector.rb +107 -0
  20. data/lib/droonga/collector_plugin.rb +82 -0
  21. data/lib/droonga/command_mapper.rb +1 -1
  22. data/lib/droonga/{proxy.rb → dispatcher.rb} +116 -151
  23. data/lib/droonga/distributor.rb +51 -0
  24. data/lib/droonga/distributor_plugin.rb +59 -0
  25. data/lib/droonga/engine.rb +9 -50
  26. data/lib/droonga/farm.rb +47 -0
  27. data/lib/droonga/forwarder.rb +125 -0
  28. data/lib/droonga/handler.rb +69 -60
  29. data/lib/droonga/handler_plugin.rb +22 -11
  30. data/lib/droonga/input_message.rb +51 -0
  31. data/lib/droonga/job_queue.rb +5 -1
  32. data/lib/droonga/job_queue_schema.rb +1 -1
  33. data/lib/droonga/logger.rb +1 -1
  34. data/lib/droonga/partition.rb +76 -0
  35. data/lib/droonga/pluggable.rb +62 -0
  36. data/lib/droonga/plugin.rb +18 -16
  37. data/lib/droonga/plugin/{adapter_groonga.rb → adapter/groonga.rb} +10 -10
  38. data/lib/droonga/plugin/adapter/groonga/select.rb +13 -4
  39. data/lib/droonga/plugin/collector/basic.rb +142 -0
  40. data/lib/droonga/plugin/distributor/crud.rb +43 -0
  41. data/lib/droonga/plugin/distributor/groonga.rb +37 -0
  42. data/lib/droonga/plugin/distributor/search.rb +273 -0
  43. data/lib/droonga/plugin/distributor/watch.rb +39 -0
  44. data/lib/droonga/plugin/{handler_add.rb → handler/add.rb} +6 -6
  45. data/lib/droonga/plugin/{handler_forward.rb → handler/forward.rb} +9 -4
  46. data/lib/droonga/plugin/{handler_groonga.rb → handler/groonga.rb} +36 -4
  47. data/lib/droonga/plugin/handler/groonga/column_create.rb +5 -9
  48. data/lib/droonga/plugin/handler/groonga/table_create.rb +9 -18
  49. data/lib/droonga/plugin/{handler_search.rb → handler/search.rb} +4 -4
  50. data/lib/droonga/plugin/{handler_watch.rb → handler/watch.rb} +4 -4
  51. data/lib/droonga/plugin_loader.rb +45 -0
  52. data/lib/droonga/plugin_registerable.rb +51 -0
  53. data/lib/droonga/plugin_repository.rb +56 -0
  54. data/lib/droonga/processor.rb +64 -0
  55. data/lib/droonga/searcher.rb +16 -7
  56. data/lib/droonga/server.rb +5 -9
  57. data/lib/droonga/sweeper.rb +1 -1
  58. data/lib/droonga/watch_schema.rb +1 -1
  59. data/lib/droonga/watcher.rb +1 -1
  60. data/lib/droonga/worker.rb +21 -9
  61. data/lib/fluent/plugin/out_droonga.rb +33 -15
  62. data/lib/groonga_command_converter.rb +1 -1
  63. data/sample/cluster/fluentd.conf +0 -1
  64. data/test/command/config/default/catalog.json +43 -0
  65. data/test/command/config/default/fluentd.conf +11 -0
  66. data/test/command/fixture/documents.jsons +208 -0
  67. data/test/command/fixture/user-table-array.jsons +38 -0
  68. data/test/command/fixture/user-table.jsons +38 -0
  69. data/test/command/run-test.rb +35 -0
  70. data/test/command/suite/add/minimum.expected +12 -0
  71. data/test/command/suite/add/minimum.test +11 -0
  72. data/test/command/suite/add/with-values.expected +12 -0
  73. data/test/command/suite/add/with-values.test +17 -0
  74. data/test/command/suite/add/without-key.expected +12 -0
  75. data/test/command/suite/add/without-key.test +16 -0
  76. data/test/command/suite/groonga/column_create/scalar.expected +34 -0
  77. data/test/command/suite/groonga/column_create/scalar.test +17 -0
  78. data/test/command/suite/groonga/column_create/vector.expected +34 -0
  79. data/test/command/suite/groonga/column_create/vector.test +18 -0
  80. data/test/command/suite/groonga/select/minimum.expected +26 -0
  81. data/test/command/suite/groonga/select/minimum.test +8 -0
  82. data/test/command/suite/groonga/table_create/array.expected +17 -0
  83. data/test/command/suite/groonga/table_create/array.test +8 -0
  84. data/test/command/suite/groonga/table_create/hash.expected +17 -0
  85. data/test/command/suite/groonga/table_create/hash.test +8 -0
  86. data/test/command/suite/search/array-attribute-label.expected +25 -0
  87. data/test/command/suite/search/array-attribute-label.test +30 -0
  88. data/test/command/suite/search/chained-queries.expected +45 -0
  89. data/test/command/suite/search/chained-queries.test +43 -0
  90. data/test/command/suite/search/complex.expected +52 -0
  91. data/test/command/suite/search/complex.test +25 -0
  92. data/test/command/suite/search/condition-nested.expected +19 -0
  93. data/test/command/suite/search/condition-nested.test +29 -0
  94. data/test/command/suite/search/condition-query.expected +28 -0
  95. data/test/command/suite/search/condition-query.test +25 -0
  96. data/test/command/suite/search/condition-script.expected +28 -0
  97. data/test/command/suite/search/condition-script.test +28 -0
  98. data/test/command/suite/search/hash-attribute-label.expected +34 -0
  99. data/test/command/suite/search/hash-attribute-label.test +38 -0
  100. data/test/command/suite/search/minimum.expected +13 -0
  101. data/test/command/suite/search/minimum.test +16 -0
  102. data/test/command/suite/search/multiple-queries.expected +39 -0
  103. data/test/command/suite/search/multiple-queries.test +39 -0
  104. data/test/command/suite/search/output-range.expected +28 -0
  105. data/test/command/suite/search/output-range.test +25 -0
  106. data/test/command/suite/search/simple.expected +52 -0
  107. data/test/command/suite/search/simple.test +24 -0
  108. data/test/command/suite/search/sort-and-output-range.expected +25 -0
  109. data/test/command/suite/search/sort-and-output-range.test +29 -0
  110. data/test/command/suite/search/sort-range.expected +28 -0
  111. data/test/command/suite/search/sort-range.test +28 -0
  112. data/test/command/suite/search/sort-with-invisible-column.expected +28 -0
  113. data/test/command/suite/search/sort-with-invisible-column.test +28 -0
  114. data/test/unit/fixtures/array.grn +18 -0
  115. data/test/{fixtures → unit/fixtures}/catalog.json +0 -0
  116. data/test/{fixtures → unit/fixtures}/document.grn +20 -9
  117. data/test/unit/fixtures/reference/array.grn +11 -0
  118. data/test/unit/fixtures/reference/hash.grn +7 -0
  119. data/test/{helper.rb → unit/helper.rb} +2 -1
  120. data/test/{helper → unit/helper}/fixture.rb +1 -1
  121. data/test/unit/helper/plugin_helper.rb +38 -0
  122. data/test/{helper → unit/helper}/sandbox.rb +19 -6
  123. data/test/{helper → unit/helper}/stub_worker.rb +1 -1
  124. data/test/{helper → unit/helper}/watch_helper.rb +1 -13
  125. data/test/{plugin → unit/plugin}/adapter/groonga/test_select.rb +108 -4
  126. data/test/unit/plugin/collector/test_basic.rb +558 -0
  127. data/test/unit/plugin/distributor/test_search.rb +914 -0
  128. data/test/{plugin → unit/plugin}/handler/groonga/test_column_create.rb +18 -14
  129. data/test/{plugin → unit/plugin}/handler/groonga/test_table_create.rb +13 -11
  130. data/test/{plugin/handler/test_handler_add.rb → unit/plugin/handler/test_add.rb} +2 -14
  131. data/test/{plugin/handler/test_handler_groonga.rb → unit/plugin/handler/test_groonga.rb} +6 -26
  132. data/test/unit/plugin/handler/test_search.rb +601 -0
  133. data/test/{plugin/handler/test_handler_watch.rb → unit/plugin/handler/test_watch.rb} +2 -2
  134. data/test/{run-test.rb → unit/run-test.rb} +3 -3
  135. data/test/{test_adapter.rb → unit/test_adapter.rb} +17 -14
  136. data/test/{test_catalog.rb → unit/test_catalog.rb} +4 -4
  137. data/test/{test_command_mapper.rb → unit/test_command_mapper.rb} +1 -1
  138. data/test/{test_groonga_command_converter.rb → unit/test_groonga_command_converter.rb} +3 -3
  139. data/test/{test_job_queue_schema.rb → unit/test_job_queue_schema.rb} +1 -1
  140. data/test/{test_output.rb → unit/test_output.rb} +9 -9
  141. data/test/{test_handler.rb → unit/test_plugin.rb} +19 -22
  142. data/test/unit/test_plugin_repository.rb +89 -0
  143. data/test/{test_sweeper.rb → unit/test_sweeper.rb} +1 -1
  144. data/test/{test_watch_schema.rb → unit/test_watch_schema.rb} +1 -1
  145. data/test/{test_watcher.rb → unit/test_watcher.rb} +1 -1
  146. metadata +226 -66
  147. data/lib/droonga/executor.rb +0 -289
  148. data/lib/droonga/plugin/handler_proxy.rb +0 -82
  149. data/test/plugin/handler/test_handler_search.rb +0 -512
  150. data/test/test_worker.rb +0 -144
@@ -0,0 +1,914 @@
1
+ # Copyright (C) 2013 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
+ require "droonga/plugin/distributor/search"
17
+
18
+ class SearchDistributorTest < Test::Unit::TestCase
19
+ include PluginHelper
20
+
21
+ def setup
22
+ setup_database
23
+ setup_plugin(Droonga::SearchDistributor)
24
+ end
25
+
26
+ def teardown
27
+ teardown_plugin
28
+ teardown_database
29
+ end
30
+
31
+ class MultipleQueriesTest < SearchDistributorTest
32
+ def test_distribute
33
+ envelope = {
34
+ "type" => "search",
35
+ "dataset" => "Droonga",
36
+ "body" => {
37
+ "queries" => {
38
+ "query1" => {
39
+ "source" => "User",
40
+ "output" => {
41
+ "format" => "complex",
42
+ "elements" => ["count", "records"],
43
+ "attributes" => [],
44
+ "offset" => 0,
45
+ "limit" => 10,
46
+ },
47
+ },
48
+ "query2" => {
49
+ "source" => "User",
50
+ "output" => {
51
+ "format" => "complex",
52
+ "elements" => ["count", "records"],
53
+ "attributes" => [],
54
+ "offset" => 0,
55
+ "limit" => 20,
56
+ },
57
+ },
58
+ "query3" => {
59
+ "source" => "User",
60
+ "output" => {
61
+ "format" => "complex",
62
+ "elements" => ["count", "records"],
63
+ "attributes" => [],
64
+ "offset" => 0,
65
+ "limit" => 30,
66
+ },
67
+ },
68
+ },
69
+ },
70
+ }
71
+
72
+ @plugin.process("search", envelope)
73
+
74
+ message = []
75
+
76
+ message << {
77
+ "type" => "reduce",
78
+ "body" => {
79
+ "query1" => {
80
+ "query1_reduced" => {
81
+ "count" => {
82
+ "type" => "sum",
83
+ },
84
+ "records" => {
85
+ "type" => "sort",
86
+ "operators" => [],
87
+ "limit" => 10,
88
+ },
89
+ },
90
+ },
91
+ },
92
+ "inputs" => ["query1"],
93
+ "outputs" => ["query1_reduced"],
94
+ }
95
+ message << {
96
+ "type" => "reduce",
97
+ "body" => {
98
+ "query2" => {
99
+ "query2_reduced" => {
100
+ "count" => {
101
+ "type" => "sum",
102
+ },
103
+ "records" => {
104
+ "type" => "sort",
105
+ "operators" => [],
106
+ "limit" => 20,
107
+ },
108
+ },
109
+ },
110
+ },
111
+ "inputs" => ["query2"],
112
+ "outputs" => ["query2_reduced"],
113
+ }
114
+ message << {
115
+ "type" => "reduce",
116
+ "body" => {
117
+ "query3" => {
118
+ "query3_reduced" => {
119
+ "count" => {
120
+ "type" => "sum",
121
+ },
122
+ "records" => {
123
+ "type" => "sort",
124
+ "operators" => [],
125
+ "limit" => 30,
126
+ },
127
+ },
128
+ },
129
+ },
130
+ "inputs" => ["query3"],
131
+ "outputs" => ["query3_reduced"],
132
+ }
133
+
134
+ gatherer = {
135
+ "type" => "gather",
136
+ "body" => {
137
+ "query1_reduced" => {
138
+ "output" => "query1",
139
+ "element" => "records",
140
+ "offset" => 0,
141
+ "limit" => 10,
142
+ "format" => "complex",
143
+ "attributes" => [],
144
+ },
145
+ "query2_reduced" => {
146
+ "output" => "query2",
147
+ "element" => "records",
148
+ "offset" => 0,
149
+ "limit" => 20,
150
+ "format" => "complex",
151
+ "attributes" => [],
152
+ },
153
+ "query3_reduced" => {
154
+ "output" => "query3",
155
+ "element" => "records",
156
+ "offset" => 0,
157
+ "limit" => 30,
158
+ "format" => "complex",
159
+ "attributes" => [],
160
+ },
161
+ },
162
+ "inputs" => [
163
+ "query1_reduced",
164
+ "query2_reduced",
165
+ "query3_reduced",
166
+ ],
167
+ "post" => true,
168
+ }
169
+ message << gatherer
170
+
171
+ searcher = {
172
+ "type" => "broadcast",
173
+ "command" => "search",
174
+ "dataset" => "Droonga",
175
+ "body" => {
176
+ "queries" => {
177
+ "query1" => {
178
+ "source" => "User",
179
+ "output" => {
180
+ "format" => "simple",
181
+ "elements" => ["count", "records"],
182
+ "attributes" => [],
183
+ "offset" => 0,
184
+ "limit" => 10,
185
+ },
186
+ },
187
+ "query2" => {
188
+ "source" => "User",
189
+ "output" => {
190
+ "format" => "simple",
191
+ "elements" => ["count", "records"],
192
+ "attributes" => [],
193
+ "offset" => 0,
194
+ "limit" => 20,
195
+ },
196
+ },
197
+ "query3" => {
198
+ "source" => "User",
199
+ "output" => {
200
+ "format" => "simple",
201
+ "elements" => ["count", "records"],
202
+ "attributes" => [],
203
+ "offset" => 0,
204
+ "limit" => 30,
205
+ },
206
+ },
207
+ },
208
+ },
209
+ "outputs" => [
210
+ "query1",
211
+ "query2",
212
+ "query3",
213
+ ],
214
+ "replica" => "random",
215
+ }
216
+ message << searcher
217
+
218
+ assert_equal(message, @posted.last.last)
219
+ end
220
+ end
221
+
222
+ class SingleQueryTest < SearchDistributorTest
223
+ def test_no_output
224
+ envelope = {
225
+ "type" => "search",
226
+ "dataset" => "Droonga",
227
+ "body" => {
228
+ "queries" => {
229
+ "no_output" => {
230
+ "source" => "User",
231
+ "sortBy" => {
232
+ "keys" => ["name"],
233
+ "offset" => 0,
234
+ "limit" => 1,
235
+ },
236
+ },
237
+ },
238
+ },
239
+ }
240
+
241
+ @plugin.process("search", envelope)
242
+
243
+ message = []
244
+ message << gatherer(envelope, :no_output => true)
245
+ message << searcher(envelope, :no_output => true)
246
+ assert_equal(message, @posted.last.last)
247
+ end
248
+
249
+ def test_no_records_element
250
+ envelope = {
251
+ "type" => "search",
252
+ "dataset" => "Droonga",
253
+ "body" => {
254
+ "queries" => {
255
+ "no_records" => {
256
+ "source" => "User",
257
+ "sortBy" => {
258
+ "keys" => ["name"],
259
+ "offset" => 0,
260
+ "limit" => 1,
261
+ },
262
+ "output" => {
263
+ "elements" => ["count"],
264
+ },
265
+ },
266
+ },
267
+ },
268
+ }
269
+
270
+ @plugin.process("search", envelope)
271
+
272
+ message = []
273
+ message << reducer(envelope, {
274
+ "count" => {
275
+ "type" => "sum",
276
+ },
277
+ })
278
+ message << gatherer(envelope)
279
+ message << searcher(envelope, :output_limit => 0)
280
+ assert_equal(message, @posted.last.last)
281
+ end
282
+
283
+ def test_no_output_limit
284
+ envelope = {
285
+ "type" => "search",
286
+ "dataset" => "Droonga",
287
+ "body" => {
288
+ "queries" => {
289
+ "no_limit" => {
290
+ "source" => "User",
291
+ "output" => {
292
+ "format" => "complex",
293
+ "elements" => ["count", "records"],
294
+ },
295
+ },
296
+ },
297
+ },
298
+ }
299
+
300
+ @plugin.process("search", envelope)
301
+
302
+ message = []
303
+ message << reducer(envelope, {
304
+ "count" => {
305
+ "type" => "sum",
306
+ },
307
+ })
308
+ message << gatherer(envelope)
309
+ message << searcher(envelope, :output_offset => 0,
310
+ :output_limit => 0)
311
+ assert_equal(message, @posted.last.last)
312
+ end
313
+
314
+ def test_have_records
315
+ envelope = {
316
+ "type" => "search",
317
+ "dataset" => "Droonga",
318
+ "body" => {
319
+ "queries" => {
320
+ "have_records" => {
321
+ "source" => "User",
322
+ "output" => {
323
+ "format" => "complex",
324
+ "elements" => ["records"],
325
+ "attributes" => ["_key", "name", "age"],
326
+ "offset" => 0,
327
+ "limit" => 1,
328
+ },
329
+ },
330
+ },
331
+ },
332
+ }
333
+
334
+ @plugin.process("search", envelope)
335
+
336
+ message = []
337
+ message << reducer(envelope, {
338
+ "records" => {
339
+ "type" => "sort",
340
+ "operators" => [],
341
+ "limit" => 1,
342
+ },
343
+ })
344
+ message << gatherer(envelope, :offset => 0,
345
+ :limit => 1,
346
+ :element => "records",
347
+ :format => "complex",
348
+ :attributes => ["_key", "name", "age"])
349
+ message << searcher(envelope, :output_offset => 0,
350
+ :output_limit => 1)
351
+ assert_equal(message, @posted.last.last)
352
+ end
353
+
354
+ def test_have_output_offset
355
+ envelope = {
356
+ "type" => "search",
357
+ "dataset" => "Droonga",
358
+ "body" => {
359
+ "queries" => {
360
+ "have_records" => {
361
+ "source" => "User",
362
+ "output" => {
363
+ "format" => "complex",
364
+ "elements" => ["records"],
365
+ "attributes" => ["_key", "name", "age"],
366
+ "offset" => 1,
367
+ "limit" => 1,
368
+ },
369
+ },
370
+ },
371
+ },
372
+ }
373
+
374
+ @plugin.process("search", envelope)
375
+
376
+ message = []
377
+ message << reducer(envelope, {
378
+ "records" => {
379
+ "type" => "sort",
380
+ "operators" => [],
381
+ "limit" => 2,
382
+ },
383
+ })
384
+ message << gatherer(envelope, :offset => 1,
385
+ :limit => 1,
386
+ :element => "records",
387
+ :format => "complex",
388
+ :attributes => ["_key", "name", "age"])
389
+ message << searcher(envelope, :output_offset => 0,
390
+ :output_limit => 2)
391
+ assert_equal(message, @posted.last.last)
392
+ end
393
+
394
+ def test_have_simple_sortBy
395
+ envelope = {
396
+ "type" => "search",
397
+ "dataset" => "Droonga",
398
+ "body" => {
399
+ "queries" => {
400
+ "have_records" => {
401
+ "source" => "User",
402
+ "sortBy" => ["name"],
403
+ "output" => {
404
+ "format" => "complex",
405
+ "elements" => ["records"],
406
+ "attributes" => ["_key", "name", "age"],
407
+ "offset" => 0,
408
+ "limit" => 1,
409
+ },
410
+ },
411
+ },
412
+ },
413
+ }
414
+
415
+ @plugin.process("search", envelope)
416
+
417
+ message = []
418
+ message << reducer(envelope, {
419
+ "records" => {
420
+ "type" => "sort",
421
+ "operators" => [
422
+ { "column" => 1, "operator" => "<" },
423
+ ],
424
+ "limit" => 1,
425
+ },
426
+ })
427
+ message << gatherer(envelope, :offset => 0,
428
+ :limit => 1,
429
+ :element => "records",
430
+ :format => "complex",
431
+ :attributes => ["_key", "name", "age"])
432
+ message << searcher(envelope, :output_offset => 0,
433
+ :output_limit => 1)
434
+ assert_equal(message, @posted.last.last)
435
+ end
436
+
437
+ def test_have_sortBy
438
+ envelope = {
439
+ "type" => "search",
440
+ "dataset" => "Droonga",
441
+ "body" => {
442
+ "queries" => {
443
+ "have_records" => {
444
+ "source" => "User",
445
+ "sortBy" => {
446
+ "keys" => ["name"],
447
+ },
448
+ "output" => {
449
+ "format" => "complex",
450
+ "elements" => ["records"],
451
+ "attributes" => ["_key", "name", "age"],
452
+ "offset" => 0,
453
+ "limit" => 1,
454
+ },
455
+ },
456
+ },
457
+ },
458
+ }
459
+
460
+ @plugin.process("search", envelope)
461
+
462
+ message = []
463
+ message << reducer(envelope, {
464
+ "records" => {
465
+ "type" => "sort",
466
+ "operators" => [
467
+ { "column" => 1, "operator" => "<" },
468
+ ],
469
+ "limit" => 1,
470
+ },
471
+ })
472
+ message << gatherer(envelope, :offset => 0,
473
+ :limit => 1,
474
+ :element => "records",
475
+ :format => "complex",
476
+ :attributes => ["_key", "name", "age"])
477
+ message << searcher(envelope, :sort_offset => 0,
478
+ :sort_limit => 1,
479
+ :output_offset => 0,
480
+ :output_limit => 1)
481
+ assert_equal(message, @posted.last.last)
482
+ end
483
+
484
+ def test_have_sortBy_offset_limit
485
+ envelope = {
486
+ "type" => "search",
487
+ "dataset" => "Droonga",
488
+ "body" => {
489
+ "queries" => {
490
+ "have_records" => {
491
+ "source" => "User",
492
+ "sortBy" => {
493
+ "keys" => ["name"],
494
+ "offset" => 1,
495
+ "limit" => 2,
496
+ },
497
+ "output" => {
498
+ "format" => "complex",
499
+ "elements" => ["records"],
500
+ "attributes" => ["_key", "name", "age"],
501
+ "offset" => 4,
502
+ "limit" => 8,
503
+ },
504
+ },
505
+ },
506
+ },
507
+ }
508
+
509
+ @plugin.process("search", envelope)
510
+
511
+ message = []
512
+ message << reducer(envelope, {
513
+ "records" => {
514
+ "type" => "sort",
515
+ "operators" => [
516
+ { "column" => 1, "operator" => "<" },
517
+ ],
518
+ "limit" => 1 + 4 + [2, 8].min,
519
+ },
520
+ })
521
+ message << gatherer(envelope, :offset => 5,
522
+ :limit => 2,
523
+ :element => "records",
524
+ :format => "complex",
525
+ :attributes => ["_key", "name", "age"])
526
+ message << searcher(envelope, :sort_offset => 0,
527
+ :sort_limit => 7,
528
+ :output_offset => 0,
529
+ :output_limit => 7)
530
+ assert_equal(message, @posted.last.last)
531
+ end
532
+
533
+ def test_have_sortBy_with_infinity_output_limit
534
+ envelope = {
535
+ "type" => "search",
536
+ "dataset" => "Droonga",
537
+ "body" => {
538
+ "queries" => {
539
+ "have_records" => {
540
+ "source" => "User",
541
+ "sortBy" => {
542
+ "keys" => ["name"],
543
+ "offset" => 1,
544
+ "limit" => 2,
545
+ },
546
+ "output" => {
547
+ "format" => "complex",
548
+ "elements" => ["records"],
549
+ "attributes" => ["_key", "name", "age"],
550
+ "offset" => 4,
551
+ "limit" => -1,
552
+ },
553
+ },
554
+ },
555
+ },
556
+ }
557
+
558
+ @plugin.process("search", envelope)
559
+
560
+ message = []
561
+ message << reducer(envelope, {
562
+ "records" => {
563
+ "type" => "sort",
564
+ "operators" => [
565
+ { "column" => 1, "operator" => "<" },
566
+ ],
567
+ "limit" => 1 + 4 + 2,
568
+ },
569
+ })
570
+ message << gatherer(envelope, :offset => 5,
571
+ :limit => 2,
572
+ :element => "records",
573
+ :format => "complex",
574
+ :attributes => ["_key", "name", "age"])
575
+ message << searcher(envelope, :sort_offset => 0,
576
+ :sort_limit => 7,
577
+ :output_offset => 0,
578
+ :output_limit => 7)
579
+ assert_equal(message, @posted.last.last)
580
+ end
581
+
582
+ def test_have_sortBy_with_infinity_sort_limit
583
+ envelope = {
584
+ "type" => "search",
585
+ "dataset" => "Droonga",
586
+ "body" => {
587
+ "queries" => {
588
+ "have_records" => {
589
+ "source" => "User",
590
+ "sortBy" => {
591
+ "keys" => ["name"],
592
+ "offset" => 1,
593
+ "limit" => -1,
594
+ },
595
+ "output" => {
596
+ "format" => "complex",
597
+ "elements" => ["records"],
598
+ "attributes" => ["_key", "name", "age"],
599
+ "offset" => 4,
600
+ "limit" => 8,
601
+ },
602
+ },
603
+ },
604
+ },
605
+ }
606
+
607
+ @plugin.process("search", envelope)
608
+
609
+ message = []
610
+ message << reducer(envelope, {
611
+ "records" => {
612
+ "type" => "sort",
613
+ "operators" => [
614
+ { "column" => 1, "operator" => "<" },
615
+ ],
616
+ "limit" => 1 + 4 + 8,
617
+ },
618
+ })
619
+ message << gatherer(envelope, :offset => 5,
620
+ :limit => 8,
621
+ :element => "records",
622
+ :format => "complex",
623
+ :attributes => ["_key", "name", "age"])
624
+ message << searcher(envelope, :sort_offset => 0,
625
+ :sort_limit => 8,
626
+ :output_offset => 0,
627
+ :output_limit => 8)
628
+ assert_equal(message, @posted.last.last)
629
+ end
630
+
631
+ def test_have_sortBy_with_infinity_limit
632
+ envelope = {
633
+ "type" => "search",
634
+ "dataset" => "Droonga",
635
+ "body" => {
636
+ "queries" => {
637
+ "have_records" => {
638
+ "source" => "User",
639
+ "sortBy" => {
640
+ "keys" => ["name"],
641
+ "offset" => 1,
642
+ "limit" => -1,
643
+ },
644
+ "output" => {
645
+ "format" => "complex",
646
+ "elements" => ["records"],
647
+ "attributes" => ["_key", "name", "age"],
648
+ "offset" => 4,
649
+ "limit" => -1,
650
+ },
651
+ },
652
+ },
653
+ },
654
+ }
655
+
656
+ @plugin.process("search", envelope)
657
+
658
+ message = []
659
+ message << reducer(envelope, {
660
+ "records" => {
661
+ "type" => "sort",
662
+ "operators" => [
663
+ { "column" => 1, "operator" => "<" },
664
+ ],
665
+ "limit" => -1,
666
+ },
667
+ })
668
+ message << gatherer(envelope, :offset => 5,
669
+ :limit => -1,
670
+ :element => "records",
671
+ :format => "complex",
672
+ :attributes => ["_key", "name", "age"])
673
+ message << searcher(envelope, :sort_offset => 0,
674
+ :sort_limit => -1,
675
+ :output_offset => 0,
676
+ :output_limit => -1)
677
+ assert_equal(message, @posted.last.last)
678
+ end
679
+
680
+ def test_have_sortBy_with_multiple_sort_keys
681
+ envelope = {
682
+ "type" => "search",
683
+ "dataset" => "Droonga",
684
+ "body" => {
685
+ "queries" => {
686
+ "have_records" => {
687
+ "source" => "User",
688
+ "sortBy" => {
689
+ "keys" => ["-age", "name"],
690
+ "limit" => -1,
691
+ },
692
+ "output" => {
693
+ "format" => "complex",
694
+ "elements" => ["records"],
695
+ "attributes" => ["_key", "name", "age"],
696
+ "limit" => -1,
697
+ },
698
+ },
699
+ },
700
+ },
701
+ }
702
+
703
+ @plugin.process("search", envelope)
704
+
705
+ message = []
706
+ message << reducer(envelope, {
707
+ "records" => {
708
+ "type" => "sort",
709
+ "operators" => [
710
+ { "column" => 2, "operator" => ">" },
711
+ { "column" => 1, "operator" => "<" },
712
+ ],
713
+ "limit" => -1,
714
+ },
715
+ })
716
+ message << gatherer(envelope, :offset => 0,
717
+ :limit => -1,
718
+ :element => "records",
719
+ :format => "complex",
720
+ :attributes => ["_key", "name", "age"])
721
+ message << searcher(envelope, :sort_offset => 0,
722
+ :sort_limit => -1,
723
+ :output_offset => 0,
724
+ :output_limit => -1)
725
+ assert_equal(message, @posted.last.last)
726
+ end
727
+
728
+ def test_have_sortBy_with_missing_sort_attributes
729
+ envelope = {
730
+ "type" => "search",
731
+ "dataset" => "Droonga",
732
+ "body" => {
733
+ "queries" => {
734
+ "have_records" => {
735
+ "source" => "User",
736
+ "sortBy" => {
737
+ "keys" => ["-public_age", "public_name"],
738
+ "limit" => -1,
739
+ },
740
+ "output" => {
741
+ "format" => "complex",
742
+ "elements" => ["records"],
743
+ "attributes" => ["_key", "name", "age"],
744
+ "limit" => -1,
745
+ },
746
+ },
747
+ },
748
+ },
749
+ }
750
+
751
+ @plugin.process("search", envelope)
752
+
753
+ message = []
754
+ message << reducer(envelope, {
755
+ "records" => {
756
+ "type" => "sort",
757
+ "operators" => [
758
+ { "column" => 3, "operator" => ">" },
759
+ { "column" => 4, "operator" => "<" },
760
+ ],
761
+ "limit" => -1,
762
+ },
763
+ })
764
+ message << gatherer(envelope, :offset => 0,
765
+ :limit => -1,
766
+ :element => "records",
767
+ :format => "complex",
768
+ :attributes => ["_key", "name", "age"])
769
+ message << searcher(envelope, :sort_offset => 0,
770
+ :sort_limit => -1,
771
+ :output_offset => 0,
772
+ :output_limit => -1,
773
+ :extra_attributes => ["public_age", "public_name"])
774
+ assert_equal(message, @posted.last.last)
775
+ end
776
+
777
+ def test_hash_attributes
778
+ envelope = {
779
+ "type" => "search",
780
+ "dataset" => "Droonga",
781
+ "body" => {
782
+ "queries" => {
783
+ "have_records" => {
784
+ "source" => "User",
785
+ "sortBy" => {
786
+ "keys" => ["-public_age", "public_name"],
787
+ "limit" => -1,
788
+ },
789
+ "output" => {
790
+ "format" => "complex",
791
+ "elements" => ["records"],
792
+ "attributes" => {
793
+ "id" => "_key",
794
+ "name" => { "source" => "name" },
795
+ "age" => { "source" => "age" },
796
+ },
797
+ "limit" => -1,
798
+ },
799
+ },
800
+ },
801
+ },
802
+ }
803
+
804
+ @plugin.process("search", envelope)
805
+
806
+ message = []
807
+ message << reducer(envelope, {
808
+ "records" => {
809
+ "type" => "sort",
810
+ "operators" => [
811
+ { "column" => 3, "operator" => ">" },
812
+ { "column" => 4, "operator" => "<" },
813
+ ],
814
+ "limit" => -1,
815
+ },
816
+ })
817
+ message << gatherer(envelope, :offset => 0,
818
+ :limit => -1,
819
+ :element => "records",
820
+ :format => "complex",
821
+ :attributes => ["id", "name", "age"])
822
+ message << searcher(envelope, :sort_offset => 0,
823
+ :sort_limit => -1,
824
+ :output_offset => 0,
825
+ :output_limit => -1,
826
+ :extra_attributes => ["public_age", "public_name"])
827
+ assert_equal(message, @posted.last.last)
828
+ end
829
+
830
+ private
831
+ def reducer(search_request_envelope, reducer_body)
832
+ queries = search_request_envelope["body"]["queries"]
833
+ query_name = queries.keys.first
834
+
835
+ reducer = {
836
+ "type" => "reduce",
837
+ "body" => {
838
+ query_name => {
839
+ "#{query_name}_reduced" => reducer_body,
840
+ },
841
+ },
842
+ "inputs" => [query_name],
843
+ "outputs" => ["#{query_name}_reduced"],
844
+ }
845
+
846
+ reducer
847
+ end
848
+
849
+ def gatherer(search_request_envelope, options={})
850
+ queries = search_request_envelope["body"]["queries"]
851
+ query_name = queries.keys.first
852
+
853
+ gatherer = {
854
+ "type" => "gather",
855
+ "body" => {
856
+ },
857
+ "inputs" => [
858
+ ],
859
+ "post" => true,
860
+ }
861
+
862
+ unless options[:no_output]
863
+ output = {
864
+ "output" => query_name,
865
+ }
866
+ if options[:element]
867
+ output.merge!({
868
+ "element" => options[:element],
869
+ "offset" => options[:offset] || 0,
870
+ "limit" => options[:limit] || 0,
871
+ "format" => options[:format] || "simple",
872
+ "attributes" => options[:attributes] || [],
873
+ })
874
+ end
875
+ gatherer["body"]["#{query_name}_reduced"] = output
876
+ gatherer["inputs"] << "#{query_name}_reduced"
877
+ end
878
+
879
+ gatherer
880
+ end
881
+
882
+ def searcher(search_request_envelope, options={})
883
+ searcher = search_request_envelope.dup
884
+
885
+ queries = searcher["body"]["queries"]
886
+ query_name = queries.keys.first
887
+ query = queries.values.first
888
+ if options[:extra_attributes]
889
+ query["output"]["attributes"] += options[:extra_attributes]
890
+ end
891
+ if options[:sort_offset]
892
+ query["sortBy"]["offset"] = options[:sort_offset]
893
+ end
894
+ if options[:sort_limit]
895
+ query["sortBy"]["limit"] = options[:sort_limit]
896
+ end
897
+ if options[:output_offset]
898
+ query["output"]["offset"] = options[:output_offset]
899
+ end
900
+ if options[:output_limit]
901
+ query["output"]["limit"] = options[:output_limit]
902
+ end
903
+
904
+ outputs = []
905
+ outputs << query_name unless options[:no_output]
906
+
907
+ searcher["type"] = "broadcast"
908
+ searcher["command"] = "search"
909
+ searcher["outputs"] = outputs
910
+ searcher["replica"] = "random"
911
+ searcher
912
+ end
913
+ end
914
+ end