Merge lp:~percona-toolkit-dev/percona-toolkit/advisor-json-output into lp:percona-toolkit/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
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+108351@code.launchpad.net
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.

Subscribers

People subscribed via source and target branches