groonga-query-log 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2013-2017 Kouhei Sutou <kou@clear-code.com>
1
+ # Copyright (C) 2013-2018 Kouhei Sutou <kou@clear-code.com>
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -19,133 +19,149 @@ require "optparse"
19
19
  require "groonga-query-log"
20
20
 
21
21
  module GroongaQueryLog
22
- module Command
23
- class VerifyServer
24
- def initialize
25
- @options = ServerVerifier::Options.new
26
- end
27
-
28
- def run(command_line, &callback)
29
- input_paths = create_parser.parse(command_line)
30
- verifier = ServerVerifier.new(@options)
31
- if input_paths.empty?
32
- verifier.verify($stdin, &callback)
33
- else
34
- input_paths.each do |input_path|
35
- File.open(input_path) do |input|
36
- verifier.verify(input, &callback)
22
+ module Command
23
+ class VerifyServer
24
+ def initialize
25
+ @options = ServerVerifier::Options.new
26
+ end
27
+
28
+ def run(command_line, &callback)
29
+ input_paths = create_parser.parse(command_line)
30
+ same = true
31
+ verifier = ServerVerifier.new(@options)
32
+ if input_paths.empty?
33
+ same = verifier.verify($stdin, &callback)
34
+ else
35
+ input_paths.each do |input_path|
36
+ File.open(input_path) do |input|
37
+ unless verifier.verify(input, &callback)
38
+ same = false
37
39
  end
38
40
  end
39
41
  end
40
- true
41
42
  end
43
+ same
44
+ end
42
45
 
43
- private
44
- def create_parser
45
- parser = OptionParser.new
46
- parser.version = VERSION
47
- parser.banner += " QUERY_LOG1 QUERY_LOG2 ..."
46
+ private
47
+ def create_parser
48
+ parser = OptionParser.new
49
+ parser.version = VERSION
50
+ parser.banner += " QUERY_LOG1 QUERY_LOG2 ..."
48
51
 
49
- parser.separator("")
50
- parser.separator("Options:")
52
+ parser.separator("")
53
+ parser.separator("Options:")
51
54
 
52
- available_protocols = [:gqtp, :http]
53
- available_protocols_label = "[#{available_protocols.join(', ')}]"
55
+ available_protocols = [:gqtp, :http]
56
+ available_protocols_label = "[#{available_protocols.join(', ')}]"
54
57
 
55
- parser.on("--groonga1-host=HOST",
56
- "Host name or IP address of Groonga server 1",
57
- "[#{@options.groonga1.host}]") do |host|
58
- @options.groonga1.host = host
59
- end
58
+ parser.on("--groonga1-host=HOST",
59
+ "Host name or IP address of Groonga server 1",
60
+ "[#{@options.groonga1.host}]") do |host|
61
+ @options.groonga1.host = host
62
+ end
60
63
 
61
- parser.on("--groonga1-port=PORT", Integer,
62
- "Port number of Groonga server 1",
63
- "[#{@options.groonga1.port}]") do |port|
64
- @options.groonga1.port = port
65
- end
64
+ parser.on("--groonga1-port=PORT", Integer,
65
+ "Port number of Groonga server 1",
66
+ "[#{@options.groonga1.port}]") do |port|
67
+ @options.groonga1.port = port
68
+ end
66
69
 
67
- parser.on("--groonga1-protocol=PROTOCOL", available_protocols,
68
- "Protocol of Groonga server 1",
69
- available_protocols_label) do |protocol|
70
- @options.groonga1.protocol = protocol
71
- end
70
+ parser.on("--groonga1-protocol=PROTOCOL", available_protocols,
71
+ "Protocol of Groonga server 1",
72
+ available_protocols_label) do |protocol|
73
+ @options.groonga1.protocol = protocol
74
+ end
72
75
 
73
- parser.on("--groonga2-host=HOST",
74
- "Host name or IP address of Groonga server 2",
75
- "[#{@options.groonga2.host}]") do |host|
76
- @options.groonga2.host = host
77
- end
76
+ parser.on("--groonga2-host=HOST",
77
+ "Host name or IP address of Groonga server 2",
78
+ "[#{@options.groonga2.host}]") do |host|
79
+ @options.groonga2.host = host
80
+ end
78
81
 
