droonga-engine 1.0.7 → 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/bin/droonga-engine-absorb-data +200 -86
  4. data/bin/droonga-engine-configure +14 -11
  5. data/bin/droonga-engine-join +178 -59
  6. data/droonga-engine.gemspec +1 -1
  7. data/install.sh +1 -0
  8. data/lib/droonga/buffered_tcp_socket.rb +2 -2
  9. data/lib/droonga/catalog/base.rb +18 -0
  10. data/lib/droonga/catalog/dataset.rb +8 -0
  11. data/lib/droonga/catalog/version1.rb +3 -12
  12. data/lib/droonga/catalog/version2.rb +5 -12
  13. data/lib/droonga/command/droonga_engine.rb +6 -61
  14. data/lib/droonga/command/droonga_engine_service.rb +1 -0
  15. data/lib/droonga/command/droonga_engine_worker.rb +1 -0
  16. data/lib/droonga/command/remote.rb +18 -1
  17. data/lib/droonga/command/serf_event_handler.rb +3 -0
  18. data/lib/droonga/data_absorber.rb +234 -44
  19. data/lib/droonga/distributed_command_planner.rb +5 -5
  20. data/lib/droonga/engine.rb +27 -15
  21. data/lib/droonga/engine/version.rb +1 -1
  22. data/lib/droonga/handler_runner.rb +4 -0
  23. data/lib/droonga/node_status.rb +6 -2
  24. data/lib/droonga/path.rb +8 -14
  25. data/lib/droonga/planner.rb +4 -3
  26. data/lib/droonga/plugin/metadata/handler_action.rb +8 -0
  27. data/lib/droonga/plugins/groonga/column_create.rb +1 -1
  28. data/lib/droonga/plugins/groonga/column_list.rb +23 -1
  29. data/lib/droonga/plugins/groonga/column_remove.rb +1 -1
  30. data/lib/droonga/plugins/groonga/column_rename.rb +1 -1
  31. data/lib/droonga/plugins/groonga/delete.rb +1 -1
  32. data/lib/droonga/plugins/groonga/select.rb +17 -2
  33. data/lib/droonga/plugins/groonga/table_create.rb +26 -1
  34. data/lib/droonga/plugins/groonga/table_remove.rb +1 -1
  35. data/lib/droonga/plugins/search.rb +1 -1
  36. data/lib/droonga/plugins/search/distributed_search_planner.rb +15 -7
  37. data/lib/droonga/processor.rb +3 -2
  38. data/lib/droonga/searcher.rb +31 -15
  39. data/lib/droonga/serf.rb +1 -0
  40. data/lib/droonga/service_installation.rb +2 -2
  41. data/lib/droonga/single_step.rb +2 -2
  42. data/test/command/fixture/event.jsons +3 -2
  43. data/test/command/fixture/user-table.jsons +3 -2
  44. data/test/command/fixture/users.jsons +25 -0
  45. data/test/command/run-test.rb +13 -1
  46. data/test/command/suite/groonga/column_create/scalar.test +3 -2
  47. data/test/command/suite/groonga/column_create/vector.test +3 -2
  48. data/test/command/suite/groonga/column_list/{success.expected → no-key.expected} +0 -0
  49. data/test/command/suite/groonga/column_list/{success.test → no-key.test} +1 -1
  50. data/test/command/suite/groonga/column_list/with-key.expected +96 -0
  51. data/test/command/suite/groonga/column_list/with-key.test +25 -0
  52. data/test/command/suite/groonga/column_remove/success.test +3 -2
  53. data/test/command/suite/groonga/column_remove/unknown-column.test +3 -2
  54. data/test/command/suite/groonga/column_rename/success.test +3 -2
  55. data/test/command/suite/groonga/column_rename/unknown-column.test +3 -2
  56. data/test/command/suite/groonga/delete/duplicated-identifiers.test +3 -2
  57. data/test/command/suite/groonga/delete/no-identifier.test +3 -2
  58. data/test/command/suite/groonga/select/output_columns/default/array.expected +33 -0
  59. data/test/command/suite/groonga/select/output_columns/default/array.test +38 -0
  60. data/test/command/suite/groonga/select/output_columns/nonexistent.expected +28 -0
  61. data/test/command/suite/groonga/select/output_columns/nonexistent.test +26 -0
  62. data/test/command/suite/groonga/table_create/dat-without-key-type.expected +14 -0
  63. data/test/command/suite/groonga/table_create/dat-without-key-type.test +8 -0
  64. data/test/command/suite/groonga/table_create/dat.expected +13 -0
  65. data/test/command/suite/groonga/table_create/dat.test +9 -0
  66. data/test/command/suite/groonga/table_create/hash-without-key-type.expected +14 -0
  67. data/test/command/suite/groonga/table_create/hash-without-key-type.test +8 -0
  68. data/test/command/suite/groonga/table_create/hash.test +3 -2
  69. data/test/command/suite/groonga/table_create/pat-without-key-type.expected +14 -0
  70. data/test/command/suite/groonga/table_create/pat-without-key-type.test +8 -0
  71. data/test/command/suite/groonga/table_create/pat.expected +13 -0
  72. data/test/command/suite/groonga/table_create/pat.test +9 -0
  73. data/test/command/suite/groonga/table_list/success.test +3 -2
  74. data/test/unit/catalog/test_version1.rb +2 -2
  75. data/test/unit/catalog/test_version2.rb +3 -3
  76. data/test/unit/helper.rb +2 -2
  77. data/test/unit/helper/distributed_search_planner_helper.rb +9 -1
  78. data/test/unit/plugins/groonga/select/test_adapter_input.rb +15 -2
  79. data/test/unit/plugins/groonga/test_column_list.rb +119 -4
  80. data/test/unit/plugins/groonga/test_table_create.rb +29 -0
  81. data/test/unit/plugins/search/planner/test_basic.rb +2 -2
  82. data/test/unit/plugins/search/test_planner.rb +10 -2
  83. metadata +43 -8
