X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2FCursor.pm;h=f6d8816a715cd26123f1967781306f51cf0f1949;hb=9686d044b747a9365de4b2c043d6c1e33b5c76a1;hp=ec7af93c1c166068d151e72c794ff2116f4a7990;hpb=d8540d8199066c1c0f3fde450f68bceb0e341531;p=freeside.git diff --git a/FS/FS/Cursor.pm b/FS/FS/Cursor.pm index ec7af93c1..f6d8816a7 100644 --- a/FS/FS/Cursor.pm +++ b/FS/FS/Cursor.pm @@ -2,10 +2,11 @@ package FS::Cursor; use strict; use vars qw($DEBUG $buffer); -use FS::Record qw(dbh); +use FS::Record; +use FS::UID qw(myconnect driver_name); use Scalar::Util qw(refaddr); -$DEBUG = 0; +$DEBUG = 2; # this might become a parameter at some point, but right now, you can # "local $FS::Cursor::buffer = X;" @@ -38,11 +39,14 @@ and returns an FS::Cursor object to fetch the rows one at a time. sub new { my $class = shift; my $q = FS::Record::_query(@_); # builds the statement and parameter list + my $dbh = myconnect(); my $self = { query => $q, class => 'FS::' . ($q->{table} || 'Record'), buffer => [], + dbh => $dbh, + position => 0, # for mysql }; bless $self, $class; @@ -53,11 +57,20 @@ sub new { $self->{pid} = $$; $self->{id} = sprintf('cursor%08x', refaddr($self)); - my $statement = "DECLARE ".$self->{id}." CURSOR FOR ".$q->{statement}; - my $sth = dbh->prepare($statement) - or die dbh->errstr; - my $bind = 0; + my $statement; + if ( driver_name() eq 'Pg' ) { + $statement = "DECLARE ".$self->{id}." CURSOR FOR ".$q->{statement}; + } elsif ( driver_name() eq 'mysql' ) { + # build a cursor from scratch + $statement = "CREATE TEMPORARY TABLE $self->{id} + (rownum INT AUTO_INCREMENT, PRIMARY KEY (rownum)) + $q->{statement}"; + } + + my $sth = $dbh->prepare($statement) + or die $dbh->errstr; + my $bind = 1; foreach my $value ( @{ $q->{value} } ) { my $bind_type = shift @{ $q->{bind_type} }; $sth->bind_param($bind++, $value, $bind_type ); @@ -65,7 +78,14 @@ sub new { $sth->execute or die $sth->errstr; - $self->{fetch} = dbh->prepare("FETCH FORWARD $buffer FROM ".$self->{id}); + if ( driver_name() eq 'Pg' ) { + $self->{fetch} = $dbh->prepare("FETCH FORWARD $buffer FROM ".$self->{id}); + } elsif ( driver_name() eq 'mysql' ) { + # make sure we're not holding any locks on the tables mentioned + # in the query + $dbh->commit if driver_name() eq 'mysql'; + $self->{fetch} = $dbh->prepare("SELECT * FROM $self->{id} ORDER BY rownum LIMIT ?, $buffer"); + } $self; } @@ -96,16 +116,26 @@ sub fetch { sub refill { my $self = shift; my $sth = $self->{fetch}; + $sth->bind_param(1, $self->{position}) if driver_name() eq 'mysql'; $sth->execute or die $sth->errstr; my $result = $self->{fetch}->fetchall_arrayref( {} ); $self->{buffer} = $result; + $self->{position} += $sth->rows; scalar @$result; } sub DESTROY { my $self = shift; return unless $self->{pid} eq $$; - dbh->do('CLOSE '. $self->{id}) or die dbh->errstr; # clean-up the cursor in Pg + if ( driver_name() eq 'Pg' ) { + $self->{dbh}->do('CLOSE '. $self->{id}) + or die $self->{dbh}->errstr; # clean-up the cursor in Pg + } elsif ( driver_name() eq 'mysql' ) { + # nothing; the temporary table will evaporate when the + # session closes. + } + $self->{dbh}->rollback; + $self->{dbh}->disconnect; } =back @@ -116,7 +146,8 @@ Replace all uses of qsearch with this. =head1 BUGS -Doesn't support MySQL. +MySQL doesn't support cursors in interactive sessions, only in stored +procedures, so we implement our own. This has not been extensively tested. The cursor will close prematurely if any code issues a rollback/commit. If you need protection against this use qsearch or fork and get a new dbh