File Coverage

blib/lib/Alzabo/RDBMSRules.pm
Criterion Covered Total %
statement 15 217 6.9
branch 0 64 0.0
condition 0 30 0.0
subroutine 5 56 8.9
pod 42 50 84.0
total 62 417 14.8


line stmt bran cond sub pod time code
1             package Alzabo::RDBMSRules;
2              
3 11     11   80 use strict;
  11         22  
  11         476  
4 11     11   60 use vars qw($VERSION);
  11         25  
  11         590  
5              
6 11     11   58 use Alzabo::Exceptions ( abbr => [ 'recreate_table_exception' ] );
  11         19  
  11         94  
7              
8 11     11   65 use Class::Factory::Util;
  11         22  
  11         107  
9 11     11   579 use Params::Validate qw( validate validate_pos );
  11         20  
  11         36602  
10             Params::Validate::validation_options( on_fail => sub { Alzabo::Exception::Params->throw( error => join '', @_ ) } );
11              
12             $VERSION = 2.0;
13              
14             1;
15              
16             sub new
17             {
18 0     0 1   shift;
19 0           my %p = @_;
20              
21 0           eval "use Alzabo::RDBMSRules::$p{rdbms};";
22 0 0         Alzabo::Exception::Eval->throw( error => $@ ) if $@;
23 0           return "Alzabo::RDBMSRules::$p{rdbms}"->new(@_);
24             }
25              
26 0     0 1   sub available { __PACKAGE__->subclasses }
27              
28             # validation
29              
30             sub validate_schema_name
31             {
32 0     0 1   shift()->_virtual;
33             }
34              
35             sub validate_table_name
36             {
37 0     0 1   shift()->_virtual;
38             }
39              
40             sub validate_column_name
41             {
42 0     0 1   shift()->_virtual;
43             }
44              
45             sub validate_column_type
46             {
47 0     0 1   shift()->_virtual;
48             }
49              
50             sub validate_column_length
51             {
52 0     0 1   shift()->_virtual;
53             }
54              
55             sub validate_table_attribute
56             {
57 0     0 0   shift()->_virtual;
58             }
59              
60             sub validate_column_attribute
61             {
62 0     0 1   shift()->_virtual;
63             }
64              
65             sub validate_primary_key
66             {
67 0     0 1   shift()->_virtual;
68             }
69              
70             sub validate_sequenced_attribute
71             {
72 0     0 1   shift()->_virtual;
73             }
74              
75             sub validate_index
76             {
77 0     0 1   shift()->_virtual;
78             }
79              
80             sub type_is_numeric
81             {
82 0     0 1   my $self = shift;
83 0           my $col = shift;
84              
85 0   0       return $self->type_is_integer($col) || $self->type_is_floating_point($col);
86             }
87              
88             sub type_is_integer
89             {
90 0     0 1   shift()->_virtual;
91             }
92              
93             sub type_is_floating_point
94             {
95 0     0 1   shift()->_virtual;
96             }
97              
98             sub type_is_character
99             {
100 0     0 1   shift()->_virtual;
101             }
102              
103             sub type_is_date
104             {
105 0     0 1   shift()->_virtual;
106             }
107              
108             sub type_is_datetime
109             {
110 0     0 1   shift()->_virtual;
111             }
112              
113             sub type_is_time
114             {
115 0     0 1   shift()->_virtual;
116             }
117              
118             sub type_is_time_interval
119             {
120 0     0 1   shift()->_virtual;
121             }
122              
123             sub type_is_blob
124             {
125 0     0 0   shift()->_virtual;
126             }
127              
128             sub blob_type
129             {
130 0     0 0   shift()->virtual;
131             }
132              
133             # feature probing
134              
135             sub column_types
136             {
137 0     0 1   shift()->_virtual;
138             }
139              
140             sub feature
141             {
142 0     0 1   return 0;
143             }
144              
145 0     0 1   sub quote_identifiers { 0 }
146              
147 0     0 0   sub quote_identifiers_character { '' }
148              
149             sub schema_attributes
150             {
151 0     0 0   shift()->_virtual;
152             }
153              
154             sub table_attributes
155             {
156 0     0 0   shift()->_virtual;
157             }
158              
159             sub column_attributes
160             {
161 0     0 0   shift()->_virtual;
162             }
163              
164             sub schema_sql
165             {
166 0     0 1   my $self = shift;
167              
168 0           validate_pos( @_, { isa => 'Alzabo::Schema' } );
169              
170 0           my $schema = shift;
171              
172 0           my @sql;
173              
174 0           local $self->{state};
175              
176 0           foreach my $t ( $schema->tables )
177             {
178 0           push @sql, $self->table_sql($t);
179             }
180              
181 0 0         return @sql, @{ $self->{state}{deferred_sql} || [] };
  0            
182             }
183              
184             sub table_sql
185             {
186 0     0 1   shift()->_virtual;
187             }
188              
189             sub column_sql
190             {
191 0     0 1   shift()->_virtual;
192             }
193              
194             sub index_sql
195             {
196 0     0 1   my $self = shift;
197 0           my $index = shift;
198              
199 0           my $index_name = $index->id;
200 0           $index_name = $self->quote_identifiers_character . $index_name . $self->quote_identifiers_character;
201              
202 0           my $sql = 'CREATE';
203 0 0         $sql .= ' UNIQUE' if $index->unique;
204 0           $sql .= " INDEX $index_name ON ";
205 0           $sql .= $self->quote_identifiers_character;
206 0           $sql .= $index->table->name;
207 0           $sql .= $self->quote_identifiers_character;
208 0           $sql .= ' ( ';
209              
210 0 0         if ( defined $index->function )
211             {
212 0           $sql .= $index->function;
213             }
214             else
215             {
216 0           $sql .=
217             ( join ', ',
218 0           map { $self->quote_identifiers_character . $_->name . $self->quote_identifiers_character }
219             $index->columns
220             );
221             }
222              
223 0           $sql .= ' )';
224              
225 0           return $sql;
226             }
227              
228             sub foreign_key_sql
229             {
230 0     0 1   shift()->_virtual;
231             }
232              
233             sub drop_table_sql
234             {
235 0     0 1   my $self = shift;
236              
237 0           my $name = shift->name;
238 0           $name = $self->quote_identifiers_character . $name . $self->quote_identifiers_character;
239              
240 0           return "DROP TABLE $name";
241             }
242              
243             sub drop_column_sql
244             {
245 0     0 1   shift()->_virtual;
246             }
247              
248             sub drop_index_sql
249             {
250 0     0 1   shift()->_virtual;
251             }
252              
253             sub drop_foreign_key_sql
254             {
255 0     0 1   shift()->_virtual;
256             }
257              
258             sub column_sql_add
259             {
260 0     0 1   shift()->_virtual;
261             }
262              
263             sub column_sql_diff
264             {
265 0     0 1   shift()->_virtual;
266             }
267              
268             sub index_sql_diff
269             {
270 0     0 1   my $self = shift;
271              
272 0           validate( @_, { new => { isa => 'Alzabo::Index' },
273             old => { isa => 'Alzabo::Index' } } );
274              
275 0           my %p = @_;
276              
277 0           my $new_sql = $self->index_sql($p{new});
278              
279 0           my @sql;
280 0 0         if ( $new_sql ne $self->index_sql($p{old}) )
281             {
282 0           push @sql, $self->drop_index_sql( $p{old}, $p{new}->table->name );
283 0           push @sql, $new_sql;
284             }
285              
286 0           return @sql;
287             }
288              
289             sub alter_primary_key_sql
290             {
291 0     0 1   shift()->_virtual;
292             }
293              
294             sub can_alter_table_name
295             {
296 0     0 1   1;
297             }
298              
299             sub can_alter_column_name
300             {
301 0     0 1   1;
302             }
303              
304             sub alter_table_name_sql
305             {
306 0     0 1   shift()->_virtual;
307             }
308              
309             sub alter_column_name_sql
310             {
311 0     0 1   shift()->_virtual;
312             }
313              
314             sub recreate_table_sql
315             {
316 0     0 1   shift()->_virtual;
317             }
318              
319             =pod
320              
321             sub reverse_engineer
322             {
323             my $self = shift;
324             my $schema = shift;
325              
326             my $dbh = $schema->driver->handle;
327              
328             foreach my $table ( $dbh->tables )
329             {
330             my $t = $schema->make_table( name => $table );
331              
332             $self->reverse_engineer_table($t);
333             }
334             }
335              
336             sub reverse_engineer_table
337             {
338             my $self = shift;
339             my $table = shift;
340              
341             my $dbh = $table->schema->driver->handle;
342              
343             my $sth = $dbh->column_info( undef, $table->schema->name, $table->name, undef );
344              
345             while ( my $col_info = $sth->fetchrow_hashref )
346             {
347             use Data::Dumper; warn Dumper $col_info;
348             my %attr = ( name => $col_info->{COLUMN_NAME},
349             type => $col_info->{TYPE_NAME},
350             nullable => $col_info->{NULLABLE} ? 1 : 0,
351             );
352              
353             $attr{size} =
354             $col_info->{COLUMN_SIZE} if $col_info->{COLUMN_SIZE};
355              
356             $attr{precision} =
357             $col_info->{DECIMAL_DIGITS} if $col_info->{DECIMAL_DIGITS};
358              
359             $attr{default} =
360             $col_info->{COLUMN_DEF} if defined $col_info->{COLUMN_DEF};
361              
362             $attr{comment} =
363             $col_info->{REMARKS} if defined $col_info->{REMARKS};
364              
365             $table->make_column(%attr);
366             }
367              
368             $self->reverse_engineer_table_primary_key($table);
369             }
370              
371             sub reverse_engineer_table_primary_key
372             {
373             my $self = shift;
374             my $table = shift;
375              
376             my $dbh = $table->schema->driver->handle;
377              
378             my $sth = $dbh->column_info( undef, $table->schema->name, $table->name );
379              
380             while ( my $pk_info = $sth->fetchrow_hashref )
381             {
382             $table->add_primary_key( $table->column( $pk_info->{COLUMN_NAME} ) );
383             }
384             }
385              
386             =cut
387              
388             sub rules_id
389             {
390 0     0 0   shift()->_virtual;
391             }
392              
393             sub schema_sql_diff
394             {
395 0     0 1   my $self = shift;
396              
397 0           validate( @_, { new => { isa => 'Alzabo::Schema' },
398             old => { isa => 'Alzabo::Schema' } } );
399              
400 0           my %p = @_;
401              
402 0           local $self->{state};
403              
404 0           my @sql;
405             my %changed_name;
406 0           foreach my $new_t ( $p{new}->tables )
407             {
408             # When syncing against an existing schema, the table may be
409             # present with its new name.
410 0           my $old_t;
411 0 0         if ( defined $new_t->former_name )
412             {
413 0           $old_t = eval { $p{old}->table( $new_t->former_name ) };
  0            
414             }
415              
416 0   0       $old_t ||= eval { $p{old}->table( $new_t->name ) };
  0            
417              
418 0 0         if ($old_t)
419             {
420 0 0         if ( $old_t->name ne $new_t->name )
421             {
422 0           $changed_name{ $old_t->name } = 1;
423              
424 0 0         if ( $self->can_alter_table_name )
425             {
426 0           push @sql, $self->alter_table_name_sql($new_t);
427             }
428             else
429             {
430 0           push @sql, $self->recreate_table_sql( new => $new_t,
431             old => $old_t,
432             );
433 0           push @sql, $self->rename_sequences( new => $new_t,
434             old => $old_t,
435             );
436              
437             # no need to do more because table will be
438             # recreated from scratch
439 0           next;
440             }
441             }
442              
443             push @sql,
444 0           eval { $self->table_sql_diff( new => $new_t,
  0            
445             old => $old_t ) };
446              
447 0 0         if ( my $e = Exception::Class->caught('Alzabo::Exception::RDBMSRules::RecreateTable' ) )
    0          
448             {
449 0           push @sql, $self->recreate_table_sql( new => $new_t,
450             old => $old_t,
451             );
452             }
453             elsif ( $e = $@ )
454             {
455 0           die $e;
456             }
457             }
458             else
459             {
460 0           push @sql, $self->table_sql($new_t);
461 0           foreach my $fk ( $new_t->all_foreign_keys )
462             {
463 0           push @{ $self->{state}{deferred_sql} }, $self->foreign_key_sql($fk);
  0            
464             }
465             }
466             }
467              
468 0           foreach my $old_t ( $p{old}->tables )
469             {
470 0 0 0       unless ( $changed_name{ $old_t->name } ||
471 0           eval { $p{new}->table( $old_t->name ) } )
472             {
473 0           push @sql, $self->drop_table_sql($old_t);
474             }
475             }
476              
477 0 0         return @sql, @{ $self->{state}{deferred_sql} || [] };
  0            
478             }
479              
480             sub table_sql_diff
481             {
482 0     0 1   my $self = shift;
483              
484 0           validate( @_, { new => { isa => 'Alzabo::Table' },
485             old => { isa => 'Alzabo::Table' } } );
486              
487 0           my %p = @_;
488 0           my @sql;
489 0           foreach my $old_i ( $p{old}->indexes )
490             {
491 0 0         unless ( eval { $p{new}->index( $old_i->id ) } )
  0            
492             {
493             push @sql, $self->drop_index_sql($old_i, $p{new}->name)
494 0 0 0       if eval { $p{new}->columns( map { $_->name } $old_i->columns ) } && ! $@;
  0            
  0            
495             }
496             }
497              
498 0           my %changed_name;
499 0           foreach my $new_c ( $p{new}->columns )
500             {
501 0 0 0       $changed_name{ $new_c->former_name } = 1
502             if defined $new_c->former_name && $new_c->former_name ne $new_c->name;
503             }
504              
505 0           foreach my $old_c ( $p{old}->columns )
506             {
507 0 0 0       unless ( $changed_name{ $old_c->name } ||
508 0           ( my $new_c = eval { $p{new}->column( $old_c->name ) } )
509             )
510             {
511 0           push @sql, $self->drop_column_sql( new_table => $p{new},
512             old => $old_c );
513             }
514             }
515              
516 0           foreach my $new_c ( $p{new}->columns )
517             {
518             # When syncing against an existing schema, the column may be
519             # present with its new name.
520 0           my $old_c;
521 0 0         if ( defined $new_c->former_name )
522             {
523 0           $old_c = eval { $p{old}->column( $new_c->former_name ) };
  0            
524             }
525              
526 0   0       $old_c ||= eval { $p{old}->column( $new_c->name ) };
  0            
527              
528 0 0         if ($old_c)
529             {
530 0 0         if ( $old_c->name ne $new_c->name )
531             {
532 0 0         if ( $self->can_alter_column_name )
533             {
534 0           push @sql, $self->alter_column_name_sql($new_c);
535             }
536             else
537             {
538             # no need to do more because table will be
539             # recreated from scratch
540 0           recreate_table_exception();
541             }
542             }
543              
544 0           push @sql, $self->column_sql_diff( new => $new_c,
545             old => $old_c,
546             );
547             }
548             else
549             {
550 0           push @sql, $self->column_sql_add($new_c);
551             }
552             }
553              
554 0           foreach my $new_i ( $p{new}->indexes )
555             {
556 0 0         if ( my $old_i = eval { $p{old}->index( $new_i->id ) } )
  0            
557             {
558 0           push @sql, $self->index_sql_diff( new => $new_i,
559             old => $old_i );
560             }
561             else
562             {
563 0           push @sql, $self->index_sql($new_i)
564             }
565             }
566              
567 0           foreach my $new_fk ( $p{new}->all_foreign_keys )
568             {
569 0 0         unless ( grep { $new_fk->id eq $_->id } $p{old}->all_foreign_keys )
  0            
570             {
571 0           push @{ $self->{state}{deferred_sql} }, $self->foreign_key_sql($new_fk)
  0            
572             }
573             }
574              
575 0           foreach my $old_fk ( $p{old}->all_foreign_keys )
576             {
577 0 0         unless ( grep { $old_fk->id eq $_->id } $p{new}->all_foreign_keys )
  0            
578             {
579 0           push @sql, $self->drop_foreign_key_sql($old_fk);
580             }
581             }
582              
583 0           my $pk_changed;
584 0           foreach my $old_pk ( $p{old}->primary_key )
585             {
586 0 0         next if $changed_name{ $old_pk->name };
587              
588 0           my $new_col = eval { $p{new}->column( $old_pk->name ) };
  0            
589 0 0 0       unless ( $new_col && $new_col->is_primary_key )
590             {
591 0           push @sql, $self->alter_primary_key_sql( new => $p{new},
592             old => $p{old} );
593              
594 0           $pk_changed = 1;
595 0           last;
596             }
597             }
598              
599 0 0         unless ($pk_changed)
600             {
601 0           foreach my $new_pk ( $p{new}->primary_key )
602             {
603 0           my $old_col = eval { $p{old}->column( $new_pk->name ) };
  0            
604              
605 0 0 0       next if $new_pk->former_name && $changed_name{ $new_pk->former_name };
606              
607 0 0 0       unless ( $old_col && $old_col->is_primary_key )
608             {
609 0           push @sql, $self->alter_primary_key_sql( new => $p{new},
610             old => $p{old} );
611              
612 0           last;
613             }
614             }
615             }
616              
617 0           my $alter_attributes;
618 0           foreach my $new_att ( $p{new}->attributes )
619             {
620 0 0         unless ( $p{old}->has_attribute( attribute => $new_att, case_sensitive => 1 ) )
621             {
622 0           $alter_attributes = 1;
623              
624 0           push @sql, $self->alter_table_attributes_sql( new => $p{new},
625             old => $p{old},
626             );
627              
628 0           last;
629             }
630             }
631              
632 0 0         unless ($alter_attributes)
633             {
634 0           foreach my $old_att ( $p{old}->attributes )
635             {
636 0 0         unless ( $p{new}->has_attribute( attribute => $old_att, case_sensitive => 1 ) )
637             {
638 0           $alter_attributes = 1;
639              
640 0           push @sql, $self->alter_table_attributes_sql( new => $p{new},
641             old => $p{old},
642             );
643              
644 0           last;
645             }
646             }
647             }
648              
649 0           return @sql;
650             }
651              
652              
653             sub _virtual
654             {
655 0     0     my $self = shift;
656              
657 0           my $sub = (caller(1))[3];
658 0           Alzabo::Exception::VirtualMethod->throw( error =>
659             "$sub is a virtual method and must be subclassed in " . ref $self );
660             }
661              
662             __END__