package DBIx::AssertIndex;
use strict;
use warnings;
use 5.008_009;
our $VERSION = '0.01';
use DBI;
our $OUTPUT = *STDERR;
sub import {
my ($class) = @_;
no warnings qw(redefine prototype);
my $original_st_execute = \&DBI::st::execute;
*DBI::st::execute = __explain_and_st_execute($original_st_execute);
foreach my $db_method (qw/do selectall_arrayref selectrow_array selectrow_arrayref /){
no strict qw(refs);
my $original = \&{"DBI::db::$db_method"};
*{"DBI::db::$db_method"} = __explain_and_db_XXX($original, $original_st_execute);
}
};
sub __explain_and_db_XXX {
my($original_db_XXX, $original_st_execute) = @_;
return sub {
my ($dbh, $statement, @rest ) = @_;
__expain($original_st_execute, $dbh, $statement, @rest);
return $original_db_XXX->(@_);
};
}
sub __explain_and_st_execute {
my $original_st_execute = shift;
return sub {
my ($sth, @rest ) = @_;
my $dbh = $sth->{Database};
my $statement = $sth->{Statement};
__expain($original_st_execute, $dbh, $statement, @rest);
return $original_st_execute->(@_);
};
}
sub __expain {
my($original_st_execute, $dbh, $statement, @rest) = @_;
return unless($dbh->{Driver}{Name} eq 'mysql');
return unless $statement =~ m/^\s*SELECT/i;
return unless $statement =~ m/FROM/mi;
my $explain_sth = $dbh->prepare( 'explain ' . $statement );
$original_st_execute->($explain_sth, @rest);
__assert_explain($explain_sth->fetchall_arrayref( +{} ), $statement);
}
sub __assert_explain {
my($explains, $statement) = @_;
my $clean_statement = __clean_statement($statement);
my @using_no_key = grep {__should_alert($_) } @$explains;
return unless @using_no_key;
__warn->('[explain alert] ', $clean_statement);
}
sub __should_alert {
my ( $explain_by_table ) = @_;
my $extra = $explain_by_table->{Extra};
my $type = $explain_by_table->{type};
my $possible_key = $explain_by_table->{possible_keys};
# search uniq/primary key but not found any rows
return 0 if $extra and $extra =~ m/^Impossible/;
# not using any index
return 0 if defined $possible_key;
return 1 if $type and $type eq 'ALL';
return 0;
}
sub __clean_statement {
my $statement = shift;
$statement =~ s/\n/ /g;
$statement =~ s/\s+/ /g;
return $statement;
}
sub __warn {
my($message, $statement, $using_no_key) = @_;
if (ref $OUTPUT eq 'CODE') {
$OUTPUT->(
message => $message,
statement => $statement,
using_no_key => $using_no_key,
);
} else {
print {$OUTPUT} $message, ' statement:', $statement;
}
}
1;
__END__
=head1 NAME
DBIx::AssertIndex - show error when SQL query doesn't use index.
=head1 SYNOPSIS
use DBIx::AssertIndex;
my $row = $dbh->selectrow_hashref(q{SELECT * FROM some_table WHERE no_indexed_column = 'foo'});
or
> starman -MDBIx::AssertIndex app.psgi
=head1 DESCRIPTION
DBIx::AssertIndex is run explain with SELECT SQL and detect query without any index.
Works only DBD::mysql.
=head1 AUTHOR
daichi hiroki E<lt>daichi.hiroki@mixi.co.jpE<gt>
=head1 SEE ALSO
=head1 LICENSE
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut