Merge lp:~percona-toolkit-dev/percona-toolkit/advisor-json-output into lp:percona-toolkit/2.1
- advisor-json-output
- Merge into 2.1
Proposed by
Daniel Nichter
Status: | Merged |
---|---|
Merged at revision: | 276 |
Proposed branch: | lp:~percona-toolkit-dev/percona-toolkit/advisor-json-output |
Merge into: | lp:percona-toolkit/2.1 |
Diff against target: |
743 lines (+434/-28) (has conflicts) 3 files modified
bin/pt-query-advisor (+235/-26) lib/Transformers.pm (+94/-0) t/lib/Transformers.t (+105/-2) Text conflict in bin/pt-query-advisor |
To merge this branch: | bzr merge lp:~percona-toolkit-dev/percona-toolkit/advisor-json-output |
Related bugs: | |
Related blueprints: |
pt-query-advisor JSON output
(Medium)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Nichter | Approve | ||
Review via email: mp+108351@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/pt-query-advisor' |
2 | --- bin/pt-query-advisor 2012-05-24 20:55:51 +0000 |
3 | +++ bin/pt-query-advisor 2012-06-01 14:45:24 +0000 |
4 | @@ -2688,6 +2688,7 @@ |
5 | |
6 | use Time::Local qw(timegm timelocal); |
7 | use Digest::MD5 qw(md5_hex); |
8 | +use B qw(); |
9 | |
10 | require Exporter; |
11 | our @ISA = qw(Exporter); |
12 | @@ -2705,6 +2706,7 @@ |
13 | any_unix_timestamp |
14 | make_checksum |
15 | crc32 |
16 | + encode_json |
17 | ); |
18 | |
19 | our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; |
20 | @@ -2912,6 +2914,96 @@ |
21 | return $crc ^ 0xFFFFFFFF; |
22 | } |
23 | |
24 | +my $got_json = eval { require JSON }; |
25 | +sub encode_json { |
26 | + return JSON::encode_json(@_) if $got_json; |
27 | + my ( $data ) = @_; |
28 | + return (object_to_json($data) || ''); |
29 | +} |
30 | + |
31 | + |
32 | +sub object_to_json { |
33 | + my ($obj) = @_; |
34 | + my $type = ref($obj); |
35 | + |
36 | + if($type eq 'HASH'){ |
37 | + return hash_to_json($obj); |
38 | + } |
39 | + elsif($type eq 'ARRAY'){ |
40 | + return array_to_json($obj); |
41 | + } |
42 | + else { |
43 | + return value_to_json($obj); |
44 | + } |
45 | +} |
46 | + |
47 | +sub hash_to_json { |
48 | + my ($obj) = @_; |
49 | + my @res; |
50 | + for my $k ( sort { $a cmp $b } keys %$obj ) { |
51 | + push @res, string_to_json( $k ) |
52 | + . ":" |
53 | + . ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) ); |
54 | + } |
55 | + return '{' . ( @res ? join( ",", @res ) : '' ) . '}'; |
56 | +} |
57 | + |
58 | +sub array_to_json { |
59 | + my ($obj) = @_; |
60 | + my @res; |
61 | + |
62 | + for my $v (@$obj) { |
63 | + push @res, object_to_json($v) || value_to_json($v); |
64 | + } |
65 | + |
66 | + return '[' . ( @res ? join( ",", @res ) : '' ) . ']'; |
67 | +} |
68 | + |
69 | +sub value_to_json { |
70 | + my ($value) = @_; |
71 | + |
72 | + return 'null' if(!defined $value); |
73 | + |
74 | + my $b_obj = B::svref_2object(\$value); # for round trip problem |
75 | + my $flags = $b_obj->FLAGS; |
76 | + return $value # as is |
77 | + if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV? |
78 | + |
79 | + my $type = ref($value); |
80 | + |
81 | + if( !$type ) { |
82 | + return string_to_json($value); |
83 | + } |
84 | + else { |
85 | + return 'null'; |
86 | + } |
87 | + |
88 | +} |
89 | + |
90 | +my %esc = ( |
91 | + "\n" => '\n', |
92 | + "\r" => '\r', |
93 | + "\t" => '\t', |
94 | + "\f" => '\f', |
95 | + "\b" => '\b', |
96 | + "\"" => '\"', |
97 | + "\\" => '\\\\', |
98 | + "\'" => '\\\'', |
99 | +); |
100 | + |
101 | +sub string_to_json { |
102 | + my ($arg) = @_; |
103 | + |
104 | + $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; |
105 | + $arg =~ s/\//\\\//g; |
106 | + $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; |
107 | + |
108 | + utf8::upgrade($arg); |
109 | + utf8::encode($arg); |
110 | + |
111 | + return '"' . $arg . '"'; |
112 | +} |
113 | + |
114 | sub _d { |
115 | my ($package, undef, $line) = caller 0; |
116 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
117 | @@ -5130,6 +5222,7 @@ |
118 | return bless $self, $class; |
119 | } |
120 | |
121 | +<<<<<<< TREE |
122 | sub get_create_table { |
123 | my ( $self, $dbh, $db, $tbl ) = @_; |
124 | die "I need a dbh parameter" unless $dbh; |
125 | @@ -5179,6 +5272,55 @@ |
126 | return $href->{$key}; |
127 | } |
128 | |
129 | +======= |
130 | +sub get_create_table { |
131 | + my ( $self, $dbh, $db, $tbl ) = @_; |
132 | + die "I need a dbh parameter" unless $dbh; |
133 | + die "I need a db parameter" unless $db; |
134 | + die "I need a tbl parameter" unless $tbl; |
135 | + my $q = $self->{Quoter}; |
136 | + |
137 | + my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, ' |
138 | + . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), } |
139 | + . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, ' |
140 | + . '@@SQL_QUOTE_SHOW_CREATE := 1 */'; |
141 | + PTDEBUG && _d($sql); |
142 | + eval { $dbh->do($sql); }; |
143 | + PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR); |
144 | + |
145 | + $sql = 'USE ' . $q->quote($db); |
146 | + PTDEBUG && _d($dbh, $sql); |
147 | + $dbh->do($sql); |
148 | + |
149 | + $sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl); |
150 | + PTDEBUG && _d($sql); |
151 | + my $href; |
152 | + eval { $href = $dbh->selectrow_hashref($sql); }; |
153 | + if ( $EVAL_ERROR ) { |
154 | + PTDEBUG && _d($EVAL_ERROR); |
155 | + return; |
156 | + } |
157 | + |
158 | + $sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, ' |
159 | + . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */'; |
160 | + PTDEBUG && _d($sql); |
161 | + $dbh->do($sql); |
162 | + |
163 | + my ($key) = grep { m/create table/i } keys %$href; |
164 | + if ( $key ) { |
165 | + PTDEBUG && _d('This table is a base table'); |
166 | + $href->{$key} =~ s/\b[ ]{2,}/ /g; |
167 | + $href->{$key} .= "\n"; |
168 | + } |
169 | + else { |
170 | + PTDEBUG && _d('This table is a view'); |
171 | + ($key) = grep { m/create view/i } keys %$href; |
172 | + } |
173 | + |
174 | + return $href->{$key}; |
175 | +} |
176 | + |
177 | +>>>>>>> MERGE-SOURCE |
178 | sub parse { |
179 | my ( $self, $ddl, $opts ) = @_; |
180 | return unless $ddl; |
181 | @@ -6195,8 +6337,10 @@ |
182 | return $event; |
183 | }; |
184 | |
185 | + my $json = $o->get('report-type')->{json} |
186 | + ? {} : undef; |
187 | # Print info (advice) about each rule that matched this query. |
188 | - if ( $groupby eq 'none' ) { |
189 | + if ( $groupby eq 'none' || $json ) { |
190 | push @pipeline, sub { |
191 | my ( %args ) = @_; |
192 | PTDEBUG && _d('callback: print advice'); |
193 | @@ -6210,6 +6354,7 @@ |
194 | severity_count => \%severity_count, |
195 | verbose => $o->get('verbose'), |
196 | report_format => $o->get('report-format'), |
197 | + json => $json, |
198 | Advisor => $adv, |
199 | ); |
200 | return $event; |
201 | @@ -6322,7 +6467,7 @@ |
202 | # ######################################################################## |
203 | # Aggregate and report items for group-by reports |
204 | # ######################################################################## |
205 | - if ( $groupby ne 'none' ) { |
206 | + if ( $groupby ne 'none' && !$json ) { |
207 | print_grouped_report( |
208 | advice_queue => \%advice_queue, |
209 | group_by => $groupby, |
210 | @@ -6334,7 +6479,7 @@ |
211 | # ######################################################################## |
212 | # Create and print profile of each items note/warn/crit count. |
213 | # ######################################################################## |
214 | - if ( keys %severity_count ) { |
215 | + if ( keys %severity_count && !$json ) { |
216 | eval { |
217 | my $profile = new ReportFormatter( |
218 | long_last_column => 1, |
219 | @@ -6365,6 +6510,8 @@ |
220 | }; |
221 | } |
222 | |
223 | + print Transformers::encode_json($json), "\n" if $json; |
224 | + |
225 | return 0; |
226 | } |
227 | |
228 | @@ -6380,6 +6527,7 @@ |
229 | my $adv = $args{Advisor}; |
230 | my $seen_id = $args{seen_id}; |
231 | my $severity_count = $args{severity_count}; |
232 | + my $json = $args{json}; |
233 | |
234 | my $advice = $event->{advice}; |
235 | my $near_pos = $event->{near_pos}; |
236 | @@ -6388,7 +6536,9 @@ |
237 | |
238 | # Header |
239 | my $query_id = $event->{query_id} || ""; |
240 | - print "\n# Query ID 0x$query_id at byte " . ($event->{pos_in_log} || 0) . "\n"; |
241 | + |
242 | + print "\n# Query ID 0x$query_id at byte " . ($event->{pos_in_log} || 0) . "\n" |
243 | + unless $json; |
244 | |
245 | # New check IDs and their descriptions |
246 | foreach my $i ( 1..$n_advice ) { |
247 | @@ -6409,9 +6559,10 @@ |
248 | : $verbose == 2 ? "$desc[0] $desc[1]" # fuller |
249 | : $verbose > 2 ? $desc # complete |
250 | : ''; # none |
251 | - print "# ", uc $info->{severity}, " $rule_id $desc\n"; |
252 | + print "# ", uc $info->{severity}, " $rule_id $desc\n" |
253 | + unless $json; |
254 | |
255 | - if ( $pos ) { |
256 | + if ( $pos && !$json ) { |
257 | my $offset = $pos > POS_CONTEXT ? $pos - POS_CONTEXT : 0; |
258 | print "# matches near: ", |
259 | substr($event->{arg}, $offset, ($pos - $offset) + POS_CONTEXT), |
260 | @@ -6419,14 +6570,24 @@ |
261 | } |
262 | } |
263 | |
264 | + if ( $json ) { |
265 | + my $info_for_json = { |
266 | + rule => $rule_id, |
267 | + %$info |
268 | + }; |
269 | + push @{$json->{$query_id} ||= []}, $info_for_json; |
270 | + } |
271 | + |
272 | $severity_count->{$query_id}->{$info->{severity}}++; |
273 | } |
274 | |
275 | - # Already seen check IDs |
276 | - print "# Also: @seen_ids\n" if scalar @seen_ids; |
277 | - |
278 | - # The query |
279 | - print "$event->{arg}\n"; |
280 | + if ( !$json ) { |
281 | + # Already seen check IDs |
282 | + print "# Also: @seen_ids\n" if scalar @seen_ids; |
283 | + |
284 | + # The query |
285 | + print "$event->{arg}\n"; |
286 | + } |
287 | |
288 | return; |
289 | } |
290 | @@ -6476,7 +6637,6 @@ |
291 | my $verbose = $args{verbose} || 0; |
292 | my %seen; |
293 | |
294 | - |
295 | foreach my $groupby_attrib ( sort keys %$advice_queue ) { |
296 | print "\n" . ($groupby eq 'query_id' ? "0x" : "") . $groupby_attrib; |
297 | foreach my $groupby_value (sort keys %{$advice_queue->{$groupby_attrib}}){ |
298 | @@ -6549,14 +6709,29 @@ |
299 | |
300 | Usage: pt-query-advisor [OPTION...] [FILE] |
301 | |
302 | +<<<<<<< TREE |
303 | pt-query-advisor analyzes queries and advises on possible problems. It can read |
304 | queries from several types of log files, or you can use the --query or --review |
305 | options. |
306 | +======= |
307 | +pt-query-advisor detects bad patterns in a SQL query from the text alone. |
308 | +>>>>>>> MERGE-SOURCE |
309 | |
310 | +<<<<<<< TREE |
311 | To analyze all queries in a MySQL slow query log file: |
312 | +======= |
313 | +Analyze all queries in a log in MySQL's slow query log format: |
314 | +>>>>>>> MERGE-SOURCE |
315 | |
316 | pt-query-advisor /path/to/slow-query.log |
317 | |
318 | +<<<<<<< TREE |
319 | +======= |
320 | +Get queries from tcpdump using pt-query-digest: |
321 | + |
322 | + pt-query-digest --type tcpdump.txt --print --no-report | pt-query-advisor |
323 | + |
324 | +>>>>>>> MERGE-SOURCE |
325 | =head1 RISKS |
326 | |
327 | The following section is included to inform users about the potential risks, |
328 | @@ -6565,8 +6740,14 @@ |
329 | tools) and those created by bugs. |
330 | |
331 | pt-query-advisor simply reads queries and examines them, and is thus |
332 | +<<<<<<< TREE |
333 | very low risk. At the time of this release we know of no issues that could harm |
334 | users. |
335 | +======= |
336 | +very low risk. |
337 | + |
338 | +At the time of this release there are no known bugs that could harm users. |
339 | +>>>>>>> MERGE-SOURCE |
340 | |
341 | The authoritative source for updated information is always the online issue |
342 | tracking system. Issues that affect this tool will be marked as such. You can |
343 | @@ -6648,14 +6829,16 @@ |
344 | |
345 | severity: warn |
346 | |
347 | -SELECT without WHERE. The SELECT statement has no WHERE clause. |
348 | +SELECT without WHERE. The SELECT statement has no WHERE clause and could |
349 | +examine many more rows than intended. |
350 | |
351 | =item CLA.002 |
352 | |
353 | severity: note |
354 | |
355 | ORDER BY RAND(). ORDER BY RAND() is a very inefficient way to |
356 | -retrieve a random row from the results. |
357 | +retrieve a random row from the results, because it sorts the entire result |
358 | +and then throws most of it away. |
359 | |
360 | =item CLA.003 |
361 | |
362 | @@ -6663,7 +6846,8 @@ |
363 | |
364 | LIMIT with OFFSET. Paginating a result set with LIMIT and OFFSET is |
365 | O(n^2) complexity, and will cause performance problems as the data |
366 | -grows larger. |
367 | +grows larger. Pagination techniques such as bookmarked scans are much more |
368 | +efficient. |
369 | |
370 | =item CLA.004 |
371 | |
372 | @@ -6677,21 +6861,30 @@ |
373 | |
374 | severity: warn |
375 | |
376 | -ORDER BY constant column. |
377 | +ORDER BY constant column. This is probably a bug in your SQL; at best it is a |
378 | +useless operation that does not change the query results. |
379 | |
380 | =item CLA.006 |
381 | |
382 | severity: warn |
383 | |
384 | -GROUP BY or ORDER BY different tables will force a temp table and filesort. |
385 | +GROUP BY or ORDER BY on different tables. This will force the use of a temporary |
386 | +table and filesort, which can be a huge performance problem and can consume |
387 | +large amounts of memory and temporary space on disk. |
388 | |
389 | =item CLA.007 |
390 | |
391 | severity: warn |
392 | |
393 | +<<<<<<< TREE |
394 | ORDER BY clauses that sort the results in different directions prevents indexes |
395 | from being used. All expressions in the ORDER BY clause must be ordered either |
396 | ASC or DESC so that MySQL can use an index. |
397 | +======= |
398 | +ORDER BY different directions. All tables in the ORDER BY clause must be in the |
399 | +same direction, either ASC or DESC, or MySQL cannot use an index to avoid a sort |
400 | +after generating results. |
401 | +>>>>>>> MERGE-SOURCE |
402 | |
403 | =item COL.001 |
404 | |
405 | @@ -6723,8 +6916,9 @@ |
406 | |
407 | severity: warn |
408 | |
409 | -Unquoted date/time literal. A query such as "WHERE col<2010-02-12" |
410 | -is valid SQL but is probably a bug; the literal should be quoted. |
411 | +Unquoted date/time literal. A query such as "WHERE col<2010-02-12" is valid SQL |
412 | +but is probably a bug, because it will be interpreted as "WHERE col<1996"; the |
413 | +literal should be quoted. |
414 | |
415 | =item KWR.001 |
416 | |
417 | @@ -6739,23 +6933,25 @@ |
418 | |
419 | severity: crit |
420 | |
421 | -Mixing comma and ANSI joins. Mixing comma joins and ANSI joins |
422 | -is confusing to humans, and the behavior differs between some |
423 | -MySQL versions. |
424 | +Mixing comma and ANSI joins. Mixing comma joins and ANSI joins is confusing to |
425 | +humans, and the behavior and precedence differs between some MySQL versions, |
426 | +which can introduce bugs. |
427 | |
428 | =item JOI.002 |
429 | |
430 | severity: crit |
431 | |
432 | A table is joined twice. The same table appears at least twice in the |
433 | -FROM clause. |
434 | +FROM clause in a manner that can be reduced to a single access to the table. |
435 | |
436 | =item JOI.003 |
437 | |
438 | severity: warn |
439 | |
440 | -Reference to outer table column in WHERE clause prevents OUTER JOIN, |
441 | -implicitly converts to INNER JOIN. |
442 | +OUTER JOIN defeated. The reference to an outer table column in the WHERE clause |
443 | +prevents the OUTER JOIN from returning any non-matched rows, which implicitly |
444 | +converts the query to an INNER JOIN. This is probably a bug in the query or a |
445 | +misunderstanding of how OUTER JOIN works. |
446 | |
447 | =item JOI.004 |
448 | |
449 | @@ -6786,7 +6982,8 @@ |
450 | |
451 | severity: note |
452 | |
453 | -!= is non-standard. Use the <> operator to test for inequality. |
454 | +The != operator is non-standard. Use the <> operator to test for inequality |
455 | +instead. |
456 | |
457 | =item SUB.001 |
458 | |
459 | @@ -6794,9 +6991,14 @@ |
460 | |
461 | IN() and NOT IN() subqueries are poorly optimized. MySQL executes the subquery |
462 | as a dependent subquery for each row in the outer query. This is a frequent |
463 | +<<<<<<< TREE |
464 | cause of serious performance problems. This might improve in version 5.6 of |
465 | MySQL, but for versions 5.1 and older, the query should be rewritten as a JOIN |
466 | or a LEFT OUTER JOIN, respectively. |
467 | +======= |
468 | +cause of serious performance problems in MySQL 5.5 and older versions. The |
469 | +query probably should be rewritten as a JOIN or a LEFT OUTER JOIN, respectively. |
470 | +>>>>>>> MERGE-SOURCE |
471 | |
472 | =back |
473 | |
474 | @@ -6978,6 +7180,13 @@ |
475 | The type of input to parse (default slowlog). The permitted types are |
476 | slowlog and genlog. |
477 | |
478 | +=item --report-type |
479 | + |
480 | +type: Hash |
481 | + |
482 | +Alternative formats to output the report. Currently, only "json" is |
483 | +recognized -- anything else is ignored and the default behavior used. |
484 | + |
485 | =item --user |
486 | |
487 | short form: -u; type: string |
488 | |
489 | === modified file 'lib/Transformers.pm' |
490 | --- lib/Transformers.pm 2012-01-19 19:46:56 +0000 |
491 | +++ lib/Transformers.pm 2012-06-01 14:45:24 +0000 |
492 | @@ -29,6 +29,7 @@ |
493 | |
494 | use Time::Local qw(timegm timelocal); |
495 | use Digest::MD5 qw(md5_hex); |
496 | +use B qw(); |
497 | |
498 | require Exporter; |
499 | our @ISA = qw(Exporter); |
500 | @@ -46,6 +47,7 @@ |
501 | any_unix_timestamp |
502 | make_checksum |
503 | crc32 |
504 | + encode_json |
505 | ); |
506 | |
507 | our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; |
508 | @@ -285,6 +287,98 @@ |
509 | return $crc ^ 0xFFFFFFFF; |
510 | } |
511 | |
512 | +my $got_json = eval { require JSON }; |
513 | +sub encode_json { |
514 | + return JSON::encode_json(@_) if $got_json; |
515 | + my ( $data ) = @_; |
516 | + return (object_to_json($data) || ''); |
517 | +} |
518 | + |
519 | +# The following is a stripped down version of JSON::PP by Makamaka Hannyaharamitu |
520 | +# https://metacpan.org/module/JSON::PP |
521 | + |
522 | +sub object_to_json { |
523 | + my ($obj) = @_; |
524 | + my $type = ref($obj); |
525 | + |
526 | + if($type eq 'HASH'){ |
527 | + return hash_to_json($obj); |
528 | + } |
529 | + elsif($type eq 'ARRAY'){ |
530 | + return array_to_json($obj); |
531 | + } |
532 | + else { |
533 | + return value_to_json($obj); |
534 | + } |
535 | +} |
536 | + |
537 | +sub hash_to_json { |
538 | + my ($obj) = @_; |
539 | + my @res; |
540 | + for my $k ( sort { $a cmp $b } keys %$obj ) { |
541 | + push @res, string_to_json( $k ) |
542 | + . ":" |
543 | + . ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) ); |
544 | + } |
545 | + return '{' . ( @res ? join( ",", @res ) : '' ) . '}'; |
546 | +} |
547 | + |
548 | +sub array_to_json { |
549 | + my ($obj) = @_; |
550 | + my @res; |
551 | + |
552 | + for my $v (@$obj) { |
553 | + push @res, object_to_json($v) || value_to_json($v); |
554 | + } |
555 | + |
556 | + return '[' . ( @res ? join( ",", @res ) : '' ) . ']'; |
557 | +} |
558 | + |
559 | +sub value_to_json { |
560 | + my ($value) = @_; |
561 | + |
562 | + return 'null' if(!defined $value); |
563 | + |
564 | + my $b_obj = B::svref_2object(\$value); # for round trip problem |
565 | + my $flags = $b_obj->FLAGS; |
566 | + return $value # as is |
567 | + if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV? |
568 | + |
569 | + my $type = ref($value); |
570 | + |
571 | + if( !$type ) { |
572 | + return string_to_json($value); |
573 | + } |
574 | + else { |
575 | + return 'null'; |
576 | + } |
577 | + |
578 | +} |
579 | + |
580 | +my %esc = ( |
581 | + "\n" => '\n', |
582 | + "\r" => '\r', |
583 | + "\t" => '\t', |
584 | + "\f" => '\f', |
585 | + "\b" => '\b', |
586 | + "\"" => '\"', |
587 | + "\\" => '\\\\', |
588 | + "\'" => '\\\'', |
589 | +); |
590 | + |
591 | +sub string_to_json { |
592 | + my ($arg) = @_; |
593 | + |
594 | + $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; |
595 | + $arg =~ s/\//\\\//g; |
596 | + $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; |
597 | + |
598 | + utf8::upgrade($arg); |
599 | + utf8::encode($arg); |
600 | + |
601 | + return '"' . $arg . '"'; |
602 | +} |
603 | + |
604 | sub _d { |
605 | my ($package, undef, $line) = caller 0; |
606 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
607 | |
608 | === modified file 't/lib/Transformers.t' |
609 | --- t/lib/Transformers.t 2012-03-06 13:56:08 +0000 |
610 | +++ t/lib/Transformers.t 2012-06-01 14:45:24 +0000 |
611 | @@ -1,5 +1,7 @@ |
612 | #!/usr/bin/perl |
613 | |
614 | +# This file is encoded in UTF-8. |
615 | + |
616 | BEGIN { |
617 | die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" |
618 | unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; |
619 | @@ -12,14 +14,14 @@ |
620 | use strict; |
621 | use warnings FATAL => 'all'; |
622 | use English qw(-no_match_vars); |
623 | -use Test::More tests => 49; |
624 | +use Test::More tests => 74; |
625 | |
626 | use Transformers; |
627 | use PerconaTest; |
628 | |
629 | Transformers->import( qw(parse_timestamp micro_t shorten secs_to_time |
630 | time_to_secs percentage_of unix_timestamp make_checksum any_unix_timestamp |
631 | - ts crc32) ); |
632 | + ts crc32 encode_json) ); |
633 | |
634 | # ############################################################################# |
635 | # micro_t() tests. |
636 | @@ -189,6 +191,107 @@ |
637 | 'any_unix_timestamp MySQL expression that looks like another type' |
638 | ); |
639 | |
640 | +{ |
641 | + # Tests borrowed from http://api.metacpan.org/source/MAKAMAKA/JSON-2.53/t/08_pc_base.t |
642 | + my $obj = {}; |
643 | + my $js = encode_json($obj); |
644 | + is($js,'{}', '{}'); |
645 | + |
646 | + $obj = []; |
647 | + $js = encode_json($obj); |
648 | + is($js,'[]', '[]'); |
649 | + |
650 | + $obj = {"foo" => "bar"}; |
651 | + $js = encode_json($obj); |
652 | + is($js,'{"foo":"bar"}', '{"foo":"bar"}'); |
653 | + |
654 | + $js = encode_json({"foo" => ""}); |
655 | + is($js,'{"foo":""}', '{"foo":""}'); |
656 | + |
657 | + $js = encode_json({"foo" => " "}); |
658 | + is($js,'{"foo":" "}' ,'{"foo":" "}'); |
659 | + |
660 | + $js = encode_json({"foo" => "0"}); |
661 | + is($js,'{"foo":"0"}',q|{"foo":"0"} - autoencode (default)|); |
662 | + |
663 | + $js = encode_json({"foo" => "0 0"}); |
664 | + is($js,'{"foo":"0 0"}','{"foo":"0 0"}'); |
665 | + |
666 | + $js = encode_json([1,2,3]); |
667 | + is($js,'[1,2,3]'); |
668 | + |
669 | + $js = encode_json({"foo"=>{"bar"=>"hoge"}}); |
670 | + is($js,q|{"foo":{"bar":"hoge"}}|); |
671 | + |
672 | + $obj = [{"foo"=>[1,2,3]},-0.12,{"a"=>"b"}]; |
673 | + $js = encode_json($obj); |
674 | + is($js,q|[{"foo":[1,2,3]},-0.12,{"a":"b"}]|); |
675 | + |
676 | + $obj = ["\x01"]; |
677 | + is(encode_json($obj),'["\\u0001"]'); |
678 | + |
679 | + $obj = ["\e"]; |
680 | + is(encode_json($obj),'["\\u001b"]'); |
681 | + |
682 | + { |
683 | + # http://api.metacpan.org/source/MAKAMAKA/JSON-2.53/t/07_pc_esc.t |
684 | + use utf8; |
685 | + |
686 | + $obj = {test => qq|abc"def|}; |
687 | + my $str = encode_json($obj); |
688 | + is($str,q|{"test":"abc\"def"}|); |
689 | + |
690 | + $obj = {qq|te"st| => qq|abc"def|}; |
691 | + $str = encode_json($obj); |
692 | + is($str,q|{"te\"st":"abc\"def"}|); |
693 | + |
694 | + $obj = {test => q|abc\def|}; |
695 | + $str = encode_json($obj); |
696 | + is($str,q|{"test":"abc\\\\def"}|); |
697 | + |
698 | + $obj = {test => "abc\bdef"}; |
699 | + $str = encode_json($obj); |
700 | + is($str,q|{"test":"abc\bdef"}|); |
701 | + |
702 | + $obj = {test => "abc\fdef"}; |
703 | + $str = encode_json($obj); |
704 | + is($str,q|{"test":"abc\fdef"}|); |
705 | + |
706 | + $obj = {test => "abc\ndef"}; |
707 | + $str = encode_json($obj); |
708 | + is($str,q|{"test":"abc\ndef"}|); |
709 | + |
710 | + $obj = {test => "abc\rdef"}; |
711 | + $str = encode_json($obj); |
712 | + is($str,q|{"test":"abc\rdef"}|); |
713 | + |
714 | + $obj = {test => "abc-def"}; |
715 | + $str = encode_json($obj); |
716 | + is($str,q|{"test":"abc-def"}|); |
717 | + |
718 | + $obj = {test => "abc(def"}; |
719 | + $str = encode_json($obj); |
720 | + is($str,q|{"test":"abc(def"}|); |
721 | + |
722 | + $obj = {test => "abc\\def"}; |
723 | + $str = encode_json($obj); |
724 | + is($str,q|{"test":"abc\\\\def"}|); |
725 | + |
726 | + |
727 | + $obj = {test => "あいうえお"}; |
728 | + $str = encode_json($obj); |
729 | + my $expect = q|{"test":"あいうえお"}|; |
730 | + utf8::encode($expect); |
731 | + is($str,$expect); |
732 | + |
733 | + $obj = {"あいうえお" => "かきくけこ"}; |
734 | + $str = encode_json($obj); |
735 | + $expect = q|{"あいうえお":"かきくけこ"}|; |
736 | + utf8::encode($expect); |
737 | + is($str,$expect); |
738 | + } |
739 | +} |
740 | + |
741 | |
742 | # ############################################################################# |
743 | # Done. |