~percona-toolkit-dev/percona-toolkit/fix-log-parser-writer-bug-963225

« back to all changes in this revision

Viewing changes to lib/QueryReview.pm

  • Committer: Daniel Nichter
  • Date: 2011-06-24 17:22:06 UTC
  • Revision ID: daniel@percona.com-20110624172206-c7q4s4ad6r260zz6
Add lib/, t/lib/, and sandbox/.  All modules are updated and passing on MySQL 5.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This program is copyright 2008-2011 Percona Inc.
 
2
# Feedback and improvements are welcome.
 
3
#
 
4
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
5
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
6
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
7
#
 
8
# This program is free software; you can redistribute it and/or modify it under
 
9
# the terms of the GNU General Public License as published by the Free Software
 
10
# Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
11
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
12
# licenses.
 
13
#
 
14
# You should have received a copy of the GNU General Public License along with
 
15
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
16
# Place, Suite 330, Boston, MA  02111-1307  USA.
 
17
# ###########################################################################
 
18
# QueryReview package $Revision: 7342 $
 
19
# ###########################################################################
 
20
 
 
21
package QueryReview;
 
22
 
 
23
# This module is an interface to a "query review table" in which certain
 
24
# historical information about classes of queries is stored.  See the docs on
 
25
# mk-query-digest for context.
 
26
 
 
27
use strict;
 
28
use warnings FATAL => 'all';
 
29
use English qw(-no_match_vars);
 
30
Transformers->import(qw(make_checksum parse_timestamp));
 
31
 
 
32
use Data::Dumper;
 
33
 
 
34
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
35
 
 
36
# These columns are the minimal set of columns for every review table.  TODO:
 
37
# maybe it's possible to specify this in the tool's POD and pass it in so it's
 
38
# not hardcoded here and liable to get out of sync.
 
39
my %basic_cols = map { $_ => 1 }
 
40
   qw(checksum fingerprint sample first_seen last_seen reviewed_by
 
41
      reviewed_on comments);
 
42
my %skip_cols  = map { $_ => 1 } qw(fingerprint sample checksum);
 
43
 
 
44
# Required args:
 
45
# dbh           A dbh to the server with the query review table.
 
46
# db_tbl        Full quoted db.tbl name of the query review table.
 
47
#               Make sure the table exists! It's not checked here;
 
48
#               check it before instantiating an object.
 
49
# tbl_struct    Return val from TableParser::parse() for db_tbl.
 
50
#               This is used to discover what columns db_tbl has.
 
51
# quoter        Quoter object.
 
52
#
 
53
# Optional args:
 
54
# ts_default    SQL expression to use when inserting a new row into
 
55
#               the review table.  If nothing else is specified, NOW()
 
56
#               is the default.  This is for dependency injection while
 
57
#               testing.
 
58
sub new {
 
59
   my ( $class, %args ) = @_;
 
60
   foreach my $arg ( qw(dbh db_tbl tbl_struct quoter) ) {
 
61
      die "I need a $arg argument" unless $args{$arg};
 
62
   }
 
63
 
 
64
   foreach my $col ( keys %basic_cols ) {
 
65
      die "Query review table $args{db_tbl} does not have a $col column"
 
66
         unless $args{tbl_struct}->{is_col}->{$col};
 
67
   }
 
68
 
 
69
   my $now = defined $args{ts_default} ? $args{ts_default} : 'NOW()';
 
70
 
 
71
   # Design statements to INSERT and statements to SELECT from the review table.
 
72
   my $sql = <<"      SQL";
 
73
      INSERT INTO $args{db_tbl}
 
74
      (checksum, fingerprint, sample, first_seen, last_seen)
 
75
      VALUES(CONV(?, 16, 10), ?, ?, COALESCE(?, $now), COALESCE(?, $now))
 
76
      ON DUPLICATE KEY UPDATE
 
77
         first_seen = IF(
 
78
            first_seen IS NULL,
 
79
            COALESCE(?, $now),
 
80
            LEAST(first_seen, COALESCE(?, $now))),
 
81
         last_seen = IF(
 
82
            last_seen IS NULL,
 
83
            COALESCE(?, $now),
 
84
            GREATEST(last_seen, COALESCE(?, $now)))
 
85
      SQL
 
86
   MKDEBUG && _d('SQL to insert into review table:', $sql);
 
87
   my $insert_sth = $args{dbh}->prepare($sql);
 
88
 
 
89
   # The SELECT statement does not need to get the fingerprint, sample or
 
90
   # checksum.
 
91
   my @review_cols = grep { !$skip_cols{$_} } @{$args{tbl_struct}->{cols}};
 
92
   $sql = "SELECT "
 
93
        . join(', ', map { $args{quoter}->quote($_) } @review_cols)
 
94
        . ", CONV(checksum, 10, 16) AS checksum_conv FROM $args{db_tbl}"
 
95
        . " WHERE checksum=CONV(?, 16, 10)";
 
96
   MKDEBUG && _d('SQL to select from review table:', $sql);
 
97
   my $select_sth = $args{dbh}->prepare($sql);
 
98
 
 
99
   my $self = {
 
100
      dbh         => $args{dbh},
 
101
      db_tbl      => $args{db_tbl},
 
102
      insert_sth  => $insert_sth,
 
103
      select_sth  => $select_sth,
 
104
      tbl_struct  => $args{tbl_struct},
 
105
      quoter      => $args{quoter},
 
106
      ts_default  => $now,
 
107
   };
 
108
   return bless $self, $class;
 
109
}
 