@@ -17,17 +17,17 @@
17
17
 
18
18
  module Droonga
19
19
  class DistributedCommandPlanner
20
- attr_accessor :key, :dataset
20
+ attr_accessor :key
21
21
 
22
22
  REDUCE_SUM = "sum"
23
23
 
24
24
  DEFAULT_LIMIT = -1
25
25
 
26
- def initialize(source_message)
26
+ def initialize(dataset, source_message)
27
+ @dataset = dataset
27
28
  @source_message = source_message
28
29
 
29
30
  @key = nil
30
- @dataset = nil
31
31
  @outputs = []
32
32
 
33
33
  @reducers = []
@@ -58,7 +58,7 @@ module Droonga
58
58
  def scatter(record, options={})
59
59
  @processor = {
60
60
  "command" => @source_message["type"],
61
- "dataset" => @dataset || @source_message["dataset"],
61
+ "dataset" => @dataset.name,
62
62
  "body" => options[:body] || @source_message["body"],
63
63
  "record" => record,
64
64
  "type" => "scatter",
@@ -71,7 +71,7 @@ module Droonga
71
71
  def broadcast(options={})
72
72
  processor = {
73
73
  "command" => @source_message["type"],
74
- "dataset" => @dataset || @source_message["dataset"],
74
+ "dataset" => @dataset.name,
75
75
  "body" => options[:body] || @source_message["body"],
76
76
  "type" => "broadcast",
77
77
  "outputs" => [],
@@ -24,6 +24,7 @@ require "droonga/catalog_loader"
24
24
  require "droonga/dispatcher"
25
25
  require "droonga/file_observer"
26
26
  require "droonga/live_nodes_list_loader"
27
+ require "droonga/node_status"
27
28
 
28
29
  module Droonga
29
30
  class Engine
@@ -39,6 +40,12 @@ module Droonga
39
40
  @live_nodes_list_observer.on_change = lambda do
40
41
  @state.live_nodes = load_live_nodes
41
42
  end
43
+ @node_status_observer = FileObserver.new(loop, Path.node_status)
44
+ @node_status_observer.on_change = lambda do
45
+ logger.trace("reloading node_status: start")
46
+ node_status.reload
47
+ logger.trace("reloading node_status: done")
48
+ end
42
49
  @on_ready = nil
43
50
  end
44
51
 
@@ -49,6 +56,7 @@ module Droonga
49
56
  end
50
57
  @state.start
51
58
  @live_nodes_list_observer.start
59
+ @node_status_observer.start
52
60
  @dispatcher.start
53
61
  logger.trace("start: done")
54
62
  end
@@ -56,9 +64,10 @@ module Droonga
56
64
  def stop_gracefully
57
65
  logger.trace("stop_gracefully: start")
58
66
  @live_nodes_list_observer.stop
67
+ @node_status_observer.stop
59
68
  on_finish = lambda do
60
69
  logger.trace("stop_gracefully/on_finish: start")
61
- output_last_processed_timestamp
70
+ save_last_processed_message_timestamp
62
71
  @dispatcher.stop_gracefully do
63
72
  @state.shutdown
64
73
  yield
@@ -78,8 +87,9 @@ module Droonga
78
87
  # It may be called after stop_gracefully.
79
88
  def stop_immediately
80
89
  logger.trace("stop_immediately: start")
81
- output_last_processed_timestamp
90
+ save_last_processed_message_timestamp
82
91
  @live_nodes_list_observer.stop
92
+ @node_status_observer.stop
83
93
  @dispatcher.stop_immediately
84
94
  @state.shutdown
85
95
  logger.trace("stop_immediately: done")
@@ -87,10 +97,14 @@ module Droonga
87
97
 
88
98
  def process(message)
89
99
  return unless effective_message?(message)
90
- @last_processed_timestamp = message["date"]
100
+ @last_processed_message_timestamp = message["date"]
91
101
  @dispatcher.process_message(message)
92
102
  end
93
103
 
104
+ def node_status
105
+ @node_status ||= NodeStatus.new
106
+ end
107
+
94
108
  private
95
109
  def load_catalog
96
110
  catalog_path = Path.catalog
@@ -116,14 +130,10 @@ module Droonga
116
130
  Dispatcher.new(@state, @catalog)
117
131
  end
118
132
 
119
- def output_last_processed_timestamp
120
- logger.trace("output_last_processed_timestamp: start")
121
- path = Path.last_processed_timestamp
122
- FileUtils.mkdir_p(path.dirname.to_s)
123
- path.open("w") do |file|
124
- file.write(@last_processed_timestamp)
125
- end
126
- logger.trace("output_last_processed_timestamp: done")
133
+ def save_last_processed_message_timestamp
134
+ logger.trace("output_last_processed_message_timestamp: start")
135
+ node_status.set(:last_processed_message_timestamp, @last_processed_message_timestamp.to_s)
136
+ logger.trace("output_last_processed_message_timestamp: done")
127
137
  end
128
138
 
129
139
  def effective_message?(message)
@@ -131,17 +141,19 @@ module Droonga
131
141
  return true if effective_timestamp.nil?
132
142
 
133
143
  message_timestamp = Time.parse(message["date"])
144
+ logger.trace("checking effective_message_timestamp (#{effective_timestamp}) vs message_timestamp(message_timestamp)")
134
145
  return false if effective_timestamp >= message_timestamp
135
146
 
136
- FileUtils.rm(Path.effective_timestamp.to_s)
147
+ logger.trace("deleting obsolete effective_message_timestamp: start")
148
+ node_status.delete(:effective_message_timestamp)
149
+ logger.trace("deleting obsolete effective_message_timestamp: done")
137
150
  true
138
151
  end
139
152
 
140
153
  def effective_message_timestamp
141
- path = Path.effective_message_timestamp
142
- return nil unless path.exist?
154
+ timestamp = node_status.get(:effective_message_timestamp)
155
+ return nil unless timestamp
143
156
 
144
- timestamp = path.read
145
157
  begin
146
158
  Time.parse(timestamp)
147
159
  rescue ArgumentError
@@ -15,6 +15,6 @@
15
15
 
16
16
  module Droonga
17
17
  class Engine
18
- VERSION = "1.0.7"
18
+ VERSION = "1.0.8"
19
19
  end
20
20
  end
@@ -44,6 +44,10 @@ module Droonga
44
44
  logger.trace("shutdown: done")
45
45
  end
46
46
 
47
+ def change_schema?(type)
48
+ find_handler_class(type).action.change_schema?
49
+ end
50
+
47
51
  def prefer_synchronous?(type)
48
52
  find_handler_class(type).action.synchronous?
49
53
  end
@@ -20,7 +20,7 @@ require "droonga/safe_file_writer"
20
20
  module Droonga
21
21
  class NodeStatus
22
22
  def initialize
23
- @status = load
23
+ reload
24
24
  end
25
25
 
26
26
  def have?(key)
@@ -45,13 +45,17 @@ module Droonga
45
45
  SafeFileWriter.write(status_file, JSON.pretty_generate(@status))
46
46
  end
47
47
 
48
+ def reload
49
+ @status = load
50
+ end
51
+
48
52
  private
49
53
  def normalize_key(key)
50
54
  key.to_sym
51
55
  end
52
56
 
53
57
  def status_file
54
- @status_file ||= Path.state + "status_file"
58
+ @status_file ||= Path.node_status
55
59
  end
56
60
 
57
61
  def load
data/lib/droonga/path.rb CHANGED
@@ -34,34 +34,28 @@ module Droonga
34
34
  ENV[BASE_DIR_ENV_NAME] = new_base
35
35
  end
36
36
 
37
- def databases
38
- base + "database"
37
+ def databases(base_path=nil)
38
+ base_path ||= base
39
+ path = Pathname(base_path) + "databases"
40
+ path.expand_path
39
41
  end
40
42
 
41
43
  def state
42
44
  base + "state"
43
45
  end
44
46
 
45
- def live_nodes
46
- state + "live-nodes.json"
47
- end
48
-
49
- def last_processed_timestamp
50
- state + "last-processed.timestamp"
47
+ def node_status
48
+ state + "status_file"
51
49
  end
52
50
 
53
- def effective_message_timestamp
54
- state + "effective-message.timestamp"
51
+ def live_nodes
52
+ state + "live-nodes.json"
55
53
  end
56
54
 
57
55
  def config
58
56
  base + "droonga-engine.yaml"
59
57
  end
60
58
 
61
- def default_pid_file
62
- base + "droonga-engine.pid"
63
- end
64
-
65
59
  def default_log_file
66
60
  base + "droonga-engine.log"
67
61
  end
@@ -22,7 +22,8 @@ module Droonga
22
22
  include Loggable
23
23
  include ErrorMessages
24
24
 
25
- def initialize
25
+ def initialize(dataset)
26
+ @dataset = dataset
26
27
  end
27
28
 
28
29
  def plan(message)
@@ -31,14 +32,14 @@ module Droonga
31
32
 
32
33
  private
33
34
  def scatter(message, record, options={})
34
- planner = DistributedCommandPlanner.new(message)
35
+ planner = DistributedCommandPlanner.new(@dataset, message)
35
36
  planner.scatter(record)
36
37
  planner.reduce(options[:reduce])
37
38
  planner.plan
38
39
  end
39
40
 
40
41
  def broadcast(message, options={})
41
- planner = DistributedCommandPlanner.new(message)
42
+ planner = DistributedCommandPlanner.new(@dataset, message)
42
43
  planner.broadcast(:write => options[:write])
43
44
  planner.reduce(options[:reduce])
44
45
  planner.plan
@@ -21,6 +21,14 @@ module Droonga
21
21
  @handler_class = handler_class
22
22
  end
23
23
 
24
+ def change_schema?
25
+ configuration[:change_schema]
26
+ end
27
+
28
+ def change_schema=(boolean)
29
+ configuration[:change_schema] = boolean
30
+ end
31
+
24
32
  def synchronous?
25
33
  configuration[:synchronous]
26
34
  end
@@ -103,7 +103,7 @@ module Droonga
103
103
  end
104
104
 
105
105
  class Handler < Droonga::Handler
106
- action.synchronous = true
106
+ action.change_schema = true
107
107
 
108
108
  def handle(message)
109
109
  command = Command.new(@context)
@@ -47,6 +47,28 @@ module Droonga
47
47
  private
48
48
  def list_columns(table_name)
49
49
  table = @context[table_name]
50
+ virtual_columns(table) + real_columns(table)
51
+ end
52
+
53
+ def virtual_columns(table)
54
+ if not table.support_key? or table.domain.nil?
55
+ return []
56
+ end
57
+ [
58
+ [
59
+ table.id, # id
60
+ "_key", # name
61
+ "", # path
62
+ "", # type
63
+ "COLUMN_SCALAR", # flags
64
+ table.name, # domain
65
+ table.domain.name, # range
66
+ [], # source
67
+ ]
68
+ ]
69
+ end
70
+
71
+ def real_columns(table)
50
72
  table.columns.collect do |column|
51
73
  format_column(column)
52
74
  end
@@ -98,7 +120,7 @@ module Droonga
98
120
  return [] unless column.is_a?(::Groonga::IndexColumn)
99
121
  column.sources.collect do |source|
100
122
  if source.is_a?(::Groonga::Table)
101
- "_key"
123
+ source.name
102
124
  else
103
125
  source.local_name
104
126
  end
@@ -46,7 +46,7 @@ module Droonga
46
46
  end
47
47
 
48
48
  class Handler < Droonga::Handler
49
- action.synchronous = true
49
+ action.change_schema = true
50
50
 
51
51
  def handle(message)
52
52
  command = Command.new(@context)
@@ -48,7 +48,7 @@ module Droonga
48
48
  end
49
49
 
50
50
  class Handler < Droonga::Handler
51
- action.synchronous = true
51
+ action.change_schema = true
52
52
 
53
53
  def handle(message)
54
54
  command = Command.new(@context)
@@ -97,7 +97,7 @@ module Droonga
97
97
  end
98
98
 
99
99
  class Handler < Droonga::Handler
100
- action.synchronous = true
100
+ action.change_schema = true
101
101
 
102
102
  def handle(message)
103
103
  command = Command.new(@context)
@@ -28,7 +28,7 @@ module Droonga
28
28
  @result_name = @table + "_result"
29
29
 
30
30
  output_columns = select_request["output_columns"] || "_id, _key, *"
31
- attributes = output_columns.split(/\s*,\s*/)
31
+ attributes = convert_output_columns(output_columns)
32
32
  offset = (select_request["offset"] || "0").to_i
33
33
  limit = (select_request["limit"] || "10").to_i
34
34
 
@@ -131,7 +131,7 @@ module Droonga
131
131
  drilldown_keys = drilldown_keys.split(",")
132
132
 
133
133
  sort_keys = (select_request["drilldown_sortby"] || "").split(",")
134
- columns = (select_request["drilldown_output_columns"] || "_key,_nsubrecs").split(",")
134
+ columns = convert_output_columns(select_request["drilldown_output_columns"] || "_key,_nsubrecs")
135
135
  offset = (select_request["drilldown_offset"] || "0").to_i
136
136
  limit = (select_request["drilldown_limit"] || "10").to_i
137
137
 
@@ -165,6 +165,20 @@ module Droonga
165
165
  end
166
166
  queries
167
167
  end
168
+
169
+ # for a backward compatibility for command_version=1,
170
+ # whitespace-separeted case (without functions) should be accepted.
171
+ COMMAND_VERSION_1_ONLY_PATTERN = /\A[^\s,()]+(\s+[^\s,()]+)+\z/
172
+
173
+ def convert_output_columns(output_columns)
174
+ output_columns = output_columns.strip
175
+ command_version_is_1 = output_columns =~ COMMAND_VERSION_1_ONLY_PATTERN
176
+ if command_version_is_1
177
+ output_columns.split(/\s+/)
178
+ else
179
+ output_columns.split(/\s*,\s*/)
180
+ end
181
+ end
168
182
  end
169
183
 
170
184
  class ResponseConverter
@@ -223,6 +237,7 @@ module Droonga
223
237
  records.collect do |record|
224
238
  record.collect.each_with_index do |value, i|
225
239
  name, type = attributes[i]
240
+ _ = name # suppress a warning
226
241
  case type
227
242
  when "Time"
228
243
  normalize_time(value).to_f
@@ -35,6 +35,8 @@ module Droonga
35
35
  :result => false)
36
36
  end
37
37
 
38
+ validate_key_type
39
+
38
40
  options = parse_command
39
41
  ::Groonga::Schema.define(:context => @context) do |schema|
40
42
  schema.create_table(name, options)
@@ -88,10 +90,33 @@ module Droonga
88
90
  return unless @command["normalizer"]
89
91
  options[:normalizer] = @command["normalizer"]
90
92
  end
93
+
94
+ def validate_key_type
95
+ if @command.table_hash_key? and @command["key_type"].nil?
96
+ message = "key_type is required for TABLE_HASH_KEY table"
97
+ raise CommandError.new(:status => Status::INVALID_ARGUMENT,
98
+ :message => message,
99
+ :result => false)
100
+ end
101
+
102
+ if @command.table_pat_key? and @command["key_type"].nil?
103
+ message = "key_type is required for TABLE_PAT_KEY table"
104
+ raise CommandError.new(:status => Status::INVALID_ARGUMENT,
105
+ :message => message,
106
+ :result => false)
107
+ end
108
+
109
+ if @command.table_dat_key? and @command["key_type"].nil?
110
+ message = "key_type is required for TABLE_DAT_KEY table"
111
+ raise CommandError.new(:status => Status::INVALID_ARGUMENT,
112
+ :message => message,
113
+ :result => false)
114
+ end
115
+ end
91
116
  end
92
117
 
93
118
  class Handler < Droonga::Handler
94
- action.synchronous = true
119
+ action.change_schema = true
95
120
 
96
121
  def handle(message)
97
122
  command = Command.new(@context)