line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Alzabo::Runtime::ForeignKey; |
2
|
|
|
|
|
|
|
|
3
|
11
|
|
|
11
|
|
65
|
use strict; |
|
11
|
|
|
|
|
20
|
|
|
11
|
|
|
|
|
475
|
|
4
|
11
|
|
|
11
|
|
57
|
use vars qw( $VERSION %DELETED ); |
|
11
|
|
|
|
|
22
|
|
|
11
|
|
|
|
|
522
|
|
5
|
|
|
|
|
|
|
|
6
|
11
|
|
|
11
|
|
61
|
use Alzabo::Runtime; |
|
11
|
|
|
|
|
23
|
|
|
11
|
|
|
|
|
331
|
|
7
|
11
|
|
|
11
|
|
73
|
use Alzabo::Exceptions ( abbr => 'params_exception' ); |
|
11
|
|
|
|
|
27
|
|
|
11
|
|
|
|
|
99
|
|
8
|
|
|
|
|
|
|
|
9
|
11
|
|
|
11
|
|
71
|
use Params::Validate qw( validate ARRAYREF OBJECT ); |
|
11
|
|
|
|
|
26
|
|
|
11
|
|
|
|
|
1248
|
|
10
|
|
|
|
|
|
|
Params::Validate::validation_options |
11
|
|
|
|
|
|
|
( on_fail => sub { params_exception join '', @_ } ); |
12
|
|
|
|
|
|
|
|
13
|
11
|
|
|
11
|
|
67
|
use base qw(Alzabo::ForeignKey); |
|
11
|
|
|
|
|
25
|
|
|
11
|
|
|
|
|
10825
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
$VERSION = 2.0; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
1; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# FIXME - needs docs |
20
|
|
|
|
|
|
|
sub new |
21
|
|
|
|
|
|
|
{ |
22
|
0
|
|
|
0
|
0
|
|
my $proto = shift; |
23
|
0
|
|
0
|
|
|
|
my $class = ref $proto || $proto; |
24
|
|
|
|
|
|
|
|
25
|
0
|
|
|
|
|
|
validate( @_, { columns_from => { type => ARRAYREF | OBJECT }, |
26
|
|
|
|
|
|
|
columns_to => { type => ARRAYREF | OBJECT }, |
27
|
|
|
|
|
|
|
} ); |
28
|
0
|
|
|
|
|
|
my %p = @_; |
29
|
|
|
|
|
|
|
|
30
|
0
|
|
|
|
|
|
my $self = bless {}, $class; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# XXX - needs a little more validation, like that both "sides" |
33
|
|
|
|
|
|
|
# have the same number of columns |
34
|
0
|
|
|
|
|
|
$self->{columns_from} = $p{columns_from}; |
35
|
0
|
|
|
|
|
|
$self->{columns_to} = $p{columns_to}; |
36
|
|
|
|
|
|
|
|
37
|
0
|
|
|
|
|
|
return $self; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
sub register_insert |
41
|
|
|
|
|
|
|
{ |
42
|
0
|
|
|
0
|
1
|
|
shift->_insert_or_update( 'insert', @_ ); |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub register_update |
46
|
|
|
|
|
|
|
{ |
47
|
0
|
|
|
0
|
1
|
|
shift->_insert_or_update( 'update', @_ ); |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub _insert_or_update |
51
|
|
|
|
|
|
|
{ |
52
|
0
|
|
|
0
|
|
|
my $self = shift; |
53
|
0
|
|
|
|
|
|
my $type = shift; |
54
|
0
|
|
|
|
|
|
my %vals = @_; |
55
|
|
|
|
|
|
|
|
56
|
0
|
|
|
|
|
|
my $driver = $self->table_from->schema->driver; |
57
|
|
|
|
|
|
|
|
58
|
0
|
|
|
|
|
|
my @one_to_one_where; |
59
|
|
|
|
|
|
|
my @one_to_one_vals; |
60
|
|
|
|
|
|
|
|
61
|
0
|
|
|
|
|
|
my $has_nulls = grep { ! defined } values %vals; |
|
0
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
|
63
|
0
|
|
|
|
|
|
foreach my $pair ( $self->column_pairs ) |
64
|
|
|
|
|
|
|
{ |
65
|
|
|
|
|
|
|
# if we're inserting into a table we don't check if its primary |
66
|
|
|
|
|
|
|
# key exists elsewhere, no matter what the cardinality of the |
67
|
|
|
|
|
|
|
# relation. Otherwise, we end up in cycles where it is impossible |
68
|
|
|
|
|
|
|
# to insert things into the table. |
69
|
0
|
0
|
0
|
|
|
|
next if $type eq 'insert' && $pair->[0]->is_primary_key; |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# A table is always allowed to make updates to its own primary |
72
|
|
|
|
|
|
|
# key columns ... |
73
|
0
|
0
|
0
|
|
|
|
if ( ( $type eq 'update' || $pair->[1]->is_primary_key ) |
|
|
|
0
|
|
|
|
|
74
|
|
|
|
|
|
|
&& ! $pair->[0]->is_primary_key ) |
75
|
|
|
|
|
|
|
{ |
76
|
0
|
0
|
|
|
|
|
$self->_check_existence( $pair->[1] => $vals{ $pair->[0]->name } ) |
77
|
|
|
|
|
|
|
if defined $vals{ $pair->[0]->name }; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# Except when the PK has a one-to-one relationship to some |
81
|
|
|
|
|
|
|
# other table, and the update would cause a duplication in the |
82
|
|
|
|
|
|
|
# other table. |
83
|
0
|
0
|
0
|
|
|
|
if ( $self->is_one_to_one && ! $has_nulls ) |
84
|
|
|
|
|
|
|
{ |
85
|
0
|
|
|
|
|
|
push @one_to_one_where, [ $pair->[0], '=', $vals{ $pair->[0]->name } ]; |
86
|
0
|
|
|
|
|
|
push @one_to_one_vals, $pair->[0]->name . ' = ' . $vals{ $pair->[0]->name }; |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
0
|
0
|
0
|
|
|
|
if ( $self->is_one_to_one && ! $has_nulls ) |
91
|
|
|
|
|
|
|
{ |
92
|
0
|
0
|
0
|
|
|
|
if ( @one_to_one_where && |
93
|
|
|
|
|
|
|
$self->table_from->row_count( where => \@one_to_one_where ) ) |
94
|
|
|
|
|
|
|
{ |
95
|
0
|
|
|
|
|
|
my $err = '(' . (join ', ', @one_to_one_vals) . ') already exists in the ' . $self->table_from->name . ' table'; |
96
|
0
|
|
|
|
|
|
Alzabo::Exception::ReferentialIntegrity->throw( error => $err ); |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub _check_existence |
102
|
|
|
|
|
|
|
{ |
103
|
0
|
|
|
0
|
|
|
my $self = shift; |
104
|
0
|
|
|
|
|
|
my ($col, $val) = @_; |
105
|
|
|
|
|
|
|
|
106
|
0
|
0
|
|
|
|
|
unless ( $self->table_to->row_count( where => [ $col, '=', $val ] ) ) |
107
|
|
|
|
|
|
|
{ |
108
|
0
|
|
|
|
|
|
Alzabo::Exception::ReferentialIntegrity->throw( error => 'Foreign key must exist in foreign table. No rows in ' . $self->table_to->name . ' where ' . $col->name . " = $val" ); |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
sub register_delete |
113
|
|
|
|
|
|
|
{ |
114
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
115
|
0
|
|
|
|
|
|
my $row = shift; |
116
|
|
|
|
|
|
|
|
117
|
0
|
|
|
|
|
|
my @update = grep { $_->nullable } $self->columns_to; |
|
0
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
|
119
|
0
|
0
|
0
|
|
|
|
return unless $self->to_is_dependent || @update; |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# Find the rows in the other table that are related to the row |
122
|
|
|
|
|
|
|
# being deleted. |
123
|
0
|
|
|
|
|
|
my @where = map { [ $_->[1], '=', $row->select( $_->[0]->name ) ] } $self->column_pairs; |
|
0
|
|
|
|
|
|
|
124
|
0
|
|
|
|
|
|
my $cursor = $self->table_to->rows_where( where => \@where ); |
125
|
|
|
|
|
|
|
|
126
|
0
|
|
|
|
|
|
while ( my $related_row = $cursor->next ) |
127
|
|
|
|
|
|
|
{ |
128
|
|
|
|
|
|
|
# This is a class variable so that multiple foreign key |
129
|
|
|
|
|
|
|
# objects don't try to delete the same rows |
130
|
0
|
0
|
|
|
|
|
next if $DELETED{ $related_row->id_as_string }; |
131
|
|
|
|
|
|
|
|
132
|
0
|
0
|
|
|
|
|
if ($self->to_is_dependent) |
|
|
0
|
|
|
|
|
|
133
|
|
|
|
|
|
|
{ |
134
|
0
|
|
|
|
|
|
local %DELETED = %DELETED; |
135
|
0
|
|
|
|
|
|
$DELETED{ $related_row->id_as_string } = 1; |
136
|
|
|
|
|
|
|
# dependent relationship so delete other row (may begin a |
137
|
|
|
|
|
|
|
# chain reaction!) |
138
|
0
|
|
|
|
|
|
$related_row->delete; |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
elsif (@update) |
141
|
|
|
|
|
|
|
{ |
142
|
|
|
|
|
|
|
# not dependent so set the column(s) to null |
143
|
0
|
|
|
|
|
|
$related_row->update( map { $_->name => undef } @update ); |
|
0
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
__END__ |