79
- parser.on("--groonga2-port=PORT", Integer,
80
- "Port number of Groonga server 2",
81
- "[#{@options.groonga2.port}]") do |port|
82
- @options.groonga2.port = port
83
- end
82
+ parser.on("--groonga2-port=PORT", Integer,
83
+ "Port number of Groonga server 2",
84
+ "[#{@options.groonga2.port}]") do |port|
85
+ @options.groonga2.port = port
86
+ end
84
87
 
85
- parser.on("--groonga2-protocol=PROTOCOL", available_protocols,
86
- "Protocol of Groonga server 2",
87
- available_protocols_label) do |protocol|
88
- @options.groonga2.protocol = protocol
89
- end
88
+ parser.on("--groonga2-protocol=PROTOCOL", available_protocols,
89
+ "Protocol of Groonga server 2",
90
+ available_protocols_label) do |protocol|
91
+ @options.groonga2.protocol = protocol
92
+ end
90
93
 
91
- parser.on("--n-clients=N", Integer,
92
- "The max number of concurrency",
93
- "[#{@options.n_clients}]") do |n_clients|
94
- @options.n_clients = n_clients
95
- end
94
+ parser.on("--n-clients=N", Integer,
95
+ "The max number of concurrency",
96
+ "[#{@options.n_clients}]") do |n_clients|
97
+ @options.n_clients = n_clients
98
+ end
96
99
 
97
- parser.on("--request-queue-size=SIZE", Integer,
98
- "The size of request queue",
99
- "[auto]") do |size|
100
- @options.request_queue_size = size
101
- end
100
+ parser.on("--request-queue-size=SIZE", Integer,
101
+ "The size of request queue",
102
+ "[auto]") do |size|
103
+ @options.request_queue_size = size
104
+ end
102
105
 
103
- parser.on("--disable-cache",
104
- "Add 'cache=no' parameter to request",
105
- "[#{@options.disable_cache?}]") do
106
- @options.disable_cache = true
107
- end
106
+ parser.on("--disable-cache",
107
+ "Add 'cache=no' parameter to request",
108
+ "[#{@options.disable_cache?}]") do
109
+ @options.disable_cache = true
110
+ end
108
111
 
109
- parser.on("--target-command-name=NAME",
110
- "Add NAME to target command names",
111
- "You can specify this option zero or more times",
112
- "See also --target-command-names") do |name|
113
- @options.target_command_names << name
114
- end
112
+ parser.on("--target-command-name=NAME",
113
+ "Add NAME to target command names",
114
+ "You can specify this option zero or more times",
115
+ "See also --target-command-names") do |name|
116
+ @options.target_command_names << name
117
+ end
115
118
 
116
- target_command_names_label = @options.target_command_names.join(", ")
117
- parser.on("--target-command-names=NAME1,NAME2,...", Array,
118
- "Replay only NAME1,NAME2,... commands",
119
- "You can use glob to choose command name",
120
- "[#{target_command_names_label}]") do |names|
121
- @options.target_command_names = names
122
- end
119
+ target_command_names_label = @options.target_command_names.join(", ")
120
+ parser.on("--target-command-names=NAME1,NAME2,...", Array,
121
+ "Replay only NAME1,NAME2,... commands",
122
+ "You can use glob to choose command name",
123
+ "[#{target_command_names_label}]") do |names|
124
+ @options.target_command_names = names
125
+ end
123
126
 
124
- parser.on("--no-care-order",
125
- "Don't care order of select response records") do
126
- @options.care_order = false
127
- end
127
+ parser.on("--no-care-order",
128
+ "Don't care order of select response records") do
129
+ @options.care_order = false
130
+ end
128
131
 
129
- parser.on("--output=PATH",
130
- "Output results to PATH",
131
- "[stdout]") do |path|
132
- @options.output_path = path
133
- end
132
+ parser.on("--output=PATH",
133
+ "Output results to PATH",
134
+ "[stdout]") do |path|
135
+ @options.output_path = path
136
+ end
134
137
 
