From cf347793be9255f38596e5b052cc20176d15bf6e Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Tue, 7 Feb 2012 11:07:41 -0300 Subject: [PATCH 1/3] Add an encode_json function to Transformers.pm --- lib/Transformers.pm | 94 +++++++++++++++++++++++++++++++++++++ t/lib/Transformers.t | 107 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 199 insertions(+), 2 deletions(-) diff --git a/lib/Transformers.pm b/lib/Transformers.pm index 254c8eab..80f57748 100644 --- a/lib/Transformers.pm +++ b/lib/Transformers.pm @@ -29,6 +29,7 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0; use Time::Local qw(timegm timelocal); use Digest::MD5 qw(md5_hex); +use B qw(); require Exporter; our @ISA = qw(Exporter); @@ -46,6 +47,7 @@ our @EXPORT_OK = qw( any_unix_timestamp make_checksum crc32 + encode_json ); our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; @@ -285,6 +287,98 @@ sub crc32 { return $crc ^ 0xFFFFFFFF; } +my $got_json = eval { require JSON }; +sub encode_json { + return JSON::encode_json(@_) if $got_json; + my ( $data ) = @_; + return (object_to_json($data) || ''); +} + +# The following is a stripped down version of JSON::PP by Makamaka Hannyaharamitu +# https://metacpan.org/module/JSON::PP + +sub object_to_json { + my ($obj) = @_; + my $type = ref($obj); + + if($type eq 'HASH'){ + return hash_to_json($obj); + } + elsif($type eq 'ARRAY'){ + return array_to_json($obj); + } + else { + return value_to_json($obj); + } +} + +sub hash_to_json { + my ($obj) = @_; + my @res; + for my $k ( sort { $a cmp $b } keys %$obj ) { + push @res, string_to_json( $k ) + . ":" + . ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) ); + } + return '{' . ( @res ? join( ",", @res ) : '' ) . '}'; +} + +sub array_to_json { + my ($obj) = @_; + my @res; + + for my $v (@$obj) { + push @res, object_to_json($v) || value_to_json($v); + } + + return '[' . ( @res ? join( ",", @res ) : '' ) . ']'; +} + +sub value_to_json { + my ($value) = @_; + + return 'null' if(!defined $value); + + my $b_obj = B::svref_2object(\$value); # for round trip problem + my $flags = $b_obj->FLAGS; + return $value # as is + if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV? + + my $type = ref($value); + + if( !$type ) { + return string_to_json($value); + } + else { + return 'null'; + } + +} + +my %esc = ( + "\n" => '\n', + "\r" => '\r', + "\t" => '\t', + "\f" => '\f', + "\b" => '\b', + "\"" => '\"', + "\\" => '\\\\', + "\'" => '\\\'', +); + +sub string_to_json { + my ($arg) = @_; + + $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; + $arg =~ s/\//\\\//g; + $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; + + utf8::upgrade($arg); + utf8::encode($arg); + + return '"' . $arg . '"'; +} + sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } diff --git a/t/lib/Transformers.t b/t/lib/Transformers.t index 30a1f81b..ae257ca6 100644 --- a/t/lib/Transformers.t +++ b/t/lib/Transformers.t @@ -1,5 +1,7 @@ #!/usr/bin/perl +# This file is encoded in UTF-8. + BEGIN { die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; @@ -12,14 +14,14 @@ BEGIN { use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); -use Test::More tests => 49; +use Test::More tests => 74; use Transformers; use PerconaTest; Transformers->import( qw(parse_timestamp micro_t shorten secs_to_time time_to_secs percentage_of unix_timestamp make_checksum any_unix_timestamp - ts crc32) ); + ts crc32 encode_json) ); # ############################################################################# # micro_t() tests. @@ -195,6 +197,107 @@ is( 'any_unix_timestamp MySQL expression that looks like another type' ); +{ + # Tests borrowed from http://api.metacpan.org/source/MAKAMAKA/JSON-2.53/t/08_pc_base.t + my $obj = {}; + my $js = encode_json($obj); + is($js,'{}', '{}'); + + $obj = []; + $js = encode_json($obj); + is($js,'[]', '[]'); + + $obj = {"foo" => "bar"}; + $js = encode_json($obj); + is($js,'{"foo":"bar"}', '{"foo":"bar"}'); + + $js = encode_json({"foo" => ""}); + is($js,'{"foo":""}', '{"foo":""}'); + + $js = encode_json({"foo" => " "}); + is($js,'{"foo":" "}' ,'{"foo":" "}'); + + $js = encode_json({"foo" => "0"}); + is($js,'{"foo":"0"}',q|{"foo":"0"} - autoencode (default)|); + + $js = encode_json({"foo" => "0 0"}); + is($js,'{"foo":"0 0"}','{"foo":"0 0"}'); + + $js = encode_json([1,2,3]); + is($js,'[1,2,3]'); + + $js = encode_json({"foo"=>{"bar"=>"hoge"}}); + is($js,q|{"foo":{"bar":"hoge"}}|); + + $obj = [{"foo"=>[1,2,3]},-0.12,{"a"=>"b"}]; + $js = encode_json($obj); + is($js,q|[{"foo":[1,2,3]},-0.12,{"a":"b"}]|); + + $obj = ["\x01"]; + is(encode_json($obj),'["\\u0001"]'); + + $obj = ["\e"]; + is(encode_json($obj),'["\\u001b"]'); + + { + # http://api.metacpan.org/source/MAKAMAKA/JSON-2.53/t/07_pc_esc.t + use utf8; + + $obj = {test => qq|abc"def|}; + my $str = encode_json($obj); + is($str,q|{"test":"abc\"def"}|); + + $obj = {qq|te"st| => qq|abc"def|}; + $str = encode_json($obj); + is($str,q|{"te\"st":"abc\"def"}|); + + $obj = {test => q|abc\def|}; + $str = encode_json($obj); + is($str,q|{"test":"abc\\\\def"}|); + + $obj = {test => "abc\bdef"}; + $str = encode_json($obj); + is($str,q|{"test":"abc\bdef"}|); + + $obj = {test => "abc\fdef"}; + $str = encode_json($obj); + is($str,q|{"test":"abc\fdef"}|); + + $obj = {test => "abc\ndef"}; + $str = encode_json($obj); + is($str,q|{"test":"abc\ndef"}|); + + $obj = {test => "abc\rdef"}; + $str = encode_json($obj); + is($str,q|{"test":"abc\rdef"}|); + + $obj = {test => "abc-def"}; + $str = encode_json($obj); + is($str,q|{"test":"abc-def"}|); + + $obj = {test => "abc(def"}; + $str = encode_json($obj); + is($str,q|{"test":"abc(def"}|); + + $obj = {test => "abc\\def"}; + $str = encode_json($obj); + is($str,q|{"test":"abc\\\\def"}|); + + + $obj = {test => "あいうえお"}; + $str = encode_json($obj); + my $expect = q|{"test":"あいうえお"}|; + utf8::encode($expect); + is($str,$expect); + + $obj = {"あいうえお" => "かきくけこ"}; + $str = encode_json($obj); + $expect = q|{"あいうえお":"かきくけこ"}|; + utf8::encode($expect); + is($str,$expect); + } +} + # ############################################################################# # Done. From 07059929548fd4e2c1813bf19bd7ac1b3f4b621d Mon Sep 17 00:00:00 2001 From: Brian Fraser Date: Tue, 7 Feb 2012 11:07:54 -0300 Subject: [PATCH 2/3] pt-query-advisor: Add a '--report-type json' option. As per https://blueprints.launchpad.net/percona-toolkit/+spec/pt-query-advisor-and-json --- bin/pt-query-advisor | 310 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 249 insertions(+), 61 deletions(-) diff --git a/bin/pt-query-advisor b/bin/pt-query-advisor index 2525e6e4..16c06f7e 100755 --- a/bin/pt-query-advisor +++ b/bin/pt-query-advisor @@ -141,12 +141,14 @@ sub parse_options { sub as_string { my ( $self, $dsn, $props ) = @_; return $dsn unless ref $dsn; - my %allowed = $props ? map { $_=>1 } @$props : (); + my @keys = $props ? @$props : sort keys %$dsn; return join(',', - map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) } - grep { defined $dsn->{$_} && $self->{opts}->{$_} } - grep { !$props || $allowed{$_} } - sort keys %$dsn ); + map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) } + grep { + exists $self->{opts}->{$_} + && exists $dsn->{$_} + && defined $dsn->{$_} + } @keys); } sub usage { @@ -1315,7 +1317,7 @@ sub _parse_size { $opt->{value} = ($pre || '') . $num; } else { - $self->save_error("Invalid size for --$opt->{long}"); + $self->save_error("Invalid size for --$opt->{long}: $val"); } return; } @@ -1460,6 +1462,48 @@ sub join_quote { return $db ? "$db.$tbl" : $tbl; } +sub serialize_list { + my ( $self, @args ) = @_; + return unless @args; + + return $args[0] if @args == 1 && !defined $args[0]; + + die "Cannot serialize multiple values with undef/NULL" + if grep { !defined $_ } @args; + + return join ',', map { quotemeta } @args; +} + +sub deserialize_list { + my ( $self, $string ) = @_; + return $string unless defined $string; + my @escaped_parts = $string =~ / + \G # Start of string, or end of previous match. + ( # Each of these is an element in the original list. + [^\\,]* # Anything not a backslash or a comma + (?: # When we get here, we found one of the above. + \\. # A backslash followed by something so we can continue + [^\\,]* # Same as above. + )* # Repeat zero of more times. + ) + , # Comma dividing elements + /sxgc; + + push @escaped_parts, pos($string) ? substr( $string, pos($string) ) : $string; + + my @unescaped_parts = map { + my $part = $_; + + my $char_class = utf8::is_utf8($part) # If it's a UTF-8 string, + ? qr/(?=\p{ASCII})\W/ # We only care about non-word + : qr/(?=\p{ASCII})\W|[\x{80}-\x{FF}]/; # Otherwise, + $part =~ s/\\($char_class)/$1/g; + $part; + } @escaped_parts; + + return @unescaped_parts; +} + 1; } # ########################################################################### @@ -2624,6 +2668,7 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0; use Time::Local qw(timegm timelocal); use Digest::MD5 qw(md5_hex); +use B qw(); require Exporter; our @ISA = qw(Exporter); @@ -2641,6 +2686,7 @@ our @EXPORT_OK = qw( any_unix_timestamp make_checksum crc32 + encode_json ); our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; @@ -2848,6 +2894,96 @@ sub crc32 { return $crc ^ 0xFFFFFFFF; } +my $got_json = eval { require JSON }; +sub encode_json { + return JSON::encode_json(@_) if $got_json; + my ( $data ) = @_; + return (object_to_json($data) || ''); +} + + +sub object_to_json { + my ($obj) = @_; + my $type = ref($obj); + + if($type eq 'HASH'){ + return hash_to_json($obj); + } + elsif($type eq 'ARRAY'){ + return array_to_json($obj); + } + else { + return value_to_json($obj); + } +} + +sub hash_to_json { + my ($obj) = @_; + my @res; + for my $k ( sort { $a cmp $b } keys %$obj ) { + push @res, string_to_json( $k ) + . ":" + . ( object_to_json( $obj->{$k} ) || value_to_json( $obj->{$k} ) ); + } + return '{' . ( @res ? join( ",", @res ) : '' ) . '}'; +} + +sub array_to_json { + my ($obj) = @_; + my @res; + + for my $v (@$obj) { + push @res, object_to_json($v) || value_to_json($v); + } + + return '[' . ( @res ? join( ",", @res ) : '' ) . ']'; +} + +sub value_to_json { + my ($value) = @_; + + return 'null' if(!defined $value); + + my $b_obj = B::svref_2object(\$value); # for round trip problem + my $flags = $b_obj->FLAGS; + return $value # as is + if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV? + + my $type = ref($value); + + if( !$type ) { + return string_to_json($value); + } + else { + return 'null'; + } + +} + +my %esc = ( + "\n" => '\n', + "\r" => '\r', + "\t" => '\t', + "\f" => '\f', + "\b" => '\b', + "\"" => '\"', + "\\" => '\\\\', + "\'" => '\\\'', +); + +sub string_to_json { + my ($arg) = @_; + + $arg =~ s/([\x22\x5c\n\r\t\f\b])/$esc{$1}/g; + $arg =~ s/\//\\\//g; + $arg =~ s/([\x00-\x08\x0b\x0e-\x1f])/'\\u00' . unpack('H2', $1)/eg; + + utf8::upgrade($arg); + utf8::encode($arg); + + return '"' . $arg . '"'; +} + sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } @@ -5274,19 +5410,56 @@ sub new { return bless $self, $class; } +sub get_create_table { + my ( $self, $dbh, $db, $tbl ) = @_; + die "I need a dbh parameter" unless $dbh; + die "I need a db parameter" unless $db; + die "I need a tbl parameter" unless $tbl; + my $q = $self->{Quoter}; + + my $sql = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, ' + . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), } + . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, ' + . '@@SQL_QUOTE_SHOW_CREATE := 1 */'; + PTDEBUG && _d($sql); + eval { $dbh->do($sql); }; + PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR); + + $sql = 'USE ' . $q->quote($db); + PTDEBUG && _d($dbh, $sql); + $dbh->do($sql); + + $sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl); + PTDEBUG && _d($sql); + my $href; + eval { $href = $dbh->selectrow_hashref($sql); }; + if ( $EVAL_ERROR ) { + PTDEBUG && _d($EVAL_ERROR); + return; + } + + $sql = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, ' + . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */'; + PTDEBUG && _d($sql); + $dbh->do($sql); + + my ($key) = grep { m/create table/i } keys %$href; + if ( $key ) { + PTDEBUG && _d('This table is a base table'); + $href->{$key} =~ s/\b[ ]{2,}/ /g; + $href->{$key} .= "\n"; + } + else { + PTDEBUG && _d('This table is a view'); + ($key) = grep { m/create view/i } keys %$href; + } + + return $href->{$key}; +} + sub parse { my ( $self, $ddl, $opts ) = @_; return unless $ddl; - if ( ref $ddl eq 'ARRAY' ) { - if ( lc $ddl->[0] eq 'table' ) { - $ddl = $ddl->[1]; - } - else { - return { - engine => 'VIEW', - }; - } - } if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) { die "Cannot parse table definition; is ANSI quoting " @@ -5593,41 +5766,31 @@ sub remove_auto_increment { return $ddl; } -sub remove_secondary_indexes { - my ( $self, $ddl ) = @_; - my $sec_indexes_ddl; - my $tbl_struct = $self->parse($ddl); - - if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) { - my $clustered_key = $tbl_struct->{clustered_key}; - $clustered_key ||= ''; - - my @sec_indexes = map { - my $key_def = $_->{ddl}; - $key_def =~ s/([\(\)])/\\$1/g; - $ddl =~ s/\s+$key_def//i; - - my $key_ddl = "ADD $_->{ddl}"; - $key_ddl .= ',' unless $key_ddl =~ m/,$/; - $key_ddl; - } - grep { $_->{name} ne $clustered_key } - values %{$tbl_struct->{keys}}; - PTDEBUG && _d('Secondary indexes:', Dumper(\@sec_indexes)); - - if ( @sec_indexes ) { - $sec_indexes_ddl = join(' ', @sec_indexes); - $sec_indexes_ddl =~ s/,$//; - } - - $ddl =~ s/,(\n\) )/$1/s; +sub get_table_status { + my ( $self, $dbh, $db, $like ) = @_; + my $q = $self->{Quoter}; + my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db); + my @params; + if ( $like ) { + $sql .= ' LIKE ?'; + push @params, $like; } - else { - PTDEBUG && _d('Not removing secondary indexes from', - $tbl_struct->{engine}, 'table'); + PTDEBUG && _d($sql, @params); + my $sth = $dbh->prepare($sql); + eval { $sth->execute(@params); }; + if ($EVAL_ERROR) { + PTDEBUG && _d($EVAL_ERROR); + return; } - - return $ddl, $sec_indexes_ddl, $tbl_struct; + my @tables = @{$sth->fetchall_arrayref({})}; + @tables = map { + my %tbl; # Make a copy with lowercased keys + @tbl{ map { lc $_ } keys %$_ } = values %$_; + $tbl{engine} ||= $tbl{type} || $tbl{comment}; + delete $tbl{type}; + \%tbl; + } @tables; + return @tables; } sub _d { @@ -6312,8 +6475,10 @@ sub main { return $event; }; + my $json = $o->get('report-type')->{json} + ? {} : undef; # Print info (advice) about each rule that matched this query. - if ( $groupby eq 'none' ) { + if ( $groupby eq 'none' || $json ) { push @pipeline, sub { my ( %args ) = @_; PTDEBUG && _d('callback: print advice'); @@ -6327,6 +6492,7 @@ sub main { severity_count => \%severity_count, verbose => $o->get('verbose'), report_format => $o->get('report-format'), + json => $json, Advisor => $adv, ); return $event; @@ -6439,7 +6605,7 @@ sub main { # ######################################################################## # Aggregate and report items for group-by reports # ######################################################################## - if ( $groupby ne 'none' ) { + if ( $groupby ne 'none' && !$json ) { print_grouped_report( advice_queue => \%advice_queue, group_by => $groupby, @@ -6451,7 +6617,7 @@ sub main { # ######################################################################## # Create and print profile of each items note/warn/crit count. # ######################################################################## - if ( keys %severity_count ) { + if ( keys %severity_count && !$json ) { eval { my $profile = new ReportFormatter( long_last_column => 1, @@ -6482,6 +6648,8 @@ sub main { }; } + print Transformers::encode_json($json), "\n" if $json; + return 0; } @@ -6497,6 +6665,7 @@ sub print_advice { my $adv = $args{Advisor}; my $seen_id = $args{seen_id}; my $severity_count = $args{severity_count}; + my $json = $args{json}; my $advice = $event->{advice}; my $near_pos = $event->{near_pos}; @@ -6505,7 +6674,9 @@ sub print_advice { # Header my $query_id = $event->{query_id} || ""; - print "\n# Query ID 0x$query_id at byte " . ($event->{pos_in_log} || 0) . "\n"; + + print "\n# Query ID 0x$query_id at byte " . ($event->{pos_in_log} || 0) . "\n" + unless $json; # New check IDs and their descriptions foreach my $i ( 1..$n_advice ) { @@ -6526,9 +6697,10 @@ sub print_advice { : $verbose == 2 ? "$desc[0] $desc[1]" # fuller : $verbose > 2 ? $desc # complete : ''; # none - print "# ", uc $info->{severity}, " $rule_id $desc\n"; + print "# ", uc $info->{severity}, " $rule_id $desc\n" + unless $json; - if ( $pos ) { + if ( $pos && !$json ) { my $offset = $pos > POS_CONTEXT ? $pos - POS_CONTEXT : 0; print "# matches near: ", substr($event->{arg}, $offset, ($pos - $offset) + POS_CONTEXT), @@ -6536,14 +6708,24 @@ sub print_advice { } } + if ( $json ) { + my $info_for_json = { + rule => $rule_id, + %$info + }; + push @{$json->{$query_id} ||= []}, $info_for_json; + } + $severity_count->{$query_id}->{$info->{severity}}++; } - # Already seen check IDs - print "# Also: @seen_ids\n" if scalar @seen_ids; - - # The query - print "$event->{arg}\n"; + if ( !$json ) { + # Already seen check IDs + print "# Also: @seen_ids\n" if scalar @seen_ids; + + # The query + print "$event->{arg}\n"; + } return; } @@ -6593,7 +6775,6 @@ sub print_grouped_report { my $verbose = $args{verbose} || 0; my %seen; - foreach my $groupby_attrib ( sort keys %$advice_queue ) { print "\n" . ($groupby eq 'query_id' ? "0x" : "") . $groupby_attrib; foreach my $groupby_value (sort keys %{$advice_queue->{$groupby_attrib}}){ @@ -7107,6 +7288,13 @@ type: Array The type of input to parse (default slowlog). The permitted types are slowlog and genlog. +=item --report-type + +type: Hash + +Alternative formats to output the report. Currently, only "json" is +recognized -- anything else is ignored and the default behavior used. + =item --user short form: -u; type: string From 04cb40a815c3c8ae0fa3d4771af3becfdd43a719 Mon Sep 17 00:00:00 2001 From: "baron@percona.com" <> Date: Tue, 7 Feb 2012 23:42:48 -0500 Subject: [PATCH 3/3] Update pt-query-advisor documentation, especially the rule text --- bin/pt-query-advisor | 61 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/bin/pt-query-advisor b/bin/pt-query-advisor index 2525e6e4..d4136af6 100755 --- a/bin/pt-query-advisor +++ b/bin/pt-query-advisor @@ -6666,17 +6666,12 @@ pt-query-advisor - Analyze queries and advise on possible problems. Usage: pt-query-advisor [OPTION...] [FILE] -pt-query-advisor analyzes queries and advises on possible problems. -Queries are given either by specifying slowlog files, --query, or --review. +pt-query-advisor detects bad patterns in a SQL query from the text alone. -Analyze all queries in a slow log: +Analyze all queries in a log in MySQL's slow query log format: pt-query-advisor /path/to/slow-query.log -Analyze all queires in a general log: - - pt-query-advisor --type genlog mysql.log - Get queries from tcpdump using pt-query-digest: pt-query-digest --type tcpdump.txt --print --no-report | pt-query-advisor @@ -6691,8 +6686,7 @@ tools) and those created by bugs. pt-query-advisor simply reads queries and examines them, and is thus very low risk. -At the time of this release there is a bug that may cause an infinite (or -very long) loop when parsing very large queries. +At the time of this release there are no known bugs that could harm users. The authoritative source for updated information is always the online issue tracking system. Issues that affect this tool will be marked as such. You can @@ -6777,14 +6771,16 @@ wildcard is potentially a bug in the SQL. severity: warn -SELECT without WHERE. The SELECT statement has no WHERE clause. +SELECT without WHERE. The SELECT statement has no WHERE clause and could +examine many more rows than intended. =item CLA.002 severity: note ORDER BY RAND(). ORDER BY RAND() is a very inefficient way to -retrieve a random row from the results. +retrieve a random row from the results, because it sorts the entire result +and then throws most of it away. =item CLA.003 @@ -6792,7 +6788,8 @@ severity: note LIMIT with OFFSET. Paginating a result set with LIMIT and OFFSET is O(n^2) complexity, and will cause performance problems as the data -grows larger. +grows larger. Pagination techniques such as bookmarked scans are much more +efficient. =item CLA.004 @@ -6806,21 +6803,24 @@ query is changed. severity: warn -ORDER BY constant column. +ORDER BY constant column. This is probably a bug in your SQL; at best it is a +useless operation that does not change the query results. =item CLA.006 severity: warn -GROUP BY or ORDER BY different tables will force a temp table and filesort. +GROUP BY or ORDER BY on different tables. This will force the use of a temporary +table and filesort, which can be a huge performance problem and can consume +large amounts of memory and temporary space on disk. =item CLA.007 severity: warn -ORDER BY different directions prevents index from being used. All tables -in the ORDER BY clause must be either ASC or DESC, else MySQL cannot use -an index. +ORDER BY different directions. All tables in the ORDER BY clause must be in the +same direction, either ASC or DESC, or MySQL cannot use an index to avoid a sort +after generating results. =item COL.001 @@ -6852,8 +6852,9 @@ more efficient to store IP addresses as integers. severity: warn -Unquoted date/time literal. A query such as "WHERE col<2010-02-12" -is valid SQL but is probably a bug; the literal should be quoted. +Unquoted date/time literal. A query such as "WHERE col<2010-02-12" is valid SQL +but is probably a bug, because it will be interpreted as "WHERE col<1996"; the +literal should be quoted. =item KWR.001 @@ -6868,23 +6869,25 @@ result screens. severity: crit -Mixing comma and ANSI joins. Mixing comma joins and ANSI joins -is confusing to humans, and the behavior differs between some -MySQL versions. +Mixing comma and ANSI joins. Mixing comma joins and ANSI joins is confusing to +humans, and the behavior and precedence differs between some MySQL versions, +which can introduce bugs. =item JOI.002 severity: crit A table is joined twice. The same table appears at least twice in the -FROM clause. +FROM clause in a manner that can be reduced to a single access to the table. =item JOI.003 severity: warn -Reference to outer table column in WHERE clause prevents OUTER JOIN, -implicitly converts to INNER JOIN. +OUTER JOIN defeated. The reference to an outer table column in the WHERE clause +prevents the OUTER JOIN from returning any non-matched rows, which implicitly +converts the query to an INNER JOIN. This is probably a bug in the query or a +misunderstanding of how OUTER JOIN works. =item JOI.004 @@ -6915,7 +6918,8 @@ non-deterministic results, depending on the query execution plan. severity: note -!= is non-standard. Use the <> operator to test for inequality. +The != operator is non-standard. Use the <> operator to test for inequality +instead. =item SUB.001 @@ -6923,9 +6927,8 @@ severity: crit IN() and NOT IN() subqueries are poorly optimized. MySQL executes the subquery as a dependent subquery for each row in the outer query. This is a frequent -cause of serious performance problems. This might change version 6.0 of MySQL, -but for versions 5.1 and older, the query should be rewritten as a JOIN or a -LEFT OUTER JOIN, respectively. +cause of serious performance problems in MySQL 5.5 and older versions. The +query probably should be rewritten as a JOIN or a LEFT OUTER JOIN, respectively. =back