File Coverage

blib/lib/Alzabo/Create/Schema.pm
Criterion Covered Total %
statement 48 445 10.7
branch 0 174 0.0
condition 0 63 0.0
subroutine 16 52 30.7
pod 21 24 87.5
total 85 758 11.2


line stmt bran cond sub pod time code
1             package Alzabo::Create::Schema;
2              
3 9     9   116015 use strict;
  9         24  
  9         531  
4 9     9   63 use vars qw($VERSION);
  9         22  
  9         608  
5              
6 9     9   5525 use Alzabo::ChangeTracker;
  9         33  
  9         348  
7 9     9   67 use Alzabo::Config;
  9         34  
  9         244  
8 9     9   7704 use Alzabo::Create;
  9         25  
  9         191  
9 9     9   50 use Alzabo::Driver;
  9         17  
  9         265  
10             use Alzabo::Exceptions
11 9     9   47 ( abbr => [ qw( params_exception system_exception ) ] );
  9         20  
  9         83  
12 9     9   50 use Alzabo::RDBMSRules;
  9         18  
  9         217  
13 9     9   5471 use Alzabo::Runtime;
  9         36  
  9         59  
14 9     9   72 use Alzabo::SQLMaker;
  9         21  
  9         317  
15 9     9   54 use Alzabo::Utils;
  9         22  
  9         193  
16              
17 9     9   56 use File::Spec;
  9         22  
  9         384  
18              
19 9     9   57 use Params::Validate qw( :all );
  9         21  
  9         2864  
20             Params::Validate::validation_options
21             ( on_fail => sub { params_exception join '', @_ } );
22              
23 9     9   64 use Storable ();
  9         22  
  9         171  
24 9     9   53 use Tie::IxHash;
  9         22  
  9         242  
25              
26 9     9   52 use base qw( Alzabo::Schema );
  9         27  
  9         68367  