135
- parser.on("--[no-]verify-cache",
136
- "Verify cache for each query.",
137
- "[#{@options.verify_cache?}]") do |verify_cache|
138
- @options.verify_cache = verify_cache
139
- end
138
+ parser.on("--[no-]verify-cache",
139
+ "Verify cache for each query.",
140
+ "[#{@options.verify_cache?}]") do |verify_cache|
141
+ @options.verify_cache = verify_cache
142
+ end
140
143
 
141
- parser.separator("Debug options:")
142
- parser.separator("")
144
+ parser.on("--ignore-drilldown-key=KEY",
145
+ "Don't compare drilldown result for KEY",
146
+ "You can specify multiple drilldown keys by",
147
+ "specifying this option multiple times") do |key|
148
+ @options.ignored_drilldown_keys << key
149
+ end
143
150
 
144
- parser.on("--abort-on-exception",
145
- "Abort on exception in threads") do
146
- Thread.abort_on_exception = true
147
- end
151
+ parser.on("--[no-]stop-on-failure",
152
+ "Stop execution on the first failure",
153
+ "(#{@options.stop_on_failure?})") do |boolean|
154
+ @options.stop_on_failure = boolean
155
+ end
156
+
157
+ parser.separator("Debug options:")
158
+ parser.separator("")
159
+
160
+ parser.on("--abort-on-exception",
161
+ "Abort on exception in threads") do
162
+ Thread.abort_on_exception = true
148
163
  end
149
164
  end
150
165
  end
166
+ end
151
167
  end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 Kouhei Sutou <kou@clear-code.com>
1
+ # Copyright (C) 2014-2018 Kouhei Sutou <kou@clear-code.com>
2
2
  #
3
3
  # This library is free software; you can redistribute it and/or
4
4
  # modify it under the terms of the GNU Lesser General Public
@@ -15,199 +15,266 @@
15
15
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
  module GroongaQueryLog
18
- class ResponseComparer
19
- def initialize(command, response1, response2, options={})
20
- @command = command
21
- @response1 = response1
22
- @response2 = response2
23
- @options = options
24
- @options[:care_order] = true if @options[:care_order].nil?
25
- end
18
+ class ResponseComparer
19
+ def initialize(command, response1, response2, options={})
20
+ @command = command
21
+ @response1 = response1
22
+ @response2 = response2
23
+ @options = options.dup
24
+ @options[:care_order] = true if @options[:care_order].nil?
25
+ @options[:ignored_drilldown_keys] ||= []
26
+ end
26
27
 
27
- def same?
28
- if error_response?(@response1) or error_response?(@response2)
29
- if error_response?(@response1) and error_response?(@response2)
30
- same_error_response?
31
- else
32
- false
33
- end
28
+ def same?
29
+ if error_response?(@response1) or error_response?(@response2)
30
+ if error_response?(@response1) and error_response?(@response2)
31
+ same_error_response?
34
32
  else
35
- case @command.name
36
- when "select", "logical_select"
37
- same_select_response?
38
- when "status"
39
- same_cache_hit_rate?
40
- else
41
- same_response?
42
- end
33
+ false
34
+ end
35
+ else
36
+ case @command.name
37
+ when "select", "logical_select"
38
+ same_select_response?
39
+ when "status"
40
+ same_cache_hit_rate?
41
+ else
42
+ same_response?
43
43
  end
44
44
  end
45
+ end
45
46
 
46
- private
47
- def error_response?(response)
48
- response.is_a?(Groonga::Client::Response::Error)
49
- end
47
+ private
48
+ def error_response?(response)
49
+ response.is_a?(Groonga::Client::Response::Error)
50
+ end
50
51
 
51
- def same_error_response?
52
- return_code1 = @response1.header[0]
53
- return_code2 = @response2.header[0]
54
- return_code1 == return_code2
55
- end
52
+ def same_error_response?
53
+ return_code1 = @response1.header[0]
54
+ return_code2 = @response2.header[0]
55
+ return_code1 == return_code2
56
+ end
56
57
 
57
- def same_response?
58
- @response1.body == @response2.body
59
- end
58
+ def same_response?
59
+ @response1.body == @response2.body
60
+ end
60
61
 
