droonga-engine 1.0.2 → 1.0.3

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 (203) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +7 -0
  5. data/Rakefile +6 -2
  6. data/bin/droonga-engine +2 -2
  7. data/bin/{droonga-catalog-generate → droonga-engine-catalog-generate} +15 -3
  8. data/bin/droonga-engine-serf-event-handler +20 -0
  9. data/bin/droonga-engine-service +2 -2
  10. data/doc/text/news.md +21 -1
  11. data/droonga-engine.gemspec +5 -2
  12. data/lib/droonga/catalog/collection_volume.rb +12 -0
  13. data/lib/droonga/catalog/dataset.rb +25 -0
  14. data/lib/droonga/catalog/single_volume.rb +10 -0
  15. data/lib/droonga/catalog/slice.rb +4 -0
  16. data/lib/droonga/catalog/version1.rb +59 -48
  17. data/lib/droonga/catalog/version2.rb +10 -20
  18. data/lib/droonga/catalog/volume_collection.rb +27 -4
  19. data/lib/droonga/catalog_generator.rb +12 -5
  20. data/lib/droonga/catalog_observer.rb +17 -35
  21. data/lib/droonga/command/droonga_engine.rb +436 -0
  22. data/lib/droonga/command/droonga_engine_service.rb +273 -0
  23. data/lib/droonga/command/serf_event_handler.rb +85 -0
  24. data/lib/droonga/dispatcher.rb +8 -8
  25. data/lib/droonga/engine.rb +90 -26
  26. data/lib/droonga/engine/version.rb +1 -1
  27. data/lib/droonga/engine_state.rb +29 -3
  28. data/lib/droonga/internal_fluent_message_receiver.rb +100 -0
  29. data/lib/droonga/live_nodes_list_loader.rb +48 -0
  30. data/lib/droonga/live_nodes_list_observer.rb +72 -0
  31. data/lib/droonga/path.rb +47 -0
  32. data/lib/droonga/plugins/dump.rb +279 -38
  33. data/lib/droonga/plugins/groonga/select.rb +26 -14
  34. data/lib/droonga/plugins/search.rb +30 -2
  35. data/lib/droonga/plugins/search/distributed_search_planner.rb +28 -11
  36. data/lib/droonga/processor.rb +4 -0
  37. data/lib/droonga/searcher.rb +26 -0
  38. data/lib/droonga/serf.rb +119 -0
  39. data/lib/droonga/serf_downloader.rb +90 -0
  40. data/lib/droonga/server.rb +2 -2
  41. data/lib/droonga/service_control_protocol.rb +26 -0
  42. data/sample/cluster/catalog.json +1 -1
  43. data/test/command/config/default/catalog.json +2 -2
  44. data/test/command/config/version1/catalog.json +1 -1
  45. data/test/command/fixture/documents.jsons +18 -18
  46. data/test/command/fixture/event.jsons +4 -4
  47. data/test/command/fixture/user-table-array.jsons +4 -4
  48. data/test/command/fixture/user-table.jsons +5 -5
  49. data/test/command/suite/add/dimension/column.catalog.json +1 -1
  50. data/test/command/suite/add/dimension/column.test +4 -4
  51. data/test/command/suite/add/dimension/integer.catalog.json +1 -1
  52. data/test/command/suite/add/dimension/integer.test +4 -4
  53. data/test/command/suite/add/error/invalid-integer.test +1 -1
  54. data/test/command/suite/add/error/invalid-time.test +1 -1
  55. data/test/command/suite/add/error/missing-key.test +1 -1
  56. data/test/command/suite/add/error/missing-table.test +1 -1
  57. data/test/command/suite/add/error/unknown-column.test +1 -1
  58. data/test/command/suite/add/error/unknown-table.test +1 -1
  59. data/test/command/suite/add/minimum.test +1 -1
  60. data/test/command/suite/add/vector/short_text.catalog.json +26 -0
  61. data/test/command/suite/add/vector/short_text.expected +42 -0
  62. data/test/command/suite/add/vector/short_text.test +35 -0
  63. data/test/command/suite/add/with-values.test +1 -1
  64. data/test/command/suite/add/without-key.test +1 -1
  65. data/test/command/suite/dump/column/index.catalog.json +40 -0
  66. data/test/command/suite/dump/column/index.expected +195 -0
  67. data/test/command/suite/dump/column/index.test +5 -0
  68. data/test/command/suite/dump/column/scalar.catalog.json +19 -0
  69. data/test/command/suite/dump/column/scalar.expected +99 -0
  70. data/test/command/suite/dump/column/scalar.test +5 -0
  71. data/test/command/suite/dump/column/vector.catalog.json +22 -0
  72. data/test/command/suite/dump/column/vector.expected +108 -0
  73. data/test/command/suite/dump/column/vector.test +5 -0
  74. data/test/command/suite/dump/record/vector/reference.catalog.json +27 -0
  75. data/test/command/suite/dump/record/vector/reference.expected +213 -0
  76. data/test/command/suite/dump/record/vector/reference.test +21 -0
  77. data/test/command/suite/dump/table/array.catalog.json +13 -0
  78. data/test/command/suite/dump/table/array.expected +63 -0
  79. data/test/command/suite/dump/table/array.test +5 -0
  80. data/test/command/suite/dump/table/double_array_trie.catalog.json +14 -0
  81. data/test/command/suite/dump/table/double_array_trie.expected +66 -0
  82. data/test/command/suite/dump/table/double_array_trie.test +5 -0
  83. data/test/command/suite/dump/table/hash.catalog.json +14 -0
  84. data/test/command/suite/dump/table/hash.expected +66 -0
  85. data/test/command/suite/dump/table/hash.test +5 -0
  86. data/test/command/suite/dump/table/patricia_trie.catalog.json +14 -0
  87. data/test/command/suite/dump/table/patricia_trie.expected +66 -0
  88. data/test/command/suite/dump/table/patricia_trie.test +5 -0
  89. data/test/command/suite/groonga/column_create/scalar.test +2 -2
  90. data/test/command/suite/groonga/column_create/unknown-table.test +1 -1
  91. data/test/command/suite/groonga/column_create/vector.test +2 -2
  92. data/test/command/suite/groonga/column_list/success.test +3 -3
  93. data/test/command/suite/groonga/column_list/unknown-table.test +1 -1
  94. data/test/command/suite/groonga/column_remove/success.test +3 -3
  95. data/test/command/suite/groonga/column_remove/unknown-column.test +2 -2
  96. data/test/command/suite/groonga/column_remove/unknown-table.test +1 -1
  97. data/test/command/suite/groonga/column_rename/success.test +3 -3
  98. data/test/command/suite/groonga/column_rename/unknown-column.test +2 -2
  99. data/test/command/suite/groonga/column_rename/unknown-table.test +1 -1
  100. data/test/command/suite/groonga/delete/duplicated-identifiers.test +2 -2
  101. data/test/command/suite/groonga/delete/filter.test +2 -2
  102. data/test/command/suite/groonga/delete/invalid-filter.test +1 -1
  103. data/test/command/suite/groonga/delete/no-identifier.test +2 -2
  104. data/test/command/suite/groonga/delete/success.test +2 -2
  105. data/test/command/suite/groonga/delete/unknown-table.test +1 -1
  106. data/test/command/suite/groonga/select/minimum.expected +24 -1
  107. data/test/command/suite/groonga/select/minimum.test +1 -1
  108. data/test/command/suite/groonga/select/type/time.catalog.json +19 -0
  109. data/test/command/suite/groonga/select/type/time.expected +37 -0
  110. data/test/command/suite/groonga/select/type/time.test +35 -0
  111. data/test/command/suite/groonga/table_create/array.test +1 -1
  112. data/test/command/suite/groonga/table_create/hash.test +1 -1
  113. data/test/command/suite/groonga/table_list/success.test +2 -2
  114. data/test/command/suite/groonga/table_remove/success.test +1 -1
  115. data/test/command/suite/groonga/table_remove/unknown-table.test +1 -1
  116. data/test/command/suite/message/error/unknown-type.expected +1 -1
  117. data/test/command/suite/message/error/unknown-type.test +1 -1
  118. data/test/command/suite/search/adjusters/multiple.catalog.json +1 -1
  119. data/test/command/suite/search/adjusters/multiple.test +3 -3
  120. data/test/command/suite/search/adjusters/one.catalog.json +1 -1
  121. data/test/command/suite/search/adjusters/one.test +3 -3
  122. data/test/command/suite/search/attributes/array.expected +7 -0
  123. data/test/command/suite/search/attributes/array.test +1 -1
  124. data/test/command/suite/search/attributes/hash.expected +18 -0
  125. data/test/command/suite/search/attributes/hash.test +1 -1
  126. data/test/command/suite/search/complex.expected +12 -0
  127. data/test/command/suite/search/complex.test +1 -1
  128. data/test/command/suite/search/condition/nested.catalog.json +37 -0
  129. data/test/command/suite/search/condition/nested.expected +7 -0
  130. data/test/command/suite/search/condition/nested.test +103 -2
  131. data/test/command/suite/search/condition/query.catalog.json +37 -0
  132. data/test/command/suite/search/condition/query.expected +7 -0
  133. data/test/command/suite/search/condition/query.test +103 -2
  134. data/test/command/suite/search/condition/query/nonexistent_column.catalog.json +1 -1
  135. data/test/command/suite/search/condition/query/nonexistent_column.test +2 -2
  136. data/test/command/suite/search/condition/query/syntax_error.catalog.json +1 -1
  137. data/test/command/suite/search/condition/query/syntax_error.test +2 -2
  138. data/test/command/suite/search/condition/script.catalog.json +37 -0
  139. data/test/command/suite/search/condition/script.expected +7 -0
  140. data/test/command/suite/search/condition/script.test +103 -2
  141. data/test/command/suite/search/error/cyclic-source.test +1 -1
  142. data/test/command/suite/search/error/deeply-cyclic-source.test +1 -1
  143. data/test/command/suite/search/error/missing-source-parameter.test +1 -1
  144. data/test/command/suite/search/error/no-query.test +1 -1
  145. data/test/command/suite/search/error/unknown-source.test +1 -1
  146. data/test/command/suite/search/group/count.test +1 -1
  147. data/test/command/suite/search/group/limit.test +1 -1
  148. data/test/command/suite/search/group/string.catalog.json +41 -0
  149. data/test/command/suite/search/group/string.expected +18 -18
  150. data/test/command/suite/search/group/string.test +67 -22
  151. data/test/command/suite/search/group/subrecord/with-sort.catalog.json +1 -1
  152. data/test/command/suite/search/group/subrecord/with-sort.test +5 -5
  153. data/test/command/suite/search/multiple/chained.catalog.json +37 -0
  154. data/test/command/suite/search/multiple/chained.expected +14 -0
  155. data/test/command/suite/search/multiple/chained.test +103 -2
  156. data/test/command/suite/search/multiple/parallel.expected +14 -0
  157. data/test/command/suite/search/multiple/parallel.test +1 -1
  158. data/test/command/suite/search/output/attributes/invalid.catalog.json +1 -1
  159. data/test/command/suite/search/output/attributes/invalid.test +2 -2
  160. data/test/command/suite/search/output/attributes/star.catalog.json +23 -0
  161. data/test/command/suite/search/output/attributes/star.expected +27 -0
  162. data/test/command/suite/search/output/attributes/star.test +32 -0
  163. data/test/command/suite/search/range/only-output.expected +7 -0
  164. data/test/command/suite/search/range/only-output.test +1 -1
  165. data/test/command/suite/search/range/only-sort.expected +7 -0
  166. data/test/command/suite/search/range/only-sort.test +1 -1
  167. data/test/command/suite/search/range/sort-and-output.expected +7 -0
  168. data/test/command/suite/search/range/sort-and-output.test +1 -1
  169. data/test/command/suite/search/range/too-large-output-offset.expected +8 -0
  170. data/test/command/suite/search/range/too-large-output-offset.test +1 -1
  171. data/test/command/suite/search/range/too-large-sort-offset.expected +8 -0
  172. data/test/command/suite/search/range/too-large-sort-offset.test +1 -1
  173. data/test/command/suite/search/response/elapsed_time.catalog.json +1 -1
  174. data/test/command/suite/search/response/elapsed_time.test +2 -2
  175. data/test/command/suite/search/response/records/value/time.expected +12 -0
  176. data/test/command/suite/search/response/records/value/time.test +1 -1
  177. data/test/command/suite/search/simple.expected +12 -0
  178. data/test/command/suite/search/simple.test +1 -1
  179. data/test/command/suite/search/sort/default-offset-limit.expected +7 -0
  180. data/test/command/suite/search/sort/default-offset-limit.test +1 -1
  181. data/test/command/suite/search/sort/invisible-column.expected +7 -0
  182. data/test/command/suite/search/sort/invisible-column.test +1 -1
  183. data/test/unit/catalog/test_collection_volume.rb +16 -0
  184. data/test/unit/catalog/test_dataset.rb +36 -0
  185. data/test/unit/catalog/test_single_volume.rb +9 -0
  186. data/test/unit/catalog/test_slice.rb +11 -0
  187. data/test/unit/catalog/test_version1.rb +7 -12
  188. data/test/unit/catalog/test_version2.rb +7 -0
  189. data/test/unit/catalog/test_volume_collection.rb +28 -0
  190. data/test/unit/fixtures/catalog/version1.json +10 -3
  191. data/test/unit/fixtures/catalog/version2.json +2 -2
  192. data/test/unit/plugins/groonga/select/test_adapter_output.rb +8 -14
  193. data/test/unit/plugins/groonga/test_column_create.rb +5 -5
  194. data/test/unit/plugins/groonga/test_column_remove.rb +2 -2
  195. data/test/unit/plugins/groonga/test_column_rename.rb +2 -2
  196. data/test/unit/plugins/groonga/test_delete.rb +2 -2
  197. data/test/unit/plugins/groonga/test_table_create.rb +9 -9
  198. data/test/unit/plugins/groonga/test_table_remove.rb +1 -1
  199. data/test/unit/test_catalog_generator.rb +1 -1
  200. data/test/unit/test_schema_applier.rb +2 -2
  201. data/test/unit/test_watch_schema.rb +4 -4
  202. metadata +241 -72
  203. data/lib/droonga/engine/command/droonga_engine.rb +0 -441
