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,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