[Dbix-class] bug in delete_all: CascadeActions::delete removes
parent before children
Noel Burton-Krahn
noel at burton-krahn.com
Fri Oct 24 22:26:35 BST 2008
DBIx's cascading delete_all (in DBIx::Class::ResultSet) it broken,
because it deletes the parent table before it deletes the children.
The database will throw a referential integrity exception when the
parent is deleted before the children. I've attached a test program
below. Here's a fixed version in
DBIx-Class-0.08010/lib/DBIx/Class/Relationship/CascadeActions.pm:
Regards,
Noel Burton-Krahn
###################################
# fixed DBIx/Class/Relationship/CascadeActions.pm in DBIx-Class-0.08010
sub delete {
my ($self, @rest) = @_;
# delete from tables that depend on me first
my $source = $self->result_source;
my %rels = map { $_ => $source->relationship_info($_) }
$source->relationships;
my @cascade = grep { $rels{$_}{attrs}{cascade_delete} } keys %rels;
foreach my $rel (@cascade) {
$self->search_related($rel)->delete_all;
}
# delete me
return $self->next::method(@rest) unless ref $self;
# I'm just ignoring this for class deletes because hell, the db should
# be handling this anyway. Assuming we have joins we probably actually
# *could* do them, but I'd rather not.
my $ret = $self->next::method(@rest);
return $ret;
}
# Test program
###################################
#! /usr/bin/perl -w
=head1 NAME
dbix_cascade_delete.t - reproduce DBIx's failure in delete_all
=head1 DESCRIPTION
DBIx::Class::ResultSet::delete_all fails in version 0.08010 because it
deletes the parent before the children
=head1 AUTHOR
Noel Burton-Krahn <noel at burton-krahn.com>
=cut
use strict;
use warnings;
#--------------------
package My::DBIx::Class;
use base qw/DBIx::Class/;
__PACKAGE__->load_components(qw/PK::Auto Core/);
use overload '""' => 'dump';
sub dump {
my($self) = shift;
return join(" ", map { "$_=" . $self->get_column($_) } $self->columns);
}
#--------------------
package MySchema::Person;
use base qw/My::DBIx::Class/;
__PACKAGE__->table('person');
__PACKAGE__->add_columns(qw(person_id name));
__PACKAGE__->set_primary_key('person_id');
__PACKAGE__->has_many(address => 'MySchema::Address', 'person_id');
#--------------------
package MySchema::Address;
use base qw/My::DBIx::Class/;
__PACKAGE__->table('address');
__PACKAGE__->add_columns(qw(address_id person_id address));
__PACKAGE__->set_primary_key('address_id');
__PACKAGE__->belongs_to(person => 'MySchema::Person', 'person_id');
#--------------------
package MySchema;
use base qw/DBIx::Class::Schema/;
__PACKAGE__->load_classes({
'MySchema' => [ qw(Person Address) ],
});
#--------------------
package Test::DbixCascaseDelete;
use Test::More tests => 16;
# create a mysql database to test with
system(<<'EOS');
mysqladmin -f drop mytest >/dev/null 2>&1
mysqladmin create mytest
mysql mytest <<ESQL
create table person (
person_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(1024) NOT NULL
) ENGINE=INNODB;
create table address (
address_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,person_id INT NOT NULL
,address VARCHAR(1024) NOT NULL
,FOREIGN KEY (person_id) REFERENCES person (person_id)
) ENGINE=INNODB;
ESQL
#mysql mytest <<ESQL
#show tables;
#show create table person;
#show create table address;
#ESQL
EOS
;
is($?, 0, "create database");
# connect
my $schema = MySchema->connect("dbi:mysql:mytest", 'script', 'tlby14')
or die("connect: $!");
ok($schema, "connect to db");
#$schema->storage->debug(1);
my $rs;
my $person;
$person = $schema->resultset('Person')->create({ name => 'fred'});
ok($person, "create Person: $person");
$rs = $schema->resultset('Person')->search();
while( my $row = $rs->next() ) {
$person = $row;
}
ok($rs, "found Person: $person");
my $address;
for my $i (1..3) {
$address = $schema->resultset('Address')->create({ person => $person,
address => "fred's address $i"});
ok($address, "create Address: $address");
}
$rs = $schema->resultset('Address')->search({ person_id =>
$person->person_id });
while( my $row = $rs->next ) {
$address = $row;
ok($address, "found created Address: $address");
}
ok($address->person, "address->person: " . $address->person->dump);
$rs = $person->address_rs;
while( my $row = $rs->next ) {
$address = $row;
ok($address, "person->address: $address");
}
$rs = $schema->resultset('Person')->search({ name => 'fred'});
$rs->delete_all;
ok(1, "delete_all");
is($rs->count, 0, "Person really gone");
More information about the DBIx-Class
mailing list