@@ -15,6 +15,6 @@
15
15
 
16
16
  module Droonga
17
17
  class Engine
18
- VERSION = "1.0.2"
18
+ VERSION = "1.0.3"
19
19
  end
20
20
  end
@@ -13,6 +13,8 @@
13
13
  # License along with this library; if not, write to the Free Software
14
14
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
15
 
16
+ require "English"
17
+
16
18
  require "coolio"
17
19
 
18
20
  require "droonga/loggable"
@@ -26,15 +28,19 @@ module Droonga
26
28
 
27
29
  attr_reader :loop
28
30
  attr_reader :name
31
+ attr_reader :internal_name
29
32
  attr_reader :forwarder
30
33
  attr_reader :replier
31
- def initialize(loop, name)
34
+ attr_accessor :on_finish
35
+ def initialize(loop, name, internal_name)
32
36
  @loop = loop
33
37
  @name = name
38
+ @internal_name = internal_name
34
39
  @sessions = {}
35
40
  @current_id = 0
36
41
  @forwarder = Forwarder.new(@loop)
37
42
  @replier = Replier.new(@forwarder)
43
+ @on_finish = nil
38
44
  end
39
45
 
40
46
  def start
@@ -50,13 +56,26 @@ module Droonga
50
56
  end
51
57
 
