droonga-engine 1.0.7 → 1.0.8

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 (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)