=head1 NAME PApp::SQL - absolutely easy yet fast and powerful sql access =head1 SYNOPSIS use PApp::SQL; # to be written =head1 DESCRIPTION This module provides you with easy-to-use functions to execute sql commands (using DBI). Despite being easy to use, they are also quite efficient and allow you to write faster programs in less lines of code. =over 4 =cut package PApp::SQL; use DBI; #use PApp::Exception; # not yet used BEGIN { use base Exporter; $VERSION = 0.1; @EXPORT = qw( sql_exec sql_fetch sql_fetchall sql_exists sql_insertid $sql_exec ); @EXPORT_OK = qw( connect_cached ); require XSLoader; XSLoader::load PApp::SQL, $VERSION; } our $sql_exec; # last result of sql_exec's execute call our $DBH; # the default database handle our $database; # the current SQL::Database object, if applicable our %dbcache; =item $dbh = connect_cached $id, $dsn, $user, $pass, $flags, $connect (not exported by by default) Connect to the database given by C<($dsn,$user,$pass)>, while using the flags from C<$flags>. These are just the same arguments as given to Cconnect>. The database handle will be cached under the unique id C<$id>. If the same id is requested later, the cached handle will be checked (using ping), and the connection will be re-established if necessary (be sure to prefix your application or module name to the id to make it "more" unique. Things like __PACKAGE__ . __LINE__ work fine as well). If specified, C<$connect> is a callback (e.g. a coderef) that will be called each time a new connection is being established, with the new C<$dbh> as first argument. Examples: # try your luck opening the papp database without access info $dbh = connect_cached __FILE__, "DBI:mysql:papp"; =cut sub connect_cached { my ($id, $dsn, $user, $pass, $flags, $connect) = @_; # the following line is duplicated in PApp::SQL::Database::new $id = "$id\0$dsn\0$user\0$pass"; unless ($dbcache{$id} && $dbcache{$id}->ping) { #warn "connecting to ($dsn|$user|$pass|$flags)\n";#d# # first, nuke our cache (sooory ;) cachesize cachesize 0; # then connect anew $dbcache{$id} = eval { DBI->connect($dsn, $user, $pass, $flags) } || eval { DBI->connect($dsn, $user, $pass, $flags) } || die $DBI::errstr; $connect->($dbcache{$id}) if $connect; } $dbcache{$id}; } =item $sth = sql_exec [dbh,] [bind-vals...,] "sql-statement", [arguments...] C is the most important and most-used function in this module. Runs the given sql command with the given parameters and returns the statement handle. The command and the statement handle will be cached (with the database handle and the sql string as key), so prepare will be called only once for each distinct sql call (please keep in mind that the returned statement will always be the same, so, if you call C with the same dbh and sql-statement twice (e.g. in a subroutine you called), the statement handle for the first call mustn't be used. The database handle (the first argument) is optional. If it is missing, C first tries to use the variable C<$DBH> in the current (= calling) package and, if that fails, it tries to use database handle in C<$PApp::SQL::DBH>, which you can set before calling these functions. The actual return value from the C<$sth->execute> call is stored in the package-global (and exported) variable C<$sql_exec>. If any error occurs C will throw an exception. Examples: # easy one my $st = sql_exec "select name, id from table where id = ?", $id; while (my ($name, $id) = $st->fetchrow_array) { ... }; # the fastest way to use dbi, using bind_columns my $st = sql_exec \my($name, $id), "select name, id from table where id = ?", $id; while ($st->fetch) { ...} # now use a different dastabase: sql_exec $dbh, "update file set name = ?", "oops.txt"; =item sql_fetch Execute a sql-statement and fetch the first row of results. Depending on the caller context the row will be returned as a list (array context), or just the first columns. In table form: CONTEXT RESULT void () scalar first column list array C is quite efficient in conjunction with bind variables: sql_fetch \my($name, $amount), "select name, amount from table where id name = ?", "Toytest"; But of course the normal way to call it is simply: my($name, $amount) = sql_fetch "select ...", args... ... and it's still quite fast unless you fetch large amounts of data. =item sql_fetchall Similarly to C, but all result rows will be fetched (this is of course inefficient for large results!). The context is ignored (only list context makes sense), but the result still depends on the number of columns in the result: COLUMNS RESULT 0 () 1 (row1, row2, row3...) many ([row1], [row2], [row3]...) Examples (all of which are inefficient): for (sql_fetchall "select id from table") { ... } my @names = sql_fetchall "select name from user"; for (sql_fetchall "select name, age, place from user") { my ($name, $age, $place) = @$_; } =item sql_exists " where ...", args... Check wether the result of the sql-statement "select xxx from $first_argument" would be empty or not (that is, imagine the string "select from" were prepended to your statement (it isn't)). Should work with every database but can be quite slow, except on mysql, where this should be quite fast. Examples: print "user 7 exists!\n" if sql_exists "user where id = ?", 7; die "duplicate key" if sql_exists "user where name = ? and pass = ?", "stefan", "geheim"; =cut # uncodumented, since unportable (only works with DBH even!). yet it is exported (aaargh!) sub sql_insertid { $DBH->{mysql_insertid}; } =item [old-size] = cachesize [new-size] Returns (and possibly changes) the LRU cache size used by C. The default is somewhere around 50 (= the 50 last recently used statements will be cached). It shouldn't be too large, since a simple linear listed is used for the cache at the moment (which, for small (<100) cache sizes is actually quite fast). The function always returns the cache size in effect I the call, so, to nuke the cache (for example, when a database connection has died or you want to garbage collect old database/statement handles), this construct can be used: PApp::SQL::cachesize PApp::SQL::cachesize 0; =cut =item reinitialize [not exported] Clears any internal caches (statement cache, database handle cache). =cut sub reinitialize { cachesize cachesize 0; for (values %dbcache) { eval { $_->disconnect }; } undef %dbcache; } =back =cut package PApp::SQL::Database; =head2 THE DATABASE CLASS Again (sigh) the problem of persistency. What do you do when you have to serialize on object that contains (or should contain) a database handle? Short answer: you don't. Long answer: you can embed the necessary information to recreate the dbh when needed. The C class does that, in a relatively efficient fashion: the overhead is currently a single method call per access (you can cache the real dbh if you want). =over 4 =item $db = new > The C call takes the same arguments as C (obviously, if you supply a connect callback it better is serializable, see L!) and returns a serializable database class. No database handle is actually being created. =item $db->dbh Return the database handle as fast as possible (usually just a hash lookup). =item $db->checked_dbh Return the database handle, but first check that the database is still available and re-open the connection if necessary. =cut sub new($$;@) { my $class = shift; my ($id, $dsn, $user, $pass, $flags, $connect) = @_; # the following line is duplicated in PApp::SQL::Database::new my $id2 = "$id\0$dsn\0$user\0$pass"; bless [$id2, $flags, $connect], $class; } # the following two functions better be fast! sub dbh($) { $dbcache{$_[0][0]} || $_[0]->checked_dbh; } sub checked_dbh($) { my $dbh = $dbcache{$_[0][0]}; $dbh && $dbh->ping ? $dbh : PApp::SQL::connect_cached((split /\x00/, $_[0][0]), $_[0][1], $_[0][2]); } =item $db->dsn Return the DSN (L) fo the database object (e.g. for error messages). =cut sub dsn($) { my $self = shift; $self->[1][1]; } =back =cut reinitialize; 1; =head1 BUGS As of this writing, sql_fetch and sql_fetchall are not very well tested (they were just re-written in C). sql_exists could be faster (it is written very ugly to not change the current package). =head1 SEE ALSO L. =head1 AUTHOR Marc Lehmann http://www.goof.com/pcg/marc/ =cut