52
58
  def local_route?(route)
53
- route.start_with?(@name)
59
+ route.start_with?(@name) or route.start_with?(@internal_name)
60
+ end
61
+
62
+ def farm_path(route)
63
+ if /\A[^:]+:\d+\/[^.]+/ =~ route
64
+ name = $MATCH
65
+ if name == @internal_name
66
+ @name
67
+ else
68
+ name
69
+ end
70
+ else
71
+ route
72
+ end
54
73
  end
55
74
 
56
75
  def generate_id
57
76
  id = @current_id
58
77
  @current_id = id.succ
59
- return [@name, id].join(".#")
78
+ return [@internal_name, id].join(".#")
60
79
  end
61
80
 
62
81
  def find_session(id)
@@ -69,6 +88,13 @@ module Droonga
69
88
 
70
89
  def unregister_session(id)
71
90
  @sessions.delete(id)
91
+ unless have_session?
92
+ @on_finish.call if @on_finish
93
+ end
94
+ end
95
+
96
+ def have_session?
97
+ not @sessions.empty?
72
98
  end
73
99
 
74
100
  private
@@ -0,0 +1,100 @@
1
+ # Copyright (C) 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 "socket"
17
+ require "ipaddr"
18
+
19
+ require "droonga/fluent_message_receiver"
20
+
21
+ module Droonga
22
+ class InternalFluentMessageReceiver
23
+ include Loggable
24
+
25
+ def initialize(loop, host, &on_message)
26
+ @loop = loop
27
+ @host = host
28
+ @on_message = on_message
29
+ end
30
+
31
+ def start
32
+ logger.trace("start: start")
33
+ start_listen_socket
34
+ start_heartbeat_socket
35
+ start_message_receiver
36
+ logger.trace("start: done")
37
+
38
+ [@host, @port]
39
+ end
40
+
41
+ def shutdown
42
+ logger.trace("shutdown: start")
43
+ shutdown_message_receiver
44
+ shutdown_heartbeat_socket
45
+ shutdown_listen_socket
46
+ logger.trace("shutdown: done")
47
+ end
48
+
49
+ private
50
+ def start_listen_socket
51
+ logger.trace("start_listen_socket: start")
52
+ @listen_socket = TCPServer.new(@host, 0)
53
+ @port = @listen_socket.addr[1]
54
+ logger.trace("start_listen_socket: done")
55
+ end
56
+
57
+ def shutdown_listen_socket
58
+ logger.trace("shutdown_listen_socket: start")
59
+ logger.trace("shutdown_listen_socket: done")
60
+ end
61
+
62
+ def address_family
63
+ ip_address = IPAddr.new(IPSocket.getaddress(@host))
64
+ ip_address.family
65
+ end
66
+
67
+ def start_heartbeat_socket
68
+ logger.trace("start_heartbeat_socket: start")
69
+ @heartbeat_socket = UDPSocket.new(address_family)
70
+ @heartbeat_socket.bind(@host, @port)
71
+ logger.trace("start_heartbeat_socket: done")
72
+ end
73
+
74
+ def shutdown_heartbeat_socket
75
+ logger.trace("shutdown_heartbeat_socket: start")
76
+ logger.trace("shutdown_heartbeat_socket: done")
77
+ end
78
+
79
+ def start_message_receiver
80
+ logger.trace("start_heartbeat_socket: start")
81
+ options = {
82
+ :listen_fd => @listen_socket.fileno,
83
+ :heartbeat_fd => @heartbeat_socket.fileno,
84
+ }
85
+ @message_receiver = FluentMessageReceiver.new(@loop, options, &@on_message)
86
+ @message_receiver.start
87
+ logger.trace("start_heartbeat_socket: done")
88
+ end
89
+
90
+ def shutdown_message_receiver
91
+ logger.trace("shutdown_message_receiver: start")
92
+ @message_receiver.shutdown
93
+ logger.trace("shutdown_message_receiver: done")
94
+ end
95
+
96
+ def log_tag
97
+ "internal-fluent-message-receiver"
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (C) 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 "pathname"
17
+ require "json"
18
+
19
+ module Droonga
20
+ class LiveNodesListLoader
21
+ def initialize(path)
22
+ @path = path
23
+ end
24
+
25
+ def load
26
+ list = parse
27
+ list.keys
28
+ end
29
+
30
+ private
31
+ def parse
32
+ return default_list unless @path.exist?
33
+
34
+ contents = @path.read
35
+ return default_list if contents.empty?
36
+
37
+ begin
38
+ JSON.parse(contents)
39
+ rescue JSON::ParserError
40
+ default_list
41
+ end
42
+ end
43
+
44
+ def default_list
45
+ {}
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,72 @@
1
+ # Copyright (C) 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 "fileutils"
17
+ require "listen"
18
+
19
+ require "droonga/path"
20
+ require "droonga/loggable"
21
+ require "droonga/live_nodes_list_loader"
22
+
23
+ module Droonga
24
+ class LiveNodesListObserver
25
+ class << self
26
+ FILE_NAME = "live-nodes.json"
27
+
28
+ def path
29
+ Path.state + FILE_NAME
30
+ end
31
+ end
32
+
33
+ include Loggable
34
+
35
+ attr_accessor :on_update
36
+
37
+ def initialize
38
+ end
39
+
40
+ def start
41
+ path = self.class.path
42
+ file_name = path.expand_path.to_s
43
+ directory = path.dirname.to_s
44
+ FileUtils.mkdir_p(directory)
45
+ @listener = Listen.to(directory) do |modified, added, removed|
46
+ if added.include?(file_name) or
47
+ modified.include?(file_name)
48
+ load_list!
49
+ end
50
+ end
51
+ @listener.start
52
+ end
53
+
54
+ def stop
55
+ @listener.stop
56
+ end
57
+
58
+ def load_list!
59
+ path = self.class.path
60
+ loader = LiveNodesListLoader.new(path)
61
+ live_nodes = loader.load
62
+ logger.info("loaded", :path => path.to_s, :live_nodes => live_nodes)
63
+
64
+ on_update.call(live_nodes) if on_update
65
+ end
66
+
67
+ private
68
+ def log_tag
69
+ "live-nodes-list-observer"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ # Copyright (C) 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 "pathname"
17
+
18
+ module Droonga
19
+ module Path
20
+ BASE_DIR_ENV_NAME = "DROONGA_BASE_DIR"
21
+
22
+ class << self
23
+ def setup
24
+ base_dir = ENV[BASE_DIR_ENV_NAME] || Dir.pwd
25
+ ENV[BASE_DIR_ENV_NAME] = File.expand_path(base_dir)
26
+ end
27
+
28
+ def base
29
+ @base ||= Pathname.new(ENV[BASE_DIR_ENV_NAME] || Dir.pwd).expand_path
30
+ end
31
+
32
+ def base=(new_base)
33
+ @base = nil
34
+ ENV[BASE_DIR_ENV_NAME] = new_base
35
+ end
36
+
37
+ def state
38
+ base + "state"
39
+ end
40
+
41
+ def catalog
42
+ base_file_name = ENV["DROONGA_CATALOG"] || "catalog.json"
43
+ Pathname.new(base_file_name).expand_path(base)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -26,65 +26,306 @@ module Droonga
26
26
 