61
- def same_select_response?
62
- if care_order?
63
- if all_output_columns?
64
- same_records_all_output_columns?
65
- elsif have_unary_minus_output_column?
66
- same_records_unary_minus_output_column?
67
- else
68
- same_response?
69
- end
62
+ def same_select_response?
63
+ if care_order?
64
+ if all_output_columns?
65
+ return false unless same_records_all_output_columns?
66
+ elsif have_unary_minus_output_column?
67
+ return false unless same_records_unary_minus_output_column?
70
68
  else
71
- same_size_response?
69
+ return false unless same_records?
72
70
  end
71
+ same_drilldowns?
72
+ else
73
+ same_size_response?
73
74
  end
75
+ end
74
76
 
75
- def same_cache_hit_rate?
76
- cache_hit_rate1 = @response1.body["cache_hit_rate"]
77
- cache_hit_rate2 = @response2.body["cache_hit_rate"]
78
- (cache_hit_rate1 - cache_hit_rate2).abs < (10 ** -13)
79
- end
77
+ def same_cache_hit_rate?
78
+ cache_hit_rate1 = @response1.body["cache_hit_rate"]
79
+ cache_hit_rate2 = @response2.body["cache_hit_rate"]
80
+ (cache_hit_rate1 - cache_hit_rate2).abs < (10 ** -13)
81
+ end
82
+
83
+ def care_order?
84
+ return false unless @options[:care_order]
85
+ return false if random_sort?
86
+
87
+ true
88
+ end
89
+
90
+ def random_score?
91
+ return false unless @command.respond_to?(:scorer)
92
+ /\A_score\s*=\s*rand\(\)\z/ === @command.scorer
93
+ end
80
94
 
81
- def care_order?
82
- return false unless @options[:care_order]
83
- return false if random_sort?
95
+ def random_sort?
96
+ random_score? and score_sort?
97
+ end
84
98
 
85
- true
99
+ def score_sort?
100
+ sort_items = @command.sort_keys
101
+ normalized_sort_items = sort_items.collect do |item|
102
+ item.gsub(/\A[+-]/, "")
86
103
  end
104
+ normalized_sort_items.include?("_score")
105
+ end
106
+
107
+ def same_size_response?
108
+ records_result1 = @response1.body[0] || []
109
+ records_result2 = @response2.body[0] || []
110
+ return false if records_result1.size != records_result2.size
87
111
 
88
- def random_score?
89
- return false unless @command.respond_to?(:scorer)
90
- /\A_score\s*=\s*rand\(\)\z/ === @command.scorer
112
+ n_hits1 = records_result1[0]
113
+ n_hits2 = records_result2[0]
114
+ return false if n_hits1 != n_hits2
115
+
116
+ columns1 = records_result1[1]
117
+ columns2 = records_result2[1]
118
+ if all_output_columns?
119
+ columns1.sort_by(&:first) == columns2.sort_by(&:first)
120
+ else
121
+ columns1 == columns2
91
122
  end
123
+ end
92
124
 
93
- def random_sort?
94
- random_score? and score_sort?
125
+ def have_unary_minus_output_column?
126
+ output_columns = @command.output_columns
127
+ return false if output_columns.nil?
128
+ output_columns.split(/\s*,?\s*/).any? {|column| column.start_with?("-")}
129
+ end
130
+
131
+ def same_records_unary_minus_output_column?
132
+ records_result1 = @response1.body[0] || []
133
+ records_result2 = @response2.body[0] || []
134
+ return false if records_result1.size != records_result2.size
135
+
136
+ n_hits1 = records_result1[0]
137
+ n_hits2 = records_result2[0]
138
+ return false if n_hits1 != n_hits2
139
+
140
+ columns1 = records_result1[1]
141
+ columns2 = records_result2[1]
142
+ records1 = records_result1[2..-1]
143
+ records2 = records_result2[2..-1]
144
+
145
+ if columns1.size != columns2.size
146
+ if columns2.size > columns1.size
147
+ columns1, columns2 = columns2, columns1
148
+ records1, records2 = records2, records1
149
+ end
95
150
  end
96
151
 
