droonga-engine 1.0.1

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 (341) hide show
  1. data/.dir-locals.el +3 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +15 -0
  4. data/.yardopts +7 -0
  5. data/Gemfile +66 -0
  6. data/LICENSE.txt +14 -0
  7. data/README.md +17 -0
  8. data/Rakefile +64 -0
  9. data/benchmark/benchmark.rb +123 -0
  10. data/benchmark/utils.rb +246 -0
  11. data/benchmark/watch/benchmark-notify.rb +143 -0
  12. data/benchmark/watch/benchmark-notify.sh +20 -0
  13. data/benchmark/watch/benchmark-publish.rb +120 -0
  14. data/benchmark/watch/benchmark-scan.rb +213 -0
  15. data/bin/droonga-catalog-generate +103 -0
  16. data/bin/droonga-engine +20 -0
  17. data/bin/droonga-engine-service +20 -0
  18. data/doc/text/news.md +106 -0
  19. data/droonga-engine.gemspec +52 -0
  20. data/lib/droonga/adapter.rb +48 -0
  21. data/lib/droonga/adapter_runner.rb +104 -0
  22. data/lib/droonga/catalog/base.rb +41 -0
  23. data/lib/droonga/catalog/collection_volume.rb +106 -0
  24. data/lib/droonga/catalog/dataset.rb +69 -0
  25. data/lib/droonga/catalog/errors.rb +113 -0
  26. data/lib/droonga/catalog/schema.rb +186 -0
  27. data/lib/droonga/catalog/single_volume.rb +28 -0
  28. data/lib/droonga/catalog/slice.rb +41 -0
  29. data/lib/droonga/catalog/version1.rb +427 -0
  30. data/lib/droonga/catalog/version2.rb +96 -0
  31. data/lib/droonga/catalog/version2_validator.rb +63 -0
  32. data/lib/droonga/catalog/volume.rb +33 -0
  33. data/lib/droonga/catalog/volume_collection.rb +56 -0
  34. data/lib/droonga/catalog_generator.rb +156 -0
  35. data/lib/droonga/catalog_loader.rb +56 -0
  36. data/lib/droonga/catalog_observer.rb +83 -0
  37. data/lib/droonga/collector.rb +38 -0
  38. data/lib/droonga/collector_message.rb +71 -0
  39. data/lib/droonga/collector_runner.rb +64 -0
  40. data/lib/droonga/collectors/and.rb +26 -0
  41. data/lib/droonga/collectors/or.rb +26 -0
  42. data/lib/droonga/collectors/sum.rb +26 -0
  43. data/lib/droonga/collectors.rb +18 -0
  44. data/lib/droonga/dispatcher.rb +326 -0
  45. data/lib/droonga/distributed_command_planner.rb +179 -0
  46. data/lib/droonga/distributor.rb +87 -0
  47. data/lib/droonga/engine/command/droonga_engine.rb +441 -0
  48. data/lib/droonga/engine/version.rb +20 -0
  49. data/lib/droonga/engine.rb +80 -0
  50. data/lib/droonga/engine_state.rb +79 -0
  51. data/lib/droonga/error.rb +73 -0
  52. data/lib/droonga/error_messages.rb +33 -0
  53. data/lib/droonga/event_loop.rb +46 -0
  54. data/lib/droonga/farm.rb +58 -0
  55. data/lib/droonga/fluent_message_receiver.rb +191 -0
  56. data/lib/droonga/fluent_message_sender.rb +140 -0
  57. data/lib/droonga/forwarder.rb +119 -0
  58. data/lib/droonga/handler.rb +49 -0
  59. data/lib/droonga/handler_message.rb +61 -0
  60. data/lib/droonga/handler_messenger.rb +119 -0
  61. data/lib/droonga/handler_runner.rb +125 -0
  62. data/lib/droonga/input_message.rb +51 -0
  63. data/lib/droonga/job_protocol.rb +20 -0
  64. data/lib/droonga/job_pusher.rb +179 -0
  65. data/lib/droonga/job_receiver.rb +70 -0
  66. data/lib/droonga/loggable.rb +29 -0
  67. data/lib/droonga/logger.rb +142 -0
  68. data/lib/droonga/message_matcher.rb +109 -0
  69. data/lib/droonga/output_message.rb +55 -0
  70. data/lib/droonga/planner.rb +47 -0
  71. data/lib/droonga/pluggable.rb +31 -0
  72. data/lib/droonga/plugin/metadata/adapter_input_message.rb +39 -0
  73. data/lib/droonga/plugin/metadata/adapter_output_message.rb +39 -0
  74. data/lib/droonga/plugin/metadata/collector_message.rb +39 -0
  75. data/lib/droonga/plugin/metadata/handler_action.rb +39 -0
  76. data/lib/droonga/plugin/metadata/input_message.rb +54 -0
  77. data/lib/droonga/plugin.rb +43 -0
  78. data/lib/droonga/plugin_loader.rb +63 -0
  79. data/lib/droonga/plugin_registry.rb +66 -0
  80. data/lib/droonga/plugins/basic.rb +54 -0
  81. data/lib/droonga/plugins/crud.rb +145 -0
  82. data/lib/droonga/plugins/dump.rb +97 -0
  83. data/lib/droonga/plugins/error.rb +51 -0
  84. data/lib/droonga/plugins/groonga/column_create.rb +123 -0
  85. data/lib/droonga/plugins/groonga/column_list.rb +124 -0
  86. data/lib/droonga/plugins/groonga/column_remove.rb +65 -0
  87. data/lib/droonga/plugins/groonga/column_rename.rb +67 -0
  88. data/lib/droonga/plugins/groonga/delete.rb +117 -0
  89. data/lib/droonga/plugins/groonga/generic_command.rb +105 -0
  90. data/lib/droonga/plugins/groonga/generic_response.rb +43 -0
  91. data/lib/droonga/plugins/groonga/select.rb +236 -0
  92. data/lib/droonga/plugins/groonga/table_create.rb +111 -0
  93. data/lib/droonga/plugins/groonga/table_list.rb +120 -0
  94. data/lib/droonga/plugins/groonga/table_remove.rb +57 -0
  95. data/lib/droonga/plugins/groonga.rb +37 -0
  96. data/lib/droonga/plugins/search/distributed_search_planner.rb +407 -0
  97. data/lib/droonga/plugins/search.rb +146 -0
  98. data/lib/droonga/plugins/watch.rb +178 -0
  99. data/lib/droonga/processor.rb +63 -0
  100. data/lib/droonga/reducer.rb +169 -0
  101. data/lib/droonga/replier.rb +49 -0
  102. data/lib/droonga/schema_applier.rb +167 -0
  103. data/lib/droonga/searcher/mecab_filter.rb +67 -0
  104. data/lib/droonga/searcher.rb +733 -0
  105. data/lib/droonga/server.rb +45 -0
  106. data/lib/droonga/session.rb +99 -0
  107. data/lib/droonga/single_step.rb +68 -0
  108. data/lib/droonga/single_step_definition.rb +54 -0
  109. data/lib/droonga/slice.rb +122 -0
  110. data/lib/droonga/status_code.rb +25 -0
  111. data/lib/droonga/step_runner.rb +64 -0
  112. data/lib/droonga/sweeper.rb +42 -0
  113. data/lib/droonga/test/stub_handler.rb +37 -0
  114. data/lib/droonga/test/stub_handler_message.rb +35 -0
  115. data/lib/droonga/test/stub_handler_messenger.rb +34 -0
  116. data/lib/droonga/test/stub_planner.rb +31 -0
  117. data/lib/droonga/test.rb +21 -0
  118. data/lib/droonga/watch_schema.rb +92 -0
  119. data/lib/droonga/watcher.rb +257 -0
  120. data/lib/droonga/worker.rb +61 -0
  121. data/sample/cluster/catalog.json +42 -0
  122. data/sample/mecab_filter/data.grn +7 -0
  123. data/sample/mecab_filter/ddl.grn +7 -0
  124. data/sample/mecab_filter/search_with_mecab_filter.json +21 -0
  125. data/sample/mecab_filter/search_without_mecab_filter.json +21 -0
  126. data/test/command/config/default/catalog.json +85 -0
  127. data/test/command/config/default/fluentd.conf +11 -0
  128. data/test/command/config/version1/catalog.json +68 -0
  129. data/test/command/config/version1/fluentd.conf +11 -0
  130. data/test/command/fixture/documents.jsons +208 -0
  131. data/test/command/fixture/event.jsons +41 -0
  132. data/test/command/fixture/user-table-array.jsons +38 -0
  133. data/test/command/fixture/user-table.jsons +47 -0
  134. data/test/command/run-test.rb +34 -0
  135. data/test/command/suite/add/dimension/column.catalog.json +28 -0
  136. data/test/command/suite/add/dimension/column.expected +41 -0
  137. data/test/command/suite/add/dimension/column.test +51 -0
  138. data/test/command/suite/add/dimension/integer.catalog.json +19 -0
  139. data/test/command/suite/add/dimension/integer.expected +41 -0
  140. data/test/command/suite/add/dimension/integer.test +51 -0
  141. data/test/command/suite/add/error/invalid-integer.expected +46 -0
  142. data/test/command/suite/add/error/invalid-integer.test +12 -0
  143. data/test/command/suite/add/error/invalid-time.expected +46 -0
  144. data/test/command/suite/add/error/invalid-time.test +12 -0
  145. data/test/command/suite/add/error/missing-key.expected +25 -0
  146. data/test/command/suite/add/error/missing-key.test +16 -0
  147. data/test/command/suite/add/error/missing-table.expected +25 -0
  148. data/test/command/suite/add/error/missing-table.test +16 -0
  149. data/test/command/suite/add/error/unknown-column.expected +46 -0
  150. data/test/command/suite/add/error/unknown-column.test +12 -0
  151. data/test/command/suite/add/error/unknown-table.expected +25 -0
  152. data/test/command/suite/add/error/unknown-table.test +17 -0
  153. data/test/command/suite/add/minimum.expected +6 -0
  154. data/test/command/suite/add/minimum.test +11 -0
  155. data/test/command/suite/add/with-values.expected +6 -0
  156. data/test/command/suite/add/with-values.test +17 -0
  157. data/test/command/suite/add/without-key.expected +6 -0
  158. data/test/command/suite/add/without-key.test +16 -0
  159. data/test/command/suite/groonga/column_create/scalar.expected +26 -0
  160. data/test/command/suite/groonga/column_create/scalar.test +17 -0
  161. data/test/command/suite/groonga/column_create/unknown-table.expected +14 -0
  162. data/test/command/suite/groonga/column_create/unknown-table.test +7 -0
  163. data/test/command/suite/groonga/column_create/vector.expected +26 -0
  164. data/test/command/suite/groonga/column_create/vector.test +18 -0
  165. data/test/command/suite/groonga/column_list/success.expected +86 -0
  166. data/test/command/suite/groonga/column_list/success.test +24 -0
  167. data/test/command/suite/groonga/column_list/unknown-table.expected +13 -0
  168. data/test/command/suite/groonga/column_list/unknown-table.test +7 -0
  169. data/test/command/suite/groonga/column_remove/success.expected +39 -0
  170. data/test/command/suite/groonga/column_remove/success.test +25 -0
  171. data/test/command/suite/groonga/column_remove/unknown-column.expected +27 -0
  172. data/test/command/suite/groonga/column_remove/unknown-column.test +16 -0
  173. data/test/command/suite/groonga/column_remove/unknown-table.expected +14 -0
  174. data/test/command/suite/groonga/column_remove/unknown-table.test +7 -0
  175. data/test/command/suite/groonga/column_rename/success.expected +39 -0
  176. data/test/command/suite/groonga/column_rename/success.test +26 -0
  177. data/test/command/suite/groonga/column_rename/unknown-column.expected +27 -0
  178. data/test/command/suite/groonga/column_rename/unknown-column.test +16 -0
  179. data/test/command/suite/groonga/column_rename/unknown-table.expected +14 -0
  180. data/test/command/suite/groonga/column_rename/unknown-table.test +7 -0
  181. data/test/command/suite/groonga/delete/duplicated-identifiers.expected +27 -0
  182. data/test/command/suite/groonga/delete/duplicated-identifiers.test +17 -0
  183. data/test/command/suite/groonga/delete/filter.expected +19 -0
  184. data/test/command/suite/groonga/delete/filter.test +19 -0
  185. data/test/command/suite/groonga/delete/invalid-filter.expected +14 -0
  186. data/test/command/suite/groonga/delete/invalid-filter.test +9 -0
  187. data/test/command/suite/groonga/delete/no-identifier.expected +27 -0
  188. data/test/command/suite/groonga/delete/no-identifier.test +15 -0
  189. data/test/command/suite/groonga/delete/success.expected +19 -0
  190. data/test/command/suite/groonga/delete/success.test +19 -0
  191. data/test/command/suite/groonga/delete/unknown-table.expected +14 -0
  192. data/test/command/suite/groonga/delete/unknown-table.test +7 -0
  193. data/test/command/suite/groonga/select/minimum.expected +22 -0
  194. data/test/command/suite/groonga/select/minimum.test +8 -0
  195. data/test/command/suite/groonga/table_create/array.expected +14 -0
  196. data/test/command/suite/groonga/table_create/array.test +8 -0
  197. data/test/command/suite/groonga/table_create/hash.expected +13 -0
  198. data/test/command/suite/groonga/table_create/hash.test +8 -0
  199. data/test/command/suite/groonga/table_list/success.expected +71 -0
  200. data/test/command/suite/groonga/table_list/success.test +15 -0
  201. data/test/command/suite/groonga/table_remove/success.expected +13 -0
  202. data/test/command/suite/groonga/table_remove/success.test +8 -0
  203. data/test/command/suite/groonga/table_remove/unknown-table.expected +14 -0
  204. data/test/command/suite/groonga/table_remove/unknown-table.test +7 -0
  205. data/test/command/suite/message/error/missing-dataset.expected +9 -0
  206. data/test/command/suite/message/error/missing-dataset.test +5 -0
  207. data/test/command/suite/message/error/unknown-dataset.expected +9 -0
  208. data/test/command/suite/message/error/unknown-dataset.test +6 -0
  209. data/test/command/suite/message/error/unknown-type.expected +9 -0
  210. data/test/command/suite/message/error/unknown-type.test +6 -0
  211. data/test/command/suite/search/adjusters/multiple.catalog.json +38 -0
  212. data/test/command/suite/search/adjusters/multiple.expected +19 -0
  213. data/test/command/suite/search/adjusters/multiple.test +75 -0
  214. data/test/command/suite/search/adjusters/one.catalog.json +38 -0
  215. data/test/command/suite/search/adjusters/one.expected +19 -0
  216. data/test/command/suite/search/adjusters/one.test +66 -0
  217. data/test/command/suite/search/attributes/array.expected +21 -0
  218. data/test/command/suite/search/attributes/array.test +28 -0
  219. data/test/command/suite/search/attributes/hash.expected +30 -0
  220. data/test/command/suite/search/attributes/hash.test +36 -0
  221. data/test/command/suite/search/complex.expected +48 -0
  222. data/test/command/suite/search/complex.test +23 -0
  223. data/test/command/suite/search/condition/nested.expected +15 -0
  224. data/test/command/suite/search/condition/nested.test +27 -0
  225. data/test/command/suite/search/condition/query/nonexistent_column.catalog.json +37 -0
  226. data/test/command/suite/search/condition/query/nonexistent_column.expected +48 -0
  227. data/test/command/suite/search/condition/query/nonexistent_column.test +33 -0
  228. data/test/command/suite/search/condition/query/syntax_error.catalog.json +36 -0
  229. data/test/command/suite/search/condition/query/syntax_error.expected +48 -0
  230. data/test/command/suite/search/condition/query/syntax_error.test +33 -0
  231. data/test/command/suite/search/condition/query.expected +24 -0
  232. data/test/command/suite/search/condition/query.test +23 -0
  233. data/test/command/suite/search/condition/script.expected +24 -0
  234. data/test/command/suite/search/condition/script.test +26 -0
  235. data/test/command/suite/search/error/cyclic-source.expected +14 -0
  236. data/test/command/suite/search/error/cyclic-source.test +12 -0
  237. data/test/command/suite/search/error/deeply-cyclic-source.expected +17 -0
  238. data/test/command/suite/search/error/deeply-cyclic-source.test +15 -0
  239. data/test/command/suite/search/error/missing-source-parameter.expected +13 -0
  240. data/test/command/suite/search/error/missing-source-parameter.test +11 -0
  241. data/test/command/suite/search/error/no-query.expected +9 -0
  242. data/test/command/suite/search/error/no-query.test +7 -0
  243. data/test/command/suite/search/error/unknown-source.expected +52 -0
  244. data/test/command/suite/search/error/unknown-source.test +12 -0
  245. data/test/command/suite/search/group/count.expected +10 -0
  246. data/test/command/suite/search/group/count.test +18 -0
  247. data/test/command/suite/search/group/limit.expected +15 -0
  248. data/test/command/suite/search/group/limit.test +20 -0
  249. data/test/command/suite/search/group/string.expected +32 -0
  250. data/test/command/suite/search/group/string.test +40 -0
  251. data/test/command/suite/search/group/subrecord/with-sort.catalog.json +33 -0
  252. data/test/command/suite/search/group/subrecord/with-sort.expected +30 -0
  253. data/test/command/suite/search/group/subrecord/with-sort.test +81 -0
  254. data/test/command/suite/search/multiple/chained.expected +41 -0
  255. data/test/command/suite/search/multiple/chained.test +39 -0
  256. data/test/command/suite/search/multiple/parallel.expected +35 -0
  257. data/test/command/suite/search/multiple/parallel.test +35 -0
  258. data/test/command/suite/search/output/attributes/invalid.catalog.json +13 -0
  259. data/test/command/suite/search/output/attributes/invalid.expected +44 -0
  260. data/test/command/suite/search/output/attributes/invalid.test +28 -0
  261. data/test/command/suite/search/range/only-output.expected +24 -0
  262. data/test/command/suite/search/range/only-output.test +23 -0
  263. data/test/command/suite/search/range/only-sort.expected +24 -0
  264. data/test/command/suite/search/range/only-sort.test +26 -0
  265. data/test/command/suite/search/range/sort-and-output.expected +21 -0
  266. data/test/command/suite/search/range/sort-and-output.test +27 -0
  267. data/test/command/suite/search/range/too-large-output-offset.expected +12 -0
  268. data/test/command/suite/search/range/too-large-output-offset.test +23 -0
  269. data/test/command/suite/search/range/too-large-sort-offset.expected +12 -0
  270. data/test/command/suite/search/range/too-large-sort-offset.test +26 -0
  271. data/test/command/suite/search/response/elapsed_time.catalog.json +13 -0
  272. data/test/command/suite/search/response/elapsed_time.expected +11 -0
  273. data/test/command/suite/search/response/elapsed_time.test +26 -0
  274. data/test/command/suite/search/response/records/value/time.expected +20 -0
  275. data/test/command/suite/search/response/records/value/time.test +22 -0
  276. data/test/command/suite/search/simple.expected +48 -0
  277. data/test/command/suite/search/simple.test +22 -0
  278. data/test/command/suite/search/sort/default-offset-limit.expected +39 -0
  279. data/test/command/suite/search/sort/default-offset-limit.test +24 -0
  280. data/test/command/suite/search/sort/invisible-column.expected +24 -0
  281. data/test/command/suite/search/sort/invisible-column.test +26 -0
  282. data/test/command/suite/watch/subscribe.expected +6 -0
  283. data/test/command/suite/watch/subscribe.test +9 -0
  284. data/test/command/suite/watch/unsubscribe.expected +6 -0
  285. data/test/command/suite/watch/unsubscribe.test +9 -0
  286. data/test/performance/run-test.rb +56 -0
  287. data/test/performance/watch/catalog.json +33 -0
  288. data/test/performance/watch/feed.json +9 -0
  289. data/test/performance/watch/fluentd.conf +11 -0
  290. data/test/performance/watch/subscribe.json +3 -0
  291. data/test/unit/catalog/test_collection_volume.rb +103 -0
  292. data/test/unit/catalog/test_dataset.rb +104 -0
  293. data/test/unit/catalog/test_schema.rb +226 -0
  294. data/test/unit/catalog/test_single_volume.rb +31 -0
  295. data/test/unit/catalog/test_slice.rb +92 -0
  296. data/test/unit/catalog/test_version1.rb +361 -0
  297. data/test/unit/catalog/test_version2.rb +124 -0
  298. data/test/unit/catalog/test_version2_validator.rb +66 -0
  299. data/test/unit/catalog/test_volume_collection.rb +50 -0
  300. data/test/unit/fixtures/array.grn +18 -0
  301. data/test/unit/fixtures/catalog/version1.json +40 -0
  302. data/test/unit/fixtures/catalog/version2.json +62 -0
  303. data/test/unit/fixtures/document.grn +34 -0
  304. data/test/unit/fixtures/reference/array.grn +11 -0
  305. data/test/unit/fixtures/reference/hash.grn +7 -0
  306. data/test/unit/helper/distributed_search_planner_helper.rb +83 -0
  307. data/test/unit/helper/fixture.rb +28 -0
  308. data/test/unit/helper/plugin_helper.rb +38 -0
  309. data/test/unit/helper/sandbox.rb +86 -0
  310. data/test/unit/helper/stub_worker.rb +27 -0
  311. data/test/unit/helper/watch_helper.rb +23 -0
  312. data/test/unit/helper.rb +28 -0
  313. data/test/unit/plugins/crud/test_add.rb +190 -0
  314. data/test/unit/plugins/groonga/select/test_adapter_input.rb +510 -0
  315. data/test/unit/plugins/groonga/select/test_adapter_output.rb +201 -0
  316. data/test/unit/plugins/groonga/test_column_create.rb +171 -0
  317. data/test/unit/plugins/groonga/test_column_list.rb +170 -0
  318. data/test/unit/plugins/groonga/test_column_remove.rb +98 -0
  319. data/test/unit/plugins/groonga/test_column_rename.rb +105 -0
  320. data/test/unit/plugins/groonga/test_delete.rb +127 -0
  321. data/test/unit/plugins/groonga/test_table_create.rb +147 -0
  322. data/test/unit/plugins/groonga/test_table_list.rb +184 -0
  323. data/test/unit/plugins/groonga/test_table_remove.rb +61 -0
  324. data/test/unit/plugins/search/planner/test_basic.rb +120 -0
  325. data/test/unit/plugins/search/planner/test_group_by.rb +573 -0
  326. data/test/unit/plugins/search/planner/test_output.rb +388 -0
  327. data/test/unit/plugins/search/planner/test_sort_by.rb +938 -0
  328. data/test/unit/plugins/search/test_collector.rb +806 -0
  329. data/test/unit/plugins/search/test_handler.rb +930 -0
  330. data/test/unit/plugins/search/test_planner.rb +174 -0
  331. data/test/unit/plugins/test_basic.rb +510 -0
  332. data/test/unit/plugins/test_groonga.rb +70 -0
  333. data/test/unit/plugins/test_watch.rb +211 -0
  334. data/test/unit/run-test.rb +56 -0
  335. data/test/unit/test_catalog_generator.rb +93 -0
  336. data/test/unit/test_message_matcher.rb +160 -0
  337. data/test/unit/test_schema_applier.rb +59 -0
  338. data/test/unit/test_sweeper.rb +95 -0
  339. data/test/unit/test_watch_schema.rb +57 -0
  340. data/test/unit/test_watcher.rb +336 -0
  341. metadata +759 -0
