Use weak refs for schema object attributes
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Schema / Trigger.pm
1 package SQL::Translator::Schema::Trigger;
2
3 =pod
4
5 =head1 NAME
6
7 SQL::Translator::Schema::Trigger - SQL::Translator trigger object
8
9 =head1 SYNOPSIS
10
11   use SQL::Translator::Schema::Trigger;
12   my $trigger = SQL::Translator::Schema::Trigger->new(
13     name                => 'foo',
14     perform_action_when => 'before', # or after
15     database_events     => [qw/update insert/], # also update, update_on, delete
16     fields              => [],       # if event is "update"
17     on_table            => 'foo',    # table name
18     action              => '...',    # text of trigger
19     schema              => $schema,  # Schema object
20     scope               => 'row',    # or statement
21   );
22
23 =head1 DESCRIPTION
24
25 C<SQL::Translator::Schema::Trigger> is the trigger object.
26
27 =head1 METHODS
28
29 =cut
30
31 use Moo;
32 use SQL::Translator::Utils qw(parse_list_arg ex2err throw);
33 use SQL::Translator::Types qw(schema_obj);
34 use List::MoreUtils qw(uniq);
35
36 with qw(
37   SQL::Translator::Schema::Role::BuildArgs
38   SQL::Translator::Schema::Role::Extra
39   SQL::Translator::Schema::Role::Error
40   SQL::Translator::Schema::Role::Compare
41 );
42
43 our $VERSION = '1.59';
44
45 =head2 new
46
47 Object constructor.
48
49   my $schema = SQL::Translator::Schema::Trigger->new;
50
51 =cut
52
53 around BUILDARGS => sub {
54     my ($orig, $self, @args) = @_;
55     my $args = $self->$orig(@args);
56     if (exists $args->{on_table}) {
57         my $arg = delete $args->{on_table};
58         my $table = $args->{schema}->get_table($arg)
59             or die "Table named $arg doesn't exist";
60         $args->{table} = $table;
61     }
62     if (exists $args->{database_event}) {
63         $args->{database_events} = delete $args->{database_event};
64     }
65     return $args;
66 };
67
68 =head2 perform_action_when
69
70 Gets or sets whether the event happens "before" or "after" the
71 C<database_event>.
72
73   $trigger->perform_action_when('after');
74
75 =cut
76
77 has perform_action_when => (
78     is => 'rw',
79     coerce => sub { defined $_[0] ? lc $_[0] : $_[0] },
80     isa => sub {
81         throw("Invalid argument '$_[0]' to perform_action_when")
82             if defined $_[0] and $_[0] !~ m/^(before|after)$/i;
83     },
84 );
85
86 around perform_action_when => \&ex2err;
87
88 sub database_event {
89
90 =pod
91
92 =head2 database_event
93
94 Obsolete please use database_events!
95
96 =cut
97
98     my $self = shift;
99
100     return $self->database_events( @_ );
101 }
102
103 =head2 database_events
104
105 Gets or sets the events that triggers the trigger.
106
107   my $ok = $trigger->database_events('insert');
108
109 =cut
110
111 has database_events => (
112     is => 'rw',
113     coerce => sub { [ map { lc } ref $_[0] eq 'ARRAY' ? @{$_[0]} : ($_[0]) ] },
114     isa => sub {
115         my @args    = @{$_[0]};
116         my %valid   = map { $_, 1 } qw[ insert update update_on delete ];
117         my @invalid = grep { !defined $valid{ $_ } } @args;
118
119         if ( @invalid ) {
120             throw(
121                 sprintf("Invalid events '%s' in database_events",
122                     join(', ', @invalid)
123                 )
124             );
125         }
126     },
127 );
128
129 around database_events => sub {
130     my ($orig,$self) = (shift, shift);
131
132     if (@_) {
133         ex2err($orig, $self, ref $_[0] eq 'ARRAY' ? $_[0] : \@_)
134             or return;
135     }
136
137     return wantarray
138         ? @{ $self->$orig || [] }
139         : $self->$orig;
140 };
141
142 =head2 fields
143
144 Gets and set which fields to monitor for C<database_event>.
145
146   $view->fields('id');
147   $view->fields('id', 'name');
148   $view->fields( 'id, name' );
149   $view->fields( [ 'id', 'name' ] );
150   $view->fields( qw[ id name ] );
151
152   my @fields = $view->fields;
153
154 =cut
155
156 has fields => (
157     is => 'rw',
158     coerce => sub {
159         my @fields = uniq @{parse_list_arg($_[0])};
160         @fields ? \@fields : undef;
161     },
162 );
163
164 around fields => sub {
165     my $orig   = shift;
166     my $self   = shift;
167     my $fields = parse_list_arg( @_ );
168     $self->$orig($fields) if @$fields;
169
170     return wantarray ? @{ $self->$orig || [] } : $self->$orig;
171 };
172
173 =head2 table
174
175 Gets or set the table on which the trigger works, as a L<SQL::Translator::Schema::Table> object.
176   $trigger->table($triggered_table);
177
178 =cut
179
180 has table => ( is => 'rw', isa => schema_obj('Table'), weak_ref => 1 );
181
182 around table => \&ex2err;
183
184 sub on_table {
185
186 =pod
187
188 =head2 on_table
189
190 Gets or set the table name on which the trigger works, as a string.
191   $trigger->on_table('foo');
192
193 =cut
194
195     my ($self, $arg) = @_;
196     if ( @_ == 2 ) {
197         my $table = $self->schema->get_table($arg);
198         die "Table named $arg doesn't exist"
199             if !$table;
200         $self->table($table);
201     }
202     return $self->table->name;
203 }
204
205 =head2 action
206
207 Gets or set the action of the trigger.
208
209   $trigger->action(
210       q[
211         BEGIN
212           select ...;
213           update ...;
214         END
215       ]
216   );
217
218 =cut
219
220 has action => ( is => 'rw', default => sub { '' } );
221
222 sub is_valid {
223
224 =pod
225
226 =head2 is_valid
227
228 Determine whether the trigger is valid or not.
229
230   my $ok = $trigger->is_valid;
231
232 =cut
233
234     my $self = shift;
235
236     for my $attr (
237         qw[ name perform_action_when database_events on_table action ]
238     ) {
239         return $self->error("Invalid: missing '$attr'") unless $self->$attr();
240     }
241
242     return $self->error("Missing fields for UPDATE ON") if
243         $self->database_event eq 'update_on' && !$self->fields;
244
245     return 1;
246 }
247
248 =head2 name
249
250 Get or set the trigger's name.
251
252   my $name = $trigger->name('foo');
253
254 =cut
255
256 has name => ( is => 'rw', default => sub { '' } );
257
258 =head2 order
259
260 Get or set the trigger's order.
261
262   my $order = $trigger->order(3);
263
264 =cut
265
266 has order => ( is => 'rw', default => sub { 0 } );
267
268 around order => sub {
269     my ( $orig, $self, $arg ) = @_;
270
271     if ( defined $arg && $arg =~ /^\d+$/ ) {
272         return $self->$orig($arg);
273     }
274
275     return $self->$orig;
276 };
277
278 =head2 scope
279
280 Get or set the trigger's scope (row or statement).
281
282     my $scope = $trigger->scope('statement');
283
284 =cut
285
286 has scope => (
287     is => 'rw',
288     isa => sub {
289         my ($arg) = @_;
290         throw( "Invalid scope '$arg'" )
291             if defined $arg and $arg !~ /^(row|statement)$/i;
292     },
293 );
294
295 around scope => \&ex2err;
296
297
298 =head2 schema
299
300 Get or set the trigger's schema object.
301
302   $trigger->schema( $schema );
303   my $schema = $trigger->schema;
304
305 =cut
306
307 has schema => (is => 'rw', isa => schema_obj('Schema'), weak_ref => 1 );
308
309 around schema => \&ex2err;
310
311 sub compare_arrays {
312
313 =pod
314
315 =head2 compare_arrays
316
317 Compare two arrays.
318
319 =cut
320
321     my ($first, $second) = @_;
322     no warnings;  # silence spurious -w undef complaints
323
324     return 0 unless (ref $first eq 'ARRAY' and ref $second eq 'ARRAY' ) ;
325
326     return 0 unless @$first == @$second;
327
328     my @first = sort @$first;
329
330     my @second = sort @$second;
331
332     for (my $i = 0; $i < scalar @first; $i++) {
333         return 0 if @first[$i] ne @second[$i];
334     }
335
336     return 1;
337 }
338
339 =head2 equals
340
341 Determines if this trigger is the same as another
342
343   my $is_identical = $trigger1->equals( $trigger2 );
344
345 =cut
346
347 around equals => sub {
348     my $orig             = shift;
349     my $self             = shift;
350     my $other            = shift;
351     my $case_insensitive = shift;
352
353     return 0 unless $self->$orig($other);
354
355     my %names;
356     for my $name ( $self->name, $other->name ) {
357         $name = lc $name if $case_insensitive;
358         $names{ $name }++;
359     }
360
361     if ( keys %names > 1 ) {
362         return $self->error('Names not equal');
363     }
364
365     if ( !$self->perform_action_when eq $other->perform_action_when ) {
366         return $self->error('perform_action_when differs');
367     }
368
369     if (
370         !compare_arrays( [$self->database_events], [$other->database_events] )
371     ) {
372         return $self->error('database_events differ');
373     }
374
375     if ( $self->on_table ne $other->on_table ) {
376         return $self->error('on_table differs');
377     }
378
379     if ( $self->action ne $other->action ) {
380         return $self->error('action differs');
381     }
382
383     if (
384         !$self->_compare_objects( scalar $self->extra, scalar $other->extra )
385     ) {
386         return $self->error('extras differ');
387     }
388
389     return 1;
390 };
391
392 # Must come after all 'has' declarations
393 around new => \&ex2err;
394
395 1;
396
397 =pod
398
399 =head1 AUTHORS
400
401 Anonymous,
402 Ken Youens-Clark E<lt>kclark@cpan.orgE<gt>.
403
404 =cut