27
27
  class Handler < Droonga::Handler
28
28
  def handle(message)
29
- replyTo = (message.raw["replyTo"] || {})["to"]
30
- return false unless replyTo
31
-
32
- request = message.request || {}
33
- messages_per_seconds = request["messagesPerSecond"] || 10000
34
- messages_per_seconds = [10, messages_per_seconds.to_i].max
35
- messages_per_100msec = messages_per_seconds / 10
36
- dumper = Enumerator.new do |yielder|
37
- n = 0
38
- each_table do |table|
39
- table.each do |record|
40
- values = {}
41
- record.attributes.each do |key, value|
42
- values[key] = value unless key.start_with?("_")
43
- end
44
- dump_message = {
45
- "dataset" => message.raw["dataset"],
46
- "body" => {
47
- "table" => table.name,
48
- "key" => record.key,
49
- "values" => values,
50
- },
51
- }
52
- messenger.forward(dump_message,
53
- "to" => replyTo,
54
- "type" => "dump.record")
55
- n = (n + 1) % messages_per_100msec
56
- yielder << nil if n.zero?
57
- end
58
- end
29
+ request = Request.new(message)
30
+ if request.need_dump?
31
+ dumper = Dumper.new(@context, loop, messenger, request)
32
+ dumper.start_dump
33
+ true
34
+ else
35
+ false
36
+ end
37
+ end
38
+ end
39
+
40
+ class Request
41
+ def initialize(message)
42
+ @message = message
43
+ end
44
+
45
+ def need_dump?
46
+ reply_to
47
+ end
48
+
49
+ def id
50
+ @message["id"]
51
+ end
52
+
53
+ def dataset
54
+ @message.raw["dataset"]
55
+ end
56
+
57
+ def reply_to
58
+ (@message.raw["replyTo"] || {})["to"]
59
+ end
60
+
61
+ def messages_per_seconds
62
+ request = (@message.request || {})
63
+ minimum_messages_per_seconds = 10
64
+ [
65
+ minimum_messages_per_seconds,
66
+ (request["messagesPerSecond"] || 10000).to_i,
67
+ ].max
68
+ end
69
+ end
70
+
71
+ class Dumper
72
+ include Loggable
73
+
74
+ def initialize(context, loop, messenger, request)
75
+ @context = context
76
+ @loop = loop
77
+ @messenger = messenger
78
+ @request = request
79
+ end
80
+
81
+ def start_dump
82
+ setup_forward_data
83
+
84
+ forward("dump.start")
85
+
86
+ dumper = Fiber.new do
87
+ dump_schema
88
+ dump_records
89
+ dump_indexes
90
+ forward("dump.end")
91
+ end
92
+
93
+ on_error = lambda do |exception|
94
+ message = "failed to dump"
95
+ logger.exception(message, $!)
96
+ error("DumpFailure", message)
59
97
  end