@@ -0,0 +1,407 @@
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_elapsed_time_mapper_and_reducer!
126
+ build_records_mapper_and_reducer!
127
+ end
128
+
129
+ def calculate_offset_and_limit!
130
+ @original_sort_offset = sort_offset
131
+ @original_output_offset = output_offset
132
+ @original_sort_limit = sort_limit
133
+ @original_output_limit = output_limit
134
+
135
+ calculate_sort_offset!
136
+ calculate_output_offset!
137
+
138
+ # We have to calculate limit based on offset.
139
+ # <A, B = limited integer (0...MAXINT)>
140
+ # | sort limit | output limit | => | worker's sort limit | worker's output limit | final limit |
141
+ # ============================= ====================================================================
142
+ # | UNLIMITED | UNLIMITED | => | UNLIMITED | UNLIMITED | UNLIMITED |
143
+ # | UNLIMITED | B | => | final_offset + B | final_offset + B | B |
144
+ # | A | UNLIMITED | => | final_offset + A | final_offset + A | A |
145
+ # | A | B | => | final_offset + max(A, B) | final_offset + min(A, B)| min(A, B) |
146
+
147
+ # XXX final_limit and final_offset calculated in many times
148
+
149
+ @records_offset = final_offset
150
+ @records_limit = final_limit
151
+
152
+ updated_sort_limit = nil
153
+ updated_output_limit = nil
154
+ if final_limit == UNLIMITED
155
+ updated_output_limit = UNLIMITED
156
+ else
157
+ if rich_sort?
158
+ updated_sort_limit = final_offset + [sort_limit, output_limit].max
159
+ end
160
+ updated_output_limit = final_offset + final_limit
161
+ end
162
+
163
+ if updated_sort_limit and updated_sort_limit != @query["sortBy"]["limit"]
164
+ @query["sortBy"]["limit"] = updated_sort_limit
165
+ end
166
+ if updated_output_limit and @output["limit"] and updated_output_limit != @output["limit"]
167
+ @output["limit"] = updated_output_limit
168
+ end
169
+ end
170
+
171
+ def calculate_sort_offset!
172
+ # Offset for workers must be zero, because we have to apply "limit" and
173
+ # "offset" on the last gathering phase instead of each reducing phase.
174
+ if rich_sort?
175
+ @query["sortBy"]["offset"] = 0
176
+ end
177
+ end
178
+
179
+ def sort_offset
180
+ if rich_sort?
181
+ @query["sortBy"]["offset"] || 0
182
+ else
183
+ 0
184
+ end
185
+ end
186
+
187
+ def output_offset
188
+ @output["offset"] || 0
189
+ end
190
+
191
+ def sort_limit
192
+ if rich_sort?
193
+ @query["sortBy"]["limit"] || UNLIMITED
194
+ else
195
+ UNLIMITED
196
+ end
197
+ end
198
+
199
+ def output_limit
200
+ @output["limit"] || 0
201
+ end
202
+
203
+ def calculate_output_offset!
204
+ @output["offset"] = 0 if have_records? and @output["offset"]
205
+ end
206
+
207
+ def final_offset
208
+ @original_sort_offset + @original_output_offset
209
+ end
210
+
211
+ def final_limit
212
+ if @original_sort_limit == UNLIMITED and
213
+ @original_output_limit == UNLIMITED
214
+ UNLIMITED
215
+ else
216
+ if @original_sort_limit == UNLIMITED
217
+ @original_output_limit
218
+ elsif @original_output_limit == UNLIMITED
219
+ @original_sort_limit
220
+ else
221
+ [@original_sort_limit, @original_output_limit].min
222
+ end
223
+ end
224
+ end
225
+
226
+ def have_records?
227
+ @output["elements"].include?("records")
228
+ end
229
+
230
+ def rich_sort?
231
+ @query["sortBy"].is_a?(Hash)
232
+ end
233
+
234
+ def unifiable?
235
+ @output["unifiable"]
236
+ end
237
+
238
+ def build_count_mapper_and_reducer!
239
+ return unless @output["elements"].include?("count")
240
+
241
+ @reducers["count"] = {
242
+ "type" => "sum",
243
+ }
244
+ if unifiable?
245
+ @query["sortBy"]["limit"] = -1 if @query["sortBy"].is_a?(Hash)
246
+ @output["limit"] = -1
247
+ mapper = {
248
+ "target" => "records",
249
+ }
250
+ unless @output["elements"].include?("records")
251
+ @records_limit = -1
252
+ @output["elements"] << "records"
253
+ @output["attributes"] ||= ["_key"]
254
+ @output_records = false
255
+ end
256
+ @mappers["count"] = mapper
257
+ end
258
+ end
259
+
260
+ def build_elapsed_time_mapper_and_reducer!
261
+ return unless @output["elements"].include?("elapsedTime")
262
+
263
+ @reducers["elapsedTime"] = {
264
+ "type" => "sum",
265
+ }
266
+ end
267
+
268
+ def build_records_mapper_and_reducer!
269
+ # Skip reducing phase for a result with no record output.
270
+ return if !@output["elements"].include?("records") || @records_limit.zero?
271
+
272
+ # Append sort key attributes to the list of output attributes
273
+ # temporarily, for the reducing phase. After all extra columns
274
+ # are removed on the gathering phase.
275
+ final_attributes = output_attribute_names
276
+ update_output_attributes!
277
+
278
+ @reducers["records"] = build_records_reducer
279
+
280
+ mapper = {}
281
+ if @output_records
282
+ mapper["format"] = @records_format unless @records_format == "simple"
283
+ mapper["attributes"] = final_attributes unless final_attributes.empty?
284
+ mapper["offset"] = @records_offset unless @records_offset.zero?
285
+ mapper["limit"] = @records_limit unless @records_limit.zero?
286
+ else
287
+ mapper["no_output"] = true
288
+ end
289
+ @mappers["records"] = mapper
290
+ end
291
+
292
+ def output_attribute_names
293
+ attributes = @output["attributes"] || []
294
+ if attributes.is_a?(Hash)
295
+ attributes.keys
296
+ else
297
+ attributes.collect do |attribute|
298
+ if attribute.is_a?(Hash)
299
+ attribute["label"] || attribute["source"]
300
+ else
301
+ attribute
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ def update_output_attributes!
308
+ @output["attributes"] = array_style_attributes
309
+ @output["attributes"] += sort_attribute_names
310
+ if unifiable? and !source_column_names.include?("_key")
311
+ @output["attributes"] << "_key"
312
+ end
313
+ end
314
+
315
+ def array_style_attributes
316
+ attributes = @output["attributes"] || []
317
+ if attributes.is_a?(Hash)
318
+ attributes.keys.collect do |key|
319
+ attribute = attributes[key]
320
+ case attribute
321
+ when String
322
+ {
323
+ "label" => key,
324
+ "source" => attribute,
325
+ }
326
+ when Hash
327
+ attribute["label"] = key
328
+ attribute
329
+ end
330
+ end
331
+ else
332
+ attributes
333
+ end
334
+ end
335
+
336
+ def source_column_names
337
+ attributes = @output["attributes"] || []
338
+ if attributes.is_a?(Hash)
339
+ attributes_hash = attributes
340
+ attributes = []
341
+ attributes_hash.each do |key, attribute|
342
+ attributes << attribute["source"] || key
343
+ end
344
+ attributes
345
+ else
346
+ attributes.collect do |attribute|
347
+ if attribute.is_a?(Hash)
348
+ attribute["source"] || attribute["label"]
349
+ else
350
+ attribute
351
+ end
352
+ end
353
+ end
354
+ end
355
+
356
+ def sort_attribute_names
357
+ sort_attributes = @sort_keys.collect do |key|
358
+ key = key[1..-1] if key[0] == "-"
359
+ key
360
+ end
361
+ attributes = source_column_names
362
+ sort_attributes.reject! do |attribute|
363
+ attributes.include?(attribute)
364
+ end
365
+ sort_attributes
366
+ end
367
+
368
+ ASCENDING_OPERATOR = "<"
369
+ DESCENDING_OPERATOR = ">"
370
+
371
+ def build_records_reducer
372
+ attributes = source_column_names
373
+ key_column_index = attributes.index("_key")
374
+
375
+ operators = @sort_keys.collect do |sort_key|
376
+ operator = ASCENDING_OPERATOR
377
+ if sort_key[0] == "-"
378
+ operator = DESCENDING_OPERATOR
379
+ sort_key = sort_key[1..-1]
380
+ end
381
+ {
382
+ "operator" => operator,
383
+ "column" => attributes.index(sort_key),
384
+ }
385
+ end
386
+
387
+ reducer = {
388
+ "type" => "sort",
389
+ "operators" => operators,
390
+ }
391
+ if unifiable? and !key_column_index.nil?
392
+ reducer["key_column"] = key_column_index
393
+ end
394
+
395
+ # On the reducing phase, we apply only "limit". We cannot apply
396
+ # "offset" on this phase because the collector merges a pair of
397
+ # results step by step even if there are three or more results.
398
+ # Instead, we apply "offset" on the gathering phase.
399
+ reducer["limit"] = @output["limit"]
400
+
401
+ reducer
402
+ end
403
+ end
404
+ end
405
+ end
406
+ end
407
+ end
@@ -0,0 +1,146 @@
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
+ require "droonga/plugin"
17
+ require "droonga/searcher"
18
+ require "droonga/plugins/search/distributed_search_planner"
19
+
20
+ module Droonga
21
+ module Plugins
22
+ module Search
23
+ extend Plugin
24
+ register("search")
25
+
26
+ class Planner < Droonga::Planner
27
+ def plan(message)
28
+ planner = DistributedSearchPlanner.new(message)
29
+ planner.plan
30
+ end
31
+ end
32
+
33
+ class Handler < Droonga::Handler
34
+ def handle(message)
35
+ searcher = Droonga::Searcher.new(@context)
36
+ values = {}
37
+ request = message.request
38
+ raise Droonga::Searcher::NoQuery.new unless request
39
+ searcher.search(request["queries"]).each do |output, value|
40
+ values[output] = value
41
+ end
42
+ values
43
+ end
44
+ end
45
+
46
+ class GatherCollector < Droonga::Collector
47
+ message.pattern = ["task.step.type", :equal, "search_gather"]
48
+
49
+ def collect(message)
50
+ output = message.input || message.name
51
+ if output.is_a?(Hash)
52
+ collect_elements(message, output["elements"])
53
+ output_name = output["output"]
54
+ else
55
+ output_name = output
56
+ end
57
+ message.values[output_name] = message.value
58
+ end
59
+
60
+ private
61
+ def collect_elements(message, elements)
62
+ return unless elements.is_a?(Hash)
63
+
64
+ value = message.value
65
+
66
+ return if value.nil?
67
+
68
+ # because "count" mapper requires all records,
69
+ # I have to apply it at first, before "limit" and "offset" are applied.
70
+ count_mapper = elements["count"]
71
+ if count_mapper
72
+ if count_mapper["no_output"]
73
+ value.delete("count")
74
+ else
75
+ value["count"] = value[count_mapper["target"]].size
76
+ end
77
+ end
78
+
79
+ records_mapper = elements["records"]
80
+ if records_mapper and value["records"]
81
+ if records_mapper["no_output"]
82
+ value.delete("records")
83
+ else
84
+ value["records"] = Reducer.apply_range(value["records"],
85
+ records_mapper)
86
+ value["records"] = apply_output_attributes_and_format(value["records"], records_mapper)
87
+ end
88
+ end
89
+ end
90
+
91
+ def apply_output_attributes_and_format(items, output)
92
+ attributes = output["attributes"] || []
93
+ if output["format"] == "complex"
94
+ items.collect do |item|
95
+ complex_item = {}
96
+ attributes.each_with_index do |label, index|
97
+ complex_item[label] = item[index]
98
+ end
99
+ complex_item
100
+ end
101
+ else
102
+ items.collect do |item|
103
+ item[0...attributes.size]
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ class ReduceCollector < Droonga::Collector
110
+ message.pattern = ["task.step.type", :equal, "search_reduce"]
111
+
112
+ def collect(message)
113
+ #XXX This is just a workaround. Errors should be handled by the framework itself.
114
+ if message.name == "errors"
115
+ basic_reduce_collector = Basic::ReduceCollector.new
116
+ return basic_reduce_collector.collect(message)
117
+ end
118
+
119
+ message.input.each do |output_name, elements|
120
+ old_value = message.values[output_name]
121
+ if old_value
122
+ value = reduce_elements(elements, old_value, message.value)
123
+ else
124
+ value = message.value
125
+ end
126
+ message.values[output_name] = value
127
+ end
128
+ end
129
+
130
+ def reduce_elements(elements, left_values, right_values)
131
+ result = {}
132
+ elements.each do |key, deal|
133
+ reducer = Reducer.new(deal)
134
+ result[key] = reducer.reduce(left_values[key], right_values[key])
135
+ end
136
+ result
137
+ end
138
+ end
139
+
140
+ define_single_step do |step|
141
+ step.name = "search"
142
+ step.handler = Handler
143
+ end
144
+ end
145
+ end
146
+ end