robins has asked for the wisdom of the Perl Monks concerning the following question:
I have a database (DBD::Pg) with metadata about some files. The files themselves are stored in a folder with the primary key from the database as the filename.
I'm trying to find the correct way to delete the record in the database and then deleting the file from the filesystem (with full atomic exception handling). But be aware that a DELETE is cascading and will either block or cascade depending on the foreign key references.
If one of these commands fail I must make sure both the database and the file are rolled back to the previous state (that is, NOT deleted). But how can I simulate an unlink without actually deleting the file? And what to do if the storage filesystem (SAN) or database server goes haywire in the middle of the transaction and drops out?
The code below works at the moment, but if you setup a bunch of objects, and iterate through them, you would want this entire batch job to be one unit of work. As far as I can tell, this function isn't good enough in that scenario.
The PF->transaction(1) call starts a new transaction (and disables DBI AutoCommit). PF->transaction(0) tries to commit the current transaction, and issues a rollback if any errors happened. RaiseError is on, and PrintEror is off. The PF->sql() call is just a wrapper to call prepare and execute in one go with automatic statement caching. The entire call to this function is wrapped in eval, using a generic error handler which calls rollback if RaiseError is triggered.
If anyone has a completely different approach to this problem, please enlighten me with your wisdom.
Are there any CPAN modules available that can help in some way with this kind of problem?
This is the code I currently use:
sub new {} sub DESTROY {] etc. ... sub unlink { my $self=shift @_; my $object_id=shift @_; unless( defined($object_id) and $self->{'pf'}->is_uuid($object_id) + ) { return 0; # FAIL } # Find out if we're already in a transaction my $in_transaction=$self->{'pf'}->in_transaction; # Don't create t +ransaction if we're already in one # Start new transaction if we aren't already in one unless ($in_transaction) { $self->{'pf'}->transaction(1); } # Get object information my $sth=$self->{'pf'}->sql('SELECT object.object_id,storage_contai +ner.fs_path,object.storage_container_id,object.size FROM object,stora +ge_container WHER # If record is something else than 1, either multiple objects or n +o objects are available, either way, it's an error. unless ($sth->rows == 1) { die __("Object not available."); } # Get object metadata from database my $object=$sth->fetchrow_hashref; # Find out if object is used as header-object somewhere else my $check_header_object_sth=$self->{'pf'}->sql('SELECT container_i +d,title FROM container WHERE object_id=?',$object_id); # Fail if object is in use in a container. if ($check_header_object_sth->rows > 0) { (my $header_container_id,my $header_container_title)=$check_he +ader_object_sth->fetchrow_array; die __x("Object is used as a header object in container: {titl +e}", title=>$header_container_title); } # Remove object from container_object (child objects of container) my $delete_container_sth=$self->{'pf'}->sql('DELETE FROM container +_object WHERE object_id=?',$object->{'object_id'}); # Remove metadata file if present my $delete_sth=$self->{'pf'}->sql('DELETE FROM object WHERE object +_id=?',$object->{'object_id'}); # Return error if unable to delete object metadata from database unless ($delete_sth->rows == 1) { die __"Unable to find object metadata to delete."; } # Update storage_container with new used size my $container_sth=$self->{'pf'}->sql('UPDATE storage_container SET + usedsize=usedsize-? WHERE storage_container_id=?',$object->{'size'}, +$object->{'storag my $tmpfilename=$object->{'fs_path'} . "/" . $object->{'object_id' +}; # Check if storage file is owned by effective uid unless ( -r $tmpfilename) { die __"Unreadable object."; } # Remove file unless(unlink $tmpfilename) { die __"Unable to delete object."; } # Commit data and finish transaction unless ($in_transaction) { $self->{'pf'}->transaction(0); } return 1; # OK }
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: Atomic use of unlink() and SQL DELETE? How?
by BrowserUk (Patriarch) on Feb 09, 2006 at 12:08 UTC | |
by robins (Acolyte) on Feb 09, 2006 at 13:33 UTC | |
by BrowserUk (Patriarch) on Feb 09, 2006 at 15:12 UTC | |
Re: Atomic use of unlink() and SQL DELETE? How?
by tirwhan (Abbot) on Feb 09, 2006 at 14:22 UTC | |
Re: Atomic use of unlink() and SQL DELETE? How?
by pboin (Deacon) on Feb 09, 2006 at 13:31 UTC | |
by robins (Acolyte) on Feb 09, 2006 at 13:47 UTC | |
Re: Atomic use of unlink() and SQL DELETE? How?
by valdez (Monsignor) on Feb 10, 2006 at 12:17 UTC | |
by robins (Acolyte) on Jun 14, 2006 at 09:31 UTC |