groonga-query-log 1.3.0 → 1.3.1

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