97
- def score_sort?
98
- sort_items = (@command.sortby || "").split(/\s*,\s*/)
99
- normalized_sort_items = sort_items.collect do |item|
100
- item.gsub(/\A[+-]/, "")
152
+ records1.each_with_index do |record1, record_index|
153
+ record2 = records2[record_index]
154
+ column_offset2 = 0
155
+ columns1.each_with_index do |name, column_index1|
156
+ column_index2 = column_offset2 + column_index1
157
+ if name != columns2[column_index2]
158
+ column_offset2 -= 1
159
+ next
160
+ end
161
+ value1 = record1[column_index1]
162
+ value1 = normalize_value(value1, columns1[column_index1])
163
+ value2 = record2[column_index2]
164
+ value2 = normalize_value(value2, columns2[column_index2])
165
+ return false if value1 != value2
101
166
  end
102
- normalized_sort_items.include?("_score")
103
167
  end
104
168
 
105
- def same_size_response?
106
- records_result1 = @response1.body[0] || []
107
- records_result2 = @response2.body[0] || []
108
- return false if records_result1.size != records_result2.size
169
+ true
170
+ end
171
+
172
+ def all_output_columns?
173
+ output_columns = @command.output_columns
174
+ output_columns.nil? or
175
+ /\A\s*\z/ === output_columns or
176
+ output_columns.split(/\s*,?\s*/).include?("*")
177
+ end
109
178
 
110
- n_hits1 = records_result1[0]
111
- n_hits2 = records_result2[0]
112
- return false if n_hits1 != n_hits2
179
+ def same_records_all_output_columns?
180
+ records_result1 = @response1.body[0] || []
181
+ records_result2 = @response2.body[0] || []
182
+ return false if records_result1.size != records_result2.size
113
183
 
114
- columns1 = records_result1[1]
115
- columns2 = records_result2[1]
116
- if all_output_columns?
117
- columns1.sort_by(&:first) == columns2.sort_by(&:first)
118
- else
119
- columns1 == columns2
184
+ n_hits1 = records_result1[0]
185
+ n_hits2 = records_result2[0]
186
+ return false if n_hits1 != n_hits2
187
+
188
+ columns1 = records_result1[1]
189
+ columns2 = records_result2[1]
190
+ return false if columns1.sort_by(&:first) != columns2.sort_by(&:first)
191
+
192
+ column_to_index1 = make_column_to_index_map(columns1)
193
+ column_to_index2 = make_column_to_index_map(columns2)
194
+
195
+ records1 = records_result1[2..-1]
196
+ records2 = records_result2[2..-1]
197
+ records1.each_with_index do |record1, record_index|
198
+ record2 = records2[record_index]
199
+ column_to_index1.each do |name, column_index1|
200
+ value1 = record1[column_index1]
201
+ value1 = normalize_value(value1, columns1[column_index1])
202
+ column_index2 = column_to_index2[name]
203
+ value2 = record2[column_index2]
204
+ value2 = normalize_value(value2, columns2[column_index2])
205
+ return false if value1 != value2
120
206
  end
121
207
  end
122
208
 
123
- def have_unary_minus_output_column?
124
- output_columns = @command.output_columns
125
- return false if output_columns.nil?
126
- output_columns.split(/\s*,?\s*/).any? {|column| column.start_with?("-")}
127
- end
209
+ true
210
+ end
128
211
 
129
- def same_records_unary_minus_output_column?
130
- records_result1 = @response1.body[0] || []
131
- records_result2 = @response2.body[0] || []
132
- return false if records_result1.size != records_result2.size
212
+ def same_records?
213
+ record_set1 = @response1.body[0] || []
214
+ record_set2 = @response2.body[0] || []
215
+ same_record_set?(record_set1,
216
+ record_set2)
217
+ end
133
218
 
134
- n_hits1 = records_result1[0]
135
- n_hits2 = records_result2[0]
136
- return false if n_hits1 != n_hits2
219
+ def same_record_set?(record_set1, record_set2)
220
+ return false if record_set1.size != record_set2.size
137
221
 
138
- columns1 = records_result1[1]
139
- columns2 = records_result2[1]
140
- records1 = records_result1[2..-1]
141
- records2 = records_result2[2..-1]
222
+ n_hits1 = record_set1[0]
223
+ n_hits2 = record_set2[0]
224
+ return false if n_hits1 != n_hits2
142
225
 
