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.
- data/.dir-locals.el +3 -0
- data/.gitignore +6 -0
- data/.travis.yml +15 -0
- data/.yardopts +7 -0
- data/Gemfile +66 -0
- data/LICENSE.txt +14 -0
- data/README.md +17 -0
- data/Rakefile +64 -0
- data/benchmark/benchmark.rb +123 -0
- data/benchmark/utils.rb +246 -0
- data/benchmark/watch/benchmark-notify.rb +143 -0
- data/benchmark/watch/benchmark-notify.sh +20 -0
- data/benchmark/watch/benchmark-publish.rb +120 -0
- data/benchmark/watch/benchmark-scan.rb +213 -0
- data/bin/droonga-catalog-generate +103 -0
- data/bin/droonga-engine +20 -0
- data/bin/droonga-engine-service +20 -0
- data/doc/text/news.md +106 -0
- data/droonga-engine.gemspec +52 -0
- data/lib/droonga/adapter.rb +48 -0
- data/lib/droonga/adapter_runner.rb +104 -0
- data/lib/droonga/catalog/base.rb +41 -0
- data/lib/droonga/catalog/collection_volume.rb +106 -0
- data/lib/droonga/catalog/dataset.rb +69 -0
- data/lib/droonga/catalog/errors.rb +113 -0
- data/lib/droonga/catalog/schema.rb +186 -0
- data/lib/droonga/catalog/single_volume.rb +28 -0
- data/lib/droonga/catalog/slice.rb +41 -0
- data/lib/droonga/catalog/version1.rb +427 -0
- data/lib/droonga/catalog/version2.rb +96 -0
- data/lib/droonga/catalog/version2_validator.rb +63 -0
- data/lib/droonga/catalog/volume.rb +33 -0
- data/lib/droonga/catalog/volume_collection.rb +56 -0
- data/lib/droonga/catalog_generator.rb +156 -0
- data/lib/droonga/catalog_loader.rb +56 -0
- data/lib/droonga/catalog_observer.rb +83 -0
- data/lib/droonga/collector.rb +38 -0
- data/lib/droonga/collector_message.rb +71 -0
- data/lib/droonga/collector_runner.rb +64 -0
- data/lib/droonga/collectors/and.rb +26 -0
- data/lib/droonga/collectors/or.rb +26 -0
- data/lib/droonga/collectors/sum.rb +26 -0
- data/lib/droonga/collectors.rb +18 -0
- data/lib/droonga/dispatcher.rb +326 -0
- data/lib/droonga/distributed_command_planner.rb +179 -0
- data/lib/droonga/distributor.rb +87 -0
- data/lib/droonga/engine/command/droonga_engine.rb +441 -0
- data/lib/droonga/engine/version.rb +20 -0
- data/lib/droonga/engine.rb +80 -0
- data/lib/droonga/engine_state.rb +79 -0
- data/lib/droonga/error.rb +73 -0
- data/lib/droonga/error_messages.rb +33 -0
- data/lib/droonga/event_loop.rb +46 -0
- data/lib/droonga/farm.rb +58 -0
- data/lib/droonga/fluent_message_receiver.rb +191 -0
- data/lib/droonga/fluent_message_sender.rb +140 -0
- data/lib/droonga/forwarder.rb +119 -0
- data/lib/droonga/handler.rb +49 -0
- data/lib/droonga/handler_message.rb +61 -0
- data/lib/droonga/handler_messenger.rb +119 -0
- data/lib/droonga/handler_runner.rb +125 -0
- data/lib/droonga/input_message.rb +51 -0
- data/lib/droonga/job_protocol.rb +20 -0
- data/lib/droonga/job_pusher.rb +179 -0
- data/lib/droonga/job_receiver.rb +70 -0
- data/lib/droonga/loggable.rb +29 -0
- data/lib/droonga/logger.rb +142 -0
- data/lib/droonga/message_matcher.rb +109 -0
- data/lib/droonga/output_message.rb +55 -0
- data/lib/droonga/planner.rb +47 -0
- data/lib/droonga/pluggable.rb +31 -0
- data/lib/droonga/plugin/metadata/adapter_input_message.rb +39 -0
- data/lib/droonga/plugin/metadata/adapter_output_message.rb +39 -0
- data/lib/droonga/plugin/metadata/collector_message.rb +39 -0
- data/lib/droonga/plugin/metadata/handler_action.rb +39 -0
- data/lib/droonga/plugin/metadata/input_message.rb +54 -0
- data/lib/droonga/plugin.rb +43 -0
- data/lib/droonga/plugin_loader.rb +63 -0
- data/lib/droonga/plugin_registry.rb +66 -0
- data/lib/droonga/plugins/basic.rb +54 -0
- data/lib/droonga/plugins/crud.rb +145 -0
- data/lib/droonga/plugins/dump.rb +97 -0
- data/lib/droonga/plugins/error.rb +51 -0
- data/lib/droonga/plugins/groonga/column_create.rb +123 -0
- data/lib/droonga/plugins/groonga/column_list.rb +124 -0
- data/lib/droonga/plugins/groonga/column_remove.rb +65 -0
- data/lib/droonga/plugins/groonga/column_rename.rb +67 -0
- data/lib/droonga/plugins/groonga/delete.rb +117 -0
- data/lib/droonga/plugins/groonga/generic_command.rb +105 -0
- data/lib/droonga/plugins/groonga/generic_response.rb +43 -0
- data/lib/droonga/plugins/groonga/select.rb +236 -0
- data/lib/droonga/plugins/groonga/table_create.rb +111 -0
- data/lib/droonga/plugins/groonga/table_list.rb +120 -0
- data/lib/droonga/plugins/groonga/table_remove.rb +57 -0
- data/lib/droonga/plugins/groonga.rb +37 -0
- data/lib/droonga/plugins/search/distributed_search_planner.rb +407 -0
- data/lib/droonga/plugins/search.rb +146 -0
- data/lib/droonga/plugins/watch.rb +178 -0
- data/lib/droonga/processor.rb +63 -0
- data/lib/droonga/reducer.rb +169 -0
- data/lib/droonga/replier.rb +49 -0
- data/lib/droonga/schema_applier.rb +167 -0
- data/lib/droonga/searcher/mecab_filter.rb +67 -0
- data/lib/droonga/searcher.rb +733 -0
- data/lib/droonga/server.rb +45 -0
- data/lib/droonga/session.rb +99 -0
- data/lib/droonga/single_step.rb +68 -0
- data/lib/droonga/single_step_definition.rb +54 -0
- data/lib/droonga/slice.rb +122 -0
- data/lib/droonga/status_code.rb +25 -0
- data/lib/droonga/step_runner.rb +64 -0
- data/lib/droonga/sweeper.rb +42 -0
- data/lib/droonga/test/stub_handler.rb +37 -0
- data/lib/droonga/test/stub_handler_message.rb +35 -0
- data/lib/droonga/test/stub_handler_messenger.rb +34 -0
- data/lib/droonga/test/stub_planner.rb +31 -0
- data/lib/droonga/test.rb +21 -0
- data/lib/droonga/watch_schema.rb +92 -0
- data/lib/droonga/watcher.rb +257 -0
- data/lib/droonga/worker.rb +61 -0
- data/sample/cluster/catalog.json +42 -0
- data/sample/mecab_filter/data.grn +7 -0
- data/sample/mecab_filter/ddl.grn +7 -0
- data/sample/mecab_filter/search_with_mecab_filter.json +21 -0
- data/sample/mecab_filter/search_without_mecab_filter.json +21 -0
- data/test/command/config/default/catalog.json +85 -0
- data/test/command/config/default/fluentd.conf +11 -0
- data/test/command/config/version1/catalog.json +68 -0
- data/test/command/config/version1/fluentd.conf +11 -0
- data/test/command/fixture/documents.jsons +208 -0
- data/test/command/fixture/event.jsons +41 -0
- data/test/command/fixture/user-table-array.jsons +38 -0
- data/test/command/fixture/user-table.jsons +47 -0
- data/test/command/run-test.rb +34 -0
- data/test/command/suite/add/dimension/column.catalog.json +28 -0
- data/test/command/suite/add/dimension/column.expected +41 -0
- data/test/command/suite/add/dimension/column.test +51 -0
- data/test/command/suite/add/dimension/integer.catalog.json +19 -0
- data/test/command/suite/add/dimension/integer.expected +41 -0
- data/test/command/suite/add/dimension/integer.test +51 -0
- data/test/command/suite/add/error/invalid-integer.expected +46 -0
- data/test/command/suite/add/error/invalid-integer.test +12 -0
- data/test/command/suite/add/error/invalid-time.expected +46 -0
- data/test/command/suite/add/error/invalid-time.test +12 -0
- data/test/command/suite/add/error/missing-key.expected +25 -0
- data/test/command/suite/add/error/missing-key.test +16 -0
- data/test/command/suite/add/error/missing-table.expected +25 -0
- data/test/command/suite/add/error/missing-table.test +16 -0
- data/test/command/suite/add/error/unknown-column.expected +46 -0
- data/test/command/suite/add/error/unknown-column.test +12 -0
- data/test/command/suite/add/error/unknown-table.expected +25 -0
- data/test/command/suite/add/error/unknown-table.test +17 -0
- data/test/command/suite/add/minimum.expected +6 -0
- data/test/command/suite/add/minimum.test +11 -0
- data/test/command/suite/add/with-values.expected +6 -0
- data/test/command/suite/add/with-values.test +17 -0
- data/test/command/suite/add/without-key.expected +6 -0
- data/test/command/suite/add/without-key.test +16 -0
- data/test/command/suite/groonga/column_create/scalar.expected +26 -0
- data/test/command/suite/groonga/column_create/scalar.test +17 -0
- data/test/command/suite/groonga/column_create/unknown-table.expected +14 -0
- data/test/command/suite/groonga/column_create/unknown-table.test +7 -0
- data/test/command/suite/groonga/column_create/vector.expected +26 -0
- data/test/command/suite/groonga/column_create/vector.test +18 -0
- data/test/command/suite/groonga/column_list/success.expected +86 -0
- data/test/command/suite/groonga/column_list/success.test +24 -0
- data/test/command/suite/groonga/column_list/unknown-table.expected +13 -0
- data/test/command/suite/groonga/column_list/unknown-table.test +7 -0
- data/test/command/suite/groonga/column_remove/success.expected +39 -0
- data/test/command/suite/groonga/column_remove/success.test +25 -0
- data/test/command/suite/groonga/column_remove/unknown-column.expected +27 -0
- data/test/command/suite/groonga/column_remove/unknown-column.test +16 -0
- data/test/command/suite/groonga/column_remove/unknown-table.expected +14 -0
- data/test/command/suite/groonga/column_remove/unknown-table.test +7 -0
- data/test/command/suite/groonga/column_rename/success.expected +39 -0
- data/test/command/suite/groonga/column_rename/success.test +26 -0
- data/test/command/suite/groonga/column_rename/unknown-column.expected +27 -0
- data/test/command/suite/groonga/column_rename/unknown-column.test +16 -0
- data/test/command/suite/groonga/column_rename/unknown-table.expected +14 -0
- data/test/command/suite/groonga/column_rename/unknown-table.test +7 -0
- data/test/command/suite/groonga/delete/duplicated-identifiers.expected +27 -0
- data/test/command/suite/groonga/delete/duplicated-identifiers.test +17 -0
- data/test/command/suite/groonga/delete/filter.expected +19 -0
- data/test/command/suite/groonga/delete/filter.test +19 -0
- data/test/command/suite/groonga/delete/invalid-filter.expected +14 -0
- data/test/command/suite/groonga/delete/invalid-filter.test +9 -0
- data/test/command/suite/groonga/delete/no-identifier.expected +27 -0
- data/test/command/suite/groonga/delete/no-identifier.test +15 -0
- data/test/command/suite/groonga/delete/success.expected +19 -0
- data/test/command/suite/groonga/delete/success.test +19 -0
- data/test/command/suite/groonga/delete/unknown-table.expected +14 -0
- data/test/command/suite/groonga/delete/unknown-table.test +7 -0
- data/test/command/suite/groonga/select/minimum.expected +22 -0
- data/test/command/suite/groonga/select/minimum.test +8 -0
- data/test/command/suite/groonga/table_create/array.expected +14 -0
- data/test/command/suite/groonga/table_create/array.test +8 -0
- data/test/command/suite/groonga/table_create/hash.expected +13 -0
- data/test/command/suite/groonga/table_create/hash.test +8 -0
- data/test/command/suite/groonga/table_list/success.expected +71 -0
- data/test/command/suite/groonga/table_list/success.test +15 -0
- data/test/command/suite/groonga/table_remove/success.expected +13 -0
- data/test/command/suite/groonga/table_remove/success.test +8 -0
- data/test/command/suite/groonga/table_remove/unknown-table.expected +14 -0
- data/test/command/suite/groonga/table_remove/unknown-table.test +7 -0
- data/test/command/suite/message/error/missing-dataset.expected +9 -0
- data/test/command/suite/message/error/missing-dataset.test +5 -0
- data/test/command/suite/message/error/unknown-dataset.expected +9 -0
- data/test/command/suite/message/error/unknown-dataset.test +6 -0
- data/test/command/suite/message/error/unknown-type.expected +9 -0
- data/test/command/suite/message/error/unknown-type.test +6 -0
- data/test/command/suite/search/adjusters/multiple.catalog.json +38 -0
- data/test/command/suite/search/adjusters/multiple.expected +19 -0
- data/test/command/suite/search/adjusters/multiple.test +75 -0
- data/test/command/suite/search/adjusters/one.catalog.json +38 -0
- data/test/command/suite/search/adjusters/one.expected +19 -0
- data/test/command/suite/search/adjusters/one.test +66 -0
- data/test/command/suite/search/attributes/array.expected +21 -0
- data/test/command/suite/search/attributes/array.test +28 -0
- data/test/command/suite/search/attributes/hash.expected +30 -0
- data/test/command/suite/search/attributes/hash.test +36 -0
- data/test/command/suite/search/complex.expected +48 -0
- data/test/command/suite/search/complex.test +23 -0
- data/test/command/suite/search/condition/nested.expected +15 -0
- data/test/command/suite/search/condition/nested.test +27 -0
- data/test/command/suite/search/condition/query/nonexistent_column.catalog.json +37 -0
- data/test/command/suite/search/condition/query/nonexistent_column.expected +48 -0
- data/test/command/suite/search/condition/query/nonexistent_column.test +33 -0
- data/test/command/suite/search/condition/query/syntax_error.catalog.json +36 -0
- data/test/command/suite/search/condition/query/syntax_error.expected +48 -0
- data/test/command/suite/search/condition/query/syntax_error.test +33 -0
- data/test/command/suite/search/condition/query.expected +24 -0
- data/test/command/suite/search/condition/query.test +23 -0
- data/test/command/suite/search/condition/script.expected +24 -0
- data/test/command/suite/search/condition/script.test +26 -0
- data/test/command/suite/search/error/cyclic-source.expected +14 -0
- data/test/command/suite/search/error/cyclic-source.test +12 -0
- data/test/command/suite/search/error/deeply-cyclic-source.expected +17 -0
- data/test/command/suite/search/error/deeply-cyclic-source.test +15 -0
- data/test/command/suite/search/error/missing-source-parameter.expected +13 -0
- data/test/command/suite/search/error/missing-source-parameter.test +11 -0
- data/test/command/suite/search/error/no-query.expected +9 -0
- data/test/command/suite/search/error/no-query.test +7 -0
- data/test/command/suite/search/error/unknown-source.expected +52 -0
- data/test/command/suite/search/error/unknown-source.test +12 -0
- data/test/command/suite/search/group/count.expected +10 -0
- data/test/command/suite/search/group/count.test +18 -0
- data/test/command/suite/search/group/limit.expected +15 -0
- data/test/command/suite/search/group/limit.test +20 -0
- data/test/command/suite/search/group/string.expected +32 -0
- data/test/command/suite/search/group/string.test +40 -0
- data/test/command/suite/search/group/subrecord/with-sort.catalog.json +33 -0
- data/test/command/suite/search/group/subrecord/with-sort.expected +30 -0
- data/test/command/suite/search/group/subrecord/with-sort.test +81 -0
- data/test/command/suite/search/multiple/chained.expected +41 -0
- data/test/command/suite/search/multiple/chained.test +39 -0
- data/test/command/suite/search/multiple/parallel.expected +35 -0
- data/test/command/suite/search/multiple/parallel.test +35 -0
- data/test/command/suite/search/output/attributes/invalid.catalog.json +13 -0
- data/test/command/suite/search/output/attributes/invalid.expected +44 -0
- data/test/command/suite/search/output/attributes/invalid.test +28 -0
- data/test/command/suite/search/range/only-output.expected +24 -0
- data/test/command/suite/search/range/only-output.test +23 -0
- data/test/command/suite/search/range/only-sort.expected +24 -0
- data/test/command/suite/search/range/only-sort.test +26 -0
- data/test/command/suite/search/range/sort-and-output.expected +21 -0
- data/test/command/suite/search/range/sort-and-output.test +27 -0
- data/test/command/suite/search/range/too-large-output-offset.expected +12 -0
- data/test/command/suite/search/range/too-large-output-offset.test +23 -0
- data/test/command/suite/search/range/too-large-sort-offset.expected +12 -0
- data/test/command/suite/search/range/too-large-sort-offset.test +26 -0
- data/test/command/suite/search/response/elapsed_time.catalog.json +13 -0
- data/test/command/suite/search/response/elapsed_time.expected +11 -0
- data/test/command/suite/search/response/elapsed_time.test +26 -0
- data/test/command/suite/search/response/records/value/time.expected +20 -0
- data/test/command/suite/search/response/records/value/time.test +22 -0
- data/test/command/suite/search/simple.expected +48 -0
- data/test/command/suite/search/simple.test +22 -0
- data/test/command/suite/search/sort/default-offset-limit.expected +39 -0
- data/test/command/suite/search/sort/default-offset-limit.test +24 -0
- data/test/command/suite/search/sort/invisible-column.expected +24 -0
- data/test/command/suite/search/sort/invisible-column.test +26 -0
- data/test/command/suite/watch/subscribe.expected +6 -0
- data/test/command/suite/watch/subscribe.test +9 -0
- data/test/command/suite/watch/unsubscribe.expected +6 -0
- data/test/command/suite/watch/unsubscribe.test +9 -0
- data/test/performance/run-test.rb +56 -0
- data/test/performance/watch/catalog.json +33 -0
- data/test/performance/watch/feed.json +9 -0
- data/test/performance/watch/fluentd.conf +11 -0
- data/test/performance/watch/subscribe.json +3 -0
- data/test/unit/catalog/test_collection_volume.rb +103 -0
- data/test/unit/catalog/test_dataset.rb +104 -0
- data/test/unit/catalog/test_schema.rb +226 -0
- data/test/unit/catalog/test_single_volume.rb +31 -0
- data/test/unit/catalog/test_slice.rb +92 -0
- data/test/unit/catalog/test_version1.rb +361 -0
- data/test/unit/catalog/test_version2.rb +124 -0
- data/test/unit/catalog/test_version2_validator.rb +66 -0
- data/test/unit/catalog/test_volume_collection.rb +50 -0
- data/test/unit/fixtures/array.grn +18 -0
- data/test/unit/fixtures/catalog/version1.json +40 -0
- data/test/unit/fixtures/catalog/version2.json +62 -0
- data/test/unit/fixtures/document.grn +34 -0
- data/test/unit/fixtures/reference/array.grn +11 -0
- data/test/unit/fixtures/reference/hash.grn +7 -0
- data/test/unit/helper/distributed_search_planner_helper.rb +83 -0
- data/test/unit/helper/fixture.rb +28 -0
- data/test/unit/helper/plugin_helper.rb +38 -0
- data/test/unit/helper/sandbox.rb +86 -0
- data/test/unit/helper/stub_worker.rb +27 -0
- data/test/unit/helper/watch_helper.rb +23 -0
- data/test/unit/helper.rb +28 -0
- data/test/unit/plugins/crud/test_add.rb +190 -0
- data/test/unit/plugins/groonga/select/test_adapter_input.rb +510 -0
- data/test/unit/plugins/groonga/select/test_adapter_output.rb +201 -0
- data/test/unit/plugins/groonga/test_column_create.rb +171 -0
- data/test/unit/plugins/groonga/test_column_list.rb +170 -0
- data/test/unit/plugins/groonga/test_column_remove.rb +98 -0
- data/test/unit/plugins/groonga/test_column_rename.rb +105 -0
- data/test/unit/plugins/groonga/test_delete.rb +127 -0
- data/test/unit/plugins/groonga/test_table_create.rb +147 -0
- data/test/unit/plugins/groonga/test_table_list.rb +184 -0
- data/test/unit/plugins/groonga/test_table_remove.rb +61 -0
- data/test/unit/plugins/search/planner/test_basic.rb +120 -0
- data/test/unit/plugins/search/planner/test_group_by.rb +573 -0
- data/test/unit/plugins/search/planner/test_output.rb +388 -0
- data/test/unit/plugins/search/planner/test_sort_by.rb +938 -0
- data/test/unit/plugins/search/test_collector.rb +806 -0
- data/test/unit/plugins/search/test_handler.rb +930 -0
- data/test/unit/plugins/search/test_planner.rb +174 -0
- data/test/unit/plugins/test_basic.rb +510 -0
- data/test/unit/plugins/test_groonga.rb +70 -0
- data/test/unit/plugins/test_watch.rb +211 -0
- data/test/unit/run-test.rb +56 -0
- data/test/unit/test_catalog_generator.rb +93 -0
- data/test/unit/test_message_matcher.rb +160 -0
- data/test/unit/test_schema_applier.rb +59 -0
- data/test/unit/test_sweeper.rb +95 -0
- data/test/unit/test_watch_schema.rb +57 -0
- data/test/unit/test_watcher.rb +336 -0
- 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
|