File Coverage

blib/lib/SLOOPS/SchemaGenerator.pm
Criterion Covered Total %
statement 20 138 14.4
branch 2 62 3.2
condition 0 2 0.0
subroutine 7 15 46.6
pod 5 9 55.5
total 34 226 15.0


line stmt bran cond sub pod time code
1             package SLOOPS::SchemaGenerator ;
2              
3 1     1   4236 use base qw/Class::AutoAccess/ ;
  1         3  
  1         1062  
4 1     1   461 use Carp;
  1         3  
  1         54  
5 1     1   5 use strict ;
  1         3  
  1         238  
6              
7             my $instance = SLOOPS::SchemaGenerator->new();
8              
9             my $debug = 0 ;
10              
11             sub import{
12             #my $callerPack = caller ;
13 1     1   9 my ($class, $options) = @_ ;
14 1 50       5 if( ! defined $debug ){
15 0   0     0 $debug = $options->{'debug'} || 0 ;
16             }
17 1 50       12 print "\n\nDebug option : $debug \n\n" if ($debug);
18             }
19              
20              
21              
22             sub instance{
23 0     0 0 0 return $instance ;
24             }
25              
26              
27             sub new{
28 1     1 0 2 my ($class) = @_ ;
29 1         6 return bless {
30             'classes' => {},
31             'dbDriver' => undef
32             }, $class ;
33             }
34              
35             sub dbh{
36 0     0 0   my ($self, $dbh ) = @_ ;
37 0 0         if( defined $dbh ) {
38 0           $self->dbDriver()->dbh($dbh);
39             }
40 0           return $self->dbDriver()->dbh();
41             }
42              
43              
44              
45             =head2 addClass
46              
47             Adds a class and all its surclasses into this generator.
48             Sets the default values of PERSIST hash is they miss.
49              
50             =cut
51              
52             sub addClass{
53 0     0 1   my ($self, $className) = @_ ;
54            
55 1     1   6 no strict 'refs' ;
  1         2  
  1         91  
56            
57             # Try to require $className ;
58 0           eval "require $className ";
59 0 0         if( $@ ){
60 0           confess("Cannot find class $className : $@");
61             }
62            
63 0           my $hash = ${"$className".'::PERSIST'} ;
  0            
64 1     1   5 use strict 'refs';
  1         2  
  1         1653  
65 0 0         if( ! defined $hash ){
66 0           confess "Class $className cannot persist";
67             }
68            
69             # Storing reference. Redondancy is avoid by the hash nature of the storage.
70 0 0         if( defined $self->classes()->{$className} ){
71             # Allready stored .
72 0           return ;
73             }
74 0 0         print "Adding class $className\n" if($debug);
75 0           $self->classes()->{$className} = $hash ;
76            
77 0           $self->setDefaults($className, $hash );
78            
79             # Storing base if neccessary.
80 0 0         if ( $hash->{'base'} ){
81 0           $self->addClass($hash->{'base'});
82             }
83            
84             # Generating sql.
85 0           $self->classes()->{$className}->{'sql'} = $self->generateSql($className, $hash);
86              
87             # Generating methods for references
88 0 0         if( $hash->{'references'} ){
89 0           $self->generateReferenceMethods($className,$hash->{'references'});
90             }
91             }
92              
93             =head2 generateReferenceMethods
94              
95             Generates the references methods in the class from the reference hash
96             of the class.
97              
98             Let a reference called foo pointing to an Bar object in the persist hash.
99             The class MUST have a foo attribute that is dedicated to a Bar object.
100              
101             This method will implement a foo method for the class that fetch the
102             perl object from the database on demand ( accessor behaviour)
103             Or just set the Foo object and check it is for the right class and save it if its not.
104              
105              
106             method template: for a reference named 'foo' to a Bar object.
107              
108             sub foo_O{
109             my ($self, $value ) = @_ ;
110             if( $value ){
111             if( ! $value->isa('Bar')){ confess("Bad class") ;}
112             $self->{'foo_Bar'} = $value ;
113             my $dbid = $value->{'_dbid_'} ;
114             if ( ! defined $dbid ){
115             confess("Object $value is not saved");
116             }
117             $self->foo($dbid)
118             return $value ;
119             }
120              
121             # if foo unset, got to fetch it.
122             my $dbid = $self->foo();
123            
124             # If no dbid is set, theres no object to fetch
125             if( ! defined $dbid ) { return undef ;}
126             # Is the object is allready fetch
127             if( defined $self->{'foo_O'} ) { return $self->{'foo_O'} ; }
128            
129             # really got to fetch
130             return $self->{'foo_O'} = Factory->instance()->fetchObject('Bar',$dbid);
131              
132             }
133              
134              
135             =cut
136              
137             sub generateReferenceMethods{
138 0     0 1   my ($self, $class , $refHash ) = @_ ;
139 0           my @refs = keys %{$refHash} ;
  0            
140 0           foreach my $ref ( @refs ){
141            
142 0           my $referencedClass = $refHash->{$ref};
143 0           my $code = qq/
144             package $class ;
145              
146             sub $ref/.'_O'.qq/{
147             my (\$self, \$value ) = \@_ ;
148            
149             my \$dbid = undef ;
150             if( \$value ){
151             if( ! \$value->isa('$referencedClass')){ Carp::confess(/."'Bad class :'".qq/.ref(\$value).' instead of $referencedClass') ;}
152             \$self->{'/.$ref.qq/_0'} = \$value ;
153             \$dbid = \$value->{'_dbid_'} ;
154             if ( ! defined \$dbid ){
155             Carp::confess(/.'"Object $value is not saved"'.qq/);
156             }
157             \$self->$ref(\$dbid);
158             \$self->{'$ref/.'_O'.qq/'} = \$value ;
159             return \$value ;
160             }
161              
162             # if foo unset, got to fetch it.
163             \$dbid = \$self->$ref();
164            
165             # If no dbid is set, theres no object to fetch
166             if( ! defined \$dbid ) { return undef ;}
167             # Is the object is allready fetch
168             if( defined \$self->{'$ref/.'_O'.qq/'} ) { return \$self->{'$ref/.'_O'.qq/'} ; }
169            
170             # really got to fetch
171             return \$self->{'$ref/.'_O'.qq/'} = SLOOPS::Factory->instance()->fetchObject('$referencedClass',\$dbid);
172              
173             } /;
174              
175 0           eval $code ;
176 0 0         if( $@ ) {
177 0           confess("Failed to implement method for reference $ref for class $class: $@");
178             }
179 0 0         print "Execute :: \n\n".$code."\n\n" if($debug);
180             }
181            
182             }
183              
184              
185             sub generateSql{
186 0     0 0   my ($self, $class , $perst ) = @_ ;
187             #$self->setDefaults($class,$perst) ;
188            
189 0           my $sql = 'CREATE TABLE '.$perst->{'table'}.' (';
190            
191 0           $sql .= ' dbid '.$self->dbDriver()->uniqKeyDecl().' ' ;
192 0 0         if( ! defined $perst->{'base'} ){
193             # This is root !
194 0           $sql .= $self->dbDriver()->autoIncrement($perst->{'table'}, 'dbid');
195 0           $sql .= ',';
196 0           $sql .= 'dbRealClass '.$self->dbDriver()->String();
197             }
198             else{
199 0           $sql .= ',CONSTRAINT FOREIGN KEY(dbid) REFERENCES '.$self->classes()->{$perst->{'base'}}->{'table'}.'(dbid)';
200             }
201 0           $sql .= ',' ;
202            
203             # Storing fields
204 0           foreach my $field ( keys %{$perst->{'fields'}} ){
  0            
205 0           $sql .= ' '.$field.' '.$perst->{'fields'}->{$field}->[0] ;
206 0           $sql .= ' DEFAULT '.$self->dbh()->quote($perst->{'fields'}->{$field}->[1]).',';
207             }
208              
209              
210             # Storing references
211 0 0         if ( $perst->{'references'} ) {
212 0           foreach my $reference ( keys %{$perst->{'references'}} ){
  0            
213 0           my $referencedClass = $perst->{'references'}->{$reference} ;
214 0           my $refHash = $self->classes()->{$referencedClass} ;
215 0 0         if( ! $refHash ){
216 0           confess(qq/Class $referencedClass not registered in $self.
217             Cannot be referenced by $class/);
218             }
219            
220 0 0         print "Referenced class: $referencedClass\n" if($debug);
221 0           $sql .= ' '.$reference.' '.$self->dbDriver()->referenceType().',';
222 0           $sql .= 'CONSTRAINT FOREIGN KEY ('.$reference.') REFERENCES '.$refHash->{'table'}.'(dbid),';
223             }
224             }
225 0           chop( $sql );
226 0           $sql .= ' )' ;
227 0 0         print "SQL for $class: ".$sql ."\n" if($debug);
228 0           return $sql ;
229             }
230              
231             =head2 setDefaults
232              
233             Sets default values for missing values in PERSIST hash for
234             a class.
235              
236             Usage:
237             my $class = .... ; # the persistent class name
238             my $h = .... ; # The PERSIST HASH of the class $class;
239              
240             $sg->setDefault($class,$h);
241              
242             =cut
243              
244             sub setDefaults{
245 0     0 1   my ($self, $class , $perst ) = @_ ;
246            
247             # Setting default table name.
248 0 0         if( ! defined $perst->{'table'} ){
249 0           my $table = $class;
250 0           $table =~ s/::/_/g ;
251 0           $table = lc $table ;
252 0           $perst->{'table'} = $table."_auto" ;
253             }
254            
255 0 0         if( ! defined $perst->{'fields'} ){
256 0           $perst->{'fields'} = {};
257             }
258            
259 0 0         if( ! defined $perst->{'references'}){
260 0           $perst->{'references'} = {};
261             }
262            
263             # Setting default datatypes.
264 0           foreach my $field ( keys %{$perst->{'fields'}} ){
  0            
265 0 0         if ( ! defined $perst->{'fields'}->{$field} ){
266 0           $perst->{'fields'}->{$field} = [ $self->dbDriver()->defaultType() ,
267             $self->dbDriver()->defaultValue() ];
268             }
269             }
270             }
271              
272              
273             =head2 deleteSchema
274              
275             Delete all the tables from the schema.
276              
277             =cut
278              
279             sub deleteSchema{
280 0     0 1   my ($self ) = @_ ;
281 0           my %persists = %{$self->classes()};
  0            
282 0           foreach my $class ( keys %persists ){
283 0 0         print "Deleting class $class \n" if($debug);
284 0           my $table = $persists{$class}->{'table'} ;
285 0           eval{
286 0           my $sql = 'DROP TABLE '.$table;
287 0           my $sth = $self->dbh()->prepare($sql);
288 0           $sth->execute();
289             };
290 0 0         if( $@ ) {
291 0           warn("Cannot delete table $table : $@ ");
292             }
293             }
294             }
295              
296             =head2 updateSchema
297              
298             Implements the schema into the database.
299             If $override is true, call deleteSchema before.
300              
301             usage:
302              
303             $sg->updateSchema([1]);
304              
305             =cut
306              
307             sub updateSchema{
308 0     0 1   my ($self, $override ) = @_ ;
309            
310 0 0         if( $override ){
311 0           $self->deleteSchema();
312             }
313            
314 0           my %persists = %{$self->classes()} ;
  0            
315             # setting to 0 the saved flags.
316 0           foreach my $class (keys %persists ){
317 0 0         print "Setting not Saved for table " .$persists{$class}->{'table'}." , class: ".$class."\n" if($debug);
318 0           $persists{$class}->{'saved'} = 0 ;
319             }
320            
321             # Then while it left tables, implement them.
322 0           while( keys %persists ){
323 0           my $classToSave = undef ;
324             # Choose a good one.
325 0           foreach my $class ( keys %persists ){
326 0           my $perst = $persists{$class};
327 0 0         if( ! defined $perst->{'base'} ){
328 0           $classToSave = $class ;
329 0           last ;
330             }
331 0 0         if( $self->classes()->{$perst->{'base'}}->{'saved'} == 1 ){
332 0           $classToSave = $class ;
333 0           last ;
334             }
335             #confess("Algorithm failure");
336             }
337 0 0         if( ! defined $classToSave ){
338 0           confess("Algorithm failure");
339             }
340 0 0         print "Considering ".$classToSave ."\n" if($debug);
341              
342 0           my $sql = $persists{$classToSave}->{'sql'} ;
343 0 0         print "Executing ".$sql ."\n" if($debug);
344            
345 0           eval{
346 0 0         print "EXECUTING \n\n $sql \n\n" if($debug);
347 0           my $sth = $self->dbh()->prepare($sql);
348 0           $sth->execute();
349             };
350 0 0         if( $@ ){
351 0           carp("Implementation of class ".$classToSave." failed :".$@);
352             }
353            
354 0           $persists{$classToSave}->{'saved'} = 1;
355            
356 0           delete $persists{$classToSave};
357             }
358            
359            
360             }
361              
362              
363             1 ;