143
- if columns1.size != columns2.size
144
- if columns2.size > columns1.size
145
- columns1, columns2 = columns2, columns1
146
- records1, records2 = records2, records1
147
- end
148
- end
226
+ columns1 = record_set1[1]
227
+ columns2 = record_set2[1]
228
+ return false if columns1 != columns2
149
229
 
150
- records1.each_with_index do |record1, record_index|
151
- record2 = records2[record_index]
152
- column_offset2 = 0
153
- columns1.each_with_index do |name, column_index1|
154
- column_index2 = column_offset2 + column_index1
155
- if name != columns2[column_index2]
156
- column_offset2 -= 1
157
- next
158
- end
159
- value1 = record1[column_index1]
160
- value2 = record2[column_index2]
161
- return false if value1 != value2
162
- end
230
+ records1 = record_set1[2..-1]
231
+ records2 = record_set2[2..-1]
232
+ records1.each_with_index do |record1, record_index|
233
+ record2 = records2[record_index]
234
+ columns1.each_with_index do |column1, column_index|
235
+ value1 = record1[column_index]
236
+ value1 = normalize_value(value1, column1)
237
+ value2 = record2[column_index]
238
+ value2 = normalize_value(value2, column1)
239
+ return false if value1 != value2
163
240
  end
164
-
165
- true
166
241
  end
167
242
 
168
- def all_output_columns?
169
- output_columns = @command.output_columns
170
- output_columns.nil? or
171
- /\A\s*\z/ === output_columns or
172
- output_columns.split(/\s*,?\s*/).include?("*")
243
+ true
244
+ end
245
+
246
+ def make_column_to_index_map(columns)
247
+ map = {}
248
+ columns.each_with_index do |(name, _), i|
249
+ map[name] = i
173
250
  end
251
+ map
252
+ end
174
253
 
175
- def same_records_all_output_columns?
176
- records_result1 = @response1.body[0] || []
177
- records_result2 = @response2.body[0] || []
178
- return false if records_result1.size != records_result2.size
179
-
180
- n_hits1 = records_result1[0]
181
- n_hits2 = records_result2[0]
182
- return false if n_hits1 != n_hits2
183
-
184
- columns1 = records_result1[1]
185
- columns2 = records_result2[1]
186
- return false if columns1.sort_by(&:first) != columns2.sort_by(&:first)
187
-
188
- column_to_index1 = make_column_to_index_map(columns1)
189
- column_to_index2 = make_column_to_index_map(columns2)
190
-
191
- records1 = records_result1[2..-1]
192
- records2 = records_result2[2..-1]
193
- records1.each_with_index do |record1, record_index|
194
- record2 = records2[record_index]
195
- column_to_index1.each do |name, column_index1|
196
- value1 = record1[column_index1]
197
- value2 = record2[column_to_index2[name]]
198
- return false if value1 != value2
199
- end
200
- end
254
+ def same_drilldowns?
255
+ drilldowns1 = @response1.body[1..-1] || []
256
+ drilldowns2 = @response2.body[1..-1] || []
257
+ return false if drilldowns1.size != drilldowns2.size
201
258
 
202
- true
259
+ drilldown_keys = @command.drilldowns
260
+ ignored_drilldown_keys = @options[:ignored_drilldown_keys]
261
+ drilldowns1.each_with_index do |drilldown1, drilldown_index|
262
+ drilldown_key = drilldown_keys[drilldown_index]
263
+ next if ignored_drilldown_keys.include?(drilldown_key)
264
+ drilldown2 = drilldowns2[drilldown_index]
265
+ return false unless same_record_set?(drilldown1, drilldown2)
203
266
  end
267
+ true
268
+ end
204
269
 
205
- def make_column_to_index_map(columns)
206
- map = {}
207
- columns.each_with_index do |(name, _), i|
208
- map[name] = i
209
- end
210
- map
270
+ def normalize_value(value, column)
271
+ type = column[1]
272
+ case type
273
+ when "Float"
274
+ value.round(10)
275
+ else
276
+ value
211
277
  end
212
278
  end
279
+ end
213
280
  end