27              
28             $VERSION = 2.0;
29              
30             1;
31              
32             sub new
33             {
34 0     0 1   my $proto = shift;
35 0   0       my $class = ref $proto || $proto;
36              
37 0           validate( @_, { rdbms => { type => SCALAR },
38             name => { type => SCALAR },
39             no_cache => { type => SCALAR, default => 0 },
40             } );
41 0           my %p = @_;
42              
43 0           my $self = bless {}, $class;
44              
45 0           params_exception "Alzabo does not support the '$p{rdbms}' RDBMS"
46 0           unless ( ( grep { $p{rdbms} eq $_ } Alzabo::Driver->available ) &&
47 0 0 0       ( grep { $p{rdbms} eq $_ } Alzabo::RDBMSRules->available ) );
48              
49 0           $self->{driver} = Alzabo::Driver->new( rdbms => $p{rdbms},
50             schema => $self );
51 0           $self->{rules} = Alzabo::RDBMSRules->new( rdbms => $p{rdbms} );
52              
53 0           $self->{sql} = Alzabo::SQLMaker->load( rdbms => $p{rdbms} );
54              
55 0 0         params_exception "Alzabo::Create::Schema->new requires a name parameter\n"
56             unless exists $p{name};
57              
58 0           $self->set_name($p{name});
59              
60 0           $self->{tables} = Tie::IxHash->new;
61              
62 0 0         $self->_save_to_cache unless $p{no_cache};
63              
64 0           return $self;
65             }
66              
67             sub load_from_file
68             {
69 0     0 1   return shift->_load_from_file(@_);
70             }
71              
72             sub reverse_engineer
73             {
74 0     0 1   my $proto = shift;
75 0   0       my $class = ref $proto || $proto;
76 0           my %p = @_;
77              
78 0           my $self = $class->new( name => $p{name},
79             rdbms => $p{rdbms},
80             no_cache => 1,
81             );
82              
83 0           delete $p{rdbms};
84 0           $self->{driver}->connect(%p);
85              
86 0           $self->{rules}->reverse_engineer($self);
87              
88 0           $self->set_instantiated(1);
89 0           my $driver = delete $self->{driver};
90 0           $self->{original} = Storable::dclone($self);
91 0           $self->{driver} = $driver;
92 0           delete $self->{original}{original};
93 0           return $self;
94             }
95              
96             sub set_name
97             {
98 0     0 1   my $self = shift;
99              
100 0           validate_pos( @_, { type => SCALAR } );
101 0           my $name = shift;
102              
103 0 0 0       return if defined $self->{name} && $name eq $self->{name};
104              
105 0           my $old_name = $self->{name};
106 0           $self->{name} = $name;
107              
108 0           eval { $self->rules->validate_schema_name($self); };
  0            
109 0 0         if ($@)
110             {
111 0           $self->{name} = $old_name;
112              
113 0           rethrow_exception($@);
114             }
115              
116             # Gotta clean up old files or we have a mess!
117 0 0         $self->delete( name => $old_name ) if $old_name;
118 0           $self->set_instantiated(0);
119 0           undef $self->{original};
120             }
121              
122             sub set_instantiated
123             {
124 0     0 1   my $self = shift;
125              
126 0           validate_pos( @_, 1 );
127 0           $self->{instantiated} = shift;
128             }
129              
130             sub make_table
131             {
132 0     0 1   my $self = shift;
133 0           my %p = @_;
134              
135 0           my %p2;
136 0           foreach ( qw( before after ) )
137             {
138 0 0         $p2{$_} = delete $p{$_} if exists $p{$_};
139             }
140 0           $self->add_table( table => Alzabo::Create::Table->new( schema => $self,
141             %p ),
142             %p2 );
143              
144 0           return $self->table( $p{name} );
145             }
146              
147             sub add_table
148             {
149 0     0 1   my $self = shift;
150              
151 0           validate( @_, { table => { isa => 'Alzabo::Create::Table' },
152             before => { optional => 1 },
153             after => { optional => 1 } } );
154 0           my %p = @_;
155              
156 0           my $table = $p{table};
157              
158 0 0         params_exception "Table " . $table->name . " already exists in schema"
159             if $self->{tables}->EXISTS( $table->name );
160              
161 0           $self->{tables}->STORE( $table->name, $table );
162              
163 0           foreach ( qw( before after ) )
164             {
165 0 0         if ( exists $p{$_} )
166             {
167 0           $self->move_table( $_ => $p{$_},
168             table => $table );
169 0           last;
170             }
171             }
172             }
173              
174             sub delete_table
175             {
176 0     0 1   my $self = shift;
177              
178 0           validate_pos( @_, { isa => 'Alzabo::Create::Table' } );
179 0           my $table = shift;
180              
181 0 0         params_exception "Table " . $table->name ." doesn't exist in schema"
182             unless $self->{tables}->EXISTS( $table->name );
183              
184 0           foreach my $fk ($table->all_foreign_keys)
185             {
186 0           foreach my $other_fk ( $fk->table_to->foreign_keys_by_table($table) )
187             {
188 0           $fk->table_to->delete_foreign_key($other_fk);
189             }
190             }
191              
192 0           $self->{tables}->DELETE( $table->name );
193             }
194              
195             sub move_table
196             {
197 0     0 1   my $self = shift;
198              
199 0           validate( @_, { table => { isa => 'Alzabo::Create::Table' },
200             before => { isa => 'Alzabo::Create::Table',
201             optional => 1 },
202             after => { isa => 'Alzabo::Create::Table',
203             optional => 1 } } );
204 0           my %p = @_;
205              
206 0 0 0       if ( exists $p{before} && exists $p{after} )
207             {
208 0           params_exception
209             "move_table method cannot be called with both 'before' and 'after' parameters";
210             }
211              
212 0 0         if ( $p{before} )
213             {
214 0 0         params_exception "Table " . $p{before}->name . " doesn't exist in schema"
215             unless $self->{tables}->EXISTS( $p{before}->name );
216             }
217             else
218             {
219 0 0         params_exception "Table " . $p{after}->name . " doesn't exist in schema"
220             unless $self->{tables}->EXISTS( $p{after}->name );
221             }
222              
223 0 0         params_exception "Table " . $p{table}->name . " doesn't exist in schema"
224             unless $self->{tables}->EXISTS( $p{table}->name );
225              
226 0           $self->{tables}->DELETE( $p{table}->name );
227              
228 0           my $index;
229 0 0         if ( $p{before} )
230             {
231 0           $index = $self->{tables}->Indices( $p{before}->name );
232             }
233             else
234             {
235 0           $index = $self->{tables}->Indices( $p{after}->name ) + 1;
236             }
237              
238 0           $self->{tables}->Splice( $index, 0, $p{table}->name => $p{table} );
239             }
240              
241             sub register_table_name_change
242             {
243 0     0 0   my $self = shift;
244              
245 0           validate( @_, { table => { isa => 'Alzabo::Create::Table' },
246             old_name => { type => SCALAR } } );
247 0           my %p = @_;
248              
249 0 0         params_exception "Table $p{old_name} doesn't exist in schema"
250             unless $self->{tables}->EXISTS( $p{old_name} );
251              
252 0           my $index = $self->{tables}->Indices( $p{old_name} );
253 0           $self->{tables}->Replace( $index, $p{table}, $p{table}->name );
254             }
255              
256             sub add_relationship
257             {
258 0     0 1   my $self = shift;
259              
260 0           my %p = @_;
261              
262 0           my $tracker = Alzabo::ChangeTracker->new;
263              
264 0           $self->_check_add_relationship_args(%p);
265              
266             # This requires an entirely new table.
267 0 0         unless ( grep { $_ ne 'n' } @{ $p{cardinality} } )
  0            
  0            
268             {
269 0           $self->_create_linking_table(%p);
270 0           return;
271             }
272              
273 0 0 0       params_exception "Must provide 'table_from' or 'columns_from' parameter"
274             unless $p{table_from} || $p{columns_from};
275              
276 0 0 0       params_exception "Must provide 'table_to' or 'columns_to' parameter"
277             unless $p{table_to} || $p{columns_to};
278              
279 0 0         $p{columns_from} =
    0          
280             ( defined $p{columns_from} ?
281             ( Alzabo::Utils::is_arrayref( $p{columns_from} ) ?
282             $p{columns_from} :
283             [ $p{columns_from} ] ) :
284             undef );
285              
286 0 0         $p{columns_to} =
    0          
287             ( defined $p{columns_to} ?
288             ( Alzabo::Utils::is_arrayref( $p{columns_to} ) ?
289             $p{columns_to} :
290             [ $p{columns_to} ] ) :
291             undef );
292              
293 0   0       my $f_table = $p{table_from} || $p{columns_from}->[0]->table;
294 0   0       my $t_table = $p{table_to} || $p{columns_to}->[0]->table;
295              
296 0 0 0       if ( $p{columns_from} && $p{columns_to} )
297             {
298 0           params_exception
299             "Cannot create a relationship with differing numbers of columns " .
300             "on either side of the relation"
301 0 0         unless @{ $p{columns_from} } == @{ $p{columns_to} };
  0            
302             }
303              
304 0           foreach ( [ columns_from => $f_table ], [ columns_to => $t_table ] )
305             {
306 0           my ($key, $table) = @$_;
307 0 0         if ( defined $p{$key} )
308             {
309 0           params_exception
310             "All the columns in a given side of the relationship ".
311             "must be from the same table"
312 0 0         if grep { $_->table ne $table } @{ $p{$key} };
  0            
313             }
314             }
315              
316             # Determined later. This is the column that the relationship is
317             # to. As in table A/column B maps _to_ table X/column Y
318 0           my ($col_from, $col_to);
319              
320             # cardinality from -> to
321 0 0 0       my $cardinality =
    0 0        
322             ( $p{cardinality}->[0] eq '1' && $p{cardinality}->[1] eq '1' ?
323             '1_to_1' :
324             $p{cardinality}->[0] eq '1' && $p{cardinality}->[1] eq 'n' ?
325             '1_to_n' :
326             'n_to_1'
327             );
328 0           my $method = "_create_${cardinality}_relationship";
329              
330 0           ($col_from, $col_to) = $self->$method( %p,
331             table_from => $f_table,
332             table_to => $t_table,
333             );
334              
335             eval
336 0           {
337 0           $f_table->make_foreign_key( columns_from => $col_from,
338             columns_to => $col_to,
339             cardinality => $p{cardinality},
340             from_is_dependent => $p{from_is_dependent},
341             to_is_dependent => $p{to_is_dependent},
342             comment => $p{comment},
343             );
344             };
345 0 0         if ($@)
346             {
347 0           $tracker->backout;
348              
349 0           rethrow_exception($@);
350             }
351              
352 0           my @fk;
353             eval
354 0           {
355 0           foreach my $c ( @$col_from )
356             {
357 0           push @fk, $f_table->foreign_keys( table => $t_table,
358             column => $c );
359             }
360             };
361 0 0         if ($@)
362             {
363 0           $tracker->backout;
364              
365 0           rethrow_exception($@);
366             }
367              
368 0     0     $tracker->add( sub { $f_table->delete_foreign_key($_) foreach @fk } );
  0            
369              
370             # cardinality to -> to
371 0 0 0       my $inverse_cardinality =
    0 0        
372             ( $p{cardinality}->[1] eq '1' && $p{cardinality}->[0] eq '1' ?
373             '1_to_1' :
374             $p{cardinality}->[1] eq '1' && $p{cardinality}->[0] eq 'n' ?
375             '1_to_n' :
376             'n_to_1'
377             );
378 0           my $inverse_method = "_create_${inverse_cardinality}_relationship";
379              
380 0           ($col_from, $col_to) = $self->$method( table_from => $t_table,
381             table_to => $f_table,
382             columns_from => $col_to,
383             columns_to => $col_from,
384 0           cardinality => [ @{ $p{cardinality} }[1,0] ],
385             from_is_dependent => $p{to_is_dependent},
386             to_is_dependent => $p{from_is_dependent},
387             );
388              
389 0 0         if ($p{from_is_dependent})
390             {
391 0           $_->nullable(0) foreach @{ $p{columns_from} };
  0            
392             }
393              
394 0 0         if ($p{to_is_dependent})
395             {
396 0           $_->nullable(0) foreach @{ $p{columns_to} };
  0            
397             }
398              
399             eval
400 0           {
401 0           $t_table->make_foreign_key( columns_from => $col_from,
402             columns_to => $col_to,
403 0           cardinality => [ @{ $p{cardinality} }[1,0] ],
404             from_is_dependent => $p{to_is_dependent},
405             to_is_dependent => $p{from_is_dependent},
406             comment => $p{comment},
407             );
408             };
409 0 0         if ($@)
410             {
411 0           $tracker->backout;
412              
413 0           rethrow_exception($@);
414             }
415             }
416             # old name - deprecated
417             *add_relation = \&add_relationship;
418              
419             sub _check_add_relationship_args
420             {
421 0     0     my $self = shift;
422 0           my %p = @_;
423              
424 0           foreach my $t ( $p{table_from}, $p{table_to} )
425             {
426 0 0         next unless defined $t;
427 0 0         params_exception "Table " . $t->name . " doesn't exist in schema"
428             unless $self->{tables}->EXISTS( $t->name );
429             }
430              
431 0           params_exception "Incorrect number of cardinality elements"
432 0 0         unless scalar @{ $p{cardinality} } == 2;
433              
434 0           foreach my $c ( @{ $p{cardinality} } )
  0            
435             {
436 0 0         params_exception "Invalid cardinality: $c"
437             unless $c =~ /^[01n]$/i;
438             }
439              
440             # No such thing as 1..0 or n..0
441 0 0         params_exception "Invalid cardinality: $p{cardinality}->[0]..$p{cardinality}->[1]"
442             if $p{cardinality}->[1] eq '0';
443             }
444              
445             sub _create_1_to_1_relationship
446             {
447 0     0     my $self = shift;
448 0           my %p = @_;
449              
450 0 0 0       return @p{ 'columns_from', 'columns_to' }
451             if $p{columns_from} && $p{columns_to};
452              
453             # Add these columns to the table which _must_ participate in the
454             # relationship, if there is one. This reduces NULL values.
455             # Otherwise, just add to the first table specified in the
456             # relation.
457 0           my @order;
458              
459             # If the from table is dependent or neither one is or both are ...
460 0 0 0       if ( $p{from_is_dependent} ||
461             $p{from_is_dependent} == $p{to_is_dependent} )
462             {
463 0           @order = ( 'from', 'to' );
464             }
465             # The to table is dependent
466             else
467             {
468 0           @order = ( 'to', 'from' );
469             }
470              
471             # Determine which table we are linking from. This gets a new
472             # column or has its column adjusted) ...
473 0           my $f_table = $p{"table_$order[0]"};
474              
475             # And which table we are linking to. We use the primary key from
476             # this table if no column has been provided.
477 0           my $t_table = $p{"table_$order[1]"};
478              
479             # Determine whether there is a column in 'to' table we can use.
480 0           my $col_to;
481 0 0         if ( $p{"columns_$order[1]"} )
482             {
483 0           $col_to = $p{"columns_$order[1]"};
484             }
485             else
486             {
487 0           my @c = $t_table->primary_key;
488              
489 0 0         params_exception $t_table->name . " has no primary key."
490             unless @c;
491              
492 0           $col_to = \@c;
493             }
494              
495 0           my ($col_from);
496 0 0         if ($p{"columns_$order[0]"})
497             {
498 0           $col_from = $p{"columns_$order[0]"};
499             }
500             else
501             {
502 0           my @new_col;
503 0           foreach my $c ( @$col_to )
504             {
505 0           push @new_col, $self->_add_foreign_key_column( table => $f_table,
506             column => $c );
507             }
508              
509 0           $col_from = \@new_col;
510             }
511              
512 0           return ($col_from, $col_to);
513             }
514              
515             # This one's simple. We always add/adjust the column in the table on
516             # the 'to' side of the relationship. This table only relates to one
517             # row in the 'from' table, but a row in the 'from' table can relate to
518             # 'n' rows in the 'to' table.
519             sub _create_1_to_n_relationship
520             {
521 0     0     my $self = shift;
522 0           my %p = @_;
523              
524 0           my $f_table = $p{table_from};
525 0           my $t_table = $p{table_to};
526              
527 0           my $col_from;
528 0 0         if ( $p{columns_from} )
529             {
530 0           $col_from = $p{columns_from};
531             }
532             else
533             {
534 0           my @c = $f_table->primary_key;
535              
536             # Is there a way to handle this properly?
537 0 0         params_exception $f_table->name . " has no primary key."
538             unless @c;
539              
540 0           $col_from = \@c;
541             }
542              
543 0           my $col_to;
544 0 0         if ($p{columns_to})
545             {
546 0           $col_to = $p{columns_to};
547             }
548             else
549             {
550             # If the columns this links to in the 'to' table ares not specified
551             # explicitly we assume that the user wants to have this coumn
552             # created/adjusted in the 'to' table.
553 0           my @new_col;
554 0           foreach my $c ( @$col_from )
555             {
556 0           push @new_col, $self->_add_foreign_key_column( table => $t_table,
557             column => $c );
558             }
559              
560 0           $col_to = \@new_col;
561             }
562              
563 0           return ($col_from, $col_to);
564             }
565              
566             sub _create_n_to_1_relationship
567             {
568 0     0     my $self = shift;
569 0           my %p = @_;
570              
571             # reverse everything ...
572 0           ($p{table_from}, $p{table_to}) = ($p{table_to}, $p{table_from});
573 0           ($p{columns_from}, $p{columns_to}) = ($p{columns_to}, $p{columns_from});
574 0           ($p{from_is_dependent}, $p{to_is_dependent}) =
575             ($p{to_is_dependent}, $p{from_is_dependent});
576              
577             # pass it into the inverse method and then swap the return values.
578             # Tada!
579 0           return ( $self->_create_1_to_n_relationship(%p) )[1,0];
580             }
581              
582             # Given two tables and a column, it will add the column to the table
583             # if it doesn't exist. Otherwise, it adjusts the column in the table
584             # to match the given column. In either case, the two columns (the one
585             # passed to the method and the one altered/created) will share a
586             # ColumnDefinition object.
587              
588             # This is called when a relationship is created and the columns aren't
589             # specified. This means that changes to the column in one table are
590             # automatically reflected in the other table, which is generally a
591             # good thing.
592             sub _add_foreign_key_column
593             {
594 0     0     my $self = shift;
595              
596 0           validate( @_, { table => { isa => 'Alzabo::Create::Table' },
597             column => { isa => 'Alzabo::Create::Column' } } );
598 0           my %p = @_;
599              
600 0           my $tracker = Alzabo::ChangeTracker->new;
601              
602             # Note: This code _does_ explicitly want to compare the string
603             # representation of the $p{column}->definition reference.
604 0           my $new_col;
605 0 0 0       if ( eval { $p{table}->column( $p{column}->name ) } &&
  0            
606             ( $p{column}->definition ne $p{table}->column( $p{column}->name )->definition ) )
607             {
608             # This will make the two column share a single definition
609             # object.
610 0           my $old_def = $p{table}->column( $p{column}->name )->definition;
611 0           $p{table}->column( $p{column}->name )->set_definition($p{column}->definition);
612              
613             $tracker->add
614 0     0     ( sub { $p{table}->column
615 0           ( $p{column}->name )->set_definition($old_def) } );
616             }
617             else
618             {
619             # Just add the new column, but use the existing definition
620             # object.
621 0           $p{table}->make_column( name => $p{column}->name,
622             definition => $p{column}->definition );
623              
624 0           my $del_col = $p{table}->column( $p{column}->name );
625 0     0     $tracker->add( sub { $p{table}->delete_column($del_col) } );
  0            
626             }
627              
628             # Return the new column we just made.
629 0           return $p{table}->column( $p{column}->name );
630             }
631              
632             sub _create_linking_table
633             {
634 0     0     my $self = shift;
635 0           my %p = @_;
636              
637 0           my $tracker = Alzabo::ChangeTracker->new;
638              
639 0   0       my $t1 = $p{table_from} || $p{columns_from}->[0]->table;
640 0   0       my $t2 = $p{table_to} || $p{columns_to}->[0]->table;
641              
642 0           my $t1_col;
643 0 0         if ($p{columns_from})
644             {
645 0           $t1_col = $p{columns_from};
646             }
647             else
648             {
649 0           my @c = $t1->primary_key;
650              
651 0 0         params_exception $t1->name . " has no primary key."
652             unless @c;
653              
654 0           $t1_col = \@c;
655             }
656              
657 0           my $t2_col;
658 0 0         if ($p{columns_to})
659             {
660 0           $t2_col = $p{columns_to};
661             }
662             else
663             {
664 0           my @c = $t2->primary_key;
665              
666 0 0         params_exception $t2->name . " has no primary key."
667             unless @c;
668              
669 0           $t2_col = \@c;
670             }
671              
672             # First we create the table.
673 0           my $linking;
674             my $name;
675              
676 0 0         if ( exists $p{name} )
    0          
677             {
678 0           $name = $p{name};
679             }
680             elsif ( lc $t1->name eq $t1->name )
681             {
682 0           $name = join '_', $t1->name, $t2->name;
683             }
684             else
685             {
686 0           $name = join '', $t1->name, $t2->name;
687             }
688              
689 0           $linking = $self->make_table( name => $name );
690 0     0     $tracker->add( sub { $self->delete_table($linking) } );
  0            
691              
692             eval
693 0           {
694 0           foreach my $c ( @$t1_col, @$t2_col )
695             {
696 0           $linking->make_column( name => $c->name,
697             definition => $c->definition,
698             primary_key => 1,
699             );
700             }
701              
702             $self->add_relationship
703 0           ( table_from => $t1,
704             table_to => $linking,
705             columns_from => $t1_col,
706 0           columns_to => [ $linking->columns( map { $_->name } @$t1_col ) ],
707             cardinality => [ '1', 'n' ],
708             from_is_dependent => $p{from_is_dependent},
709             to_is_dependent => 1,
710             comment => $p{comment},
711             );
712              
713 0           $self->add_relationship
714             ( table_from => $t2,
715             table_to => $linking,
716             columns_from => $t2_col,
717 0           columns_to => [ $linking->columns( map { $_->name } @$t2_col ) ],
718             cardinality => [ '1', 'n' ],
719             from_is_dependent => $p{to_is_dependent},
720             to_is_dependent => 1,
721             comment => $p{comment},
722             );
723             };
724              
725 0 0         if ($@)
726             {
727 0           $tracker->backout;
728              
729 0           rethrow_exception($@);
730             }
731             }
732              
733             sub instantiated
734             {
735 0     0 1   my $self = shift;
736              
737 0           return $self->{instantiated};
738             }
739              
740             sub create
741             {
742 0     0 1   my $self = shift;
743 0           my %p = @_;
744              
745 0           my @sql = $self->make_sql;
746              
747 0 0         local $self->{db_schema_name} = delete $p{schema_name}
748             if exists $p{schema_name};
749              
750 0 0         $self->{driver}->create_database(%p)
751             unless $self->_has_been_instantiated(%p);
752              
753 0           $self->{driver}->connect(%p);
754              
755 0           foreach my $statement (@sql)
756             {
757 0           $self->{driver}->do( sql => $statement );
758             }
759              
760 0           $self->save_current_name;
761              
762 0           $self->set_instantiated(1);
763 0           my $driver = delete $self->{driver};
764 0           $self->{original} = Storable::dclone($self);
765 0           $self->{driver} = $driver;
766 0           delete $self->{original}{original};
767             }
768              
769             sub _has_been_instantiated
770             {
771 0     0     my $self = shift;
772              
773 0           my $db_schema_name = $self->db_schema_name;
774              
775 0 0         return 1 if grep { $db_schema_name eq $_ } $self->{driver}->schemas(@_);
  0            
776             }
777              
778             sub make_sql
779             {
780 0     0 1   my $self = shift;
781              
782 0 0         if ($self->{instantiated})
783             {
784 0           return $self->rules->schema_sql_diff( old => $self->{original},
785             new => $self );
786             }
787             else
788             {
789 0           return $self->rules->schema_sql($self);
790             }
791             }
792              
793             sub sync_backend_sql
794             {
795 0     0 1   my $self = shift;
796 0           my %p = @_;
797              
798 0 0         local $self->{db_schema_name} = delete $p{schema_name}
799             if exists $p{schema_name};
800              
801 0 0         unless ( $self->_has_been_instantiated(%p) )
802             {
803 0           return $self->rules->schema_sql($self);
804             }
805              
806 0           my $existing = $self->reverse_engineer( %p,
807             name => $self->db_schema_name,
808             rdbms => $self->driver->driver_id,
809             );
810              
811 0           return $self->rules->schema_sql_diff( old => $existing,
812             new => $self );
813             }
814              
815             sub sync_backend
816             {
817 0     0 1   my $self = shift;
818 0           my %p = @_;
819              
820 0 0         local $self->{db_schema_name} = delete $p{schema_name}
821             if exists $p{schema_name};
822              
823 0 0         unless ( $self->_has_been_instantiated(%p) )
824             {
825 0           $self->set_instantiated(0);
826 0           return $self->create(%p);
827             }
828              
829 0           $self->{driver}->connect(%p);
830              
831 0           foreach my $statement ( $self->sync_backend_sql(%p) )
832             {
833 0           $self->driver->do( sql => $statement );
834             }
835              
836 0           $self->save_current_name;
837              
838 0           $self->set_instantiated(1);
839 0           my $driver = delete $self->{driver};
840 0           $self->{original} = Storable::dclone($self);
841 0           $self->{driver} = $driver;
842 0           delete $self->{original}{original};
843             }
844              
845             sub drop
846             {
847 0     0 1   my $self = shift;
848 0           my %p = @_;
849              
850 0 0         local $self->{db_schema_name} = delete $p{schema_name}
851             if exists $p{schema_name};
852              
853 0           $self->{driver}->drop_database(%p);
854 0           $self->set_instantiated(0);
855             }
856              
857             sub delete
858             {
859 0     0 1   my $self = shift;
860 0           my %p = @_;
861              
862 0   0       my $name = $p{name} || $self->name;
863              
864 0           my $schema_dir = File::Spec->catdir( Alzabo::Config::schema_dir(), $name );
865              
866 0           my $dh = do { local *DH; };
  0            
867 0 0         opendir $dh, $schema_dir
868             or system_exception "Unable to open $schema_dir directory: $!";
869              
870 0           foreach my $f ( grep { /\.alz|\.rdbms|\.version/ } readdir $dh )
  0            
871             {
872 0           my $file = File::Spec->catfile( $schema_dir, $f );
873 0 0         next unless -f $file;
874              
875             # untaint
876 0           ($file) = $file =~ /^(.+)$/;
877              
878 0 0         unlink $file
879             or system_exception "Unable to delete $file: $!";
880             }
881 0 0         closedir $dh
882             or system_exception "Unable to close $schema_dir: $!";
883              
884 0 0         rmdir $schema_dir
885             or system_exception "Unable to delete $schema_dir: $!";
886             }
887              
888             sub is_saved
889             {
890 0     0 1   my $self = shift;
891              
892 0           my %p = @_;
893              
894 0   0       my $name = $p{name} || $self->name;
895              
896 0           my $schema_dir = File::Spec->catdir( Alzabo::Config::schema_dir(), $name );
897              
898 0           return -d $schema_dir;
899             }
900              
901             sub save_to_file
902             {
903 0     0 1   my $self = shift;
904              
905 0           my $schema_dir = File::Spec->catdir( Alzabo::Config::schema_dir(), $self->{name} );
906 0 0         unless (-e $schema_dir)
907             {
908 0 0         mkdir $schema_dir, 0775
909             or system_exception "Unable to make directory $schema_dir: $!";
910             }
911              
912 0           my $create_save_name = $self->_base_filename( $self->{name} ) . '.create.alz';
913              
914 0           my $fh = do { local *FH; };
  0            
915 0 0         open $fh, ">$create_save_name"
916             or system_exception "Unable to write to $create_save_name: $!\n";
917              
918 0           my $driver = delete $self->{driver};
919 0 0         Storable::nstore_fd( $self, $fh )
920             or system_exception "Can't store to filehandle";
921              
922 0           $self->{driver} = $driver;
923 0 0         close $fh
924             or system_exception "Unable to close $create_save_name: $!";
925              
926 0           my $rdbms_save_name = $self->_base_filename( $self->{name} ) . '.rdbms';
927              
928 0 0         open $fh, ">$rdbms_save_name"
929             or system_exception "Unable to write to $rdbms_save_name: $!\n";
930              
931 0 0         print $fh $self->{driver}->driver_id
932             or system_exception "Can't write to $rdbms_save_name: $!";
933 0 0         close $fh
934             or system_exception "Unable to close $rdbms_save_name: $!";
935              
936 0           my $version_save_name = $self->_base_filename( $self->{name} ) . '.version';
937              
938 0 0         open $fh, ">$version_save_name"
939             or system_exception "Unable to write to $version_save_name: $!\n";
940 0 0         print $fh $Alzabo::VERSION
941             or system_exception "Can't write to $version_save_name: $!";
942 0 0         close $fh
943             or system_exception "Unable to close $version_save_name: $!";
944              
945 0           my $rt = $self->runtime_clone;
946              
947 0           my $runtime_save_name = $self->_base_filename( $self->{name} ) . '.runtime.alz';
948              
949 0 0         open $fh, ">$runtime_save_name"
950             or system_exception "Unable to write to $runtime_save_name: $!\n";
951 0 0         Storable::nstore_fd( $rt, $fh )
952             or system_exception "Can't store to filehandle";
953 0 0         close $fh
954             or system_exception "Unable to close $runtime_save_name: $!";
955              
956 0           $self->_save_to_cache;
957             }
958              
959             sub clone
960             {
961 0     0 1   my $self = shift;
962              
963 0           validate( @_, { name => { type => SCALAR } } );
964 0           my %p = @_;
965              
966 0           my $driver = delete $self->{driver};
967 0           my $clone = Storable::dclone($self);
968 0           $self->{driver} = $driver;
969              
970 0           $clone->{name} = $p{name};
971 0           $clone->{driver} = Alzabo::Driver->new( rdbms => $self->{driver}->driver_id,
972             schema => $clone );
973              
974 0           $clone->rules->validate_schema_name($clone);
975 0 0         $clone->{original}{name} = $p{name} if $p{name};
976              
977 0           $clone->set_instantiated(0);
978              
979 0           return $clone;
980             }
981              
982             sub runtime_clone
983             {
984 0     0 1   my $self = shift;
985              
986 0           my %s;
987 0           my $driver = delete $self->{driver};
988 0           my $clone = Storable::dclone($self);
989 0           $self->{driver} = $driver;
990              
991 0           foreach my $f ( qw( original instantiated rules driver ) )
992             {
993 0           delete $clone->{$f};
994             }
995              
996 0           foreach my $t ($clone->tables)
997             {
998 0           foreach my $c ($t->columns)
999             {
1000 0           my $def = $c->definition;
1001 0           bless $def, 'Alzabo::Runtime::ColumnDefinition';
1002 0           bless $c, 'Alzabo::Runtime::Column';
1003              
1004 0           delete $c->{last_instantiation_name};
1005             }
1006              
1007 0           foreach my $fk ($t->all_foreign_keys)
1008             {
1009 0           bless $fk, 'Alzabo::Runtime::ForeignKey';
1010             }
1011              
1012 0           foreach my $i ($t->indexes)
1013             {
1014 0           bless $i, 'Alzabo::Runtime::Index';
1015             }
1016              
1017 0           delete $t->{last_instantiation_name};
1018              
1019 0           bless $t, 'Alzabo::Runtime::Table';
1020             }
1021 0           bless $clone, 'Alzabo::Runtime::Schema';
1022              
1023 0           return $clone;
1024             }
1025              
1026             sub save_current_name
1027             {
1028 0     0 0   my $self = shift;
1029              
1030 0           $self->{last_instantiated_name} = $self->name;
1031              
1032 0           foreach my $table ( $self->tables )
1033             {
1034 0           $table->save_current_name;
1035             }
1036             }
1037              
1038 0     0 0   sub former_name { $_[0]->{last_instantiated_name} }
1039              
1040             # Overrides method in base to load create schema instead of runtime
1041             # schema
1042             sub _schema_file_type
1043             {
1044 0     0     return 'create';
1045             }
1046              
1047             __END__