60
98
 
61
99
  timer = Coolio::TimerWatcher.new(0.1, true)
62
100
  timer.on_timer do
63
101
  begin
64
- dumper.next
65
- rescue StopIteration
102
+ dumper.resume
103
+ rescue FiberError
66
104
  timer.detach
105
+ rescue
106
+ timer.detach
107
+ on_error.call($!)
67
108
  end
68
109
  end
69
- loop.attach(timer)
70
110
 
71
- true
111
+ @loop.attach(timer)
72
112
  end
73
113
 
74
114
  private
115
+ def setup_forward_data
116
+ @base_forward_message = {
117
+ "inReplyTo" => @request.id,
118
+ "dataset" => @request.dataset,
119
+ }
120
+ @forward_to = @request.reply_to
121
+ @n_forwarded_messages = 0
122
+ @messages_per_100msec = @request.messages_per_seconds / 10
123
+ end
124
+
125
+ def error(name, message)
126
+ message = {
127
+ "statusCode" => ErrorMessages::InternalServerError::STATUS_CODE,
128
+ "body" => {
129
+ "name" => name,
130
+ "message" => message,
131
+ },
132
+ }
133
+ error_message = @base_forward_message.merge(message)
134
+ @messenger.forward(error_message,
135
+ "to" => @forward_to,
136
+ "type" => "dump.error")
137
+ end
138
+
139
+ def forward(type, body=nil)
140
+ forward_message = @base_forward_message
141
+ if body
142
+ forward_message = forward_message.merge("body" => body)
143
+ end
144
+ @messenger.forward(forward_message,
145
+ "to" => @forward_to,
146
+ "type" => type)
147
+
148
+ @n_forwarded_messages += 1
149
+ @n_forwarded_messages %= @messages_per_100msec
150
+ Fiber.yield if @n_forwarded_messages.zero?
151
+ end
152
+
153
+ def dump_schema
154
+ reference_tables = []
155
+ each_table do |table|
156
+ if reference_table?(table)
157
+ reference_tables << table
158
+ next
159
+ end
160
+ dump_table(table)
161
+ end
162
+
163
+ reference_tables.each do |table|
164
+ dump_table(table)
165
+ end
166
+ end
167
+
168
+ def dump_table(table)
169
+ forward("dump.table", table_body(table))
170
+
171
+ columns = table.columns.sort_by(&:name)
172
+ columns.each do |column|
173
+ next if index_column?(column)
174
+ dump_column(column)
175
+ end
176
+ end
177
+
178
+ def table_body(table)
179
+ body = {
180
+ "type" => table_type(table),
181
+ "name" => table.name,
182
+ }
183
+ if table.support_key?
184
+ body["keyType"] = table.domain.name
185
+ end
186
+ if body["keyType"] == "ShortText"
187
+ if table.default_tokenizer
188
+ body["tokenizer"] = table.default_tokenizer.name
189
+ end
190
+ if table.normalizer
191
+ body["normalizer"] = table.normalizer.name
192
+ end
193
+ end
194
+ body
195
+ end
196
+
197
+ def table_type(table)
198
+ table.class.name.split(/::/).last
199
+ end
200
+
201
+ def dump_column(column)
202
+ forward("dump.column", column_body(column))
203
+ end
204
+
205
+ def column_body(column)
206
+ body = {
207
+ "table" => column.domain.name,
208
+ "name" => column.local_name,
209
+ "type" => column_type(column),
210
+ "valueType" => column.range.name,
211
+ }
212
+ case body["type"]
213
+ when "Index"
214
+ body["indexOptions"] = {
215
+ "section" => column.with_section?,
216
+ "weight" => column.with_weight?,
217
+ "position" => column.with_position?,
218
+ "sources" => index_column_sources(column),
219
+ }
220
+ when "Vector"
221
+ body["vectorOptions"] = {
222
+ "weight" => column.with_weight?,
223
+ }
224
+ end
225
+ body
226
+ end
227
+
228
+ def column_type(column)
229
+ if index_column?(column)
230
+ "Index"
231
+ elsif column.vector?
232
+ "Vector"
233
+ else
234
+ "Scalar"
235
+ end
236
+ end
237
+
238
+ def index_column_sources(index_column)
239
+ index_column.sources.collect do |source|
240
+ if source.is_a?(::Groonga::Table)
241
+ "_key"
242
+ else
243
+ source.local_name
244
+ end
245
+ end
246
+ end
247
+
248
+ def dump_records
249
+ each_table do |table|
250
+ next if index_only_table?(table)
251
+ table.each do |record|
252
+ values = {}
253
+ record.attributes.each do |key, value|
254
+ next if key.start_with?("_")
255
+ values[key] = normalize_record_value(value)
256
+ end
257
+ body = {
258
+ "table" => table.name,
259
+ "key" => record.key,
260
+ "values" => values,
261
+ }
262
+ forward("dump.record", body)
263
+ end
264
+ end
265
+ end
266
+
267
+ def normalize_record_value(value)
268
+ case value
269
+ when Array
270
+ value.collect do |element|
271
+ case element
272
+ when Hash
273
+ element["_key"]
274
+ else
275
+ element
276
+ end
277
+ end
278
+ else
279
+ value
280
+ end
281
+ end
282
+
283
+ def dump_indexes
284
+ each_index_columns do |column|
285
+ dump_column(column)
286
+ end
287
+ end
288
+
75
289
  def each_table
76
- @context.database.each(:ignore_missing_object => true) do |object|
77
- next unless object.is_a?(::Groonga::Table)
78
- next if index_only_table?(object)
290
+ options = {
291
+ :ignore_missing_object => true,
292
+ :order_by => :key,
293
+ }
294
+ @context.database.each(options) do |object|
295
+ next unless table?(object)
79
296
  yield(object)
80
297
  end
81
298
  end
82
299
 
300
+ def table?(object)
301
+ object.is_a?(::Groonga::Table)
302
+ end
303
+
83
304
  def index_only_table?(table)
84
305
  table.columns.all? do |column|
85
- column.is_a?(::Groonga::IndexColumn)
306
+ index_column?(column)
86
307
  end
87
308
  end
309
+
310
+ def reference_table?(table)
311
+ table.support_key? and table?(table.domain)
312
+ end
313
+
314
+ def index_column?(column)
315
+ column.is_a?(::Groonga::IndexColumn)
316
+ end
317
+
318
+ def each_index_columns
319
+ each_table do |table|
320
+ table.columns.each do |column|
321
+ yield(column) if index_column?(column)
322
+ end
323
+ end
324
+ end
325
+
326
+ def log_tag
327
+ "[#{Process.ppid}][#{Process.pid}] dumper"
328
+ end
88
329
  end
89
330
 
90
331
  define_single_step do |step|