Andrew Dwojc
Andrew Dwojc

Reputation: 119

How to intercept queries in a subclassed DBI?

I need to intercept all queries (in order to, say, do some structered logging). The app already uses a sub-classed DBI so I was hoping I could just inject the code I need in to the execute overload in MyDB::st class.

Apparently, this is not sufficient.

In the example below, MyDB::st::execute executes only in example 2). It does not execute in 1) where I call do nor in 3) where I call selectrow_array (even though the documentation says selectrow_array combines prepare, execute, and fetchrow_hashref).

Do I need to overload statements in MyDB::db class as well? There is quite a few (do, selectrow_array, selectrow_arrayref, selectrow_hashref, selectall_arrayref, selectall_array, selectall_hashref, selectcol_arrayref, etc.) I was hoping I could avoid that.

The example uses SQLite for simplicity but other drivers I checked worked the same.

#!/usr/bin/perl

package MyDB;
use strict;
use warnings;
use base 'DBI';

sub connect {
    return DBI::connect( 'MyDB', 'dbi:SQLite:dbname=so.sqlite', '', '',);
}


package MyDB::db;
use strict;
use warnings;
use base 'DBI::db';

sub prepare {
    my $self = shift;
    warn "  DB: PREPARE\n";
    return $self->SUPER::prepare( @_ );
}


package MyDB::st;
use strict;
use warnings;
use base 'DBI::st';

sub execute {
    my $self = shift;
    warn "  ST: EXECUTE\n";
    return $self->SUPER::execute( @_ );
}

sub fetchrow_hashref {
    my $self = shift;
    warn "  ST: FETCHROW_HASHREF\n";
    return $self->SUPER::fetchrow_hashref( @_ );
}




package main;
use strict;
use warnings;

my $dbh = MyDB::connect();

warn "1) --- DO ---\n";
$dbh->do("CREATE TABLE IF NOT EXISTS ttt (a int)");

warn "2) --- PREPARE/EXECUTE/FETCHROW_HASHREF ---\n";
my $sth = $dbh->prepare( "SELECT * FROM ttt" );
$sth->execute();
$sth->fetchrow_hashref();

warn "3) --- SELECTROW_ARRAY ---\n";
$dbh->selectrow_array( "SELECT * FROM ttt" );

Upvotes: 1

Views: 128

Answers (2)

ikegami
ikegami

Reputation: 386206

While the documentation describes certain methods in terms of other methods, the documentation doesn't claim these method call those other methods.

Each driver provides their own implementation of many of the DBI methods (e.g. selectrow_array) through the use of a template provided by DBI. Written in C, the implementation of the methods in the template frequently call the driver's functions directly rather than calling them indirectly through DBI method calls.

This makes the calls faster, but as you've discovered, it makes it harder to extend DBI.

You can find a Perl implementation of all the methods you listed inside of DBI.pm. Copy these. These implementations are all defined in terms of the following methods, limiting what you have to change:

  • prepare
  • execute
  • fetch
  • fetchrow_hashref
  • fetchrow_arrayref
  • bind_col

Upvotes: 4

oalders
oalders

Reputation: 5279

If all you want to do is log queries, you don't need to intercept anything. Try setting the DBI_TRACE environment variable.

DBI_TRACE=1=dbitrace.log

where the 1 is the log level and the second argument is a path to your log file. See https://metacpan.org/pod/DBI#DBI_TRACE and https://metacpan.org/pod/DBI#TRACING

Upvotes: 2

Related Questions