droonga-engine 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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|