110
 
 
111
# Tell QueryReview object to also prepare to save values in the review history
 
112
# table.
 
113
sub set_history_options {
 
114
   my ( $self, %args ) = @_;
 
115
   foreach my $arg ( qw(table dbh tbl_struct col_pat) ) {
 
116
      die "I need a $arg argument" unless $args{$arg};
 
117
   }
 
118
 
 
119
   # Pick out columns, attributes and metrics that need to be stored in the
 
120
   # table.
 
121
   my @cols;
 
122
   my @metrics;
 
123
   foreach my $col ( @{$args{tbl_struct}->{cols}} ) {
 
124
      my ( $attr, $metric ) = $col =~ m/$args{col_pat}/;
 
125
      next unless $attr && $metric;
 
126
 
 
127
      # TableParser lowercases the column names so, e.g., Query_time
 
128
      # becomes query_time.  We have to fix this so attribs in the event
 
129
      # match keys in $self->{history_metrics}...
 
130
 
 
131
      # If the attrib name has at least one _ then it's a multi-word
 
132
      # attrib like Query_time or Lock_time, so the first letter should
 
133
      # be uppercase.  Else, it's a one-word attrib like ts, checksum
 
134
      # or sample, so we leave it alone.  Except Filesort which is yet
 
135
      # another exception.
 
136
      $attr = ucfirst $attr if $attr =~ m/_/;
 
137
      $attr = 'Filesort' if $attr eq 'filesort';
 
138
 
 
139
      $attr =~ s/^Qc_hit/QC_Hit/;  # Qc_hit is really QC_Hit
 
140
      $attr =~ s/^Innodb/InnoDB/g; # Innodb is really InnoDB
 
141
      $attr =~ s/_io_/_IO_/g;      # io is really IO
 
142
 
 
143
      push @cols, $col;
 
144
      push @metrics, [$attr, $metric];
 
145
   }
 
146
 
 
147
   my $sql = "REPLACE INTO $args{table}("
 
148
      . join(', ',
 
149
         map { $self->{quoter}->quote($_) } ('checksum', 'sample', @cols))
 
150
      . ') VALUES (CONV(?, 16, 10), ?'
 
151
      . (@cols ? ', ' : '')  # issue 1265
 
152
      . join(', ', map {
 
153
         # ts_min and ts_max might be part of the PK, in which case they must
 
154
         # not be NULL.
 
155
         $_ eq 'ts_min' || $_ eq 'ts_max'
 
156
            ? "COALESCE(?, $self->{ts_default})"
 
157
            : '?'
 
158
        } @cols) . ')';
 
159
   MKDEBUG && _d($sql);
 
160
 
 
161
   $self->{history_sth}     = $args{dbh}->prepare($sql);
 
162
   $self->{history_metrics} = \@metrics;
 
163
 
 
164
   return;
 
165
}
 
166
 
 
167
# Save review history for a class of queries.  The incoming data is a bunch
 
168
# of hashes.  Each top-level key is an attribute name, and each second-level key
 
169
# is a metric name.  Look at the test for more examples.
 
170
sub set_review_history {
 
171
   my ( $self, $id, $sample, %data ) = @_;
 
172
   # Need to transform ts->min/max into timestamps
 
173
   foreach my $thing ( qw(min max) ) {
 
174
      next unless defined $data{ts} && defined $data{ts}->{$thing};
 
175
      $data{ts}->{$thing} = parse_timestamp($data{ts}->{$thing});
 
176
   }
 
177
   $self->{history_sth}->execute(
 
178
      make_checksum($id),
 
179
      $sample,
 
180
      map { $data{$_->[0]}->{$_->[1]} } @{$self->{history_metrics}});
 
181
}
 
182
 
 
183
# Fetch information from the database about a query that's been reviewed.
 
184
sub get_review_info {
 
185
   my ( $self, $id ) = @_;
 
186
   $self->{select_sth}->execute(make_checksum($id));
 
187
   my $review_vals = $self->{select_sth}->fetchall_arrayref({});
 
188
   if ( $review_vals && @$review_vals == 1 ) {
 
189
      return $review_vals->[0];
 
190
   }
 
191
   return undef;
 
192
}
 
193
 
 
194
# Store a query into the table.  The arguments are:
 
195
#  *  fingerprint
 
196
#  *  sample
 
197
#  *  first_seen
 
198
#  *  last_seen
 
199
# There's no need to convert the fingerprint to a checksum, no need to parse
 
200
# timestamps either.
 
201
sub set_review_info {
 
202
   my ( $self, %args ) = @_;
 
203
   $self->{insert_sth}->execute(
 
204
      make_checksum($args{fingerprint}),
 
205
      @args{qw(fingerprint sample)},
 
206
      map { $args{$_} ? parse_timestamp($args{$_}) : undef }
 
207
         qw(first_seen last_seen first_seen first_seen last_seen last_seen));
 
208
}
 
209
 
 
210
# Return the columns we'll be using from the review table.
 
211
sub review_cols {
 
212
   my ( $self ) = @_;
 
213
   return grep { !$skip_cols{$_} } @{$self->{tbl_struct}->{cols}};
 
214
}
 
215
 
 
216
sub _d {
 
217
   my ($package, undef, $line) = caller 0;
 
218
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
219
        map { defined $_ ? $_ : 'undef' }
 
220
        @_;
 
221
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
222
}
 
223
 
 
224
1;
 
225
# ###########################################################################
 
226
# End QueryReview package
 
227
# ###########################################################################