droonga-engine 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,733 @@
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 "English"
17
+ require "tsort"
18
+ require "groonga"
19
+
20
+ require "droonga/loggable"
21
+ require "droonga/error_messages"
22
+
23
+ module Droonga
24
+ class Searcher
25
+ include Loggable
26
+
27
+ class NoQuery < ErrorMessages::BadRequest
28
+ def initialize
29
+ super("You must specify one or more query.")
30
+ end
31
+ end
32
+
33
+ class MissingSourceParameter < ErrorMessages::BadRequest
34
+ def initialize(query, queries)
35
+ super("[#{query}] No source is specified. " +
36
+ "Query must have a valid source.",
37
+ queries)
38
+ end
39
+ end
40
+
41
+ class UnknownSource < ErrorMessages::NotFound
42
+ def initialize(source, queries)
43
+ super("Source not found: <#{source}> " +
44
+ "It must be a name of an existing table or another query.",
45
+ queries)
46
+ end
47
+ end
48
+
49
+ class CyclicSource < ErrorMessages::BadRequest
50
+ def initialize(queries)
51
+ super("There is cyclic reference of sources.",
52
+ queries)
53
+ end
54
+ end
55
+
56
+ class InvalidAttribute < ErrorMessages::BadRequest
57
+ attr_reader :attribute
58
+ def initialize(attribute)
59
+ detail = {
60
+ "attribute" => attribute,
61
+ }
62
+ super("Invalid attribute: <#{attribute}>", detail)
63
+ end
64
+ end
65
+
66
+ class SyntaxError < ErrorMessages::BadRequest
67
+ attr_reader :syntax
68
+ attr_reader :input
69
+ def initialize(syntax, input)
70
+ detail = {
71
+ "syntax" => syntax,
72
+ "input" => input,
73
+ }
74
+ super("Syntax error: syntax:<#{syntax}> input:<#{input}>", detail)
75
+ end
76
+ end
77
+
78
+ def initialize(context)
79
+ @context = context
80
+ end
81
+
82
+ def search(queries)
83
+ outputs = nil
84
+ logger.trace("search: start", :queries => queries)
85
+ # TODO: THIS IS JUST A WORKAROUND! We should remove it ASAP!
86
+ disable_gc do
87
+ @context.push_memory_pool do
88
+ outputs = process_queries(queries)
89
+ end
90
+ end
91
+ logger.trace("search: done")
92
+ return outputs
93
+ end
94
+
95
+ private
96
+ def disable_gc
97
+ GC.disable
98
+ begin
99
+ yield
100
+ ensure
101
+ GC.enable
102
+ end
103
+ end
104
+
105
+ def process_queries(queries)
106
+ logger.trace("process_queries: start")
107
+ if queries.nil? or queries.empty?
108
+ raise NoQuery.new
109
+ end
110
+ logger.trace("process_queries: sort: start")
111
+ sorted_queries = QuerySorter.sort(queries)
112
+ logger.trace("process_queries: sort: done")
113
+ outputs = {}
114
+ results = {}
115
+ sorted_queries.each do |name|
116
+ if queries[name]
117
+ logger.trace("process_queries: search: start",
118
+ :name => name)
119
+ search_request = SearchRequest.new(@context, queries[name], results)
120
+ search_result = QuerySearcher.search(search_request)
121
+ results[name] = search_result.records
122
+ logger.trace("process_queries: search: done",
123
+ :name => name)
124
+ if search_request.need_output?
125
+ logger.trace("process_queries: format: start",
126
+ :name => name)
127
+ outputs[name] = ResultFormatter.format(search_request, search_result)
128
+ logger.trace("process_queries: format: done",
129
+ :name => name)
130
+ end
131
+ elsif @context[name]
132
+ results[name] = @context[name]
133
+ else
134
+ raise UnknownSource.new(name, queries)
135
+ end
136
+ end
137
+ logger.trace("process_queries: done")
138
+ return outputs
139
+ end
140
+
141
+ def log_tag
142
+ "[#{Process.ppid}][#{Process.pid}] searcher"
143
+ end
144
+
145
+ class QuerySorter
146
+ include TSort
147
+
148
+ class << self
149
+ def sort(queries)
150
+ query_sorter = new
151
+ queries.each do |name, query|
152
+ source = query["source"]
153
+ raise MissingSourceParameter.new(name, queries) unless source
154
+ raise CyclicSource.new(queries) if name == source
155
+ query_sorter.add(name, [source])
156
+ end
157
+ begin
158
+ sorted_queries = query_sorter.tsort
159
+ rescue TSort::Cyclic
160
+ raise CyclicSource.new(queries)
161
+ end
162
+ sorted_queries
163
+ end
164
+
165
+ def validate_dependencies(queries)
166
+ sort(queries)
167
+ end
168
+ end
169
+
170
+ def initialize()
171
+ @queries = {}
172
+ end
173
+
174
+ def add(name, sources=[])
175
+ @queries[name] = sources
176
+ end
177
+
178
+ def tsort_each_node(&block)
179
+ @queries.each_key(&block)
180
+ end
181
+
182
+ def tsort_each_child(node, &block)
183
+ if @queries[node]
184
+ @queries[node].each(&block)
185
+ end
186
+ end
187
+ end
188
+
189
+ class SearchRequest
190
+ attr_reader :context, :query, :resolved_results
191
+
192
+ def initialize(context, query, resolved_results)
193
+ @context = context
194
+ @query = query
195
+ @resolved_results = resolved_results
196
+ end
197
+
198
+ def need_output?
199
+ @query.has_key?("output")
200
+ end
201
+
202
+ def output
203
+ @query["output"]
204
+ end
205
+
206
+ def complex_output?
207
+ output["format"] == "complex"
208
+ end
209
+
210
+ def source
211
+ source_name = @query["source"]
212
+ @resolved_results[source_name]
213
+ end
214
+ end
215
+
216
+ class SearchResult
217
+ attr_accessor :start_time, :end_time, :condition, :records, :count
218
+
219
+ def initialize
220
+ @start_time = nil
221
+ @end_time = nil
222
+ @condition = nil
223
+ @records = nil
224
+ @count = nil
225
+ end
226
+ end
227
+
228
+ class QuerySearcher
229
+ include Loggable
230
+
231
+ OPERATOR_CONVERSION_TABLE = {
232
+ "||" => Groonga::Operator::OR,
233
+ "&&" => Groonga::Operator::AND,
234
+ "-" => Groonga::Operator::AND_NOT,
235
+ }
236
+
237
+ class << self
238
+ def search(search_request)
239
+ new(search_request).search
240
+ end
241
+ end
242
+
243
+ def initialize(search_request)
244
+ @result = SearchResult.new
245
+ @request = search_request
246
+ end
247
+
248
+ def search
249
+ search_query!
250
+ @result
251
+ end
252
+
253
+ private
254
+ def parse_condition(source, expression, condition)
255
+ case condition
256
+ when String
257
+ expression.parse(condition, :syntax => :script)
258
+ when Hash
259
+ parse_condition_hash(source, expression, condition)
260
+ when Array
261
+ parse_condition_array(source, expression, condition)
262
+ else
263
+ raise "unacceptable object #{condition.inspect} assigned"
264
+ end
265
+ end
266
+
267
+ def parse_condition_hash(source, expression, condition)
268
+ options = {}
269
+ if condition["matchTo"]
270
+ matchTo = Groonga::Expression.new(context: @request.context)
271
+ matchTo.define_variable(:domain => source)
272
+ match_columns = condition["matchTo"]
273
+ match_columns = match_columns.join(",") if match_columns.is_a?(Array)
274
+ matchTo.parse(match_columns, :syntax => :script)
275
+ options[:default_column] = matchTo
276
+ end
277
+ query = condition["query"]
278
+ if query
279
+ options[:syntax] = :query
280
+ if condition["defaultOperator"]
281
+ default_operator_string = condition["defaultOperator"]
282
+ default_operator = OPERATOR_CONVERSION_TABLE[default_operator_string]
283
+ unless default_operator
284
+ raise "undefined operator assigned #{default_operator_string}"
285
+ end
286
+ options[:default_operator] = default_operator
287
+ end
288
+ if condition["allowPragma"]
289
+ options[:allow_pragma] = true
290
+ end
291
+ if condition["allowColumn"]
292
+ options[:allow_column] = true
293
+ end
294
+ syntax_errors = [
295
+ Groonga::SyntaxError,
296
+ Groonga::InvalidArgument,
297
+ Encoding::CompatibilityError,
298
+ ]
299
+ begin
300
+ expression.parse(query, options)
301
+ rescue *syntax_errors
302
+ raise SyntaxError.new("query", query)
303
+ end
304
+ elsif condition["script"]
305
+ # "script" is ignored when "query" is also assigned.
306
+ options[:syntax] = :script
307
+ if condition["allowUpdate"]
308
+ options[:allow_update] = true
309
+ end
310
+ expression.parse(condition["script"], options)
311
+ else
312
+ raise "neither 'query' nor 'script' assigned in #{condition.inspect}"
313
+ end
314
+ end
315
+
316
+ def parse_condition_array(source, expression, condition)
317
+ operator = OPERATOR_CONVERSION_TABLE[condition[0]]
318
+ unless operator
319
+ raise "undefined operator assigned #{condition[0]}"
320
+ end
321
+ if condition[1]
322
+ parse_condition(source, expression, condition[1])
323
+ end
324
+ condition[2..-1].each do |element|
325
+ parse_condition(source, expression, element)
326
+ expression.append_operation(operator, 2)
327
+ end
328
+ end
329
+
330
+ def parse_order_keys(keys)
331
+ keys.collect do |key|
332
+ if key =~ /^-/
333
+ [$POSTMATCH, :descending]
334
+ else
335
+ [key, :ascending]
336
+ end
337
+ end
338
+ end
339
+
340
+ def search_query!
341
+ logger.trace("search_query: start")
342
+
343
+ @result.start_time = Time.now
344
+
345
+ @records = @request.source
346
+
347
+ condition = @request.query["condition"]
348
+ if condition
349
+ apply_condition!(condition)
350
+
351
+ adjusters = @request.query["adjusters"]
352
+ apply_adjusters!(adjusters) if adjusters
353
+ end
354
+
355
+ group_by = @request.query["groupBy"]
356
+ apply_group_by!(group_by) if group_by
357
+
358
+ @result.count = @records.size
359
+
360
+ sort_by = @request.query["sortBy"]
361
+ apply_sort_by!(sort_by) if sort_by
362
+
363
+ logger.trace("search_query: done")
364
+ @result.records = @records
365
+ @result.end_time = Time.now
366
+ end
367
+
368
+ def apply_condition!(condition)
369
+ expression = Groonga::Expression.new(context: @request.context)
370
+ expression.define_variable(:domain => @records)
371
+ parse_condition(@records, expression, condition)
372
+ logger.trace("search_query: select: start",
373
+ :condition => condition)
374
+ @records = @records.select(expression)
375
+ logger.trace("search_query: select: done")
376
+ @result.condition = expression
377
+ end
378
+
379
+ def apply_adjusters!(adjusters)
380
+ logger.trace("search_query: adjusters: start")
381
+ adjusters.each do |adjuster|
382
+ column_name = adjuster["column"]
383
+ value = adjuster["value"]
384
+ factor = adjuster["factor"] || 1
385
+ logger.trace("search_query: adjusters: adjuster: start",
386
+ :column_name => column_name,
387
+ :value => value,
388
+ :factor => factor)
389
+ column = @request.source.column(column_name)
390
+ index, = column.indexes(:match)
391
+ # TODO: add index.nil? check
392
+ if index.nil?
393
+ # Temporary. It is just for debug on Travis CI.
394
+ logger.error("search_query: adjusters: adjuster: not found index",
395
+ :column_name => column_name,
396
+ :value => value,
397
+ :factor => factor,
398
+ :column => column,
399
+ :dump => Groonga::Schema.dump(:context => @request.context,
400
+ :syntax => :command),
401
+ :indexes => column.indexes(:match))
402
+ end
403
+ # TODO: add value.nil? check
404
+ index.search(value,
405
+ :result => @records,
406
+ :operator => :adjust,
407
+ :weight => factor)
408
+ logger.trace("search_query: adjusters: adjuster: done")
409
+ end
410
+ logger.trace("search_query: adjusters: done")
411
+ end
412
+
413
+ def apply_group_by!(group_by)
414
+ logger.trace("search_query: group: start",
415
+ :by => group_by)
416
+ case group_by
417
+ when String
418
+ @records = @records.group(group_by)
419
+ when Hash
420
+ key = group_by["key"]
421
+ max_n_sub_records = group_by["maxNSubRecords"]
422
+ @records = @records.group(key, :max_n_sub_records => max_n_sub_records)
423
+ else
424
+ raise '"groupBy" parameter must be a Hash or a String'
425
+ end
426
+ logger.trace("search_query: group: done",
427
+ :by => group_by)
428
+ end
429
+
430
+ def apply_sort_by!(sort_by)
431
+ logger.trace("search_query: sort: start",
432
+ :by => sort_by)
433
+ case sort_by
434
+ when Array
435
+ keys = parse_order_keys(sort_by)
436
+ offset = 0
437
+ limit = -1
438
+ when Hash
439
+ keys = parse_order_keys(sort_by["keys"])
440
+ offset = sort_by["offset"]
441
+ limit = sort_by["limit"]
442
+ else
443
+ raise '"sortBy" parameter must be a Hash or an Array'
444
+ end
445
+ @records = @records.sort(keys, :offset => offset, :limit => limit)
446
+ logger.trace("search_query: sort: done",
447
+ :by => sort_by)
448
+ end
449
+
450
+ def log_tag
451
+ "[#{Process.ppid}][#{Process.pid}] query_searcher"
452
+ end
453
+ end
454
+
455
+ module AttributeFormattable
456
+ def format_attribute(attribute, table)
457
+ label = attribute[:label]
458
+ source = attribute[:source]
459
+ if source == "_subrecs"
460
+ sub_record_table = table.range
461
+ sub_attributes = format(attribute[:attributes], sub_record_table)
462
+
463
+ format_attribute_subrecs(label, sub_attributes)
464
+ else
465
+ expression = attribute[:expression]
466
+ if expression
467
+ format_attribute_expression(label, expression)
468
+ else
469
+ column = table.column(source)
470
+ format_attribute_column(label, column)
471
+ end
472
+ end
473
+ end
474
+ end
475
+
476
+ class SimpleAttributesFormatter
477
+ include AttributeFormattable
478
+
479
+ def format_attribute_subrecs(label, sub_attributes)
480
+ {
481
+ "name" => label,
482
+ "attributes" => sub_attributes,
483
+ }
484
+ end
485
+
486
+ def format_attribute_column(label, column)
487
+ vector = column.respond_to?(:vector?) ? column.vector? : false
488
+ {"name" => label, "type" => column.range.name, "vector" => vector}
489
+ end
490
+
491
+ def format_attribute_expression(label, expression)
492
+ {"name" => label} # TODO include detailed information of expression
493
+ end
494
+
495
+ def format(attributes, table)
496
+ attributes.collect do |attribute|
497
+ format_attribute(attribute, table)
498
+ end
499
+ end
500
+ end
501
+
502
+ class ComplexAttributesFormatter
503
+ include AttributeFormattable
504
+
505
+ def format_attribute_subrecs(label, sub_attributes)
506
+ {
507
+ "attributes" => sub_attributes
508
+ }
509
+ end
510
+
511
+ def format_attribute_column(label, column)
512
+ vector = column.respond_to?(:vector?) ? column.vector? : false
513
+ {"type" => column.range.name, "vector" => vector}
514
+ end
515
+
516
+ def format_attribute_expression(label, expression)
517
+ {} # TODO include detailed information of expression
518
+ end
519
+
520
+ def format(attributes, table)
521
+ formatted_attributes = {}
522
+ attributes.each do |attribute|
523
+ formatted_attribute = format_attribute(attribute, table)
524
+ attribute_name = attribute[:label]
525
+ formatted_attributes[attribute_name] = formatted_attribute
526
+ end
527
+ formatted_attributes
528
+ end
529
+ end
530
+
531
+ module RecordsFormattable
532
+ def record_value(record, attribute)
533
+ if attribute[:source] == "_subrecs"
534
+ if record.table.is_a?(Groonga::Array)
535
+ target_record = record.value
536
+ else
537
+ target_record = record
538
+ end
539
+ target_record.sub_records.collect do |sub_record|
540
+ sub_attributes = attribute[:attributes]
541
+ format_record(sub_attributes, sub_record)
542
+ end
543
+ else
544
+ expression = attribute[:expression]
545
+ if expression
546
+ variable = attribute[:variable]
547
+ variable.value = record
548
+ expression.execute
549
+ else
550
+ value = record[attribute[:source]]
551
+ if value.is_a?(Groonga::Record)
552
+ value.record_id
553
+ else
554
+ value
555
+ end
556
+ end
557
+ end
558
+ end
559
+
560
+ def format(output_target_attributes, records, output_limit, output_offset)
561
+ cursor_options = {
562
+ :offset => output_offset,
563
+ :limit => output_limit
564
+ }
565
+ formatted_records = nil
566
+ records.open_cursor(cursor_options) do |cursor|
567
+ formatted_records = cursor.collect do |record|
568
+ format_record(output_target_attributes, record)
569
+ end
570
+ end
571
+ formatted_records
572
+ end
573
+ end
574
+
575
+ class SimpleRecordsFormatter
576
+ include RecordsFormattable
577
+
578
+ def format_record(attributes, record)
579
+ attributes.collect do |attribute|
580
+ record_value(record, attribute)
581
+ end
582
+ end
583
+ end
584
+
585
+ class ComplexRecordsFormatter
586
+ include RecordsFormattable
587
+
588
+ def format_record(attributes, record)
589
+ values = {}
590
+ attributes.each do |attribute|
591
+ values[attribute[:label]] = record_value(record, attribute)
592
+ end
593
+ values
594
+ end
595
+ end
596
+
597
+ class ResultFormatter
598
+ class << self
599
+ def format(search_request, search_result)
600
+ new(search_request, search_result).format
601
+ end
602
+ end
603
+
604
+ def initialize(search_request, search_result)
605
+ @request = search_request
606
+ @result = search_result
607
+ end
608
+
609
+ def format
610
+ formatted_result = {}
611
+
612
+ output_elements.each do |name|
613
+ value = format_element(name)
614
+ next if value.nil?
615
+ formatted_result[name] = value
616
+ end
617
+
618
+ formatted_result
619
+ end
620
+
621
+ private
622
+ def format_element(name)
623
+ case name
624
+ when "count"
625
+ format_count
626
+ when "attributes"
627
+ format_attributes
628
+ when "records"
629
+ format_records
630
+ when "startTime"
631
+ format_start_time
632
+ when "elapsedTime"
633
+ format_elapsed_time
634
+ else
635
+ nil
636
+ end
637
+ end
638
+
639
+ def output_elements
640
+ @request.output["elements"] || []
641
+ end
642
+
643
+ def output_offset
644
+ @request.output["offset"] || 0
645
+ end
646
+
647
+ def output_limit
648
+ @request.output["limit"] || 10
649
+ end
650
+
651
+ def format_count
652
+ @result.count
653
+ end
654
+
655
+ def format_attributes
656
+ if @request.complex_output?
657
+ formatter = ComplexAttributesFormatter.new
658
+ else
659
+ formatter = SimpleAttributesFormatter.new
660
+ end
661
+ formatter.format(output_target_attributes, @result.records)
662
+ end
663
+
664
+ def output_target_attributes
665
+ attributes = @request.output["attributes"]
666
+ normalize_target_attributes(attributes)
667
+ end
668
+
669
+ def format_records
670
+ if @request.complex_output?
671
+ formatter = ComplexRecordsFormatter.new
672
+ else
673
+ formatter = SimpleRecordsFormatter.new
674
+ end
675
+ formatter.format(output_target_attributes, @result.records, output_limit, output_offset)
676
+ end
677
+
678
+ def normalize_target_attributes(attributes, domain = @result.records)
679
+ attributes.collect do |attribute|
680
+ if attribute.is_a?(String)
681
+ attribute = {
682
+ "source" => attribute,
683
+ }
684
+ end
685
+ source = attribute["source"]
686
+ if accessor_name?(source)
687
+ expression = nil
688
+ variable = nil
689
+ else
690
+ expression = Groonga::Expression.new(context: @request.context)
691
+ variable = expression.define_variable(domain: domain)
692
+ begin
693
+ expression.parse(source, syntax: :script)
694
+ rescue Groonga::SyntaxError
695
+ raise InvalidAttribute.new(source)
696
+ end
697
+ condition = expression.define_variable(name: "$condition",
698
+ reference: true)
699
+ condition.value = @result.condition
700
+ source = nil
701
+ end
702
+ normalized_attributes = {
703
+ label: attribute["label"] || attribute["source"],
704
+ source: source,
705
+ expression: expression,
706
+ variable: variable,
707
+ }
708
+ if attribute["attributes"]
709
+ normalized_attributes[:attributes] =
710
+ normalize_target_attributes(attribute["attributes"], domain.range)
711
+ end
712
+ normalized_attributes
713
+ end
714
+ end
715
+
716
+ def accessor_name?(source)
717
+ /\A[a-zA-Z\#@$_][a-zA-Z\d\#@$_\-.]*\z/ === source
718
+ end
719
+
720
+ def format_start_time
721
+ @result.start_time
722
+ end
723
+
724
+ def format_elapsed_time
725
+ @result.end_time.to_f - @result.start_time.to_f
726
+ end
727
+ end
728
+ end
729
+ end
730
+
731
+ if ENV["DROONGA_ENABLE_SEARCH_MECAB_FILTER"] == "yes"
732
+ require "droonga/searcher/mecab_filter"
733
+ end